Jsr (#27)
This commit is contained in:
commit
7ad3ddbc0c
134 changed files with 1811 additions and 646 deletions
|
@ -285,6 +285,12 @@ module.exports = {
|
||||||
'no-restricted-imports': 'off',
|
'no-restricted-imports': 'off',
|
||||||
'import/no-relative-packages': 'off', // common-internals is symlinked from node
|
'import/no-relative-packages': 'off', // common-internals is symlinked from node
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['e2e/deno/**'],
|
||||||
|
rules: {
|
||||||
|
'import/no-unresolved': 'off',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
settings: {
|
settings: {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
**/node_modules
|
**/node_modules
|
||||||
**/private
|
**/private
|
||||||
**/dist
|
**/dist
|
||||||
|
/e2e
|
18
.github/workflows/test.yaml
vendored
18
.github/workflows/test.yaml
vendored
|
@ -90,7 +90,7 @@ jobs:
|
||||||
API_ID: ${{ secrets.TELEGRAM_API_ID }}
|
API_ID: ${{ secrets.TELEGRAM_API_ID }}
|
||||||
API_HASH: ${{ secrets.TELEGRAM_API_HASH }}
|
API_HASH: ${{ secrets.TELEGRAM_API_HASH }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: cd e2e && ./cli.sh ci
|
run: cd e2e/node && ./cli.sh ci
|
||||||
- name: Publish to canary NPM
|
- name: Publish to canary NPM
|
||||||
if: github.repository == 'mtcute/mtcute' # do not run on forks
|
if: github.repository == 'mtcute/mtcute' # do not run on forks
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
@ -98,4 +98,18 @@ jobs:
|
||||||
NPM_TOKEN: ${{ secrets.CANARY_NPM_TOKEN }}
|
NPM_TOKEN: ${{ secrets.CANARY_NPM_TOKEN }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
REGISTRY: 'https://npm.tei.su'
|
REGISTRY: 'https://npm.tei.su'
|
||||||
run: cd e2e && ./cli.sh ci-publish
|
run: cd e2e/node && ./cli.sh ci-publish
|
||||||
|
e2e-deno:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [test-node, test-web, test-bun]
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
actions: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Run end-to-end tests under Deno
|
||||||
|
env:
|
||||||
|
API_ID: ${{ secrets.TELEGRAM_API_ID }}
|
||||||
|
API_HASH: ${{ secrets.TELEGRAM_API_HASH }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: cd e2e/deno && ./cli.sh ci
|
||||||
|
|
3
e2e/deno/.dockerignore
Normal file
3
e2e/deno/.dockerignore
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/.jsr-data
|
||||||
|
/dist
|
||||||
|
/deno.lock
|
|
@ -1,3 +1,5 @@
|
||||||
# obtain these values from my.telegram.org
|
# obtain these values from my.telegram.org
|
||||||
API_ID=
|
API_ID=
|
||||||
API_HASH=
|
API_HASH=
|
||||||
|
|
||||||
|
GITHUB_TOKEN=
|
3
e2e/deno/.gitignore
vendored
Normal file
3
e2e/deno/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/.jsr-data
|
||||||
|
.env
|
||||||
|
/deno.lock
|
26
e2e/deno/Dockerfile.build
Normal file
26
e2e/deno/Dockerfile.build
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
FROM denoland/deno:bin-1.42.4 as deno-bin
|
||||||
|
|
||||||
|
FROM node:20
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=deno-bin /deno /bin/deno
|
||||||
|
# todo: remove once 1.42.5 is out
|
||||||
|
RUN deno upgrade --canary --version=2f5a6a8514ad8eadce1a0a9f1a7a419692e337ef
|
||||||
|
|
||||||
|
RUN corepack enable && \
|
||||||
|
corepack prepare pnpm@8.7.1 --activate
|
||||||
|
|
||||||
|
COPY ../.. /app/
|
||||||
|
|
||||||
|
RUN pnpm install --frozen-lockfile && \
|
||||||
|
pnpm -C packages/tl run gen-code
|
||||||
|
|
||||||
|
RUN apt update && apt install -y socat
|
||||||
|
|
||||||
|
ENV REGISTRY="http://jsr/"
|
||||||
|
ENV E2E="1"
|
||||||
|
ENV JSR="1"
|
||||||
|
ENV JSR_TOKEN="token"
|
||||||
|
|
||||||
|
ENTRYPOINT [ "node", "/app/scripts/publish.js" ]
|
||||||
|
CMD [ "all" ]
|
3
e2e/deno/Dockerfile.jsr
Normal file
3
e2e/deno/Dockerfile.jsr
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
FROM ghcr.io/teidesu/jsr-api:latest
|
||||||
|
|
||||||
|
RUN apt update && apt install -y curl
|
10
e2e/deno/Dockerfile.test
Normal file
10
e2e/deno/Dockerfile.test
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
FROM denoland/deno:1.42.4
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apt update && apt install -y socat
|
||||||
|
|
||||||
|
COPY ./ /app/
|
||||||
|
|
||||||
|
ENV DOCKER="1"
|
||||||
|
|
||||||
|
ENTRYPOINT [ "./cli.sh", "run" ]
|
30
e2e/deno/README.md
Normal file
30
e2e/deno/README.md
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# mtcute e2e tests (Deno edition)
|
||||||
|
|
||||||
|
This directory contains end-to-end tests for mtcute under Deno.
|
||||||
|
|
||||||
|
They are made for 2 purposes:
|
||||||
|
- Ensure published packages work as expected and can properly be imported
|
||||||
|
- Ensure that the library works with the actual Telegram API
|
||||||
|
|
||||||
|
To achieve the first goal, we use a local JSR instance container where we publish the package,
|
||||||
|
and then install it from there in another container
|
||||||
|
|
||||||
|
## Setting up
|
||||||
|
|
||||||
|
Before running the tests, you need to copy `.env.example` to `.env` and fill in the values
|
||||||
|
|
||||||
|
## Running tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# first start a local jsr instance
|
||||||
|
./cli.sh start
|
||||||
|
|
||||||
|
# push all packages to the local registry
|
||||||
|
./cli.sh update
|
||||||
|
# pushing a particular package is not supported due to jsr limitations
|
||||||
|
|
||||||
|
# run the tests
|
||||||
|
./cli.sh run
|
||||||
|
# or in docker
|
||||||
|
./cli.sh run-docker
|
||||||
|
```
|
77
e2e/deno/cli.sh
Executable file
77
e2e/deno/cli.sh
Executable file
|
@ -0,0 +1,77 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -eau
|
||||||
|
|
||||||
|
method=$1
|
||||||
|
shift
|
||||||
|
|
||||||
|
case "$method" in
|
||||||
|
"start")
|
||||||
|
docker compose up -d --wait jsr
|
||||||
|
node ./init-server.js
|
||||||
|
;;
|
||||||
|
"update")
|
||||||
|
# unpublish all packages
|
||||||
|
if [ -d .jsr-data/gcs/modules/@mtcute ]; then
|
||||||
|
rm -rf .jsr-data/gcs/modules/@mtcute
|
||||||
|
docker compose exec jsr-db psql registry -U user -c "delete from publishing_tasks;"
|
||||||
|
docker compose exec jsr-db psql registry -U user -c "delete from package_files;"
|
||||||
|
docker compose exec jsr-db psql registry -U user -c "delete from npm_tarballs;"
|
||||||
|
docker compose exec jsr-db psql registry -U user -c "delete from package_version_dependencies;"
|
||||||
|
docker compose exec jsr-db psql registry -U user -c "delete from package_versions;"
|
||||||
|
docker compose exec jsr-db psql registry -U user -c "delete from packages;"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# publish all packages
|
||||||
|
docker compose run --rm --build build all
|
||||||
|
|
||||||
|
# clear cache
|
||||||
|
if command -v deno &> /dev/null; then
|
||||||
|
rm -rf $(deno info --json | jq .denoDir -r)/deps
|
||||||
|
fi
|
||||||
|
if [ -f deno.lock ]; then
|
||||||
|
rm deno.lock
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"clean")
|
||||||
|
docker compose down
|
||||||
|
rm -rf .jsr-data
|
||||||
|
;;
|
||||||
|
"stop")
|
||||||
|
docker compose down
|
||||||
|
;;
|
||||||
|
"run")
|
||||||
|
if [ -f .env ]; then
|
||||||
|
source .env
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$DOCKER" ]; then
|
||||||
|
# running behind a socat proxy seems to fix some of the docker networking issues (thx kamillaova)
|
||||||
|
socat TCP-LISTEN:4873,fork,reuseaddr TCP4:jsr:80 &
|
||||||
|
socat_pid=$!
|
||||||
|
|
||||||
|
trap "kill $socat_pid" EXIT
|
||||||
|
fi
|
||||||
|
|
||||||
|
export JSR_URL=http://localhost:4873
|
||||||
|
if [ -z "$@" ]; then
|
||||||
|
deno test -A tests/**/*.ts
|
||||||
|
else
|
||||||
|
deno test -A $@
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"run-docker")
|
||||||
|
source .env
|
||||||
|
docker compose run --rm --build test $@
|
||||||
|
;;
|
||||||
|
"ci")
|
||||||
|
set -eaux
|
||||||
|
mkdir .jsr-data
|
||||||
|
./cli.sh start
|
||||||
|
./cli.sh update
|
||||||
|
docker compose run --rm --build test
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown command"
|
||||||
|
;;
|
||||||
|
esac
|
9
e2e/deno/deno.json
Normal file
9
e2e/deno/deno.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"@mtcute/web": "jsr:@mtcute/web@*",
|
||||||
|
"@mtcute/wasm": "jsr:@mtcute/wasm@*",
|
||||||
|
"@mtcute/tl": "jsr:@mtcute/tl@*",
|
||||||
|
"@mtcute/tl-runtime": "jsr:@mtcute/tl-runtime@*",
|
||||||
|
"@mtcute/core": "jsr:@mtcute/core@*"
|
||||||
|
}
|
||||||
|
}
|
78
e2e/deno/docker-compose.yaml
Normal file
78
e2e/deno/docker-compose.yaml
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
# jsr (based on https://github.com/teidesu/docker-images/blob/main/jsr/docker-compose.yaml)
|
||||||
|
jsr-db:
|
||||||
|
image: postgres:15
|
||||||
|
command: postgres -c 'max_connections=1000'
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: user
|
||||||
|
POSTGRES_PASSWORD: password
|
||||||
|
POSTGRES_DB: registry
|
||||||
|
healthcheck:
|
||||||
|
test: "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"
|
||||||
|
interval: 5s
|
||||||
|
retries: 20
|
||||||
|
start_period: 5s
|
||||||
|
volumes:
|
||||||
|
- ./.jsr-data/db:/var/lib/postgresql/data
|
||||||
|
jsr-gcs:
|
||||||
|
image: fsouza/fake-gcs-server:latest
|
||||||
|
command: -scheme http -filesystem-root=/gcs-data -port 4080
|
||||||
|
volumes:
|
||||||
|
- ./.jsr-data/gcs:/gcs-data
|
||||||
|
jsr-api:
|
||||||
|
depends_on:
|
||||||
|
jsr-db:
|
||||||
|
condition: service_healthy
|
||||||
|
jsr-gcs:
|
||||||
|
condition: service_started
|
||||||
|
healthcheck:
|
||||||
|
test: "curl --fail http://localhost:8001/sitemap.xml || exit 1"
|
||||||
|
interval: 5s
|
||||||
|
retries: 20
|
||||||
|
start_period: 5s
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.jsr
|
||||||
|
environment:
|
||||||
|
- "DATABASE_URL=postgres://user:password@jsr-db/registry"
|
||||||
|
- "GITHUB_CLIENT_ID=fake"
|
||||||
|
- "GITHUB_CLIENT_SECRET=fake"
|
||||||
|
- "GCS_ENDPOINT=http://jsr-gcs:4080"
|
||||||
|
- "MODULES_BUCKET=modules"
|
||||||
|
- "PUBLISHING_BUCKET=publishing"
|
||||||
|
- "DOCS_BUCKET=docs"
|
||||||
|
- "NPM_BUCKET=npm"
|
||||||
|
- "REGISTRY_URL=http://localhost:4873"
|
||||||
|
- "NPM_URL=http://example.com/unused"
|
||||||
|
jsr:
|
||||||
|
depends_on:
|
||||||
|
jsr-api:
|
||||||
|
condition: service_healthy
|
||||||
|
image: nginx:1.21
|
||||||
|
volumes:
|
||||||
|
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||||
|
ports:
|
||||||
|
- "4873:80"
|
||||||
|
|
||||||
|
# our stuff
|
||||||
|
build:
|
||||||
|
build:
|
||||||
|
context: ../..
|
||||||
|
dockerfile: e2e/deno/Dockerfile.build
|
||||||
|
environment:
|
||||||
|
- GITHUB_TOKEN=${GITHUB_TOKEN}
|
||||||
|
depends_on:
|
||||||
|
- jsr
|
||||||
|
test:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.test
|
||||||
|
environment:
|
||||||
|
- API_ID=${API_ID}
|
||||||
|
- API_HASH=${API_HASH}
|
||||||
|
depends_on:
|
||||||
|
- jsr
|
||||||
|
networks:
|
||||||
|
mtcute-e2e: {}
|
67
e2e/deno/init-server.js
Normal file
67
e2e/deno/init-server.js
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
const { execSync } = require('child_process')
|
||||||
|
|
||||||
|
function getDockerContainerIp(name) {
|
||||||
|
const containerId = execSync(`docker compose ps -q ${name}`).toString().trim()
|
||||||
|
const ip = execSync(`docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ${containerId}`)
|
||||||
|
.toString()
|
||||||
|
.trim()
|
||||||
|
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const stmt of [
|
||||||
|
"delete from tokens where user_id = '00000000-0000-0000-0000-000000000000';",
|
||||||
|
"insert into tokens (hash, user_id, type, expires_at) values ('3c469e9d6c5875d37a43f353d4f88e61fcf812c66eee3457465a40b0da4153e0', '00000000-0000-0000-0000-000000000000', 'web', current_date + interval '100' year);",
|
||||||
|
"update users set is_staff = true, scope_limit = 99999 where id = '00000000-0000-0000-0000-000000000000';",
|
||||||
|
]) {
|
||||||
|
execSync(`docker compose exec jsr-db psql registry -U user -c "${stmt}"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[i] Initialized database')
|
||||||
|
|
||||||
|
const GCS_URL = `http://${getDockerContainerIp('jsr-gcs')}:4080/`
|
||||||
|
const API_URL = `http://${getDockerContainerIp('jsr-api')}:8001/`
|
||||||
|
|
||||||
|
async function createBucket(name) {
|
||||||
|
try {
|
||||||
|
const resp = await fetch(`${GCS_URL}storage/v1/b`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ name }),
|
||||||
|
})
|
||||||
|
|
||||||
|
await resp.text()
|
||||||
|
|
||||||
|
return resp.ok || resp.status === 409
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
for (const bucket of ['modules', 'docs', 'publishing', 'npm']) {
|
||||||
|
const ok = await createBucket(bucket)
|
||||||
|
console.log(`[i] Created bucket ${bucket}: ${ok}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create @mtcute scope if it doesn't exist
|
||||||
|
const resp = await fetch(`${API_URL}api/scopes`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Cookie: 'token=token',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ scope: 'mtcute' }),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (resp.status !== 200 && resp.status !== 409) {
|
||||||
|
throw new Error(`Failed to create scope: ${resp.statusText} ${await resp.text()}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resp.status === 200) {
|
||||||
|
console.log('[i] Created scope mtcute')
|
||||||
|
}
|
||||||
|
})()
|
29
e2e/deno/nginx.conf
Normal file
29
e2e/deno/nginx.conf
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
events {}
|
||||||
|
|
||||||
|
http {
|
||||||
|
upstream gcs {
|
||||||
|
server jsr-gcs:4080;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream api {
|
||||||
|
server jsr-api:8001;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log /error.log debug;
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
|
||||||
|
location ~ ^/(@.*)$ {
|
||||||
|
proxy_pass http://gcs/storage/v1/b/modules/o/$1?alt=media;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://api;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
e2e/deno/tests/packaging/base-client.ts
Normal file
21
e2e/deno/tests/packaging/base-client.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { assertEquals } from 'https://deno.land/std@0.223.0/assert/mod.ts'
|
||||||
|
|
||||||
|
import { BaseTelegramClient } from '@mtcute/core/client.js'
|
||||||
|
|
||||||
|
import { getApiParams } from '../../utils.ts'
|
||||||
|
|
||||||
|
Deno.test('@mtcute/core', async (t) => {
|
||||||
|
await t.step('connects to test DC and makes help.getNearestDc', async () => {
|
||||||
|
const tg = new BaseTelegramClient({
|
||||||
|
...getApiParams(),
|
||||||
|
})
|
||||||
|
|
||||||
|
await tg.connect()
|
||||||
|
const config = await tg.call({ _: 'help.getNearestDc' })
|
||||||
|
await tg.close()
|
||||||
|
|
||||||
|
assertEquals(typeof config, 'object')
|
||||||
|
assertEquals(config._, 'nearestDc')
|
||||||
|
assertEquals(config.thisDc, 2)
|
||||||
|
})
|
||||||
|
})
|
73
e2e/deno/tests/packaging/tl-runtime.ts
Normal file
73
e2e/deno/tests/packaging/tl-runtime.ts
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { assertEquals } from 'https://deno.land/std@0.223.0/assert/mod.ts'
|
||||||
|
|
||||||
|
import { Long } from '@mtcute/core'
|
||||||
|
import { setPlatform } from '@mtcute/core/platform.js'
|
||||||
|
import { TlBinaryReader, TlBinaryWriter, TlSerializationCounter } from '@mtcute/tl-runtime'
|
||||||
|
import { WebPlatform } from '@mtcute/web'
|
||||||
|
|
||||||
|
// here we primarily want to check that everything imports properly,
|
||||||
|
// and that the code is actually executable. The actual correctness
|
||||||
|
// of the implementation is covered tested by unit tests
|
||||||
|
|
||||||
|
const p = new WebPlatform()
|
||||||
|
setPlatform(p)
|
||||||
|
|
||||||
|
Deno.test('encodings', () => {
|
||||||
|
assertEquals(p.hexEncode(new Uint8Array([1, 2, 3, 4, 5])), '0102030405')
|
||||||
|
})
|
||||||
|
|
||||||
|
Deno.test('TlBinaryReader', () => {
|
||||||
|
const map = {
|
||||||
|
'85337187': function (r: any) {
|
||||||
|
const ret: any = {}
|
||||||
|
ret._ = 'mt_resPQ'
|
||||||
|
ret.nonce = r.int128()
|
||||||
|
ret.serverNonce = r.int128()
|
||||||
|
ret.pq = r.bytes()
|
||||||
|
ret.serverPublicKeyFingerprints = r.vector(r.long)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const data =
|
||||||
|
'000000000000000001c8831ec97ae55140000000632416053e0549828cca27e966b301a48fece2fca5cf4d33f4a11ea877ba4aa5739073300817ed48941a08f98100000015c4b51c01000000216be86c022bb4c3'
|
||||||
|
const buf = p.hexDecode(data)
|
||||||
|
|
||||||
|
const r = new TlBinaryReader(map, buf, 8)
|
||||||
|
|
||||||
|
assertEquals(r.long().toString(16), '51e57ac91e83c801')
|
||||||
|
assertEquals(r.uint(), 64)
|
||||||
|
|
||||||
|
const obj: any = r.object()
|
||||||
|
assertEquals(obj._, 'mt_resPQ')
|
||||||
|
})
|
||||||
|
|
||||||
|
Deno.test('TlBinaryWriter', () => {
|
||||||
|
const map = {
|
||||||
|
mt_resPQ: function (w: any, obj: any) {
|
||||||
|
w.uint(85337187)
|
||||||
|
w.bytes(obj.pq)
|
||||||
|
w.vector(w.long, obj.serverPublicKeyFingerprints)
|
||||||
|
},
|
||||||
|
_staticSize: {} as any,
|
||||||
|
}
|
||||||
|
|
||||||
|
const obj = {
|
||||||
|
_: 'mt_resPQ',
|
||||||
|
pq: p.hexDecode('17ED48941A08F981'),
|
||||||
|
serverPublicKeyFingerprints: [Long.fromString('c3b42b026ce86b21', 16)],
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(TlSerializationCounter.countNeededBytes(map, obj), 32)
|
||||||
|
|
||||||
|
const w = TlBinaryWriter.alloc(map, 48)
|
||||||
|
w.long(Long.ZERO)
|
||||||
|
w.long(Long.fromString('51E57AC91E83C801', true, 16)) // messageId
|
||||||
|
w.object(obj)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
p.hexEncode(w.result()),
|
||||||
|
'000000000000000001c8831ec97ae551632416050817ed48941a08f98100000015c4b51c01000000216be86c022bb4c3',
|
||||||
|
)
|
||||||
|
})
|
43
e2e/deno/tests/packaging/tl-schema.ts
Normal file
43
e2e/deno/tests/packaging/tl-schema.ts
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import { assertEquals } from 'https://deno.land/std@0.223.0/assert/mod.ts'
|
||||||
|
|
||||||
|
import { Long } from '@mtcute/core'
|
||||||
|
import { setPlatform } from '@mtcute/core/platform.js'
|
||||||
|
import { tl } from '@mtcute/tl'
|
||||||
|
import { __tlReaderMap } from '@mtcute/tl/binary/reader.js'
|
||||||
|
import { __tlWriterMap } from '@mtcute/tl/binary/writer.js'
|
||||||
|
import { TlBinaryReader, TlBinaryWriter } from '@mtcute/tl-runtime'
|
||||||
|
import { WebPlatform } from '@mtcute/web'
|
||||||
|
|
||||||
|
// here we primarily want to check that @mtcute/tl correctly works with @mtcute/tl-runtime
|
||||||
|
|
||||||
|
const p = new WebPlatform()
|
||||||
|
setPlatform(p)
|
||||||
|
|
||||||
|
Deno.test('@mtcute/tl', async (t) => {
|
||||||
|
await t.step('writers map works with TlBinaryWriter', () => {
|
||||||
|
const obj = {
|
||||||
|
_: 'inputPeerUser',
|
||||||
|
userId: 123,
|
||||||
|
accessHash: Long.fromNumber(456),
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
p.hexEncode(TlBinaryWriter.serializeObject(__tlWriterMap, obj)),
|
||||||
|
'4ca5e8dd7b00000000000000c801000000000000',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await t.step('readers map works with TlBinaryReader', () => {
|
||||||
|
const buf = p.hexDecode('4ca5e8dd7b00000000000000c801000000000000')
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const obj = TlBinaryReader.deserializeObject<any>(__tlReaderMap, buf)
|
||||||
|
|
||||||
|
assertEquals(obj._, 'inputPeerUser')
|
||||||
|
assertEquals(obj.userId, 123)
|
||||||
|
assertEquals(obj.accessHash.toString(), '456')
|
||||||
|
})
|
||||||
|
|
||||||
|
await t.step('correctly checks for combinator types', () => {
|
||||||
|
assertEquals(tl.isAnyInputUser({ _: 'inputUserEmpty' }), true)
|
||||||
|
})
|
||||||
|
})
|
25
e2e/deno/tests/packaging/wasm.ts
Normal file
25
e2e/deno/tests/packaging/wasm.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { assertEquals } from 'https://deno.land/std@0.223.0/assert/mod.ts'
|
||||||
|
|
||||||
|
import { ige256Decrypt, ige256Encrypt } from '@mtcute/wasm'
|
||||||
|
import { WebCryptoProvider, WebPlatform } from '@mtcute/web'
|
||||||
|
|
||||||
|
await new WebCryptoProvider().initialize()
|
||||||
|
const platform = new WebPlatform()
|
||||||
|
|
||||||
|
Deno.test('@mtcute/wasm', async (t) => {
|
||||||
|
const key = platform.hexDecode('5468697320697320616E20696D706C655468697320697320616E20696D706C65')
|
||||||
|
const iv = platform.hexDecode('6D656E746174696F6E206F6620494745206D6F646520666F72204F70656E5353')
|
||||||
|
|
||||||
|
const data = platform.hexDecode('99706487a1cde613bc6de0b6f24b1c7aa448c8b9c3403e3467a8cad89340f53b')
|
||||||
|
const dataEnc = platform.hexDecode('792ea8ae577b1a66cb3bd92679b8030ca54ee631976bd3a04547fdcb4639fa69')
|
||||||
|
|
||||||
|
await t.step('should work with Buffers', () => {
|
||||||
|
assertEquals(ige256Encrypt(data, key, iv), dataEnc)
|
||||||
|
assertEquals(ige256Decrypt(dataEnc, key, iv), data)
|
||||||
|
})
|
||||||
|
|
||||||
|
await t.step('should work with Uint8Arrays', () => {
|
||||||
|
assertEquals(ige256Encrypt(data, key, iv), dataEnc)
|
||||||
|
assertEquals(ige256Decrypt(dataEnc, key, iv), data)
|
||||||
|
})
|
||||||
|
})
|
42
e2e/deno/utils.ts
Normal file
42
e2e/deno/utils.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import { MaybePromise, MemoryStorage } from '@mtcute/core'
|
||||||
|
import { setPlatform } from '@mtcute/core/platform.js'
|
||||||
|
import { LogManager, sleep } from '@mtcute/core/utils.js'
|
||||||
|
import { WebCryptoProvider, WebPlatform, WebSocketTransport } from '@mtcute/web'
|
||||||
|
|
||||||
|
export const getApiParams = (storage?: string) => {
|
||||||
|
if (storage) throw new Error('unsupported yet')
|
||||||
|
|
||||||
|
if (!Deno.env.has('API_ID') || !Deno.env.has('API_HASH')) {
|
||||||
|
throw new Error('API_ID and API_HASH env variables must be set')
|
||||||
|
}
|
||||||
|
|
||||||
|
setPlatform(new WebPlatform())
|
||||||
|
|
||||||
|
return {
|
||||||
|
apiId: parseInt(Deno.env.get('API_ID')!),
|
||||||
|
apiHash: Deno.env.get('API_HASH')!,
|
||||||
|
testMode: true,
|
||||||
|
storage: new MemoryStorage(),
|
||||||
|
logLevel: LogManager.VERBOSE,
|
||||||
|
transport: () => new WebSocketTransport(),
|
||||||
|
crypto: new WebCryptoProvider(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function waitFor(condition: () => MaybePromise<void>, timeout = 5000): Promise<void> {
|
||||||
|
const start = Date.now()
|
||||||
|
let lastError
|
||||||
|
|
||||||
|
while (Date.now() - start < timeout) {
|
||||||
|
try {
|
||||||
|
await condition()
|
||||||
|
|
||||||
|
return
|
||||||
|
} catch (e) {
|
||||||
|
lastError = e
|
||||||
|
await sleep(100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw lastError
|
||||||
|
}
|
5
e2e/node/.env.example
Normal file
5
e2e/node/.env.example
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# obtain these values from my.telegram.org
|
||||||
|
API_ID=
|
||||||
|
API_HASH=
|
||||||
|
|
||||||
|
GITHUB_TOKEN=
|
0
e2e/.gitignore → e2e/node/.gitignore
vendored
0
e2e/.gitignore → e2e/node/.gitignore
vendored
|
@ -5,7 +5,7 @@ RUN apk add python3 make g++ && \
|
||||||
corepack enable && \
|
corepack enable && \
|
||||||
corepack prepare pnpm@8.7.1 --activate
|
corepack prepare pnpm@8.7.1 --activate
|
||||||
|
|
||||||
COPY ../ /app/
|
COPY ../.. /app/
|
||||||
|
|
||||||
RUN pnpm install --frozen-lockfile && \
|
RUN pnpm install --frozen-lockfile && \
|
||||||
pnpm -C packages/tl run gen-code && \
|
pnpm -C packages/tl run gen-code && \
|
|
@ -12,8 +12,8 @@ services:
|
||||||
- mtcute-e2e
|
- mtcute-e2e
|
||||||
build:
|
build:
|
||||||
build:
|
build:
|
||||||
context: ..
|
context: ../..
|
||||||
dockerfile: e2e/Dockerfile.build
|
dockerfile: e2e/node/Dockerfile.build
|
||||||
environment:
|
environment:
|
||||||
- GITHUB_TOKEN=${GITHUB_TOKEN}
|
- GITHUB_TOKEN=${GITHUB_TOKEN}
|
||||||
networks:
|
networks:
|
|
@ -18,7 +18,9 @@ execSync(`npm config set //${REGISTRY.replace(/^https?:\/\//, '')}/:_authToken $
|
||||||
const commit = CURRENT_COMMIT.slice(0, 7)
|
const commit = CURRENT_COMMIT.slice(0, 7)
|
||||||
|
|
||||||
const myPkgJson = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'))
|
const myPkgJson = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'))
|
||||||
const packages = Object.keys(myPkgJson.dependencies).filter((x) => x.startsWith('@mtcute/')).map((x) => x.slice('@mtcute/'.length))
|
const packages = Object.keys(myPkgJson.dependencies)
|
||||||
|
.filter((x) => x.startsWith('@mtcute/'))
|
||||||
|
.map((x) => x.slice('@mtcute/'.length))
|
||||||
|
|
||||||
const workDir = path.join(__dirname, 'temp')
|
const workDir = path.join(__dirname, 'temp')
|
||||||
fs.mkdirSync(workDir, { recursive: true })
|
fs.mkdirSync(workDir, { recursive: true })
|
|
@ -35,6 +35,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "17.6.5",
|
"@commitlint/cli": "17.6.5",
|
||||||
"@commitlint/config-conventional": "17.6.5",
|
"@commitlint/config-conventional": "17.6.5",
|
||||||
|
"@teidesu/slow-types-compiler": "1.0.2",
|
||||||
"@types/node": "20.10.0",
|
"@types/node": "20.10.0",
|
||||||
"@types/ws": "8.5.4",
|
"@types/ws": "8.5.4",
|
||||||
"@typescript-eslint/eslint-plugin": "6.4.0",
|
"@typescript-eslint/eslint-plugin": "6.4.0",
|
||||||
|
@ -62,8 +63,8 @@
|
||||||
"semver": "7.5.1",
|
"semver": "7.5.1",
|
||||||
"ts-node": "10.9.1",
|
"ts-node": "10.9.1",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"typedoc": "0.25.3",
|
"typedoc": "0.25.12",
|
||||||
"typescript": "5.1.6",
|
"typescript": "5.4.3",
|
||||||
"vite": "5.1.6",
|
"vite": "5.1.6",
|
||||||
"vite-plugin-node-polyfills": "0.21.0",
|
"vite-plugin-node-polyfills": "0.21.0",
|
||||||
"vitest": "1.4.0"
|
"vitest": "1.4.0"
|
||||||
|
|
|
@ -16,12 +16,6 @@
|
||||||
".": "./src/index.ts",
|
".": "./src/index.ts",
|
||||||
"./utils.js": "./src/utils.ts"
|
"./utils.js": "./src/utils.ts"
|
||||||
},
|
},
|
||||||
"distOnlyFields": {
|
|
||||||
"exports": {
|
|
||||||
".": "./index.js",
|
|
||||||
"./utils.js": "./utils.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mtcute/core": "workspace:^",
|
"@mtcute/core": "workspace:^",
|
||||||
"@mtcute/wasm": "workspace:^",
|
"@mtcute/wasm": "workspace:^",
|
||||||
|
|
|
@ -11,14 +11,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "pnpm run -w build-package convert"
|
"build": "pnpm run -w build-package convert"
|
||||||
},
|
},
|
||||||
"distOnlyFields": {
|
"exports": "./src/index.ts",
|
||||||
"exports": {
|
|
||||||
".": {
|
|
||||||
"import": "./esm/index.js",
|
|
||||||
"require": "./cjs/index.js"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mtcute/core": "workspace:^"
|
"@mtcute/core": "workspace:^"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,20 +1,27 @@
|
||||||
const KNOWN_DECORATORS = ['memoizeGetters', 'makeInspectable']
|
const KNOWN_DECORATORS = ['memoizeGetters', 'makeInspectable']
|
||||||
|
|
||||||
module.exports = ({ path, glob, transformFile, packageDir, outDir }) => ({
|
module.exports = ({ path, glob, transformFile, packageDir, outDir, jsr }) => ({
|
||||||
esmOnlyDirectives: true,
|
esmOnlyDirectives: true,
|
||||||
esmImportDirectives: true,
|
esmImportDirectives: true,
|
||||||
final() {
|
final() {
|
||||||
const version = require(path.join(packageDir, 'package.json')).version
|
const version = require(path.join(packageDir, 'package.json')).version
|
||||||
const replaceVersion = (content) => content.replace('%VERSION%', version)
|
const replaceVersion = (content) => content.replace('%VERSION%', version)
|
||||||
|
|
||||||
transformFile(path.join(outDir, 'cjs/network/network-manager.js'), replaceVersion)
|
if (jsr) {
|
||||||
transformFile(path.join(outDir, 'esm/network/network-manager.js'), replaceVersion)
|
transformFile(path.join(outDir, 'network/network-manager.ts'), replaceVersion)
|
||||||
|
} else {
|
||||||
|
transformFile(path.join(outDir, 'cjs/network/network-manager.js'), replaceVersion)
|
||||||
|
transformFile(path.join(outDir, 'esm/network/network-manager.js'), replaceVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jsr) return
|
||||||
|
|
||||||
// make decorators properly tree-shakeable
|
// make decorators properly tree-shakeable
|
||||||
// very fragile, but it works for now :D
|
// very fragile, but it works for now :D
|
||||||
|
// skip for jsr for now because types aren't resolved correctly and it breaks everything (TODO: fix this)
|
||||||
const decoratorsRegex = new RegExp(
|
const decoratorsRegex = new RegExp(
|
||||||
`(${KNOWN_DECORATORS.join('|')})\\((.+?)\\);`,
|
`(${KNOWN_DECORATORS.join('|')})\\((.+?)\\)(?:;|$)`,
|
||||||
'gs',
|
'gsm',
|
||||||
)
|
)
|
||||||
|
|
||||||
const replaceDecorators = (content, file) => {
|
const replaceDecorators = (content, file) => {
|
||||||
|
@ -57,7 +64,9 @@ module.exports = ({ path, glob, transformFile, packageDir, outDir }) => ({
|
||||||
return content + '\n' + customExports.join('\n') + '\n'
|
return content + '\n' + customExports.join('\n') + '\n'
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const f of glob.sync(path.join(outDir, 'esm/highlevel/types/**/*.js'))) {
|
const globSrc = path.join(outDir, jsr ? 'highlevel/types/**/*.ts' : 'esm/highlevel/types/**/*.js')
|
||||||
|
|
||||||
|
for (const f of glob.sync(globSrc)) {
|
||||||
transformFile(f, replaceDecorators)
|
transformFile(f, replaceDecorators)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -21,34 +21,6 @@
|
||||||
"./methods.js": "./src/highlevel/methods.ts",
|
"./methods.js": "./src/highlevel/methods.ts",
|
||||||
"./platform.js": "./src/platform.ts"
|
"./platform.js": "./src/platform.ts"
|
||||||
},
|
},
|
||||||
"distOnlyFields": {
|
|
||||||
"exports": {
|
|
||||||
".": {
|
|
||||||
"import": "./esm/index.js",
|
|
||||||
"require": "./cjs/index.js"
|
|
||||||
},
|
|
||||||
"./utils.js": {
|
|
||||||
"import": "./esm/utils/index.js",
|
|
||||||
"require": "./cjs/utils/index.js"
|
|
||||||
},
|
|
||||||
"./methods.js": {
|
|
||||||
"import": "./esm/highlevel/methods.js",
|
|
||||||
"require": "./cjs/highlevel/methods.js"
|
|
||||||
},
|
|
||||||
"./platform.js": {
|
|
||||||
"import": "./esm/platform.js",
|
|
||||||
"require": "./cjs/platform.js"
|
|
||||||
},
|
|
||||||
"./client.js": {
|
|
||||||
"import": "./esm/highlevel/client.js",
|
|
||||||
"require": "./cjs/highlevel/client.js"
|
|
||||||
},
|
|
||||||
"./worker.js": {
|
|
||||||
"import": "./esm/highlevel/worker/index.js",
|
|
||||||
"require": "./cjs/highlevel/worker/index.js"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mtcute/tl": "workspace:^",
|
"@mtcute/tl": "workspace:^",
|
||||||
"@mtcute/tl-runtime": "workspace:^",
|
"@mtcute/tl-runtime": "workspace:^",
|
||||||
|
|
|
@ -33,7 +33,18 @@ export class BaseTelegramClient implements ITelegramClient {
|
||||||
private _serverUpdatesHandler: (updates: tl.TypeUpdates) => void = () => {}
|
private _serverUpdatesHandler: (updates: tl.TypeUpdates) => void = () => {}
|
||||||
private _connectionStateHandler: (state: ConnectionState) => void = () => {}
|
private _connectionStateHandler: (state: ConnectionState) => void = () => {}
|
||||||
|
|
||||||
|
readonly log
|
||||||
|
readonly mt
|
||||||
|
readonly crypto
|
||||||
|
readonly storage
|
||||||
|
|
||||||
constructor(readonly params: BaseTelegramClientOptions) {
|
constructor(readonly params: BaseTelegramClientOptions) {
|
||||||
|
this.log = this.params.logger ?? new LogManager('client')
|
||||||
|
this.mt = new MtClient({
|
||||||
|
...this.params,
|
||||||
|
logger: this.log.create('mtproto'),
|
||||||
|
})
|
||||||
|
|
||||||
if (!params.disableUpdates && params.updates !== false) {
|
if (!params.disableUpdates && params.updates !== false) {
|
||||||
this.updates = new UpdatesManager(this, params.updates)
|
this.updates = new UpdatesManager(this, params.updates)
|
||||||
this._serverUpdatesHandler = this.updates.handleUpdate.bind(this.updates)
|
this._serverUpdatesHandler = this.updates.handleUpdate.bind(this.updates)
|
||||||
|
@ -56,18 +67,13 @@ export class BaseTelegramClient implements ITelegramClient {
|
||||||
this._connectionStateHandler('offline')
|
this._connectionStateHandler('offline')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
readonly log = this.params.logger ?? new LogManager('client')
|
this.crypto = this.mt.crypto
|
||||||
readonly mt = new MtClient({
|
this.storage = new TelegramStorageManager(this.mt.storage, {
|
||||||
...this.params,
|
provider: this.params.storage,
|
||||||
logger: this.log.create('mtproto'),
|
...this.params.storageOptions,
|
||||||
})
|
})
|
||||||
readonly crypto = this.mt.crypto
|
}
|
||||||
readonly storage = new TelegramStorageManager(this.mt.storage, {
|
|
||||||
provider: this.params.storage,
|
|
||||||
...this.params.storageOptions,
|
|
||||||
})
|
|
||||||
readonly appConfig = new AppConfigManager(this)
|
readonly appConfig = new AppConfigManager(this)
|
||||||
|
|
||||||
private _prepare = asyncResettable(async () => {
|
private _prepare = asyncResettable(async () => {
|
||||||
|
|
|
@ -2285,7 +2285,10 @@ export interface TelegramClient extends ITelegramClient {
|
||||||
*
|
*
|
||||||
* @param params File download parameters
|
* @param params File download parameters
|
||||||
*/
|
*/
|
||||||
downloadAsNodeStream(location: FileDownloadLocation, params?: FileDownloadParameters): import('stream').Readable
|
downloadAsNodeStream(
|
||||||
|
location: FileDownloadLocation,
|
||||||
|
params?: FileDownloadParameters,
|
||||||
|
): import('node:stream').Readable
|
||||||
/**
|
/**
|
||||||
* Download a file and return it as a readable stream,
|
* Download a file and return it as a readable stream,
|
||||||
* streaming file contents.
|
* streaming file contents.
|
||||||
|
|
|
@ -66,7 +66,7 @@ export async function startTest(
|
||||||
let dcId = await client.getPrimaryDcId()
|
let dcId = await client.getPrimaryDcId()
|
||||||
|
|
||||||
if (params.dcId) {
|
if (params.dcId) {
|
||||||
if (!availableDcs.find((dc) => dc.id === params!.dcId)) {
|
if (!availableDcs.find((dc) => dc.id === params.dcId)) {
|
||||||
throw new MtArgumentError(`DC ID is invalid (${dcId})`)
|
throw new MtArgumentError(`DC ID is invalid (${dcId})`)
|
||||||
}
|
}
|
||||||
dcId = params.dcId
|
dcId = params.dcId
|
||||||
|
@ -85,7 +85,7 @@ export async function startTest(
|
||||||
code: () => code,
|
code: () => code,
|
||||||
codeSentCallback: (sent) => {
|
codeSentCallback: (sent) => {
|
||||||
for (let i = 0; i < sent.length; i++) {
|
for (let i = 0; i < sent.length; i++) {
|
||||||
code += phone![5]
|
code += phone[5]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -13,4 +13,4 @@ declare function downloadAsNodeStream(
|
||||||
client: ITelegramClient,
|
client: ITelegramClient,
|
||||||
location: FileDownloadLocation,
|
location: FileDownloadLocation,
|
||||||
params?: FileDownloadParameters,
|
params?: FileDownloadParameters,
|
||||||
): import('stream').Readable
|
): import('node:stream').Readable
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { ServiceOptions } from '../../storage/service/base.js'
|
||||||
import { StorageManager } from '../../storage/storage.js'
|
import { StorageManager } from '../../storage/storage.js'
|
||||||
import { PublicPart } from '../../types/utils.js'
|
import { PublicPart } from '../../types/utils.js'
|
||||||
import { ITelegramStorageProvider } from './provider.js'
|
import { ITelegramStorageProvider } from './provider.js'
|
||||||
|
@ -17,26 +18,40 @@ export interface TelegramStorageManagerExtraOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TelegramStorageManager {
|
export class TelegramStorageManager {
|
||||||
|
private provider
|
||||||
|
|
||||||
|
readonly updates
|
||||||
|
readonly self: PublicPart<CurrentUserService>
|
||||||
|
readonly refMsgs
|
||||||
|
readonly peers: PublicPart<PeersService>
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private mt: StorageManager,
|
private mt: StorageManager,
|
||||||
private options: TelegramStorageManagerOptions & TelegramStorageManagerExtraOptions,
|
private options: TelegramStorageManagerOptions & TelegramStorageManagerExtraOptions,
|
||||||
) {}
|
) {
|
||||||
|
this.provider = this.options.provider
|
||||||
|
|
||||||
private provider = this.options.provider
|
const serviceOptions: ServiceOptions = {
|
||||||
|
driver: this.mt.driver,
|
||||||
|
readerMap: this.mt.options.readerMap,
|
||||||
|
writerMap: this.mt.options.writerMap,
|
||||||
|
log: this.mt.log,
|
||||||
|
}
|
||||||
|
|
||||||
readonly updates = new UpdatesStateService(this.provider.kv, this.mt._serviceOptions)
|
this.updates = new UpdatesStateService(this.provider.kv, serviceOptions)
|
||||||
readonly self: PublicPart<CurrentUserService> = new CurrentUserService(this.provider.kv, this.mt._serviceOptions)
|
this.self = new CurrentUserService(this.provider.kv, serviceOptions)
|
||||||
readonly refMsgs = new RefMessagesService(
|
this.refMsgs = new RefMessagesService(
|
||||||
this.options.refMessages ?? {},
|
this.options.refMessages ?? {},
|
||||||
this.provider.refMessages,
|
this.provider.refMessages,
|
||||||
this.mt._serviceOptions,
|
serviceOptions,
|
||||||
)
|
)
|
||||||
readonly peers: PublicPart<PeersService> = new PeersService(
|
this.peers = new PeersService(
|
||||||
this.options.peers ?? {},
|
this.options.peers ?? {},
|
||||||
this.provider.peers,
|
this.provider.peers,
|
||||||
this.refMsgs,
|
this.refMsgs,
|
||||||
this.mt._serviceOptions,
|
serviceOptions,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
async clear(withAuthKeys = false) {
|
async clear(withAuthKeys = false) {
|
||||||
await this.provider.peers.deleteAll()
|
await this.provider.peers.deleteAll()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* eslint-disable no-restricted-imports */
|
/* eslint-disable no-restricted-imports */
|
||||||
import type { ReadStream } from 'fs'
|
import type { ReadStream } from 'node:fs'
|
||||||
|
|
||||||
import { tdFileId } from '@mtcute/file-id'
|
import { tdFileId } from '@mtcute/file-id'
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
|
|
|
@ -12,13 +12,14 @@ import { StoriesStealthMode } from './stealth-mode.js'
|
||||||
* Returned by {@link TelegramClient.getAllStories}
|
* Returned by {@link TelegramClient.getAllStories}
|
||||||
*/
|
*/
|
||||||
export class AllStories {
|
export class AllStories {
|
||||||
|
/** Peers index */
|
||||||
|
readonly _peers
|
||||||
constructor(
|
constructor(
|
||||||
/** Raw TL object */
|
/** Raw TL object */
|
||||||
readonly raw: tl.stories.RawAllStories,
|
readonly raw: tl.stories.RawAllStories,
|
||||||
) {}
|
) {
|
||||||
|
this._peers = PeersIndex.from(this.raw)
|
||||||
/** Peers index */
|
}
|
||||||
readonly _peers = PeersIndex.from(this.raw)
|
|
||||||
|
|
||||||
/** Whether there are more stories to fetch */
|
/** Whether there are more stories to fetch */
|
||||||
get hasMore(): boolean {
|
get hasMore(): boolean {
|
||||||
|
|
|
@ -129,9 +129,10 @@ export class StoryRepost {
|
||||||
* List of story viewers.
|
* List of story viewers.
|
||||||
*/
|
*/
|
||||||
export class StoryViewersList {
|
export class StoryViewersList {
|
||||||
constructor(readonly raw: tl.stories.RawStoryViewsList) {}
|
readonly _peers: PeersIndex
|
||||||
|
constructor(readonly raw: tl.stories.RawStoryViewsList) {
|
||||||
readonly _peers = PeersIndex.from(this.raw)
|
this._peers = PeersIndex.from(this.raw)
|
||||||
|
}
|
||||||
|
|
||||||
/** Next offset for pagination */
|
/** Next offset for pagination */
|
||||||
get next(): string | undefined {
|
get next(): string | undefined {
|
||||||
|
|
|
@ -100,7 +100,7 @@ export class UpdatesManager {
|
||||||
pendingQtsUpdatesPostponed = new SortedLinkedList<PendingUpdate>((a, b) => a.qtsBefore! - b.qtsBefore!)
|
pendingQtsUpdatesPostponed = new SortedLinkedList<PendingUpdate>((a, b) => a.qtsBefore! - b.qtsBefore!)
|
||||||
pendingUnorderedUpdates = new Deque<PendingUpdate>()
|
pendingUnorderedUpdates = new Deque<PendingUpdate>()
|
||||||
|
|
||||||
noDispatchEnabled = !this.params.disableNoDispatch
|
noDispatchEnabled
|
||||||
// channel id or 0 => msg id
|
// channel id or 0 => msg id
|
||||||
noDispatchMsg = new Map<number, Set<number>>()
|
noDispatchMsg = new Map<number, Set<number>>()
|
||||||
// channel id or 0 => pts
|
// channel id or 0 => pts
|
||||||
|
@ -128,14 +128,14 @@ export class UpdatesManager {
|
||||||
|
|
||||||
// whether to catch up channels from the locally stored pts
|
// whether to catch up channels from the locally stored pts
|
||||||
catchingUp = false
|
catchingUp = false
|
||||||
catchUpOnStart = this.params.catchUp ?? false
|
catchUpOnStart
|
||||||
|
|
||||||
cpts = new Map<number, number>()
|
cpts = new Map<number, number>()
|
||||||
cptsMod = new Map<number, number>()
|
cptsMod = new Map<number, number>()
|
||||||
channelDiffTimeouts = new Map<number, NodeJS.Timeout>()
|
channelDiffTimeouts = new Map<number, NodeJS.Timeout>()
|
||||||
channelsOpened = new Map<number, number>()
|
channelsOpened = new Map<number, number>()
|
||||||
|
|
||||||
log = this.client.log.create('updates')
|
log
|
||||||
private _handler: RawUpdateHandler = () => {}
|
private _handler: RawUpdateHandler = () => {}
|
||||||
|
|
||||||
private _onCatchingUp: (catchingUp: boolean) => void = () => {}
|
private _onCatchingUp: (catchingUp: boolean) => void = () => {}
|
||||||
|
@ -157,6 +157,9 @@ export class UpdatesManager {
|
||||||
this.hasTimedoutPostponed = true
|
this.hasTimedoutPostponed = true
|
||||||
this.updatesLoopCv.notify()
|
this.updatesLoopCv.notify()
|
||||||
})
|
})
|
||||||
|
this.log = client.log.create('updates')
|
||||||
|
this.catchUpOnStart = params.catchUp ?? false
|
||||||
|
this.noDispatchEnabled = !params.disableNoDispatch
|
||||||
}
|
}
|
||||||
|
|
||||||
setHandler(handler: RawUpdateHandler): void {
|
setHandler(handler: RawUpdateHandler): void {
|
||||||
|
|
|
@ -32,27 +32,28 @@ export function isProbablyPlainText(buf: Uint8Array): boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
// from https://github.com/telegramdesktop/tdesktop/blob/bec39d89e19670eb436dc794a8f20b657cb87c71/Telegram/SourceFiles/ui/image/image.cpp#L225
|
// from https://github.com/telegramdesktop/tdesktop/blob/bec39d89e19670eb436dc794a8f20b657cb87c71/Telegram/SourceFiles/ui/image/image.cpp#L225
|
||||||
const JPEG_HEADER = () => getPlatform().hexDecode(
|
const JPEG_HEADER = () =>
|
||||||
'ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e1928' +
|
getPlatform().hexDecode(
|
||||||
'2321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a0aad' +
|
'ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e1928' +
|
||||||
'aad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2d2d3c35' +
|
'2321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a0aad' +
|
||||||
'3c76414176f8a58ca5f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f' +
|
'aad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2d2d3c35' +
|
||||||
'8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8ffc0001108000000' +
|
'3c76414176f8a58ca5f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f' +
|
||||||
'0003012200021101031101ffc4001f0000010501010101010100000000000000000' +
|
'8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8ffc0001108000000' +
|
||||||
'102030405060708090a0bffc400b5100002010303020403050504040000017d0102' +
|
'0003012200021101031101ffc4001f0000010501010101010100000000000000000' +
|
||||||
'0300041105122131410613516107227114328191a1082342b1c11552d1f02433627' +
|
'102030405060708090a0bffc400b5100002010303020403050504040000017d0102' +
|
||||||
'282090a161718191a25262728292a3435363738393a434445464748494a53545556' +
|
'0300041105122131410613516107227114328191a1082342b1c11552d1f02433627' +
|
||||||
'5758595a636465666768696a737475767778797a838485868788898a92939495969' +
|
'282090a161718191a25262728292a3435363738393a434445464748494a53545556' +
|
||||||
'798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4' +
|
'5758595a636465666768696a737475767778797a838485868788898a92939495969' +
|
||||||
'd5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030' +
|
'798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4' +
|
||||||
'101010101010101010000000000000102030405060708090a0bffc400b511000201' +
|
'd5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030' +
|
||||||
'0204040304070504040001027700010203110405213106124151076171132232810' +
|
'101010101010101010000000000000102030405060708090a0bffc400b511000201' +
|
||||||
'8144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a3536' +
|
'0204040304070504040001027700010203110405213106124151076171132232810' +
|
||||||
'3738393a434445464748494a535455565758595a636465666768696a73747576777' +
|
'8144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a3536' +
|
||||||
'8797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5' +
|
'3738393a434445464748494a535455565758595a636465666768696a73747576777' +
|
||||||
'b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f' +
|
'8797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5' +
|
||||||
'3f4f5f6f7f8f9faffda000c03010002110311003f00',
|
'b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f' +
|
||||||
)
|
'3f4f5f6f7f8f9faffda000c03010002110311003f00',
|
||||||
|
)
|
||||||
let JPEG_HEADER_BYTES: Uint8Array | null = null
|
let JPEG_HEADER_BYTES: Uint8Array | null = null
|
||||||
const JPEG_FOOTER = new Uint8Array([0xff, 0xd9])
|
const JPEG_FOOTER = new Uint8Array([0xff, 0xd9])
|
||||||
|
|
||||||
|
@ -131,5 +132,5 @@ export function svgPathToFile(path: string): Uint8Array {
|
||||||
export function extractFileName(path: string): string {
|
export function extractFileName(path: string): string {
|
||||||
if (path.startsWith('file:')) path = path.slice(5)
|
if (path.startsWith('file:')) path = path.slice(5)
|
||||||
|
|
||||||
return path.split(/[\\/]/).pop()!
|
return path.split(/[\\/]/).pop()!.split('?')[0]
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,7 @@ export function batchedQuery<T, U, K extends string | number>(params: {
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise<U | null>((resolve, reject) => {
|
return new Promise<U | null>((resolve, reject) => {
|
||||||
arr!.push([item, resolve, reject])
|
arr.push([item, resolve, reject])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,13 @@ import { AppConfigManager } from '../managers/app-config-manager.js'
|
||||||
import { WorkerInvoker } from './invoker.js'
|
import { WorkerInvoker } from './invoker.js'
|
||||||
|
|
||||||
export class AppConfigManagerProxy implements PublicPart<AppConfigManager> {
|
export class AppConfigManagerProxy implements PublicPart<AppConfigManager> {
|
||||||
constructor(readonly invoker: WorkerInvoker) {}
|
readonly get: AppConfigManager['get']
|
||||||
|
readonly getField
|
||||||
|
|
||||||
private _bind = this.invoker.makeBinder<AppConfigManager>('app-config')
|
constructor(readonly invoker: WorkerInvoker) {
|
||||||
|
const bind = invoker.makeBinder<AppConfigManager>('app-config')
|
||||||
|
|
||||||
readonly get = this._bind('get')
|
this.get = bind('get')
|
||||||
readonly getField = this._bind('getField')
|
this.getField = bind('getField')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,12 +14,68 @@ export interface TelegramWorkerPortOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class TelegramWorkerPort<Custom extends WorkerCustomMethods> implements ITelegramClient {
|
export abstract class TelegramWorkerPort<Custom extends WorkerCustomMethods> implements ITelegramClient {
|
||||||
constructor(readonly options: TelegramWorkerPortOptions) {}
|
readonly log
|
||||||
|
|
||||||
|
private _connection
|
||||||
|
private _invoker
|
||||||
|
|
||||||
|
readonly storage
|
||||||
|
readonly appConfig
|
||||||
|
|
||||||
|
// bound methods
|
||||||
|
readonly prepare
|
||||||
|
private _connect
|
||||||
|
readonly close
|
||||||
|
readonly notifyLoggedIn
|
||||||
|
readonly notifyLoggedOut
|
||||||
|
readonly notifyChannelOpened
|
||||||
|
readonly notifyChannelClosed
|
||||||
|
readonly call
|
||||||
|
readonly importSession
|
||||||
|
readonly exportSession
|
||||||
|
readonly handleClientUpdate
|
||||||
|
readonly getApiCrenetials
|
||||||
|
readonly getPoolSize
|
||||||
|
readonly getPrimaryDcId
|
||||||
|
readonly computeSrpParams
|
||||||
|
readonly computeNewPasswordHash
|
||||||
|
readonly startUpdatesLoop
|
||||||
|
readonly stopUpdatesLoop
|
||||||
|
|
||||||
|
constructor(readonly options: TelegramWorkerPortOptions) {
|
||||||
|
this.log = new LogManager('worker')
|
||||||
|
|
||||||
|
this._connection = this.connectToWorker(this.options.worker, this._onMessage)
|
||||||
|
this._invoker = new WorkerInvoker(this._connection[0])
|
||||||
|
|
||||||
|
this.storage = new TelegramStorageProxy(this._invoker)
|
||||||
|
this.appConfig = new AppConfigManagerProxy(this._invoker)
|
||||||
|
|
||||||
|
const bind = this._invoker.makeBinder<ITelegramClient>('client')
|
||||||
|
|
||||||
|
this.prepare = bind('prepare')
|
||||||
|
this._connect = bind('connect')
|
||||||
|
|
||||||
|
this.close = bind('close')
|
||||||
|
this.notifyLoggedIn = bind('notifyLoggedIn')
|
||||||
|
this.notifyLoggedOut = bind('notifyLoggedOut')
|
||||||
|
this.notifyChannelOpened = bind('notifyChannelOpened')
|
||||||
|
this.notifyChannelClosed = bind('notifyChannelClosed')
|
||||||
|
this.call = bind('call')
|
||||||
|
this.importSession = bind('importSession')
|
||||||
|
this.exportSession = bind('exportSession')
|
||||||
|
this.handleClientUpdate = bind('handleClientUpdate', true)
|
||||||
|
this.getApiCrenetials = bind('getApiCrenetials')
|
||||||
|
this.getPoolSize = bind('getPoolSize')
|
||||||
|
this.getPrimaryDcId = bind('getPrimaryDcId')
|
||||||
|
this.computeSrpParams = bind('computeSrpParams')
|
||||||
|
this.computeNewPasswordHash = bind('computeNewPasswordHash')
|
||||||
|
this.startUpdatesLoop = bind('startUpdatesLoop')
|
||||||
|
this.stopUpdatesLoop = bind('stopUpdatesLoop')
|
||||||
|
}
|
||||||
|
|
||||||
abstract connectToWorker(worker: SomeWorker, handler: ClientMessageHandler): [SendFn, () => void]
|
abstract connectToWorker(worker: SomeWorker, handler: ClientMessageHandler): [SendFn, () => void]
|
||||||
|
|
||||||
readonly log = new LogManager('worker')
|
|
||||||
|
|
||||||
private _serverUpdatesHandler: (updates: tl.TypeUpdates) => void = () => {}
|
private _serverUpdatesHandler: (updates: tl.TypeUpdates) => void = () => {}
|
||||||
onServerUpdate(handler: (updates: tl.TypeUpdates) => void): void {
|
onServerUpdate(handler: (updates: tl.TypeUpdates) => void): void {
|
||||||
this._serverUpdatesHandler = handler
|
this._serverUpdatesHandler = handler
|
||||||
|
@ -69,13 +125,6 @@ export abstract class TelegramWorkerPort<Custom extends WorkerCustomMethods> imp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _connection = this.connectToWorker(this.options.worker, this._onMessage)
|
|
||||||
private _invoker = new WorkerInvoker(this._connection[0])
|
|
||||||
private _bind = this._invoker.makeBinder<ITelegramClient>('client')
|
|
||||||
|
|
||||||
readonly storage = new TelegramStorageProxy(this._invoker)
|
|
||||||
readonly appConfig = new AppConfigManagerProxy(this._invoker)
|
|
||||||
|
|
||||||
private _destroyed = false
|
private _destroyed = false
|
||||||
destroy(terminate = false): void {
|
destroy(terminate = false): void {
|
||||||
if (this._destroyed) return
|
if (this._destroyed) return
|
||||||
|
@ -91,26 +140,8 @@ export abstract class TelegramWorkerPort<Custom extends WorkerCustomMethods> imp
|
||||||
return this._invoker.invoke('custom', method as string, args) as Promise<ReturnType<Custom[T]>>
|
return this._invoker.invoke('custom', method as string, args) as Promise<ReturnType<Custom[T]>>
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly prepare = this._bind('prepare')
|
|
||||||
private _connect = this._bind('connect')
|
|
||||||
async connect(): Promise<void> {
|
async connect(): Promise<void> {
|
||||||
await this._connect()
|
await this._connect()
|
||||||
await this.storage.self.fetch() // force cache self locally
|
await this.storage.self.fetch() // force cache self locally
|
||||||
}
|
}
|
||||||
readonly close = this._bind('close')
|
|
||||||
readonly notifyLoggedIn = this._bind('notifyLoggedIn')
|
|
||||||
readonly notifyLoggedOut = this._bind('notifyLoggedOut')
|
|
||||||
readonly notifyChannelOpened = this._bind('notifyChannelOpened')
|
|
||||||
readonly notifyChannelClosed = this._bind('notifyChannelClosed')
|
|
||||||
readonly call = this._bind('call')
|
|
||||||
readonly importSession = this._bind('importSession')
|
|
||||||
readonly exportSession = this._bind('exportSession')
|
|
||||||
readonly handleClientUpdate = this._bind('handleClientUpdate', true)
|
|
||||||
readonly getApiCrenetials = this._bind('getApiCrenetials')
|
|
||||||
readonly getPoolSize = this._bind('getPoolSize')
|
|
||||||
readonly getPrimaryDcId = this._bind('getPrimaryDcId')
|
|
||||||
readonly computeSrpParams = this._bind('computeSrpParams')
|
|
||||||
readonly computeNewPasswordHash = this._bind('computeNewPasswordHash')
|
|
||||||
readonly startUpdatesLoop = this._bind('startUpdatesLoop')
|
|
||||||
readonly stopUpdatesLoop = this._bind('stopUpdatesLoop')
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { Worker as NodeWorker } from 'worker_threads'
|
import type { Worker as NodeWorker } from 'node:worker_threads'
|
||||||
|
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
|
|
||||||
|
|
|
@ -8,25 +8,32 @@ import { TelegramStorageManager } from '../storage/storage.js'
|
||||||
import { WorkerInvoker } from './invoker.js'
|
import { WorkerInvoker } from './invoker.js'
|
||||||
|
|
||||||
class CurrentUserServiceProxy implements PublicPart<CurrentUserService> {
|
class CurrentUserServiceProxy implements PublicPart<CurrentUserService> {
|
||||||
constructor(private _invoker: WorkerInvoker) {}
|
private _store
|
||||||
private _bind = this._invoker.makeBinder<CurrentUserService>('storage-self')
|
private _storeFrom
|
||||||
|
private _fetch
|
||||||
|
private _update
|
||||||
|
|
||||||
|
constructor(invoker: WorkerInvoker) {
|
||||||
|
const bind = invoker.makeBinder<CurrentUserService>('storage-self')
|
||||||
|
this._store = bind('store')
|
||||||
|
this._storeFrom = bind('storeFrom')
|
||||||
|
this._fetch = bind('fetch')
|
||||||
|
this._update = bind('update')
|
||||||
|
}
|
||||||
|
|
||||||
private _cached?: CurrentUserInfo | null
|
private _cached?: CurrentUserInfo | null
|
||||||
|
|
||||||
private _store = this._bind('store')
|
|
||||||
async store(info: CurrentUserInfo | null): Promise<void> {
|
async store(info: CurrentUserInfo | null): Promise<void> {
|
||||||
await this._store(info)
|
await this._store(info)
|
||||||
this._cached = info
|
this._cached = info
|
||||||
}
|
}
|
||||||
|
|
||||||
private _storeFrom = this._bind('storeFrom')
|
|
||||||
async storeFrom(user: tl.TypeUser): Promise<CurrentUserInfo> {
|
async storeFrom(user: tl.TypeUser): Promise<CurrentUserInfo> {
|
||||||
this._cached = await this._storeFrom(user)
|
this._cached = await this._storeFrom(user)
|
||||||
|
|
||||||
return this._cached
|
return this._cached
|
||||||
}
|
}
|
||||||
|
|
||||||
private _fetch = this._bind('fetch')
|
|
||||||
async fetch(): Promise<CurrentUserInfo | null> {
|
async fetch(): Promise<CurrentUserInfo | null> {
|
||||||
if (this._cached) return this._cached
|
if (this._cached) return this._cached
|
||||||
|
|
||||||
|
@ -45,7 +52,6 @@ class CurrentUserServiceProxy implements PublicPart<CurrentUserService> {
|
||||||
return this._cached
|
return this._cached
|
||||||
}
|
}
|
||||||
|
|
||||||
private _update = this._bind('update')
|
|
||||||
async update(params: Parameters<CurrentUserService['update']>[0]): Promise<void> {
|
async update(params: Parameters<CurrentUserService['update']>[0]): Promise<void> {
|
||||||
await this._update(params)
|
await this._update(params)
|
||||||
this._cached = await this._fetch()
|
this._cached = await this._fetch()
|
||||||
|
@ -53,28 +59,41 @@ class CurrentUserServiceProxy implements PublicPart<CurrentUserService> {
|
||||||
}
|
}
|
||||||
|
|
||||||
class PeersServiceProxy implements PublicPart<PeersService> {
|
class PeersServiceProxy implements PublicPart<PeersService> {
|
||||||
constructor(private _invoker: WorkerInvoker) {}
|
readonly updatePeersFrom
|
||||||
private _bind = this._invoker.makeBinder<PeersService>('storage-peers')
|
readonly store
|
||||||
|
readonly getById
|
||||||
|
readonly getByPhone
|
||||||
|
readonly getByUsername
|
||||||
|
readonly getCompleteById
|
||||||
|
|
||||||
readonly updatePeersFrom = this._bind('updatePeersFrom')
|
constructor(private _invoker: WorkerInvoker) {
|
||||||
readonly store = this._bind('store')
|
const bind = this._invoker.makeBinder<PeersService>('storage-peers')
|
||||||
readonly getById = this._bind('getById')
|
|
||||||
readonly getByPhone = this._bind('getByPhone')
|
this.updatePeersFrom = bind('updatePeersFrom')
|
||||||
readonly getByUsername = this._bind('getByUsername')
|
this.store = bind('store')
|
||||||
readonly getCompleteById = this._bind('getCompleteById')
|
this.getById = bind('getById')
|
||||||
|
this.getByPhone = bind('getByPhone')
|
||||||
|
this.getByUsername = bind('getByUsername')
|
||||||
|
this.getCompleteById = bind('getCompleteById')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TelegramStorageProxy implements PublicPart<TelegramStorageManager> {
|
export class TelegramStorageProxy implements PublicPart<TelegramStorageManager> {
|
||||||
constructor(private _invoker: WorkerInvoker) {}
|
readonly self
|
||||||
|
readonly peers
|
||||||
|
|
||||||
private _bind = this._invoker.makeBinder<TelegramStorageManager>('storage')
|
readonly clear
|
||||||
|
|
||||||
|
constructor(private _invoker: WorkerInvoker) {
|
||||||
|
const bind = this._invoker.makeBinder<TelegramStorageManager>('storage')
|
||||||
|
|
||||||
|
this.self = new CurrentUserServiceProxy(this._invoker)
|
||||||
|
this.peers = new PeersServiceProxy(this._invoker)
|
||||||
|
|
||||||
|
this.clear = bind('clear')
|
||||||
|
}
|
||||||
|
|
||||||
// todo - remove once we move these to updates manager
|
// todo - remove once we move these to updates manager
|
||||||
readonly updates = null as never
|
readonly updates = null as never
|
||||||
readonly refMsgs = null as never
|
readonly refMsgs = null as never
|
||||||
|
|
||||||
readonly self = new CurrentUserServiceProxy(this._invoker)
|
|
||||||
readonly peers = new PeersServiceProxy(this._invoker)
|
|
||||||
|
|
||||||
readonly clear = this._bind('clear')
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -351,7 +351,7 @@ export class MtClient extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
async close(): Promise<void> {
|
async close(): Promise<void> {
|
||||||
this._config.destroy()
|
this._config.destroy()
|
||||||
this.network.destroy()
|
await this.network.destroy()
|
||||||
|
|
||||||
await this.storage.save()
|
await this.storage.save()
|
||||||
await this.storage.destroy?.()
|
await this.storage.destroy?.()
|
||||||
|
|
|
@ -91,9 +91,9 @@ export type PendingMessage =
|
||||||
export class MtprotoSession {
|
export class MtprotoSession {
|
||||||
_sessionId = randomLong()
|
_sessionId = randomLong()
|
||||||
|
|
||||||
_authKey = new AuthKey(this._crypto, this.log, this._readerMap)
|
_authKey: AuthKey
|
||||||
_authKeyTemp = new AuthKey(this._crypto, this.log, this._readerMap)
|
_authKeyTemp: AuthKey
|
||||||
_authKeyTempSecondary = new AuthKey(this._crypto, this.log, this._readerMap)
|
_authKeyTempSecondary: AuthKey
|
||||||
|
|
||||||
_timeOffset = 0
|
_timeOffset = 0
|
||||||
_lastMessageId = Long.ZERO
|
_lastMessageId = Long.ZERO
|
||||||
|
@ -139,6 +139,10 @@ export class MtprotoSession {
|
||||||
readonly _salts: ServerSaltManager,
|
readonly _salts: ServerSaltManager,
|
||||||
) {
|
) {
|
||||||
this.log.prefix = `[SESSION ${this._sessionId.toString(16)}] `
|
this.log.prefix = `[SESSION ${this._sessionId.toString(16)}] `
|
||||||
|
|
||||||
|
this._authKey = new AuthKey(_crypto, log, _readerMap)
|
||||||
|
this._authKeyTemp = new AuthKey(_crypto, log, _readerMap)
|
||||||
|
this._authKeyTempSecondary = new AuthKey(_crypto, log, _readerMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasPendingMessages(): boolean {
|
get hasPendingMessages(): boolean {
|
||||||
|
|
|
@ -116,7 +116,9 @@ export class MultiSessionConnection extends EventEmitter {
|
||||||
// destroy extra connections
|
// destroy extra connections
|
||||||
for (let i = this._connections.length - 1; i >= this._count; i--) {
|
for (let i = this._connections.length - 1; i >= this._count; i--) {
|
||||||
this._connections[i].removeAllListeners()
|
this._connections[i].removeAllListeners()
|
||||||
this._connections[i].destroy()
|
this._connections[i].destroy().catch((err) => {
|
||||||
|
this._log.warn('error destroying connection: %s', err)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this._connections.splice(this._count)
|
this._connections.splice(this._count)
|
||||||
|
@ -199,8 +201,8 @@ export class MultiSessionConnection extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
_destroyed = false
|
_destroyed = false
|
||||||
destroy(): void {
|
async destroy(): Promise<void> {
|
||||||
this._connections.forEach((conn) => conn.destroy())
|
await Promise.all(this._connections.map((conn) => conn.destroy()))
|
||||||
this._sessions.forEach((sess) => sess.reset())
|
this._sessions.forEach((sess) => sess.reset())
|
||||||
this.removeAllListeners()
|
this.removeAllListeners()
|
||||||
|
|
||||||
|
|
|
@ -188,53 +188,16 @@ export interface RpcCallOptions {
|
||||||
*/
|
*/
|
||||||
export class DcConnectionManager {
|
export class DcConnectionManager {
|
||||||
private _salts = new ServerSaltManager()
|
private _salts = new ServerSaltManager()
|
||||||
private __baseConnectionParams = (): SessionConnectionParams => ({
|
private _log
|
||||||
crypto: this.manager.params.crypto,
|
|
||||||
initConnection: this.manager._initConnectionParams,
|
|
||||||
transportFactory: this.manager._transportFactory,
|
|
||||||
dc: this._dcs.media,
|
|
||||||
testMode: this.manager.params.testMode,
|
|
||||||
reconnectionStrategy: this.manager._reconnectionStrategy,
|
|
||||||
layer: this.manager.params.layer,
|
|
||||||
disableUpdates: this.manager.params.disableUpdates,
|
|
||||||
readerMap: this.manager.params.readerMap,
|
|
||||||
writerMap: this.manager.params.writerMap,
|
|
||||||
usePfs: this.manager.params.usePfs,
|
|
||||||
isMainConnection: false,
|
|
||||||
isMainDcConnection: this.isPrimary,
|
|
||||||
inactivityTimeout: this.manager.params.inactivityTimeout ?? 60_000,
|
|
||||||
enableErrorReporting: this.manager.params.enableErrorReporting,
|
|
||||||
salts: this._salts,
|
|
||||||
})
|
|
||||||
|
|
||||||
private _log = this.manager._log.create('dc-manager')
|
|
||||||
|
|
||||||
/** Main connection pool */
|
/** Main connection pool */
|
||||||
main: MultiSessionConnection
|
main: MultiSessionConnection
|
||||||
|
|
||||||
/** Upload connection pool */
|
/** Upload connection pool */
|
||||||
upload = new MultiSessionConnection(
|
upload: MultiSessionConnection
|
||||||
this.__baseConnectionParams(),
|
|
||||||
this.manager._connectionCount('upload', this.dcId, this.manager.params.isPremium),
|
|
||||||
this._log,
|
|
||||||
'UPLOAD',
|
|
||||||
)
|
|
||||||
|
|
||||||
/** Download connection pool */
|
/** Download connection pool */
|
||||||
download = new MultiSessionConnection(
|
download: MultiSessionConnection
|
||||||
this.__baseConnectionParams(),
|
|
||||||
this.manager._connectionCount('download', this.dcId, this.manager.params.isPremium),
|
|
||||||
this._log,
|
|
||||||
'DOWNLOAD',
|
|
||||||
)
|
|
||||||
|
|
||||||
/** Download connection pool (for small files) */
|
/** Download connection pool (for small files) */
|
||||||
downloadSmall = new MultiSessionConnection(
|
downloadSmall: MultiSessionConnection
|
||||||
this.__baseConnectionParams(),
|
|
||||||
this.manager._connectionCount('downloadSmall', this.dcId, this.manager.params.isPremium),
|
|
||||||
this._log,
|
|
||||||
'DOWNLOAD_SMALL',
|
|
||||||
)
|
|
||||||
|
|
||||||
private get _mainConnectionCount() {
|
private get _mainConnectionCount() {
|
||||||
if (!this.isPrimary) return 1
|
if (!this.isPrimary) return 1
|
||||||
|
@ -252,9 +215,29 @@ export class DcConnectionManager {
|
||||||
/** Whether this DC is the primary one */
|
/** Whether this DC is the primary one */
|
||||||
public isPrimary = false,
|
public isPrimary = false,
|
||||||
) {
|
) {
|
||||||
|
this._log = this.manager._log.create('dc-manager')
|
||||||
this._log.prefix = `[DC ${dcId}] `
|
this._log.prefix = `[DC ${dcId}] `
|
||||||
|
|
||||||
const mainParams = this.__baseConnectionParams()
|
const baseConnectionParams = (): SessionConnectionParams => ({
|
||||||
|
crypto: this.manager.params.crypto,
|
||||||
|
initConnection: this.manager._initConnectionParams,
|
||||||
|
transportFactory: this.manager._transportFactory,
|
||||||
|
dc: this._dcs.media,
|
||||||
|
testMode: this.manager.params.testMode,
|
||||||
|
reconnectionStrategy: this.manager._reconnectionStrategy,
|
||||||
|
layer: this.manager.params.layer,
|
||||||
|
disableUpdates: this.manager.params.disableUpdates,
|
||||||
|
readerMap: this.manager.params.readerMap,
|
||||||
|
writerMap: this.manager.params.writerMap,
|
||||||
|
usePfs: this.manager.params.usePfs,
|
||||||
|
isMainConnection: false,
|
||||||
|
isMainDcConnection: this.isPrimary,
|
||||||
|
inactivityTimeout: this.manager.params.inactivityTimeout ?? 60_000,
|
||||||
|
enableErrorReporting: this.manager.params.enableErrorReporting,
|
||||||
|
salts: this._salts,
|
||||||
|
})
|
||||||
|
|
||||||
|
const mainParams = baseConnectionParams()
|
||||||
mainParams.isMainConnection = true
|
mainParams.isMainConnection = true
|
||||||
mainParams.dc = _dcs.main
|
mainParams.dc = _dcs.main
|
||||||
|
|
||||||
|
@ -263,6 +246,24 @@ export class DcConnectionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.main = new MultiSessionConnection(mainParams, this._mainConnectionCount, this._log, 'MAIN')
|
this.main = new MultiSessionConnection(mainParams, this._mainConnectionCount, this._log, 'MAIN')
|
||||||
|
this.upload = new MultiSessionConnection(
|
||||||
|
baseConnectionParams(),
|
||||||
|
this.manager._connectionCount('upload', this.dcId, this.manager.params.isPremium),
|
||||||
|
this._log,
|
||||||
|
'UPLOAD',
|
||||||
|
)
|
||||||
|
this.download = new MultiSessionConnection(
|
||||||
|
baseConnectionParams(),
|
||||||
|
this.manager._connectionCount('download', this.dcId, this.manager.params.isPremium),
|
||||||
|
this._log,
|
||||||
|
'DOWNLOAD',
|
||||||
|
)
|
||||||
|
this.downloadSmall = new MultiSessionConnection(
|
||||||
|
baseConnectionParams(),
|
||||||
|
this.manager._connectionCount('downloadSmall', this.dcId, this.manager.params.isPremium),
|
||||||
|
this._log,
|
||||||
|
'DOWNLOAD_SMALL',
|
||||||
|
)
|
||||||
|
|
||||||
this._setupMulti('main')
|
this._setupMulti('main')
|
||||||
this._setupMulti('upload')
|
this._setupMulti('upload')
|
||||||
|
@ -412,11 +413,11 @@ export class DcConnectionManager {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
async destroy() {
|
||||||
this.main.destroy()
|
await this.main.destroy()
|
||||||
this.upload.destroy()
|
await this.upload.destroy()
|
||||||
this.download.destroy()
|
await this.download.destroy()
|
||||||
this.downloadSmall.destroy()
|
await this.downloadSmall.destroy()
|
||||||
this._salts.destroy()
|
this._salts.destroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -425,8 +426,8 @@ export class DcConnectionManager {
|
||||||
* Class that manages all connections to Telegram servers.
|
* Class that manages all connections to Telegram servers.
|
||||||
*/
|
*/
|
||||||
export class NetworkManager {
|
export class NetworkManager {
|
||||||
readonly _log = this.params.log.create('network')
|
readonly _log
|
||||||
readonly _storage = this.params.storage
|
readonly _storage
|
||||||
|
|
||||||
readonly _initConnectionParams: tl.RawInitConnectionRequest
|
readonly _initConnectionParams: tl.RawInitConnectionRequest
|
||||||
readonly _transportFactory: TransportFactory
|
readonly _transportFactory: TransportFactory
|
||||||
|
@ -465,6 +466,9 @@ export class NetworkManager {
|
||||||
|
|
||||||
this._onConfigChanged = this._onConfigChanged.bind(this)
|
this._onConfigChanged = this._onConfigChanged.bind(this)
|
||||||
config.onReload(this._onConfigChanged)
|
config.onReload(this._onConfigChanged)
|
||||||
|
|
||||||
|
this._log = params.log.create('network')
|
||||||
|
this._storage = params.storage
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _findDcOptions(dcId: number): Promise<DcOptions> {
|
private async _findDcOptions(dcId: number): Promise<DcOptions> {
|
||||||
|
@ -857,9 +861,9 @@ export class NetworkManager {
|
||||||
return this._primaryDc.dcId
|
return this._primaryDc.dcId
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(): void {
|
async destroy(): Promise<void> {
|
||||||
for (const dc of this._dcConnections.values()) {
|
for (const dc of this._dcConnections.values()) {
|
||||||
dc.destroy()
|
await dc.destroy()
|
||||||
}
|
}
|
||||||
this.config.offReload(this._onConfigChanged)
|
this.config.offReload(this._onConfigChanged)
|
||||||
this._resetOnNetworkChange?.()
|
this._resetOnNetworkChange?.()
|
||||||
|
|
|
@ -69,7 +69,9 @@ export abstract class PersistentConnection extends EventEmitter {
|
||||||
|
|
||||||
changeTransport(factory: TransportFactory): void {
|
changeTransport(factory: TransportFactory): void {
|
||||||
if (this._transport) {
|
if (this._transport) {
|
||||||
this._transport.close()
|
Promise.resolve(this._transport.close()).catch((err) => {
|
||||||
|
this.log.warn('error closing previous transport: %s', err)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this._transport = factory()
|
this._transport = factory()
|
||||||
|
@ -149,7 +151,9 @@ export abstract class PersistentConnection extends EventEmitter {
|
||||||
)
|
)
|
||||||
|
|
||||||
if (wait === false) {
|
if (wait === false) {
|
||||||
this.destroy()
|
this.destroy().catch((err) => {
|
||||||
|
this.log.warn('error destroying connection: %s', err)
|
||||||
|
})
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -192,7 +196,9 @@ export abstract class PersistentConnection extends EventEmitter {
|
||||||
// if we are already connected
|
// if we are already connected
|
||||||
if (this.isConnected) {
|
if (this.isConnected) {
|
||||||
this._shouldReconnectImmediately = true
|
this._shouldReconnectImmediately = true
|
||||||
this._transport.close()
|
Promise.resolve(this._transport.close()).catch((err) => {
|
||||||
|
this.log.error('error closing transport: %s', err)
|
||||||
|
})
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -201,12 +207,12 @@ export abstract class PersistentConnection extends EventEmitter {
|
||||||
this.connect()
|
this.connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectManual(): void {
|
async disconnectManual(): Promise<void> {
|
||||||
this._disconnectedManually = true
|
this._disconnectedManually = true
|
||||||
this._transport.close()
|
await this._transport.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(): void {
|
async destroy(): Promise<void> {
|
||||||
if (this._reconnectionTimeout != null) {
|
if (this._reconnectionTimeout != null) {
|
||||||
clearTimeout(this._reconnectionTimeout)
|
clearTimeout(this._reconnectionTimeout)
|
||||||
}
|
}
|
||||||
|
@ -214,7 +220,7 @@ export abstract class PersistentConnection extends EventEmitter {
|
||||||
clearTimeout(this._inactivityTimeout)
|
clearTimeout(this._inactivityTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
this._transport.close()
|
await this._transport.close()
|
||||||
this._transport.removeAllListeners()
|
this._transport.removeAllListeners()
|
||||||
this._destroyed = true
|
this._destroyed = true
|
||||||
}
|
}
|
||||||
|
@ -229,7 +235,9 @@ export abstract class PersistentConnection extends EventEmitter {
|
||||||
this.log.info('disconnected because of inactivity for %d', this.params.inactivityTimeout)
|
this.log.info('disconnected because of inactivity for %d', this.params.inactivityTimeout)
|
||||||
this._inactive = true
|
this._inactive = true
|
||||||
this._inactivityTimeout = null
|
this._inactivityTimeout = null
|
||||||
this._transport.close()
|
Promise.resolve(this._transport.close()).catch((err) => {
|
||||||
|
this.log.warn('error closing transport: %s', err)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setInactivityTimeout(timeout?: number): void {
|
setInactivityTimeout(timeout?: number): void {
|
||||||
|
|
|
@ -70,7 +70,7 @@ function makeNiceStack(error: tl.RpcError, stack: string, method?: string) {
|
||||||
* A connection to a single DC.
|
* A connection to a single DC.
|
||||||
*/
|
*/
|
||||||
export class SessionConnection extends PersistentConnection {
|
export class SessionConnection extends PersistentConnection {
|
||||||
readonly params!: SessionConnectionParams
|
declare readonly params: SessionConnectionParams
|
||||||
|
|
||||||
private _flushTimer = new EarlyTimer()
|
private _flushTimer = new EarlyTimer()
|
||||||
private _queuedDestroySession: Long[] = []
|
private _queuedDestroySession: Long[] = []
|
||||||
|
@ -78,7 +78,7 @@ export class SessionConnection extends PersistentConnection {
|
||||||
// waitForMessage
|
// waitForMessage
|
||||||
private _pendingWaitForUnencrypted: [ControllablePromise<Uint8Array>, NodeJS.Timeout][] = []
|
private _pendingWaitForUnencrypted: [ControllablePromise<Uint8Array>, NodeJS.Timeout][] = []
|
||||||
|
|
||||||
private _usePfs = this.params.usePfs ?? false
|
private _usePfs
|
||||||
private _isPfsBindingPending = false
|
private _isPfsBindingPending = false
|
||||||
private _isPfsBindingPendingInBackground = false
|
private _isPfsBindingPendingInBackground = false
|
||||||
private _pfsUpdateTimeout?: NodeJS.Timeout
|
private _pfsUpdateTimeout?: NodeJS.Timeout
|
||||||
|
@ -102,9 +102,12 @@ export class SessionConnection extends PersistentConnection {
|
||||||
this._crypto = params.crypto
|
this._crypto = params.crypto
|
||||||
this._salts = params.salts
|
this._salts = params.salts
|
||||||
this._handleRawMessage = this._handleRawMessage.bind(this)
|
this._handleRawMessage = this._handleRawMessage.bind(this)
|
||||||
|
|
||||||
|
this._usePfs = this.params.usePfs ?? false
|
||||||
|
this._online = getPlatform().isOnline?.() ?? true
|
||||||
}
|
}
|
||||||
|
|
||||||
private _online = getPlatform().isOnline?.() ?? true
|
private _online
|
||||||
|
|
||||||
getAuthKey(temp = false): Uint8Array | null {
|
getAuthKey(temp = false): Uint8Array | null {
|
||||||
const key = temp ? this._session._authKeyTemp : this._session._authKey
|
const key = temp ? this._session._authKeyTemp : this._session._authKey
|
||||||
|
@ -152,8 +155,8 @@ export class SessionConnection extends PersistentConnection {
|
||||||
this.reset()
|
this.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(): void {
|
async destroy(): Promise<void> {
|
||||||
super.destroy()
|
await super.destroy()
|
||||||
this.reset(true)
|
this.reset(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1459,7 +1462,9 @@ export class SessionConnection extends PersistentConnection {
|
||||||
if (online) {
|
if (online) {
|
||||||
this.reconnect()
|
this.reconnect()
|
||||||
} else {
|
} else {
|
||||||
this.disconnectManual()
|
this.disconnectManual().catch((err) => {
|
||||||
|
this.log.warn('error while disconnecting: %s', err)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ export interface ITelegramTransport extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
connect(dc: BasicDcOption, testMode: boolean): void
|
connect(dc: BasicDcOption, testMode: boolean): void
|
||||||
/** call to close existing connection to some DC */
|
/** call to close existing connection to some DC */
|
||||||
close(): void
|
close(): MaybePromise<void>
|
||||||
/** send a message */
|
/** send a message */
|
||||||
send(data: Uint8Array): Promise<void>
|
send(data: Uint8Array): Promise<void>
|
||||||
|
|
||||||
|
|
|
@ -8,13 +8,14 @@ interface AuthKeysState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MemoryAuthKeysRepository implements IAuthKeysRepository {
|
export class MemoryAuthKeysRepository implements IAuthKeysRepository {
|
||||||
constructor(readonly _driver: MemoryStorageDriver) {}
|
readonly state
|
||||||
|
constructor(readonly _driver: MemoryStorageDriver) {
|
||||||
readonly state = this._driver.getState<AuthKeysState>('authKeys', () => ({
|
this.state = this._driver.getState<AuthKeysState>('authKeys', () => ({
|
||||||
authKeys: new Map(),
|
authKeys: new Map(),
|
||||||
authKeysTemp: new Map(),
|
authKeysTemp: new Map(),
|
||||||
authKeysTempExpiry: new Map(),
|
authKeysTempExpiry: new Map(),
|
||||||
}))
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
set(dc: number, key: Uint8Array | null): void {
|
set(dc: number, key: Uint8Array | null): void {
|
||||||
if (key) {
|
if (key) {
|
||||||
|
|
|
@ -2,9 +2,10 @@ import { IKeyValueRepository } from '../../repository/key-value.js'
|
||||||
import { MemoryStorageDriver } from '../driver.js'
|
import { MemoryStorageDriver } from '../driver.js'
|
||||||
|
|
||||||
export class MemoryKeyValueRepository implements IKeyValueRepository {
|
export class MemoryKeyValueRepository implements IKeyValueRepository {
|
||||||
constructor(readonly _driver: MemoryStorageDriver) {}
|
readonly state
|
||||||
|
constructor(readonly _driver: MemoryStorageDriver) {
|
||||||
readonly state = this._driver.getState<Map<string, Uint8Array>>('kv', () => new Map())
|
this.state = this._driver.getState<Map<string, Uint8Array>>('kv', () => new Map())
|
||||||
|
}
|
||||||
|
|
||||||
set(key: string, value: Uint8Array): void {
|
set(key: string, value: Uint8Array): void {
|
||||||
this.state.set(key, value)
|
this.state.set(key, value)
|
||||||
|
|
|
@ -8,13 +8,14 @@ interface PeersState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MemoryPeersRepository implements IPeersRepository {
|
export class MemoryPeersRepository implements IPeersRepository {
|
||||||
constructor(readonly _driver: MemoryStorageDriver) {}
|
readonly state
|
||||||
|
constructor(readonly _driver: MemoryStorageDriver) {
|
||||||
readonly state = this._driver.getState<PeersState>('peers', () => ({
|
this.state = this._driver.getState<PeersState>('peers', () => ({
|
||||||
entities: new Map(),
|
entities: new Map(),
|
||||||
usernameIndex: new Map(),
|
usernameIndex: new Map(),
|
||||||
phoneIndex: new Map(),
|
phoneIndex: new Map(),
|
||||||
}))
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
store(peer: IPeersRepository.PeerInfo): void {
|
store(peer: IPeersRepository.PeerInfo): void {
|
||||||
const old = this.state.entities.get(peer.id)
|
const old = this.state.entities.get(peer.id)
|
||||||
|
|
|
@ -6,11 +6,12 @@ interface RefMessagesState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MemoryRefMessagesRepository implements IReferenceMessagesRepository {
|
export class MemoryRefMessagesRepository implements IReferenceMessagesRepository {
|
||||||
constructor(readonly _driver: MemoryStorageDriver) {}
|
readonly state
|
||||||
|
constructor(readonly _driver: MemoryStorageDriver) {
|
||||||
readonly state = this._driver.getState<RefMessagesState>('refMessages', () => ({
|
this.state = this._driver.getState<RefMessagesState>('refMessages', () => ({
|
||||||
refs: new Map(),
|
refs: new Map(),
|
||||||
}))
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
store(peerId: number, chatId: number, msgId: number): void {
|
store(peerId: number, chatId: number, msgId: number): void {
|
||||||
if (!this.state.refs.has(peerId)) {
|
if (!this.state.refs.has(peerId)) {
|
||||||
|
|
|
@ -10,10 +10,15 @@ export { BaseSqliteStorageDriver }
|
||||||
export * from './types.js'
|
export * from './types.js'
|
||||||
|
|
||||||
export class BaseSqliteStorage implements IMtStorageProvider, ITelegramStorageProvider {
|
export class BaseSqliteStorage implements IMtStorageProvider, ITelegramStorageProvider {
|
||||||
constructor(readonly driver: BaseSqliteStorageDriver) {}
|
readonly authKeys
|
||||||
|
readonly kv
|
||||||
|
readonly refMessages
|
||||||
|
readonly peers
|
||||||
|
|
||||||
readonly authKeys = new SqliteAuthKeysRepository(this.driver)
|
constructor(readonly driver: BaseSqliteStorageDriver) {
|
||||||
readonly kv = new SqliteKeyValueRepository(this.driver)
|
this.authKeys = new SqliteAuthKeysRepository(this.driver)
|
||||||
readonly refMessages = new SqliteRefMessagesRepository(this.driver)
|
this.kv = new SqliteKeyValueRepository(this.driver)
|
||||||
readonly peers = new SqlitePeersRepository(this.driver)
|
this.refMessages = new SqliteRefMessagesRepository(this.driver)
|
||||||
|
this.peers = new SqlitePeersRepository(this.driver)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,7 @@ interface PeerDto {
|
||||||
usernames: string
|
usernames: string
|
||||||
updated: number
|
updated: number
|
||||||
phone: string | null
|
phone: string | null
|
||||||
// eslint-disable-next-line no-restricted-globals
|
complete: Uint8Array
|
||||||
complete: Buffer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapPeerDto(dto: PeerDto): IPeersRepository.PeerInfo {
|
function mapPeerDto(dto: PeerDto): IPeersRepository.PeerInfo {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { IReferenceMessagesRepository } from '@mtcute/core'
|
import { IReferenceMessagesRepository } from '../../../highlevel/storage/repository/ref-messages.js'
|
||||||
|
|
||||||
import { BaseSqliteStorageDriver } from '../driver.js'
|
import { BaseSqliteStorageDriver } from '../driver.js'
|
||||||
import { ISqliteStatement } from '../types.js'
|
import { ISqliteStatement } from '../types.js'
|
||||||
|
|
||||||
|
|
|
@ -40,23 +40,30 @@ export interface StorageManagerExtraOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class StorageManager {
|
export class StorageManager {
|
||||||
constructor(readonly options: StorageManagerOptions & StorageManagerExtraOptions) {}
|
readonly provider
|
||||||
|
readonly driver
|
||||||
|
readonly log
|
||||||
|
readonly dcs
|
||||||
|
readonly salts
|
||||||
|
readonly keys
|
||||||
|
|
||||||
readonly provider = this.options.provider
|
constructor(readonly options: StorageManagerOptions & StorageManagerExtraOptions) {
|
||||||
readonly driver = this.provider.driver
|
this.provider = this.options.provider
|
||||||
readonly log = this.options.log.create('storage')
|
this.driver = this.provider.driver
|
||||||
|
this.log = this.options.log.create('storage')
|
||||||
|
|
||||||
readonly _serviceOptions: ServiceOptions = {
|
const serviceOptions: ServiceOptions = {
|
||||||
driver: this.driver,
|
driver: this.driver,
|
||||||
readerMap: this.options.readerMap,
|
readerMap: this.options.readerMap,
|
||||||
writerMap: this.options.writerMap,
|
writerMap: this.options.writerMap,
|
||||||
log: this.log,
|
log: this.log,
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dcs = new DefaultDcsService(this.provider.kv, serviceOptions)
|
||||||
|
this.salts = new FutureSaltsService(this.provider.kv, serviceOptions)
|
||||||
|
this.keys = new AuthKeysService(this.provider.authKeys, this.salts, serviceOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly dcs = new DefaultDcsService(this.provider.kv, this._serviceOptions)
|
|
||||||
readonly salts = new FutureSaltsService(this.provider.kv, this._serviceOptions)
|
|
||||||
readonly keys = new AuthKeysService(this.provider.authKeys, this.salts, this._serviceOptions)
|
|
||||||
|
|
||||||
private _cleanupRestore?: () => void
|
private _cleanupRestore?: () => void
|
||||||
|
|
||||||
private _load = asyncResettable(async () => {
|
private _load = asyncResettable(async () => {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue