diff --git a/.config/eslint.cjs b/.config/eslint.cjs index 7320893e..2a1ea066 100644 --- a/.config/eslint.cjs +++ b/.config/eslint.cjs @@ -285,6 +285,12 @@ module.exports = { 'no-restricted-imports': 'off', 'import/no-relative-packages': 'off', // common-internals is symlinked from node } + }, + { + files: ['e2e/deno/**'], + rules: { + 'import/no-unresolved': 'off', + } } ], settings: { diff --git a/.dockerignore b/.dockerignore index 7224b67d..83410924 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ **/node_modules **/private -**/dist \ No newline at end of file +**/dist +/e2e \ No newline at end of file diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 95b2299c..e88ccc15 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -90,7 +90,7 @@ jobs: API_ID: ${{ secrets.TELEGRAM_API_ID }} API_HASH: ${{ secrets.TELEGRAM_API_HASH }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: cd e2e && ./cli.sh ci + run: cd e2e/node && ./cli.sh ci - name: Publish to canary NPM if: github.repository == 'mtcute/mtcute' # do not run on forks continue-on-error: true @@ -98,4 +98,18 @@ jobs: NPM_TOKEN: ${{ secrets.CANARY_NPM_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 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 diff --git a/e2e/deno/.dockerignore b/e2e/deno/.dockerignore new file mode 100644 index 00000000..a1de89c1 --- /dev/null +++ b/e2e/deno/.dockerignore @@ -0,0 +1,3 @@ +/.jsr-data +/dist +/deno.lock \ No newline at end of file diff --git a/e2e/.env.example b/e2e/deno/.env.example similarity index 81% rename from e2e/.env.example rename to e2e/deno/.env.example index 2a7641ed..20a2ee7d 100644 --- a/e2e/.env.example +++ b/e2e/deno/.env.example @@ -1,3 +1,5 @@ # obtain these values from my.telegram.org API_ID= API_HASH= + +GITHUB_TOKEN= \ No newline at end of file diff --git a/e2e/deno/.gitignore b/e2e/deno/.gitignore new file mode 100644 index 00000000..75a15d22 --- /dev/null +++ b/e2e/deno/.gitignore @@ -0,0 +1,3 @@ +/.jsr-data +.env +/deno.lock \ No newline at end of file diff --git a/e2e/deno/Dockerfile.build b/e2e/deno/Dockerfile.build new file mode 100644 index 00000000..6ebd1978 --- /dev/null +++ b/e2e/deno/Dockerfile.build @@ -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" ] \ No newline at end of file diff --git a/e2e/deno/Dockerfile.jsr b/e2e/deno/Dockerfile.jsr new file mode 100644 index 00000000..170237fb --- /dev/null +++ b/e2e/deno/Dockerfile.jsr @@ -0,0 +1,3 @@ +FROM ghcr.io/teidesu/jsr-api:latest + +RUN apt update && apt install -y curl \ No newline at end of file diff --git a/e2e/deno/Dockerfile.test b/e2e/deno/Dockerfile.test new file mode 100644 index 00000000..f5a3e594 --- /dev/null +++ b/e2e/deno/Dockerfile.test @@ -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" ] \ No newline at end of file diff --git a/e2e/deno/README.md b/e2e/deno/README.md new file mode 100644 index 00000000..5b579dc9 --- /dev/null +++ b/e2e/deno/README.md @@ -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 +``` \ No newline at end of file diff --git a/e2e/deno/cli.sh b/e2e/deno/cli.sh new file mode 100755 index 00000000..c1957a90 --- /dev/null +++ b/e2e/deno/cli.sh @@ -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 \ No newline at end of file diff --git a/e2e/deno/deno.json b/e2e/deno/deno.json new file mode 100644 index 00000000..f1651f62 --- /dev/null +++ b/e2e/deno/deno.json @@ -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@*" + } +} \ No newline at end of file diff --git a/e2e/deno/docker-compose.yaml b/e2e/deno/docker-compose.yaml new file mode 100644 index 00000000..80cc9395 --- /dev/null +++ b/e2e/deno/docker-compose.yaml @@ -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: {} \ No newline at end of file diff --git a/e2e/deno/init-server.js b/e2e/deno/init-server.js new file mode 100644 index 00000000..429a2e9e --- /dev/null +++ b/e2e/deno/init-server.js @@ -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') + } +})() diff --git a/e2e/deno/nginx.conf b/e2e/deno/nginx.conf new file mode 100644 index 00000000..846ef740 --- /dev/null +++ b/e2e/deno/nginx.conf @@ -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; + } + } +} \ No newline at end of file diff --git a/e2e/deno/tests/packaging/base-client.ts b/e2e/deno/tests/packaging/base-client.ts new file mode 100644 index 00000000..b752892a --- /dev/null +++ b/e2e/deno/tests/packaging/base-client.ts @@ -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) + }) +}) diff --git a/e2e/deno/tests/packaging/tl-runtime.ts b/e2e/deno/tests/packaging/tl-runtime.ts new file mode 100644 index 00000000..b00dbeb0 --- /dev/null +++ b/e2e/deno/tests/packaging/tl-runtime.ts @@ -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', + ) +}) diff --git a/e2e/deno/tests/packaging/tl-schema.ts b/e2e/deno/tests/packaging/tl-schema.ts new file mode 100644 index 00000000..5514729e --- /dev/null +++ b/e2e/deno/tests/packaging/tl-schema.ts @@ -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(__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) + }) +}) diff --git a/e2e/deno/tests/packaging/wasm.ts b/e2e/deno/tests/packaging/wasm.ts new file mode 100644 index 00000000..865f670e --- /dev/null +++ b/e2e/deno/tests/packaging/wasm.ts @@ -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) + }) +}) diff --git a/e2e/deno/utils.ts b/e2e/deno/utils.ts new file mode 100644 index 00000000..25711aa8 --- /dev/null +++ b/e2e/deno/utils.ts @@ -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, timeout = 5000): Promise { + const start = Date.now() + let lastError + + while (Date.now() - start < timeout) { + try { + await condition() + + return + } catch (e) { + lastError = e + await sleep(100) + } + } + + throw lastError +} diff --git a/e2e/.dockerignore b/e2e/node/.dockerignore similarity index 100% rename from e2e/.dockerignore rename to e2e/node/.dockerignore diff --git a/e2e/node/.env.example b/e2e/node/.env.example new file mode 100644 index 00000000..20a2ee7d --- /dev/null +++ b/e2e/node/.env.example @@ -0,0 +1,5 @@ +# obtain these values from my.telegram.org +API_ID= +API_HASH= + +GITHUB_TOKEN= \ No newline at end of file diff --git a/e2e/.gitignore b/e2e/node/.gitignore similarity index 100% rename from e2e/.gitignore rename to e2e/node/.gitignore diff --git a/e2e/.mocharc.json b/e2e/node/.mocharc.json similarity index 100% rename from e2e/.mocharc.json rename to e2e/node/.mocharc.json diff --git a/e2e/.verdaccio/config.yaml b/e2e/node/.verdaccio/config.yaml similarity index 100% rename from e2e/.verdaccio/config.yaml rename to e2e/node/.verdaccio/config.yaml diff --git a/e2e/.verdaccio/htpasswd b/e2e/node/.verdaccio/htpasswd similarity index 100% rename from e2e/.verdaccio/htpasswd rename to e2e/node/.verdaccio/htpasswd diff --git a/e2e/Dockerfile.build b/e2e/node/Dockerfile.build similarity index 96% rename from e2e/Dockerfile.build rename to e2e/node/Dockerfile.build index 7d5721a0..e87c951b 100644 --- a/e2e/Dockerfile.build +++ b/e2e/node/Dockerfile.build @@ -5,7 +5,7 @@ RUN apk add python3 make g++ && \ corepack enable && \ corepack prepare pnpm@8.7.1 --activate -COPY ../ /app/ +COPY ../.. /app/ RUN pnpm install --frozen-lockfile && \ pnpm -C packages/tl run gen-code && \ diff --git a/e2e/Dockerfile.test b/e2e/node/Dockerfile.test similarity index 100% rename from e2e/Dockerfile.test rename to e2e/node/Dockerfile.test diff --git a/e2e/README.md b/e2e/node/README.md similarity index 100% rename from e2e/README.md rename to e2e/node/README.md diff --git a/e2e/cjs/package.json b/e2e/node/cjs/package.json similarity index 100% rename from e2e/cjs/package.json rename to e2e/node/cjs/package.json diff --git a/e2e/cjs/tests/base-client.js b/e2e/node/cjs/tests/base-client.js similarity index 100% rename from e2e/cjs/tests/base-client.js rename to e2e/node/cjs/tests/base-client.js diff --git a/e2e/cjs/tests/tl-runtime.js b/e2e/node/cjs/tests/tl-runtime.js similarity index 100% rename from e2e/cjs/tests/tl-runtime.js rename to e2e/node/cjs/tests/tl-runtime.js diff --git a/e2e/cjs/tests/tl-schema.js b/e2e/node/cjs/tests/tl-schema.js similarity index 100% rename from e2e/cjs/tests/tl-schema.js rename to e2e/node/cjs/tests/tl-schema.js diff --git a/e2e/cjs/tests/wasm.js b/e2e/node/cjs/tests/wasm.js similarity index 100% rename from e2e/cjs/tests/wasm.js rename to e2e/node/cjs/tests/wasm.js diff --git a/e2e/cjs/utils.js b/e2e/node/cjs/utils.js similarity index 100% rename from e2e/cjs/utils.js rename to e2e/node/cjs/utils.js diff --git a/e2e/cli.sh b/e2e/node/cli.sh similarity index 100% rename from e2e/cli.sh rename to e2e/node/cli.sh diff --git a/e2e/config.js b/e2e/node/config.js similarity index 100% rename from e2e/config.js rename to e2e/node/config.js diff --git a/e2e/docker-compose.yaml b/e2e/node/docker-compose.yaml similarity index 90% rename from e2e/docker-compose.yaml rename to e2e/node/docker-compose.yaml index be7dae52..07eaea85 100644 --- a/e2e/docker-compose.yaml +++ b/e2e/node/docker-compose.yaml @@ -12,8 +12,8 @@ services: - mtcute-e2e build: build: - context: .. - dockerfile: e2e/Dockerfile.build + context: ../.. + dockerfile: e2e/node/Dockerfile.build environment: - GITHUB_TOKEN=${GITHUB_TOKEN} networks: diff --git a/e2e/docker-entrypoint.sh b/e2e/node/docker-entrypoint.sh similarity index 100% rename from e2e/docker-entrypoint.sh rename to e2e/node/docker-entrypoint.sh diff --git a/e2e/esm/package.json b/e2e/node/esm/package.json similarity index 100% rename from e2e/esm/package.json rename to e2e/node/esm/package.json diff --git a/e2e/esm/tests/base-client.js b/e2e/node/esm/tests/base-client.js similarity index 100% rename from e2e/esm/tests/base-client.js rename to e2e/node/esm/tests/base-client.js diff --git a/e2e/esm/tests/tl-runtime.js b/e2e/node/esm/tests/tl-runtime.js similarity index 100% rename from e2e/esm/tests/tl-runtime.js rename to e2e/node/esm/tests/tl-runtime.js diff --git a/e2e/esm/tests/tl-schema.js b/e2e/node/esm/tests/tl-schema.js similarity index 100% rename from e2e/esm/tests/tl-schema.js rename to e2e/node/esm/tests/tl-schema.js diff --git a/e2e/esm/tests/wasm.js b/e2e/node/esm/tests/wasm.js similarity index 100% rename from e2e/esm/tests/wasm.js rename to e2e/node/esm/tests/wasm.js diff --git a/e2e/esm/utils.js b/e2e/node/esm/utils.js similarity index 100% rename from e2e/esm/utils.js rename to e2e/node/esm/utils.js diff --git a/e2e/package.json b/e2e/node/package.json similarity index 100% rename from e2e/package.json rename to e2e/node/package.json diff --git a/e2e/pnpm-workspace.yaml b/e2e/node/pnpm-workspace.yaml similarity index 100% rename from e2e/pnpm-workspace.yaml rename to e2e/node/pnpm-workspace.yaml diff --git a/e2e/publish-canary.js b/e2e/node/publish-canary.js similarity index 94% rename from e2e/publish-canary.js rename to e2e/node/publish-canary.js index f0e20ffc..59ca7257 100644 --- a/e2e/publish-canary.js +++ b/e2e/node/publish-canary.js @@ -18,7 +18,9 @@ execSync(`npm config set //${REGISTRY.replace(/^https?:\/\//, '')}/:_authToken $ const commit = CURRENT_COMMIT.slice(0, 7) 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') fs.mkdirSync(workDir, { recursive: true }) diff --git a/e2e/runner.js b/e2e/node/runner.js similarity index 100% rename from e2e/runner.js rename to e2e/node/runner.js diff --git a/e2e/ts/build-esm.cjs b/e2e/node/ts/build-esm.cjs similarity index 100% rename from e2e/ts/build-esm.cjs rename to e2e/node/ts/build-esm.cjs diff --git a/e2e/ts/mocha.esm.json b/e2e/node/ts/mocha.esm.json similarity index 100% rename from e2e/ts/mocha.esm.json rename to e2e/node/ts/mocha.esm.json diff --git a/e2e/ts/package.json b/e2e/node/ts/package.json similarity index 100% rename from e2e/ts/package.json rename to e2e/node/ts/package.json diff --git a/e2e/ts/run-esm.cjs b/e2e/node/ts/run-esm.cjs similarity index 100% rename from e2e/ts/run-esm.cjs rename to e2e/node/ts/run-esm.cjs diff --git a/e2e/ts/tests/01.auth.ts b/e2e/node/ts/tests/01.auth.ts similarity index 100% rename from e2e/ts/tests/01.auth.ts rename to e2e/node/ts/tests/01.auth.ts diff --git a/e2e/ts/tests/02.methods.ts b/e2e/node/ts/tests/02.methods.ts similarity index 100% rename from e2e/ts/tests/02.methods.ts rename to e2e/node/ts/tests/02.methods.ts diff --git a/e2e/ts/tests/03.files.ts b/e2e/node/ts/tests/03.files.ts similarity index 100% rename from e2e/ts/tests/03.files.ts rename to e2e/node/ts/tests/03.files.ts diff --git a/e2e/ts/tests/04.updates.ts b/e2e/node/ts/tests/04.updates.ts similarity index 100% rename from e2e/ts/tests/04.updates.ts rename to e2e/node/ts/tests/04.updates.ts diff --git a/e2e/ts/tests/05.worker.ts b/e2e/node/ts/tests/05.worker.ts similarity index 100% rename from e2e/ts/tests/05.worker.ts rename to e2e/node/ts/tests/05.worker.ts diff --git a/e2e/ts/tests/_worker.ts b/e2e/node/ts/tests/_worker.ts similarity index 100% rename from e2e/ts/tests/_worker.ts rename to e2e/node/ts/tests/_worker.ts diff --git a/e2e/ts/tests/packaging/base-client.ts b/e2e/node/ts/tests/packaging/base-client.ts similarity index 100% rename from e2e/ts/tests/packaging/base-client.ts rename to e2e/node/ts/tests/packaging/base-client.ts diff --git a/e2e/ts/tests/packaging/tl-runtime.ts b/e2e/node/ts/tests/packaging/tl-runtime.ts similarity index 100% rename from e2e/ts/tests/packaging/tl-runtime.ts rename to e2e/node/ts/tests/packaging/tl-runtime.ts diff --git a/e2e/ts/tests/packaging/tl-schema.ts b/e2e/node/ts/tests/packaging/tl-schema.ts similarity index 100% rename from e2e/ts/tests/packaging/tl-schema.ts rename to e2e/node/ts/tests/packaging/tl-schema.ts diff --git a/e2e/ts/tests/packaging/wasm.ts b/e2e/node/ts/tests/packaging/wasm.ts similarity index 100% rename from e2e/ts/tests/packaging/wasm.ts rename to e2e/node/ts/tests/packaging/wasm.ts diff --git a/e2e/ts/tsconfig.json b/e2e/node/ts/tsconfig.json similarity index 100% rename from e2e/ts/tsconfig.json rename to e2e/node/ts/tsconfig.json diff --git a/e2e/ts/utils.ts b/e2e/node/ts/utils.ts similarity index 100% rename from e2e/ts/utils.ts rename to e2e/node/ts/utils.ts diff --git a/package.json b/package.json index 04d654eb..da6f9654 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "devDependencies": { "@commitlint/cli": "17.6.5", "@commitlint/config-conventional": "17.6.5", + "@teidesu/slow-types-compiler": "1.0.2", "@types/node": "20.10.0", "@types/ws": "8.5.4", "@typescript-eslint/eslint-plugin": "6.4.0", @@ -62,8 +63,8 @@ "semver": "7.5.1", "ts-node": "10.9.1", "tsconfig-paths": "4.2.0", - "typedoc": "0.25.3", - "typescript": "5.1.6", + "typedoc": "0.25.12", + "typescript": "5.4.3", "vite": "5.1.6", "vite-plugin-node-polyfills": "0.21.0", "vitest": "1.4.0" diff --git a/packages/bun/package.json b/packages/bun/package.json index 1cd930d4..a35be82e 100644 --- a/packages/bun/package.json +++ b/packages/bun/package.json @@ -16,12 +16,6 @@ ".": "./src/index.ts", "./utils.js": "./src/utils.ts" }, - "distOnlyFields": { - "exports": { - ".": "./index.js", - "./utils.js": "./utils.js" - } - }, "dependencies": { "@mtcute/core": "workspace:^", "@mtcute/wasm": "workspace:^", diff --git a/packages/convert/package.json b/packages/convert/package.json index 23bfa23a..efb000a6 100644 --- a/packages/convert/package.json +++ b/packages/convert/package.json @@ -11,14 +11,7 @@ "scripts": { "build": "pnpm run -w build-package convert" }, - "distOnlyFields": { - "exports": { - ".": { - "import": "./esm/index.js", - "require": "./cjs/index.js" - } - } - }, + "exports": "./src/index.ts", "dependencies": { "@mtcute/core": "workspace:^" }, diff --git a/packages/core/build.config.cjs b/packages/core/build.config.cjs index c9699247..631e06a0 100644 --- a/packages/core/build.config.cjs +++ b/packages/core/build.config.cjs @@ -1,20 +1,27 @@ const KNOWN_DECORATORS = ['memoizeGetters', 'makeInspectable'] -module.exports = ({ path, glob, transformFile, packageDir, outDir }) => ({ +module.exports = ({ path, glob, transformFile, packageDir, outDir, jsr }) => ({ esmOnlyDirectives: true, esmImportDirectives: true, final() { const version = require(path.join(packageDir, 'package.json')).version const replaceVersion = (content) => content.replace('%VERSION%', version) - transformFile(path.join(outDir, 'cjs/network/network-manager.js'), replaceVersion) - transformFile(path.join(outDir, 'esm/network/network-manager.js'), replaceVersion) + if (jsr) { + 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 // 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( - `(${KNOWN_DECORATORS.join('|')})\\((.+?)\\);`, - 'gs', + `(${KNOWN_DECORATORS.join('|')})\\((.+?)\\)(?:;|$)`, + 'gsm', ) const replaceDecorators = (content, file) => { @@ -57,7 +64,9 @@ module.exports = ({ path, glob, transformFile, packageDir, outDir }) => ({ 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) } }, diff --git a/packages/core/package.json b/packages/core/package.json index cd25b3ca..587fc832 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -21,34 +21,6 @@ "./methods.js": "./src/highlevel/methods.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": { "@mtcute/tl": "workspace:^", "@mtcute/tl-runtime": "workspace:^", diff --git a/packages/core/src/highlevel/base.ts b/packages/core/src/highlevel/base.ts index a4e09963..e5619383 100644 --- a/packages/core/src/highlevel/base.ts +++ b/packages/core/src/highlevel/base.ts @@ -33,7 +33,18 @@ export class BaseTelegramClient implements ITelegramClient { private _serverUpdatesHandler: (updates: tl.TypeUpdates) => void = () => {} private _connectionStateHandler: (state: ConnectionState) => void = () => {} + readonly log + readonly mt + readonly crypto + readonly storage + 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) { this.updates = new UpdatesManager(this, params.updates) this._serverUpdatesHandler = this.updates.handleUpdate.bind(this.updates) @@ -56,18 +67,13 @@ export class BaseTelegramClient implements ITelegramClient { this._connectionStateHandler('offline') } }) - } - readonly log = this.params.logger ?? new LogManager('client') - readonly mt = new MtClient({ - ...this.params, - logger: this.log.create('mtproto'), - }) - readonly crypto = this.mt.crypto - readonly storage = new TelegramStorageManager(this.mt.storage, { - provider: this.params.storage, - ...this.params.storageOptions, - }) + this.crypto = this.mt.crypto + this.storage = new TelegramStorageManager(this.mt.storage, { + provider: this.params.storage, + ...this.params.storageOptions, + }) + } readonly appConfig = new AppConfigManager(this) private _prepare = asyncResettable(async () => { diff --git a/packages/core/src/highlevel/client.ts b/packages/core/src/highlevel/client.ts index a1ba78d5..e7107817 100644 --- a/packages/core/src/highlevel/client.ts +++ b/packages/core/src/highlevel/client.ts @@ -2285,7 +2285,10 @@ export interface TelegramClient extends ITelegramClient { * * @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, * streaming file contents. diff --git a/packages/core/src/highlevel/methods/auth/start-test.ts b/packages/core/src/highlevel/methods/auth/start-test.ts index ca3537e2..6ea020d5 100644 --- a/packages/core/src/highlevel/methods/auth/start-test.ts +++ b/packages/core/src/highlevel/methods/auth/start-test.ts @@ -66,7 +66,7 @@ export async function startTest( let dcId = await client.getPrimaryDcId() 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})`) } dcId = params.dcId @@ -85,7 +85,7 @@ export async function startTest( code: () => code, codeSentCallback: (sent) => { for (let i = 0; i < sent.length; i++) { - code += phone![5] + code += phone[5] } }, }) diff --git a/packages/core/src/highlevel/methods/files/download-node-stream.ts b/packages/core/src/highlevel/methods/files/download-node-stream.ts index d105922b..49ba6ad7 100644 --- a/packages/core/src/highlevel/methods/files/download-node-stream.ts +++ b/packages/core/src/highlevel/methods/files/download-node-stream.ts @@ -13,4 +13,4 @@ declare function downloadAsNodeStream( client: ITelegramClient, location: FileDownloadLocation, params?: FileDownloadParameters, -): import('stream').Readable +): import('node:stream').Readable diff --git a/packages/core/src/highlevel/storage/storage.ts b/packages/core/src/highlevel/storage/storage.ts index 0f32baab..863b202f 100644 --- a/packages/core/src/highlevel/storage/storage.ts +++ b/packages/core/src/highlevel/storage/storage.ts @@ -1,3 +1,4 @@ +import { ServiceOptions } from '../../storage/service/base.js' import { StorageManager } from '../../storage/storage.js' import { PublicPart } from '../../types/utils.js' import { ITelegramStorageProvider } from './provider.js' @@ -17,26 +18,40 @@ export interface TelegramStorageManagerExtraOptions { } export class TelegramStorageManager { + private provider + + readonly updates + readonly self: PublicPart + readonly refMsgs + readonly peers: PublicPart + constructor( private mt: StorageManager, 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) - readonly self: PublicPart = new CurrentUserService(this.provider.kv, this.mt._serviceOptions) - readonly refMsgs = new RefMessagesService( - this.options.refMessages ?? {}, - this.provider.refMessages, - this.mt._serviceOptions, - ) - readonly peers: PublicPart = new PeersService( - this.options.peers ?? {}, - this.provider.peers, - this.refMsgs, - this.mt._serviceOptions, - ) + this.updates = new UpdatesStateService(this.provider.kv, serviceOptions) + this.self = new CurrentUserService(this.provider.kv, serviceOptions) + this.refMsgs = new RefMessagesService( + this.options.refMessages ?? {}, + this.provider.refMessages, + serviceOptions, + ) + this.peers = new PeersService( + this.options.peers ?? {}, + this.provider.peers, + this.refMsgs, + serviceOptions, + ) + } async clear(withAuthKeys = false) { await this.provider.peers.deleteAll() diff --git a/packages/core/src/highlevel/types/files/utils.ts b/packages/core/src/highlevel/types/files/utils.ts index 0859ffeb..0eac28bf 100644 --- a/packages/core/src/highlevel/types/files/utils.ts +++ b/packages/core/src/highlevel/types/files/utils.ts @@ -1,5 +1,5 @@ /* eslint-disable no-restricted-imports */ -import type { ReadStream } from 'fs' +import type { ReadStream } from 'node:fs' import { tdFileId } from '@mtcute/file-id' import { tl } from '@mtcute/tl' diff --git a/packages/core/src/highlevel/types/stories/all-stories.ts b/packages/core/src/highlevel/types/stories/all-stories.ts index 24cf549b..bafc4fd7 100644 --- a/packages/core/src/highlevel/types/stories/all-stories.ts +++ b/packages/core/src/highlevel/types/stories/all-stories.ts @@ -12,13 +12,14 @@ import { StoriesStealthMode } from './stealth-mode.js' * Returned by {@link TelegramClient.getAllStories} */ export class AllStories { + /** Peers index */ + readonly _peers constructor( /** Raw TL object */ readonly raw: tl.stories.RawAllStories, - ) {} - - /** Peers index */ - readonly _peers = PeersIndex.from(this.raw) + ) { + this._peers = PeersIndex.from(this.raw) + } /** Whether there are more stories to fetch */ get hasMore(): boolean { diff --git a/packages/core/src/highlevel/types/stories/story-viewer.ts b/packages/core/src/highlevel/types/stories/story-viewer.ts index e20afddf..75e9e21d 100644 --- a/packages/core/src/highlevel/types/stories/story-viewer.ts +++ b/packages/core/src/highlevel/types/stories/story-viewer.ts @@ -129,9 +129,10 @@ export class StoryRepost { * List of story viewers. */ export class StoryViewersList { - constructor(readonly raw: tl.stories.RawStoryViewsList) {} - - readonly _peers = PeersIndex.from(this.raw) + readonly _peers: PeersIndex + constructor(readonly raw: tl.stories.RawStoryViewsList) { + this._peers = PeersIndex.from(this.raw) + } /** Next offset for pagination */ get next(): string | undefined { diff --git a/packages/core/src/highlevel/updates/manager.ts b/packages/core/src/highlevel/updates/manager.ts index fc9a3e52..226be0d7 100644 --- a/packages/core/src/highlevel/updates/manager.ts +++ b/packages/core/src/highlevel/updates/manager.ts @@ -100,7 +100,7 @@ export class UpdatesManager { pendingQtsUpdatesPostponed = new SortedLinkedList((a, b) => a.qtsBefore! - b.qtsBefore!) pendingUnorderedUpdates = new Deque() - noDispatchEnabled = !this.params.disableNoDispatch + noDispatchEnabled // channel id or 0 => msg id noDispatchMsg = new Map>() // channel id or 0 => pts @@ -128,14 +128,14 @@ export class UpdatesManager { // whether to catch up channels from the locally stored pts catchingUp = false - catchUpOnStart = this.params.catchUp ?? false + catchUpOnStart cpts = new Map() cptsMod = new Map() channelDiffTimeouts = new Map() channelsOpened = new Map() - log = this.client.log.create('updates') + log private _handler: RawUpdateHandler = () => {} private _onCatchingUp: (catchingUp: boolean) => void = () => {} @@ -157,6 +157,9 @@ export class UpdatesManager { this.hasTimedoutPostponed = true this.updatesLoopCv.notify() }) + this.log = client.log.create('updates') + this.catchUpOnStart = params.catchUp ?? false + this.noDispatchEnabled = !params.disableNoDispatch } setHandler(handler: RawUpdateHandler): void { diff --git a/packages/core/src/highlevel/utils/file-utils.ts b/packages/core/src/highlevel/utils/file-utils.ts index d51c9d46..d7b26a94 100644 --- a/packages/core/src/highlevel/utils/file-utils.ts +++ b/packages/core/src/highlevel/utils/file-utils.ts @@ -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 -const JPEG_HEADER = () => getPlatform().hexDecode( - 'ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e1928' + - '2321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a0aad' + - 'aad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2d2d3c35' + - '3c76414176f8a58ca5f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f' + - '8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8ffc0001108000000' + - '0003012200021101031101ffc4001f0000010501010101010100000000000000000' + - '102030405060708090a0bffc400b5100002010303020403050504040000017d0102' + - '0300041105122131410613516107227114328191a1082342b1c11552d1f02433627' + - '282090a161718191a25262728292a3435363738393a434445464748494a53545556' + - '5758595a636465666768696a737475767778797a838485868788898a92939495969' + - '798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4' + - 'd5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030' + - '101010101010101010000000000000102030405060708090a0bffc400b511000201' + - '0204040304070504040001027700010203110405213106124151076171132232810' + - '8144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a3536' + - '3738393a434445464748494a535455565758595a636465666768696a73747576777' + - '8797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5' + - 'b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f' + - '3f4f5f6f7f8f9faffda000c03010002110311003f00', -) +const JPEG_HEADER = () => + getPlatform().hexDecode( + 'ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e1928' + + '2321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a0aad' + + 'aad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2d2d3c35' + + '3c76414176f8a58ca5f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f' + + '8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8ffc0001108000000' + + '0003012200021101031101ffc4001f0000010501010101010100000000000000000' + + '102030405060708090a0bffc400b5100002010303020403050504040000017d0102' + + '0300041105122131410613516107227114328191a1082342b1c11552d1f02433627' + + '282090a161718191a25262728292a3435363738393a434445464748494a53545556' + + '5758595a636465666768696a737475767778797a838485868788898a92939495969' + + '798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4' + + 'd5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030' + + '101010101010101010000000000000102030405060708090a0bffc400b511000201' + + '0204040304070504040001027700010203110405213106124151076171132232810' + + '8144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a3536' + + '3738393a434445464748494a535455565758595a636465666768696a73747576777' + + '8797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5' + + 'b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f' + + '3f4f5f6f7f8f9faffda000c03010002110311003f00', + ) let JPEG_HEADER_BYTES: Uint8Array | null = null const JPEG_FOOTER = new Uint8Array([0xff, 0xd9]) @@ -131,5 +132,5 @@ export function svgPathToFile(path: string): Uint8Array { export function extractFileName(path: string): string { if (path.startsWith('file:')) path = path.slice(5) - return path.split(/[\\/]/).pop()! + return path.split(/[\\/]/).pop()!.split('?')[0] } diff --git a/packages/core/src/highlevel/utils/query-batcher.ts b/packages/core/src/highlevel/utils/query-batcher.ts index abc5e811..f5c73920 100644 --- a/packages/core/src/highlevel/utils/query-batcher.ts +++ b/packages/core/src/highlevel/utils/query-batcher.ts @@ -95,7 +95,7 @@ export function batchedQuery(params: { } return new Promise((resolve, reject) => { - arr!.push([item, resolve, reject]) + arr.push([item, resolve, reject]) }) } diff --git a/packages/core/src/highlevel/worker/app-config.ts b/packages/core/src/highlevel/worker/app-config.ts index 6f5dcbbc..cea8564a 100644 --- a/packages/core/src/highlevel/worker/app-config.ts +++ b/packages/core/src/highlevel/worker/app-config.ts @@ -3,10 +3,13 @@ import { AppConfigManager } from '../managers/app-config-manager.js' import { WorkerInvoker } from './invoker.js' export class AppConfigManagerProxy implements PublicPart { - constructor(readonly invoker: WorkerInvoker) {} + readonly get: AppConfigManager['get'] + readonly getField - private _bind = this.invoker.makeBinder('app-config') + constructor(readonly invoker: WorkerInvoker) { + const bind = invoker.makeBinder('app-config') - readonly get = this._bind('get') - readonly getField = this._bind('getField') + this.get = bind('get') + this.getField = bind('getField') + } } diff --git a/packages/core/src/highlevel/worker/port.ts b/packages/core/src/highlevel/worker/port.ts index 6078f775..1a24cec6 100644 --- a/packages/core/src/highlevel/worker/port.ts +++ b/packages/core/src/highlevel/worker/port.ts @@ -14,12 +14,68 @@ export interface TelegramWorkerPortOptions { } export abstract class TelegramWorkerPort 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('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] - readonly log = new LogManager('worker') - private _serverUpdatesHandler: (updates: tl.TypeUpdates) => void = () => {} onServerUpdate(handler: (updates: tl.TypeUpdates) => void): void { this._serverUpdatesHandler = handler @@ -69,13 +125,6 @@ export abstract class TelegramWorkerPort imp } } - private _connection = this.connectToWorker(this.options.worker, this._onMessage) - private _invoker = new WorkerInvoker(this._connection[0]) - private _bind = this._invoker.makeBinder('client') - - readonly storage = new TelegramStorageProxy(this._invoker) - readonly appConfig = new AppConfigManagerProxy(this._invoker) - private _destroyed = false destroy(terminate = false): void { if (this._destroyed) return @@ -91,26 +140,8 @@ export abstract class TelegramWorkerPort imp return this._invoker.invoke('custom', method as string, args) as Promise> } - readonly prepare = this._bind('prepare') - private _connect = this._bind('connect') async connect(): Promise { await this._connect() 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') } diff --git a/packages/core/src/highlevel/worker/protocol.ts b/packages/core/src/highlevel/worker/protocol.ts index 56401fd4..4a603578 100644 --- a/packages/core/src/highlevel/worker/protocol.ts +++ b/packages/core/src/highlevel/worker/protocol.ts @@ -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' diff --git a/packages/core/src/highlevel/worker/storage.ts b/packages/core/src/highlevel/worker/storage.ts index 49252410..4d07f754 100644 --- a/packages/core/src/highlevel/worker/storage.ts +++ b/packages/core/src/highlevel/worker/storage.ts @@ -8,25 +8,32 @@ import { TelegramStorageManager } from '../storage/storage.js' import { WorkerInvoker } from './invoker.js' class CurrentUserServiceProxy implements PublicPart { - constructor(private _invoker: WorkerInvoker) {} - private _bind = this._invoker.makeBinder('storage-self') + private _store + private _storeFrom + private _fetch + private _update + + constructor(invoker: WorkerInvoker) { + const bind = invoker.makeBinder('storage-self') + this._store = bind('store') + this._storeFrom = bind('storeFrom') + this._fetch = bind('fetch') + this._update = bind('update') + } private _cached?: CurrentUserInfo | null - private _store = this._bind('store') async store(info: CurrentUserInfo | null): Promise { await this._store(info) this._cached = info } - private _storeFrom = this._bind('storeFrom') async storeFrom(user: tl.TypeUser): Promise { this._cached = await this._storeFrom(user) return this._cached } - private _fetch = this._bind('fetch') async fetch(): Promise { if (this._cached) return this._cached @@ -45,7 +52,6 @@ class CurrentUserServiceProxy implements PublicPart { return this._cached } - private _update = this._bind('update') async update(params: Parameters[0]): Promise { await this._update(params) this._cached = await this._fetch() @@ -53,28 +59,41 @@ class CurrentUserServiceProxy implements PublicPart { } class PeersServiceProxy implements PublicPart { - constructor(private _invoker: WorkerInvoker) {} - private _bind = this._invoker.makeBinder('storage-peers') + readonly updatePeersFrom + readonly store + readonly getById + readonly getByPhone + readonly getByUsername + readonly getCompleteById - readonly updatePeersFrom = this._bind('updatePeersFrom') - readonly store = this._bind('store') - readonly getById = this._bind('getById') - readonly getByPhone = this._bind('getByPhone') - readonly getByUsername = this._bind('getByUsername') - readonly getCompleteById = this._bind('getCompleteById') + constructor(private _invoker: WorkerInvoker) { + const bind = this._invoker.makeBinder('storage-peers') + + this.updatePeersFrom = bind('updatePeersFrom') + this.store = bind('store') + this.getById = bind('getById') + this.getByPhone = bind('getByPhone') + this.getByUsername = bind('getByUsername') + this.getCompleteById = bind('getCompleteById') + } } export class TelegramStorageProxy implements PublicPart { - constructor(private _invoker: WorkerInvoker) {} + readonly self + readonly peers - private _bind = this._invoker.makeBinder('storage') + readonly clear + + constructor(private _invoker: WorkerInvoker) { + const bind = this._invoker.makeBinder('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 readonly updates = 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') } diff --git a/packages/core/src/network/client.ts b/packages/core/src/network/client.ts index d58eba62..5163a02f 100644 --- a/packages/core/src/network/client.ts +++ b/packages/core/src/network/client.ts @@ -351,7 +351,7 @@ export class MtClient extends EventEmitter { */ async close(): Promise { this._config.destroy() - this.network.destroy() + await this.network.destroy() await this.storage.save() await this.storage.destroy?.() diff --git a/packages/core/src/network/mtproto-session.ts b/packages/core/src/network/mtproto-session.ts index 93adf044..5fc4e0e0 100644 --- a/packages/core/src/network/mtproto-session.ts +++ b/packages/core/src/network/mtproto-session.ts @@ -91,9 +91,9 @@ export type PendingMessage = export class MtprotoSession { _sessionId = randomLong() - _authKey = new AuthKey(this._crypto, this.log, this._readerMap) - _authKeyTemp = new AuthKey(this._crypto, this.log, this._readerMap) - _authKeyTempSecondary = new AuthKey(this._crypto, this.log, this._readerMap) + _authKey: AuthKey + _authKeyTemp: AuthKey + _authKeyTempSecondary: AuthKey _timeOffset = 0 _lastMessageId = Long.ZERO @@ -139,6 +139,10 @@ export class MtprotoSession { readonly _salts: ServerSaltManager, ) { 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 { diff --git a/packages/core/src/network/multi-session-connection.ts b/packages/core/src/network/multi-session-connection.ts index e301294f..3ff7021a 100644 --- a/packages/core/src/network/multi-session-connection.ts +++ b/packages/core/src/network/multi-session-connection.ts @@ -116,7 +116,9 @@ export class MultiSessionConnection extends EventEmitter { // destroy extra connections for (let i = this._connections.length - 1; i >= this._count; i--) { 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) @@ -199,8 +201,8 @@ export class MultiSessionConnection extends EventEmitter { } _destroyed = false - destroy(): void { - this._connections.forEach((conn) => conn.destroy()) + async destroy(): Promise { + await Promise.all(this._connections.map((conn) => conn.destroy())) this._sessions.forEach((sess) => sess.reset()) this.removeAllListeners() diff --git a/packages/core/src/network/network-manager.ts b/packages/core/src/network/network-manager.ts index 632c45d6..df4e1d7d 100644 --- a/packages/core/src/network/network-manager.ts +++ b/packages/core/src/network/network-manager.ts @@ -188,53 +188,16 @@ export interface RpcCallOptions { */ export class DcConnectionManager { private _salts = new ServerSaltManager() - private __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, - }) - - private _log = this.manager._log.create('dc-manager') + private _log /** Main connection pool */ main: MultiSessionConnection - /** Upload connection pool */ - upload = new MultiSessionConnection( - this.__baseConnectionParams(), - this.manager._connectionCount('upload', this.dcId, this.manager.params.isPremium), - this._log, - 'UPLOAD', - ) - + upload: MultiSessionConnection /** Download connection pool */ - download = new MultiSessionConnection( - this.__baseConnectionParams(), - this.manager._connectionCount('download', this.dcId, this.manager.params.isPremium), - this._log, - 'DOWNLOAD', - ) - + download: MultiSessionConnection /** Download connection pool (for small files) */ - downloadSmall = new MultiSessionConnection( - this.__baseConnectionParams(), - this.manager._connectionCount('downloadSmall', this.dcId, this.manager.params.isPremium), - this._log, - 'DOWNLOAD_SMALL', - ) + downloadSmall: MultiSessionConnection private get _mainConnectionCount() { if (!this.isPrimary) return 1 @@ -252,9 +215,29 @@ export class DcConnectionManager { /** Whether this DC is the primary one */ public isPrimary = false, ) { + this._log = this.manager._log.create('dc-manager') 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.dc = _dcs.main @@ -263,6 +246,24 @@ export class DcConnectionManager { } 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('upload') @@ -412,11 +413,11 @@ export class DcConnectionManager { return true } - destroy() { - this.main.destroy() - this.upload.destroy() - this.download.destroy() - this.downloadSmall.destroy() + async destroy() { + await this.main.destroy() + await this.upload.destroy() + await this.download.destroy() + await this.downloadSmall.destroy() this._salts.destroy() } } @@ -425,8 +426,8 @@ export class DcConnectionManager { * Class that manages all connections to Telegram servers. */ export class NetworkManager { - readonly _log = this.params.log.create('network') - readonly _storage = this.params.storage + readonly _log + readonly _storage readonly _initConnectionParams: tl.RawInitConnectionRequest readonly _transportFactory: TransportFactory @@ -465,6 +466,9 @@ export class NetworkManager { this._onConfigChanged = this._onConfigChanged.bind(this) config.onReload(this._onConfigChanged) + + this._log = params.log.create('network') + this._storage = params.storage } private async _findDcOptions(dcId: number): Promise { @@ -857,9 +861,9 @@ export class NetworkManager { return this._primaryDc.dcId } - destroy(): void { + async destroy(): Promise { for (const dc of this._dcConnections.values()) { - dc.destroy() + await dc.destroy() } this.config.offReload(this._onConfigChanged) this._resetOnNetworkChange?.() diff --git a/packages/core/src/network/persistent-connection.ts b/packages/core/src/network/persistent-connection.ts index 4c9b2cac..6c799bae 100644 --- a/packages/core/src/network/persistent-connection.ts +++ b/packages/core/src/network/persistent-connection.ts @@ -69,7 +69,9 @@ export abstract class PersistentConnection extends EventEmitter { changeTransport(factory: TransportFactory): void { 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() @@ -149,7 +151,9 @@ export abstract class PersistentConnection extends EventEmitter { ) if (wait === false) { - this.destroy() + this.destroy().catch((err) => { + this.log.warn('error destroying connection: %s', err) + }) return } @@ -192,7 +196,9 @@ export abstract class PersistentConnection extends EventEmitter { // if we are already connected if (this.isConnected) { this._shouldReconnectImmediately = true - this._transport.close() + Promise.resolve(this._transport.close()).catch((err) => { + this.log.error('error closing transport: %s', err) + }) return } @@ -201,12 +207,12 @@ export abstract class PersistentConnection extends EventEmitter { this.connect() } - disconnectManual(): void { + async disconnectManual(): Promise { this._disconnectedManually = true - this._transport.close() + await this._transport.close() } - destroy(): void { + async destroy(): Promise { if (this._reconnectionTimeout != null) { clearTimeout(this._reconnectionTimeout) } @@ -214,7 +220,7 @@ export abstract class PersistentConnection extends EventEmitter { clearTimeout(this._inactivityTimeout) } - this._transport.close() + await this._transport.close() this._transport.removeAllListeners() 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._inactive = true 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 { diff --git a/packages/core/src/network/session-connection.ts b/packages/core/src/network/session-connection.ts index 27bb18b4..0dc08df2 100644 --- a/packages/core/src/network/session-connection.ts +++ b/packages/core/src/network/session-connection.ts @@ -70,7 +70,7 @@ function makeNiceStack(error: tl.RpcError, stack: string, method?: string) { * A connection to a single DC. */ export class SessionConnection extends PersistentConnection { - readonly params!: SessionConnectionParams + declare readonly params: SessionConnectionParams private _flushTimer = new EarlyTimer() private _queuedDestroySession: Long[] = [] @@ -78,7 +78,7 @@ export class SessionConnection extends PersistentConnection { // waitForMessage private _pendingWaitForUnencrypted: [ControllablePromise, NodeJS.Timeout][] = [] - private _usePfs = this.params.usePfs ?? false + private _usePfs private _isPfsBindingPending = false private _isPfsBindingPendingInBackground = false private _pfsUpdateTimeout?: NodeJS.Timeout @@ -102,9 +102,12 @@ export class SessionConnection extends PersistentConnection { this._crypto = params.crypto this._salts = params.salts 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 { const key = temp ? this._session._authKeyTemp : this._session._authKey @@ -152,8 +155,8 @@ export class SessionConnection extends PersistentConnection { this.reset() } - destroy(): void { - super.destroy() + async destroy(): Promise { + await super.destroy() this.reset(true) } @@ -1459,7 +1462,9 @@ export class SessionConnection extends PersistentConnection { if (online) { this.reconnect() } else { - this.disconnectManual() + this.disconnectManual().catch((err) => { + this.log.warn('error while disconnecting: %s', err) + }) } } diff --git a/packages/core/src/network/transports/abstract.ts b/packages/core/src/network/transports/abstract.ts index eaf70218..94f2be42 100644 --- a/packages/core/src/network/transports/abstract.ts +++ b/packages/core/src/network/transports/abstract.ts @@ -48,7 +48,7 @@ export interface ITelegramTransport extends EventEmitter { */ connect(dc: BasicDcOption, testMode: boolean): void /** call to close existing connection to some DC */ - close(): void + close(): MaybePromise /** send a message */ send(data: Uint8Array): Promise diff --git a/packages/core/src/storage/memory/repository/auth-keys.ts b/packages/core/src/storage/memory/repository/auth-keys.ts index 79396198..233184b5 100644 --- a/packages/core/src/storage/memory/repository/auth-keys.ts +++ b/packages/core/src/storage/memory/repository/auth-keys.ts @@ -8,13 +8,14 @@ interface AuthKeysState { } export class MemoryAuthKeysRepository implements IAuthKeysRepository { - constructor(readonly _driver: MemoryStorageDriver) {} - - readonly state = this._driver.getState('authKeys', () => ({ - authKeys: new Map(), - authKeysTemp: new Map(), - authKeysTempExpiry: new Map(), - })) + readonly state + constructor(readonly _driver: MemoryStorageDriver) { + this.state = this._driver.getState('authKeys', () => ({ + authKeys: new Map(), + authKeysTemp: new Map(), + authKeysTempExpiry: new Map(), + })) + } set(dc: number, key: Uint8Array | null): void { if (key) { diff --git a/packages/core/src/storage/memory/repository/kv.ts b/packages/core/src/storage/memory/repository/kv.ts index f97c1b63..fe2fca6f 100644 --- a/packages/core/src/storage/memory/repository/kv.ts +++ b/packages/core/src/storage/memory/repository/kv.ts @@ -2,9 +2,10 @@ import { IKeyValueRepository } from '../../repository/key-value.js' import { MemoryStorageDriver } from '../driver.js' export class MemoryKeyValueRepository implements IKeyValueRepository { - constructor(readonly _driver: MemoryStorageDriver) {} - - readonly state = this._driver.getState>('kv', () => new Map()) + readonly state + constructor(readonly _driver: MemoryStorageDriver) { + this.state = this._driver.getState>('kv', () => new Map()) + } set(key: string, value: Uint8Array): void { this.state.set(key, value) diff --git a/packages/core/src/storage/memory/repository/peers.ts b/packages/core/src/storage/memory/repository/peers.ts index 9c5cb621..6d2b7eac 100644 --- a/packages/core/src/storage/memory/repository/peers.ts +++ b/packages/core/src/storage/memory/repository/peers.ts @@ -8,13 +8,14 @@ interface PeersState { } export class MemoryPeersRepository implements IPeersRepository { - constructor(readonly _driver: MemoryStorageDriver) {} - - readonly state = this._driver.getState('peers', () => ({ - entities: new Map(), - usernameIndex: new Map(), - phoneIndex: new Map(), - })) + readonly state + constructor(readonly _driver: MemoryStorageDriver) { + this.state = this._driver.getState('peers', () => ({ + entities: new Map(), + usernameIndex: new Map(), + phoneIndex: new Map(), + })) + } store(peer: IPeersRepository.PeerInfo): void { const old = this.state.entities.get(peer.id) diff --git a/packages/core/src/storage/memory/repository/ref-messages.ts b/packages/core/src/storage/memory/repository/ref-messages.ts index cb65d409..0cb2b746 100644 --- a/packages/core/src/storage/memory/repository/ref-messages.ts +++ b/packages/core/src/storage/memory/repository/ref-messages.ts @@ -6,11 +6,12 @@ interface RefMessagesState { } export class MemoryRefMessagesRepository implements IReferenceMessagesRepository { - constructor(readonly _driver: MemoryStorageDriver) {} - - readonly state = this._driver.getState('refMessages', () => ({ - refs: new Map(), - })) + readonly state + constructor(readonly _driver: MemoryStorageDriver) { + this.state = this._driver.getState('refMessages', () => ({ + refs: new Map(), + })) + } store(peerId: number, chatId: number, msgId: number): void { if (!this.state.refs.has(peerId)) { diff --git a/packages/core/src/storage/sqlite/index.ts b/packages/core/src/storage/sqlite/index.ts index 37218f7d..038ef9f8 100644 --- a/packages/core/src/storage/sqlite/index.ts +++ b/packages/core/src/storage/sqlite/index.ts @@ -10,10 +10,15 @@ export { BaseSqliteStorageDriver } export * from './types.js' export class BaseSqliteStorage implements IMtStorageProvider, ITelegramStorageProvider { - constructor(readonly driver: BaseSqliteStorageDriver) {} + readonly authKeys + readonly kv + readonly refMessages + readonly peers - readonly authKeys = new SqliteAuthKeysRepository(this.driver) - readonly kv = new SqliteKeyValueRepository(this.driver) - readonly refMessages = new SqliteRefMessagesRepository(this.driver) - readonly peers = new SqlitePeersRepository(this.driver) + constructor(readonly driver: BaseSqliteStorageDriver) { + this.authKeys = new SqliteAuthKeysRepository(this.driver) + this.kv = new SqliteKeyValueRepository(this.driver) + this.refMessages = new SqliteRefMessagesRepository(this.driver) + this.peers = new SqlitePeersRepository(this.driver) + } } diff --git a/packages/core/src/storage/sqlite/repository/peers.ts b/packages/core/src/storage/sqlite/repository/peers.ts index 4f5a78a1..2e48b9ad 100644 --- a/packages/core/src/storage/sqlite/repository/peers.ts +++ b/packages/core/src/storage/sqlite/repository/peers.ts @@ -8,8 +8,7 @@ interface PeerDto { usernames: string updated: number phone: string | null - // eslint-disable-next-line no-restricted-globals - complete: Buffer + complete: Uint8Array } function mapPeerDto(dto: PeerDto): IPeersRepository.PeerInfo { diff --git a/packages/core/src/storage/sqlite/repository/ref-messages.ts b/packages/core/src/storage/sqlite/repository/ref-messages.ts index ff42516c..0311adea 100644 --- a/packages/core/src/storage/sqlite/repository/ref-messages.ts +++ b/packages/core/src/storage/sqlite/repository/ref-messages.ts @@ -1,5 +1,4 @@ -import { IReferenceMessagesRepository } from '@mtcute/core' - +import { IReferenceMessagesRepository } from '../../../highlevel/storage/repository/ref-messages.js' import { BaseSqliteStorageDriver } from '../driver.js' import { ISqliteStatement } from '../types.js' diff --git a/packages/core/src/storage/storage.ts b/packages/core/src/storage/storage.ts index 6563a007..5303972c 100644 --- a/packages/core/src/storage/storage.ts +++ b/packages/core/src/storage/storage.ts @@ -40,23 +40,30 @@ export interface StorageManagerExtraOptions { } 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 - readonly driver = this.provider.driver - readonly log = this.options.log.create('storage') + constructor(readonly options: StorageManagerOptions & StorageManagerExtraOptions) { + this.provider = this.options.provider + this.driver = this.provider.driver + this.log = this.options.log.create('storage') - readonly _serviceOptions: ServiceOptions = { - driver: this.driver, - readerMap: this.options.readerMap, - writerMap: this.options.writerMap, - log: this.log, + const serviceOptions: ServiceOptions = { + driver: this.driver, + readerMap: this.options.readerMap, + writerMap: this.options.writerMap, + 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 _load = asyncResettable(async () => { diff --git a/packages/core/src/utils/logger.ts b/packages/core/src/utils/logger.ts index 37285794..4c7b08c4 100644 --- a/packages/core/src/utils/logger.ts +++ b/packages/core/src/utils/logger.ts @@ -142,21 +142,24 @@ export class LogManager extends Logger { static DEBUG = 4 static VERBOSE = 5 + readonly platform + level: number + handler + constructor(tag = 'base') { // workaround because we cant pass this to super // eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-argument super(null as any, tag) // eslint-disable-next-line @typescript-eslint/no-explicit-any ;(this as any).mgr = this + + this.platform = getPlatform() + this.level = this.platform.getDefaultLogLevel() ?? DEFAULT_LOG_LEVEL + this.handler = this.platform.log.bind(this.platform) } private _filter: (tag: string) => boolean = defaultFilter - readonly platform = getPlatform() - - level = this.platform.getDefaultLogLevel() ?? DEFAULT_LOG_LEVEL - handler = this.platform.log.bind(this.platform) - /** * Create a {@link Logger} with the given tag * diff --git a/packages/crypto-node/package.json b/packages/crypto-node/package.json index 802bd7df..ddbd8d74 100644 --- a/packages/crypto-node/package.json +++ b/packages/crypto-node/package.json @@ -19,19 +19,7 @@ ], "exports": { ".": "./src/index.ts", - "./native.js": "./src/native.ts" - }, - "distOnlyFields": { - "exports": { - ".": { - "import": "./esm/index.js", - "require": "./cjs/index.js" - }, - "./native.js": { - "import": "./esm/native.cjs", - "require": "./cjs/native.cjs" - } - } + "./native.js": "./src/native.cjs" }, "dependencies": { "@mtcute/node": "workspace:^", diff --git a/packages/dispatcher/package.json b/packages/dispatcher/package.json index 1ea7cfa5..d33bd1c5 100644 --- a/packages/dispatcher/package.json +++ b/packages/dispatcher/package.json @@ -8,14 +8,7 @@ "main": "src/index.ts", "type": "module", "sideEffects": false, - "distOnlyFields": { - "exports": { - ".": { - "import": "./esm/index.js", - "require": "./cjs/index.js" - } - } - }, + "exports": "./src/index.ts", "scripts": { "build": "pnpm run -w build-package dispatcher", "gen-updates": "node ./scripts/generate.cjs" diff --git a/packages/dispatcher/src/state/providers/memory.ts b/packages/dispatcher/src/state/providers/memory.ts index 65dd3928..d72a521c 100644 --- a/packages/dispatcher/src/state/providers/memory.ts +++ b/packages/dispatcher/src/state/providers/memory.ts @@ -14,9 +14,12 @@ interface RateLimitDto { } class MemoryStateRepository implements IStateRepository { - constructor(readonly _driver: MemoryStorageDriver) {} - - readonly state = this._driver.getState>('dispatcher_fsm', () => new Map()) + readonly state + readonly rl + constructor(readonly _driver: MemoryStorageDriver) { + this.state = this._driver.getState>('dispatcher_fsm', () => new Map()) + this.rl = this._driver.getState>('rl', () => new Map()) + } setState(key: string, state: string, ttl?: number | undefined): void { this.state.set(key, { @@ -56,8 +59,6 @@ class MemoryStateRepository implements IStateRepository { } } - readonly rl = this._driver.getState>('rl', () => new Map()) - getRateLimit(key: string, now: number, limit: number, window: number): [number, number] { // leaky bucket const item = this.rl.get(key) @@ -97,7 +98,9 @@ class MemoryStateRepository implements IStateRepository { } export class MemoryStateStorage implements IStateStorageProvider { - constructor(readonly driver: MemoryStorageDriver = new MemoryStorageDriver()) {} + readonly state - readonly state = new MemoryStateRepository(this.driver) + constructor(readonly driver: MemoryStorageDriver = new MemoryStorageDriver()) { + this.state = new MemoryStateRepository(this.driver) + } } diff --git a/packages/dispatcher/src/state/providers/sqlite.ts b/packages/dispatcher/src/state/providers/sqlite.ts index 5b3423db..1273c07e 100644 --- a/packages/dispatcher/src/state/providers/sqlite.ts +++ b/packages/dispatcher/src/state/providers/sqlite.ts @@ -116,11 +116,12 @@ class SqliteStateRepository implements IStateRepository { } export class SqliteStateStorage implements IStateStorageProvider { - constructor(readonly driver: BaseSqliteStorageDriver) {} + readonly state + constructor(readonly driver: BaseSqliteStorageDriver) { + this.state = new SqliteStateRepository(driver) + } static from(provider: BaseSqliteStorage) { return new SqliteStateStorage(provider.driver) } - - readonly state = new SqliteStateRepository(this.driver) } diff --git a/packages/file-id/package.json b/packages/file-id/package.json index a63a6df0..dd06018e 100644 --- a/packages/file-id/package.json +++ b/packages/file-id/package.json @@ -5,19 +5,13 @@ "description": "Support for TDLib and Bot API file ID for mtcute", "author": "alina sireneva ", "license": "MIT", - "main": "src/index.ts", "type": "module", "sideEffects": false, "scripts": { "build": "pnpm run -w build-package file-id" }, - "distOnlyFields": { - "exports": { - ".": { - "import": "./esm/index.js", - "require": "./cjs/index.js" - } - } + "exports": { + ".": "./src/index.ts" }, "dependencies": { "@mtcute/tl-runtime": "workspace:^", diff --git a/packages/html-parser/package.json b/packages/html-parser/package.json index a4f5b738..0ec99170 100644 --- a/packages/html-parser/package.json +++ b/packages/html-parser/package.json @@ -11,19 +11,10 @@ "scripts": { "build": "pnpm run -w build-package html-parser" }, - "distOnlyFields": { - "exports": { - ".": { - "import": "./esm/index.js", - "require": "./cjs/index.js" - } - } - }, + "exports": "./src/index.ts", "dependencies": { "htmlparser2": "^6.0.1", - "long": "5.2.3" - }, - "devDependencies": { + "long": "5.2.3", "@mtcute/core": "workspace:^" } } diff --git a/packages/http-proxy/package.json b/packages/http-proxy/package.json index b790df7d..fc96c580 100644 --- a/packages/http-proxy/package.json +++ b/packages/http-proxy/package.json @@ -12,14 +12,7 @@ "docs": "typedoc", "build": "pnpm run -w build-package http-proxy" }, - "distOnlyFields": { - "exports": { - ".": { - "import": "./esm/index.js", - "require": "./cjs/index.js" - } - } - }, + "exports": "./index.ts", "dependencies": { "@mtcute/node": "workspace:^" } diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 75b094f1..7559fa44 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -11,20 +11,18 @@ "scripts": { "build": "pnpm run -w build-package i18n" }, - "distOnlyFields": { - "exports": { - ".": { - "import": "./esm/index.js", - "require": "./cjs/index.js" - }, - "./plurals/*": { - "import": "./esm/plurals/*", - "require": "./cjs/plurals/*" - } - } + "exports": { + ".": "./src/index.ts", + "./plurals/*": "./src/plurals/*" }, "devDependencies": { "@mtcute/core": "workspace:^", "@mtcute/dispatcher": "workspace:^" + }, + "jsrOnlyFields": { + "dependencies": { + "@mtcute/core": "workspace:^", + "@mtcute/dispatcher": "workspace:^" + } } } diff --git a/packages/markdown-parser/package.json b/packages/markdown-parser/package.json index da5ce34e..888c9851 100644 --- a/packages/markdown-parser/package.json +++ b/packages/markdown-parser/package.json @@ -11,18 +11,9 @@ "scripts": { "build": "pnpm run -w build-package markdown-parser" }, - "distOnlyFields": { - "exports": { - ".": { - "import": "./esm/index.js", - "require": "./cjs/index.js" - } - } - }, + "exports": "./src/index.ts", "dependencies": { - "long": "5.2.3" - }, - "devDependencies": { + "long": "5.2.3", "@mtcute/core": "workspace:^" } } diff --git a/packages/mtproxy/fake-tls.ts b/packages/mtproxy/fake-tls.ts index 5044ffd9..b8cfbf5b 100644 --- a/packages/mtproxy/fake-tls.ts +++ b/packages/mtproxy/fake-tls.ts @@ -165,7 +165,7 @@ class TlsHelloWriter implements TlsOperationHandler { pos = 0 private _domain: Buffer - private _grease = initGrease(this.crypto, 7) + private _grease private _scopes: number[] = [] constructor( @@ -175,6 +175,7 @@ class TlsHelloWriter implements TlsOperationHandler { ) { this._domain = domain this.buf = Buffer.allocUnsafe(size) + this._grease = initGrease(this.crypto, 7) } string(buf: Buffer) { diff --git a/packages/mtproxy/package.json b/packages/mtproxy/package.json index b3fef6fb..982cf135 100644 --- a/packages/mtproxy/package.json +++ b/packages/mtproxy/package.json @@ -12,14 +12,7 @@ "docs": "typedoc", "build": "pnpm run -w build-package mtproxy" }, - "distOnlyFields": { - "exports": { - ".": { - "import": "./esm/index.js", - "require": "./cjs/index.js" - } - } - }, + "exports": "./index.ts", "dependencies": { "@mtcute/node": "workspace:^" } diff --git a/packages/node/package.json b/packages/node/package.json index 55a79fe4..97508c4f 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -16,18 +16,6 @@ ".": "./src/index.ts", "./utils.js": "./src/utils.ts" }, - "distOnlyFields": { - "exports": { - ".": { - "import": "./esm/index.js", - "require": "./cjs/index.js" - }, - "./utils.js": { - "import": "./esm/utils.js", - "require": "./cjs/utils.js" - } - } - }, "dependencies": { "@mtcute/core": "workspace:^", "@mtcute/wasm": "workspace:^", diff --git a/packages/node/src/common-internals-node/platform.ts b/packages/node/src/common-internals-node/platform.ts index 49f7ca60..afd58299 100644 --- a/packages/node/src/common-internals-node/platform.ts +++ b/packages/node/src/common-internals-node/platform.ts @@ -12,9 +12,9 @@ const toBuffer = (buf: Uint8Array): Buffer => Buffer.from(buf.buffer, buf.byteOf export class NodePlatform implements ICorePlatform { // ICorePlatform - log!: typeof defaultLoggingHandler - beforeExit!: typeof beforeExit - normalizeFile!: typeof normalizeFile + declare log: typeof defaultLoggingHandler + declare beforeExit: typeof beforeExit + declare normalizeFile: typeof normalizeFile getDeviceModel(): string { return `${os.type()} ${os.arch()} ${os.release()}` diff --git a/packages/socks-proxy/package.json b/packages/socks-proxy/package.json index 58c9f74d..35e1972e 100644 --- a/packages/socks-proxy/package.json +++ b/packages/socks-proxy/package.json @@ -12,14 +12,7 @@ "docs": "typedoc", "build": "pnpm run -w build-package socks-proxy" }, - "distOnlyFields": { - "exports": { - ".": { - "import": "./esm/index.js", - "require": "./cjs/index.js" - } - } - }, + "exports": "./index.ts", "dependencies": { "@mtcute/node": "workspace:^", "ip6": "0.2.7" diff --git a/packages/test/build.config.cjs b/packages/test/build.config.cjs new file mode 100644 index 00000000..17718026 --- /dev/null +++ b/packages/test/build.config.cjs @@ -0,0 +1 @@ +module.exports = () => ({ buildCjs: false }) diff --git a/packages/test/package.json b/packages/test/package.json index aacf8ac6..65341695 100644 --- a/packages/test/package.json +++ b/packages/test/package.json @@ -35,12 +35,5 @@ "browser": { "./src/platform.js": "./src/platform.web.js" }, - "distOnlyFields": { - "exports": { - ".": { - "import": "./esm/index.js", - "require": "./cjs/index.js" - } - } - } + "exports": "./src/index.ts" } diff --git a/packages/tl-runtime/package.json b/packages/tl-runtime/package.json index 653f2cc2..0c7f523c 100644 --- a/packages/tl-runtime/package.json +++ b/packages/tl-runtime/package.json @@ -11,15 +11,9 @@ "docs": "typedoc", "build": "pnpm run -w build-package tl-runtime" }, - "distOnlyFields": { - "exports": { - ".": { - "import": "./esm/index.js", - "require": "./cjs/index.js" - } - } + "exports": { + ".": "./src/index.ts" }, - "main": "src/index.ts", "dependencies": { "long": "5.2.3" } diff --git a/packages/tl-utils/package.json b/packages/tl-utils/package.json index 6a98750a..99222aec 100644 --- a/packages/tl-utils/package.json +++ b/packages/tl-utils/package.json @@ -16,22 +16,8 @@ ".": "./src/index.ts", "./json.js": "./src/json/index.ts" }, - "distOnlyFields": { - "exports": { - ".": { - "import": "./dist/esm/index.js", - "require": "./dist/cjs/index.js" - }, - "./json.js": { - "import": "./dist/esm/json/index.js", - "require": "./dist/cjs/json/index.js" - } - } - }, "dependencies": { - "crc-32": "1.2.0" - }, - "devDependencies": { + "crc-32": "1.2.0", "@mtcute/tl-runtime": "workspace:^" } } diff --git a/packages/tl/.gitignore b/packages/tl/.gitignore index 1e8270be..c6b52dfb 100644 --- a/packages/tl/.gitignore +++ b/packages/tl/.gitignore @@ -1,8 +1,6 @@ -index.d.ts -index.js -errors.d.ts -errors.js +/index.d.ts +/index.js binary/reader.js binary/writer.js -diff.json +/diff.json diff --git a/packages/tl/build.config.cjs b/packages/tl/build.config.cjs index ab0d41d8..c8b1c6d5 100644 --- a/packages/tl/build.config.cjs +++ b/packages/tl/build.config.cjs @@ -1,4 +1,4 @@ -module.exports = ({ fs, path, outDir, packageDir }) => ({ +module.exports = ({ fs, path, outDir, packageDir, jsr, transformFile }) => ({ buildTs: false, buildCjs: false, final() { @@ -22,5 +22,61 @@ module.exports = ({ fs, path, outDir, packageDir }) => ({ for (const f of files) { fs.copyFileSync(path.join(packageDir, f), path.join(outDir, f)) } + + if (jsr) { + // jsr doesn't support cjs, so we'll need to add some shims + // todo: remove this god awfulness when tl esm rewrite + transformFile(path.join(outDir, 'index.js'), (content) => { + return [ + '/// ', + 'const exports = {};', + content, + 'export const tl = exports.tl;', + 'export const mtp = exports.mtp;', + ].join('\n') + }) + transformFile(path.join(outDir, 'binary/reader.js'), (content) => { + return [ + '/// ', + 'const exports = {};', + content, + 'export const __tlReaderMap = exports.__tlReaderMap;', + ].join('\n') + }) + transformFile(path.join(outDir, 'binary/writer.js'), (content) => { + return [ + '/// ', + 'const exports = {};', + content, + 'export const __tlWriterMap = exports.__tlWriterMap;', + ].join('\n') + }) + transformFile(path.join(outDir, 'binary/rsa-keys.js'), (content) => { + return [ + '/// ', + 'const exports = {};', + content, + 'export const __publicKeyIndex = exports.__publicKeyIndex;', + ].join('\n') + }) + + // patch deno.json to add some export maps + transformFile(path.join(outDir, 'deno.json'), (content) => { + const json = JSON.parse(content) + json.exports = {} + + for (const f of files) { + if (!f.match(/\.js(on)?$/)) continue + + if (f === 'index.js') { + json.exports['.'] = './index.js' + } else { + json.exports[`./${f}`] = `./${f}` + } + } + + return JSON.stringify(json, null, 2) + }) + } }, }) diff --git a/packages/tl/package.json b/packages/tl/package.json index caece291..8008e069 100644 --- a/packages/tl/package.json +++ b/packages/tl/package.json @@ -31,5 +31,8 @@ }, "typedoc": { "entryPoint": "index.d.ts" + }, + "jsrOnlyFields": { + "exports": {} } } \ No newline at end of file diff --git a/packages/wasm/build.config.cjs b/packages/wasm/build.config.cjs index be6444e4..9f85a6bc 100644 --- a/packages/wasm/build.config.cjs +++ b/packages/wasm/build.config.cjs @@ -1,6 +1,10 @@ -module.exports = ({ path: { join }, fs, outDir, packageDir }) => ({ +module.exports = ({ path: { join }, fs, outDir, packageDir, jsr, transformFile }) => ({ esmOnlyDirectives: true, final() { fs.cpSync(join(packageDir, 'mtcute.wasm'), join(outDir, 'mtcute.wasm')) + + if (jsr) { + transformFile(join(outDir, 'index.ts'), (code) => code.replace("'../mtcute.wasm'", "'./mtcute.wasm'")) + } }, }) diff --git a/packages/wasm/package.json b/packages/wasm/package.json index 2fea4f59..757fbd0c 100644 --- a/packages/wasm/package.json +++ b/packages/wasm/package.json @@ -17,18 +17,13 @@ ".": "./src/index.ts", "./mtcute.wasm": "./mtcute.wasm" }, - "distOnlyFields": { - "exports": { - ".": { - "import": "./esm/index.js", - "require": "./cjs/index.js" - }, - "./mtcute.wasm": "./mtcute.wasm" - } - }, + "exportsKeepPath": ["./mtcute.wasm"], "devDependencies": { "@mtcute/core": "workspace:^", "@mtcute/web": "workspace:^", "@mtcute/node": "workspace:^" + }, + "jsrOnlyFields": { + "exports": "./src/index.ts" } } diff --git a/packages/web/package.json b/packages/web/package.json index b6cb0999..774696e9 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -13,25 +13,24 @@ "build": "pnpm run -w build-package web" }, "exports": { - ".": "./src/index.ts" - }, - "distOnlyFields": { - "exports": { - ".": { - "import": "./esm/index.js", - "require": "./cjs/index.js" - }, - "./utils.js": { - "import": "./esm/utils.js", - "require": "./cjs/utils.js" - } - } + ".": "./src/index.ts", + "./utils.js": "./src/utils.ts" }, "dependencies": { "@mtcute/core": "workspace:^", - "@mtcute/wasm": "workspace:^" + "@mtcute/wasm": "workspace:^", + "events": "3.2.0" }, "devDependencies": { "@mtcute/test": "workspace:^" + }, + "denoJson": { + "compilerOptions": { + "lib": [ + "dom", + "dom.iterable", + "WebWorker" + ] + } } } diff --git a/packages/web/src/idb/index.ts b/packages/web/src/idb/index.ts index 3d76b78d..cadcd054 100644 --- a/packages/web/src/idb/index.ts +++ b/packages/web/src/idb/index.ts @@ -15,11 +15,17 @@ export { IdbStorageDriver } from './driver.js' * recommended over local storage based one. */ export class IdbStorage implements IMtStorageProvider { - constructor(readonly dbName: string) {} + readonly driver + readonly kv + readonly authKeys + readonly peers + readonly refMessages - readonly driver = new IdbStorageDriver(this.dbName) - readonly kv = new IdbKvRepository(this.driver) - readonly authKeys = new IdbAuthKeysRepository(this.driver) - readonly peers = new IdbPeersRepository(this.driver) - readonly refMessages = new IdbRefMsgRepository(this.driver) + constructor(readonly dbName: string) { + this.driver = new IdbStorageDriver(this.dbName) + this.kv = new IdbKvRepository(this.driver) + this.authKeys = new IdbAuthKeysRepository(this.driver) + this.peers = new IdbPeersRepository(this.driver) + this.refMessages = new IdbRefMsgRepository(this.driver) + } } diff --git a/packages/web/src/platform.ts b/packages/web/src/platform.ts index 027c47b5..d90f2c3a 100644 --- a/packages/web/src/platform.ts +++ b/packages/web/src/platform.ts @@ -8,8 +8,8 @@ import { defaultLoggingHandler } from './logging.js' export class WebPlatform implements ICorePlatform { // ICorePlatform - log!: typeof defaultLoggingHandler - beforeExit!: typeof beforeExit + declare log: typeof defaultLoggingHandler + declare beforeExit: typeof beforeExit getDeviceModel(): string { if (typeof navigator === 'undefined') return 'Browser' @@ -47,14 +47,14 @@ export class WebPlatform implements ICorePlatform { } // ITlPlatform - utf8ByteLength!: typeof utf8ByteLength - utf8Encode!: typeof utf8Encode - utf8Decode!: typeof utf8Decode - hexEncode!: typeof hexEncode - hexDecode!: typeof hexDecode + declare utf8ByteLength: typeof utf8ByteLength + declare utf8Encode: typeof utf8Encode + declare utf8Decode: typeof utf8Decode + declare hexEncode: typeof hexEncode + declare hexDecode: typeof hexDecode - base64Encode!: typeof base64Encode - base64Decode!: typeof base64Decode + declare base64Encode: typeof base64Encode + declare base64Decode: typeof base64Decode } WebPlatform.prototype.log = defaultLoggingHandler diff --git a/packages/web/src/utils.ts b/packages/web/src/utils.ts new file mode 100644 index 00000000..3356b98c --- /dev/null +++ b/packages/web/src/utils.ts @@ -0,0 +1 @@ +export * from '@mtcute/core/utils.js' diff --git a/packages/web/src/websocket.test.ts b/packages/web/src/websocket.test.ts index 91630352..13c7e51a 100644 --- a/packages/web/src/websocket.test.ts +++ b/packages/web/src/websocket.test.ts @@ -11,20 +11,24 @@ const p = getPlatform() describe('WebSocketTransport', () => { const create = async () => { + let closeListener: () => void = () => {} const fakeWs = vi.fn().mockImplementation(() => ({ addEventListener: vi.fn().mockImplementation((event: string, cb: () => void) => { if (event === 'open') { cb() } + if (event === 'close') { + closeListener = cb + } }), removeEventListener: vi.fn(), - close: vi.fn(), + close: vi.fn().mockImplementation(() => closeListener()), send: vi.fn(), })) const transport = new WebSocketTransport({ ws: fakeWs }) const logger = new LogManager() - logger.level = 0 + logger.level = 10 transport.setup(await defaultTestCryptoProvider(), logger) return [transport, fakeWs] as const @@ -89,9 +93,9 @@ describe('WebSocketTransport', () => { const socket = getLastSocket(ws) await vi.waitFor(() => expect(t.state()).toEqual(TransportState.Ready)) - t.close() + await t.close() + console.log('kek') - expect(socket.removeEventListener).toHaveBeenCalled() expect(socket.close).toHaveBeenCalled() }) diff --git a/packages/web/src/websocket.ts b/packages/web/src/websocket.ts index a612960e..a957cc8e 100644 --- a/packages/web/src/websocket.ts +++ b/packages/web/src/websocket.ts @@ -9,7 +9,13 @@ import { ObfuscatedPacketCodec, TransportState, } from '@mtcute/core' -import { BasicDcOption, ICryptoProvider, Logger } from '@mtcute/core/utils.js' +import { + BasicDcOption, + ControllablePromise, + createControllablePromise, + ICryptoProvider, + Logger, +} from '@mtcute/core/utils.js' export type WebSocketConstructor = { new (address: string, protocol?: string): WebSocket @@ -69,8 +75,6 @@ export abstract class BaseWebSocketTransport extends EventEmitter implements ITe this._baseDomain = baseDomain this._subdomains = subdomains this._WebSocket = ws - - this.close = this.close.bind(this) } private _updateLogPrefix() { @@ -123,20 +127,33 @@ export abstract class BaseWebSocketTransport extends EventEmitter implements ITe ) this._socket.addEventListener('open', this.handleConnect.bind(this)) this._socket.addEventListener('error', this.handleError.bind(this)) - this._socket.addEventListener('close', this.close) + this._socket.addEventListener('close', this.handleClosed.bind(this)) } - close(): void { + private _closeWaiters: ControllablePromise[] = [] + async close(): Promise { if (this._state === TransportState.Idle) return - this.log.info('connection closed') - this._state = TransportState.Idle - this._socket!.removeEventListener('close', this.close) + const promise = createControllablePromise() + this._closeWaiters.push(promise) + this._socket!.close() + + return promise + } + + handleClosed(): void { + this.log.info('connection closed') + this._state = TransportState.Idle this._socket = null this._currentDc = null this._packetCodec.reset() this.emit('close') + + for (const waiter of this._closeWaiters) { + waiter.resolve() + } + this._closeWaiters = [] } handleError(event: Event | { error: Error }): void { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9953ae05..27b2801a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,6 +18,9 @@ importers: '@commitlint/config-conventional': specifier: 17.6.5 version: 17.6.5 + '@teidesu/slow-types-compiler': + specifier: 1.0.2 + version: 1.0.2(typescript@5.4.3) '@types/node': specifier: 20.10.0 version: 20.10.0 @@ -26,10 +29,10 @@ importers: version: 8.5.4 '@typescript-eslint/eslint-plugin': specifier: 6.4.0 - version: 6.4.0(@typescript-eslint/parser@6.4.0)(eslint@8.47.0)(typescript@5.1.6) + version: 6.4.0(@typescript-eslint/parser@6.4.0)(eslint@8.47.0)(typescript@5.4.3) '@typescript-eslint/parser': specifier: 6.4.0 - version: 6.4.0(eslint@8.47.0)(typescript@5.1.6) + version: 6.4.0(eslint@8.47.0)(typescript@5.4.3) '@vitest/browser': specifier: 1.4.0 version: 1.4.0(playwright@1.42.1)(vitest@1.4.0) @@ -95,16 +98,16 @@ importers: version: 7.5.1 ts-node: specifier: 10.9.1 - version: 10.9.1(@types/node@20.10.0)(typescript@5.1.6) + version: 10.9.1(@types/node@20.10.0)(typescript@5.4.3) tsconfig-paths: specifier: 4.2.0 version: 4.2.0 typedoc: - specifier: 0.25.3 - version: 0.25.3(typescript@5.1.6) + specifier: 0.25.12 + version: 0.25.12(typescript@5.4.3) typescript: - specifier: 5.1.6 - version: 5.1.6 + specifier: 5.4.3 + version: 5.4.3 vite: specifier: 5.1.6 version: 5.1.6(@types/node@20.10.0) @@ -250,16 +253,15 @@ importers: packages/html-parser: dependencies: + '@mtcute/core': + specifier: workspace:^ + version: link:../core htmlparser2: specifier: ^6.0.1 version: 6.1.0 long: specifier: 5.2.3 version: 5.2.3 - devDependencies: - '@mtcute/core': - specifier: workspace:^ - version: link:../core packages/http-proxy: dependencies: @@ -278,13 +280,12 @@ importers: packages/markdown-parser: dependencies: - long: - specifier: 5.2.3 - version: 5.2.3 - devDependencies: '@mtcute/core': specifier: workspace:^ version: link:../core + long: + specifier: 5.2.3 + version: 5.2.3 packages/mtproxy: dependencies: @@ -390,13 +391,12 @@ importers: packages/tl-utils: dependencies: - crc-32: - specifier: 1.2.0 - version: 1.2.0 - devDependencies: '@mtcute/tl-runtime': specifier: workspace:^ version: link:../tl-runtime + crc-32: + specifier: 1.2.0 + version: 1.2.0 packages/wasm: devDependencies: @@ -418,6 +418,9 @@ importers: '@mtcute/wasm': specifier: workspace:^ version: link:../wasm + events: + specifier: 3.2.0 + version: 3.2.0 devDependencies: '@mtcute/test': specifier: workspace:^ @@ -579,13 +582,13 @@ packages: '@types/node': 20.10.0 chalk: 4.1.2 cosmiconfig: 8.1.3 - cosmiconfig-typescript-loader: 4.3.0(@types/node@20.10.0)(cosmiconfig@8.1.3)(ts-node@10.9.1)(typescript@5.1.6) + cosmiconfig-typescript-loader: 4.3.0(@types/node@20.10.0)(cosmiconfig@8.1.3)(ts-node@10.9.1)(typescript@5.4.3) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 resolve-from: 5.0.0 - ts-node: 10.9.1(@types/node@20.10.0)(typescript@5.1.6) - typescript: 5.1.6 + ts-node: 10.9.1(@types/node@20.10.0)(typescript@5.4.3) + typescript: 5.4.3 transitivePeerDependencies: - '@swc/core' - '@swc/wasm' @@ -1353,11 +1356,38 @@ packages: /@sinclair/typebox@0.27.8: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + /@teidesu/slow-types-compiler@1.0.2(typescript@5.4.3): + resolution: {integrity: sha512-N0e3J/My4t405V5qD2kr6xXwLMlaB+el7bdYKLXJ2yyrLN1eAx4elf6qkeRTF4xy0GoiWrS0gKS0RZfddSOw1w==} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + dependencies: + arg: 5.0.2 + dedent: 1.5.3 + eager-async-pool: 1.0.0 + gunzip-maybe: 1.4.2 + semver: 7.6.0 + tar-stream: 3.1.7 + ts-morph: 22.0.0 + typescript: 5.4.3 + transitivePeerDependencies: + - babel-plugin-macros + dev: true + /@tootallnate/once@2.0.0: resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} dev: false + /@ts-morph/common@0.23.0: + resolution: {integrity: sha512-m7Lllj9n/S6sOkCkRftpM7L24uvmfXQFedlW/4hENcuJH1HHm9u5EgxZb9uVjQSCGrbBWBkOGgcTxNg36r6ywA==} + dependencies: + fast-glob: 3.3.2 + minimatch: 9.0.3 + mkdirp: 3.0.1 + path-browserify: 1.0.1 + dev: true + /@tsconfig/node10@1.0.8: resolution: {integrity: sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==} dev: true @@ -1463,7 +1493,7 @@ packages: '@types/node': 20.10.0 dev: true - /@typescript-eslint/eslint-plugin@6.4.0(@typescript-eslint/parser@6.4.0)(eslint@8.47.0)(typescript@5.1.6): + /@typescript-eslint/eslint-plugin@6.4.0(@typescript-eslint/parser@6.4.0)(eslint@8.47.0)(typescript@5.4.3): resolution: {integrity: sha512-62o2Hmc7Gs3p8SLfbXcipjWAa6qk2wZGChXG2JbBtYpwSRmti/9KHLqfbLs9uDigOexG+3PaQ9G2g3201FWLKg==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: @@ -1475,10 +1505,10 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.5.1 - '@typescript-eslint/parser': 6.4.0(eslint@8.47.0)(typescript@5.1.6) + '@typescript-eslint/parser': 6.4.0(eslint@8.47.0)(typescript@5.4.3) '@typescript-eslint/scope-manager': 6.4.0 - '@typescript-eslint/type-utils': 6.4.0(eslint@8.47.0)(typescript@5.1.6) - '@typescript-eslint/utils': 6.4.0(eslint@8.47.0)(typescript@5.1.6) + '@typescript-eslint/type-utils': 6.4.0(eslint@8.47.0)(typescript@5.4.3) + '@typescript-eslint/utils': 6.4.0(eslint@8.47.0)(typescript@5.4.3) '@typescript-eslint/visitor-keys': 6.4.0 debug: 4.3.4 eslint: 8.47.0 @@ -1486,13 +1516,13 @@ packages: ignore: 5.2.4 natural-compare: 1.4.0 semver: 7.5.4 - ts-api-utils: 1.0.1(typescript@5.1.6) - typescript: 5.1.6 + ts-api-utils: 1.0.1(typescript@5.4.3) + typescript: 5.4.3 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/parser@6.4.0(eslint@8.47.0)(typescript@5.1.6): + /@typescript-eslint/parser@6.4.0(eslint@8.47.0)(typescript@5.4.3): resolution: {integrity: sha512-I1Ah1irl033uxjxO9Xql7+biL3YD7w9IU8zF+xlzD/YxY6a4b7DYA08PXUUCbm2sEljwJF6ERFy2kTGAGcNilg==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: @@ -1504,11 +1534,11 @@ packages: dependencies: '@typescript-eslint/scope-manager': 6.4.0 '@typescript-eslint/types': 6.4.0 - '@typescript-eslint/typescript-estree': 6.4.0(typescript@5.1.6) + '@typescript-eslint/typescript-estree': 6.4.0(typescript@5.4.3) '@typescript-eslint/visitor-keys': 6.4.0 debug: 4.3.4 eslint: 8.47.0 - typescript: 5.1.6 + typescript: 5.4.3 transitivePeerDependencies: - supports-color dev: true @@ -1521,7 +1551,7 @@ packages: '@typescript-eslint/visitor-keys': 6.4.0 dev: true - /@typescript-eslint/type-utils@6.4.0(eslint@8.47.0)(typescript@5.1.6): + /@typescript-eslint/type-utils@6.4.0(eslint@8.47.0)(typescript@5.4.3): resolution: {integrity: sha512-TvqrUFFyGY0cX3WgDHcdl2/mMCWCDv/0thTtx/ODMY1QhEiyFtv/OlLaNIiYLwRpAxAtOLOY9SUf1H3Q3dlwAg==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: @@ -1531,12 +1561,12 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 6.4.0(typescript@5.1.6) - '@typescript-eslint/utils': 6.4.0(eslint@8.47.0)(typescript@5.1.6) + '@typescript-eslint/typescript-estree': 6.4.0(typescript@5.4.3) + '@typescript-eslint/utils': 6.4.0(eslint@8.47.0)(typescript@5.4.3) debug: 4.3.4 eslint: 8.47.0 - ts-api-utils: 1.0.1(typescript@5.1.6) - typescript: 5.1.6 + ts-api-utils: 1.0.1(typescript@5.4.3) + typescript: 5.4.3 transitivePeerDependencies: - supports-color dev: true @@ -1546,7 +1576,7 @@ packages: engines: {node: ^16.0.0 || >=18.0.0} dev: true - /@typescript-eslint/typescript-estree@6.4.0(typescript@5.1.6): + /@typescript-eslint/typescript-estree@6.4.0(typescript@5.4.3): resolution: {integrity: sha512-iDPJArf/K2sxvjOR6skeUCNgHR/tCQXBsa+ee1/clRKr3olZjZ/dSkXPZjG6YkPtnW6p5D1egeEPMCW6Gn4yLA==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: @@ -1560,14 +1590,14 @@ packages: debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 - semver: 7.5.4 - ts-api-utils: 1.0.1(typescript@5.1.6) - typescript: 5.1.6 + semver: 7.6.0 + ts-api-utils: 1.0.1(typescript@5.4.3) + typescript: 5.4.3 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils@6.4.0(eslint@8.47.0)(typescript@5.1.6): + /@typescript-eslint/utils@6.4.0(eslint@8.47.0)(typescript@5.4.3): resolution: {integrity: sha512-BvvwryBQpECPGo8PwF/y/q+yacg8Hn/2XS+DqL/oRsOPK+RPt29h5Ui5dqOKHDlbXrAeHUTnyG3wZA0KTDxRZw==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: @@ -1578,9 +1608,9 @@ packages: '@types/semver': 7.5.0 '@typescript-eslint/scope-manager': 6.4.0 '@typescript-eslint/types': 6.4.0 - '@typescript-eslint/typescript-estree': 6.4.0(typescript@5.1.6) + '@typescript-eslint/typescript-estree': 6.4.0(typescript@5.4.3) eslint: 8.47.0 - semver: 7.5.4 + semver: 7.6.0 transitivePeerDependencies: - supports-color - typescript @@ -1826,6 +1856,10 @@ packages: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} dev: true + /arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + dev: true + /argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true @@ -1929,9 +1963,19 @@ packages: engines: {node: '>= 0.4'} dev: true + /b4a@1.6.6: + resolution: {integrity: sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==} + dev: true + /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + /bare-events@2.2.2: + resolution: {integrity: sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ==} + requiresBuild: true + dev: true + optional: true + /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -2046,12 +2090,22 @@ packages: safe-buffer: 5.2.1 dev: true + /browserify-zlib@0.1.4: + resolution: {integrity: sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==} + dependencies: + pako: 0.2.9 + dev: true + /browserify-zlib@0.2.0: resolution: {integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==} dependencies: pako: 1.0.11 dev: true + /buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: true + /buffer-xor@1.0.3: resolution: {integrity: sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==} dev: true @@ -2294,6 +2348,10 @@ packages: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} + /code-block-writer@13.0.1: + resolution: {integrity: sha512-c5or4P6erEA69TxaxTNcHUNcIn+oyxSRTOWV+pSYF+z4epXqNvwvJ70XPGjPNgue83oAFAPBRQYwpAJ/Hpe/Sg==} + dev: true + /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -2386,7 +2444,7 @@ packages: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} dev: true - /cosmiconfig-typescript-loader@4.3.0(@types/node@20.10.0)(cosmiconfig@8.1.3)(ts-node@10.9.1)(typescript@5.1.6): + /cosmiconfig-typescript-loader@4.3.0(@types/node@20.10.0)(cosmiconfig@8.1.3)(ts-node@10.9.1)(typescript@5.4.3): resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==} engines: {node: '>=12', npm: '>=6'} peerDependencies: @@ -2397,8 +2455,8 @@ packages: dependencies: '@types/node': 20.10.0 cosmiconfig: 8.1.3 - ts-node: 10.9.1(@types/node@20.10.0)(typescript@5.1.6) - typescript: 5.1.6 + ts-node: 10.9.1(@types/node@20.10.0)(typescript@5.4.3) + typescript: 5.4.3 dev: true /cosmiconfig@8.1.3: @@ -2542,6 +2600,15 @@ packages: mimic-response: 3.1.0 dev: false + /dedent@1.5.3: + resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + dev: true + /deep-eql@4.1.3: resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} engines: {node: '>=6'} @@ -2734,10 +2801,19 @@ packages: glob: 10.3.10 ora: 5.4.1 tslib: 2.6.2 - typescript: 5.2.2 + typescript: 5.4.3 yargs: 17.7.2 dev: true + /duplexify@3.7.1: + resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==} + dependencies: + end-of-stream: 1.4.4 + inherits: 2.0.4 + readable-stream: 2.3.8 + stream-shift: 1.0.3 + dev: true + /eager-async-pool@1.0.0: resolution: {integrity: sha512-A2N+pbceYEz7O2KQ3TNQSSrZsivQ9i28cuNKRxfdT9QvRCoJ51pCP90hSNybOOBfoBHk15ZThJYrQWHo4h2UjA==} dev: true @@ -2775,7 +2851,6 @@ packages: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} dependencies: once: 1.4.0 - dev: false /enhanced-resolve@5.14.1: resolution: {integrity: sha512-Vklwq2vDKtl0y/vtwjSesgJ5MYS7Etuk5txS8VdKL4AOS1aUlD96zqIfsOSLQsdv3xgMRbtkWM8eG9XDfKUPow==} @@ -3028,7 +3103,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 6.4.0(eslint@8.47.0)(typescript@5.1.6) + '@typescript-eslint/parser': 6.4.0(eslint@8.47.0)(typescript@5.4.3) debug: 3.2.7 eslint: 8.47.0 eslint-import-resolver-node: 0.3.7 @@ -3051,7 +3126,7 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 6.4.0(eslint@8.47.0)(typescript@5.1.6) + '@typescript-eslint/parser': 6.4.0(eslint@8.47.0)(typescript@5.4.3) array-includes: 3.1.6 array.prototype.findlastindex: 1.2.2 array.prototype.flat: 1.3.1 @@ -3269,15 +3344,8 @@ packages: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true - /fast-glob@3.2.12: - resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} - engines: {node: '>=8.6.0'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.5 + /fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} dev: true /fast-glob@3.3.1: @@ -3620,7 +3688,7 @@ packages: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 - fast-glob: 3.2.12 + fast-glob: 3.3.2 ignore: 5.2.0 merge2: 1.4.1 slash: 3.0.0 @@ -3639,6 +3707,18 @@ packages: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true + /gunzip-maybe@1.4.2: + resolution: {integrity: sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==} + hasBin: true + dependencies: + browserify-zlib: 0.1.4 + is-deflate: 1.0.0 + is-gzip: 1.0.0 + peek-stream: 1.1.3 + pumpify: 1.5.1 + through2: 2.0.5 + dev: true + /handlebars@4.7.8: resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} engines: {node: '>=0.4.7'} @@ -3992,6 +4072,10 @@ packages: has-tostringtag: 1.0.0 dev: true + /is-deflate@1.0.0: + resolution: {integrity: sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==} + dev: true + /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -4018,6 +4102,11 @@ packages: dependencies: is-extglob: 2.1.1 + /is-gzip@1.0.0: + resolution: {integrity: sha512-rcfALRIb1YewtnksfRIHGcIY93QnK8BIQ/2c9yDYcG/Y6+vRoJuTWBmmSEbyLLYtXm7q35pHOHbZFQBaLrhlWQ==} + engines: {node: '>=0.10.0'} + dev: true + /is-interactive@1.0.0: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} @@ -4670,6 +4759,12 @@ packages: hasBin: true dev: false + /mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + dev: true + /mlly@1.4.2: resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==} dependencies: @@ -5015,6 +5110,10 @@ packages: engines: {node: '>=6'} dev: true + /pako@0.2.9: + resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + dev: true + /pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} dev: true @@ -5123,6 +5222,14 @@ packages: sha.js: 2.4.11 dev: true + /peek-stream@1.1.3: + resolution: {integrity: sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==} + dependencies: + buffer-from: 1.1.2 + duplexify: 3.7.1 + through2: 2.0.5 + dev: true + /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} @@ -5253,6 +5360,13 @@ packages: safe-buffer: 5.2.1 dev: true + /pump@2.0.1: + resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==} + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + dev: true + /pump@3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} dependencies: @@ -5260,6 +5374,14 @@ packages: once: 1.4.0 dev: false + /pumpify@1.5.1: + resolution: {integrity: sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==} + dependencies: + duplexify: 3.7.1 + inherits: 2.0.4 + pump: 2.0.1 + dev: true + /punycode@1.4.1: resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} dev: true @@ -5289,6 +5411,10 @@ packages: /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + /queue-tick@1.0.1: + resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} + dev: true + /quick-lru@4.0.1: resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} engines: {node: '>=8'} @@ -5593,8 +5719,8 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - /shiki@0.14.2: - resolution: {integrity: sha512-ltSZlSLOuSY0M0Y75KA+ieRaZ0Trf5Wl3gutE7jzLuIcWxLp5i/uEnLoQWNvgKXQ5OMpGkJnVMRLAuzjc0LJ2A==} + /shiki@0.14.7: + resolution: {integrity: sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==} dependencies: ansi-sequence-parser: 1.1.0 jsonc-parser: 3.2.0 @@ -5775,6 +5901,19 @@ packages: xtend: 4.0.2 dev: true + /stream-shift@1.0.3: + resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} + dev: true + + /streamx@2.16.1: + resolution: {integrity: sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==} + dependencies: + fast-fifo: 1.3.2 + queue-tick: 1.0.1 + optionalDependencies: + bare-events: 2.2.2 + dev: true + /string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -5923,6 +6062,14 @@ packages: readable-stream: 3.6.0 dev: false + /tar-stream@3.1.7: + resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + dependencies: + b4a: 1.6.6 + fast-fifo: 1.3.2 + streamx: 2.16.1 + dev: true + /tar@6.1.11: resolution: {integrity: sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==} engines: {node: '>= 10'} @@ -5953,6 +6100,13 @@ packages: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true + /through2@2.0.5: + resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + dependencies: + readable-stream: 2.3.8 + xtend: 4.0.2 + dev: true + /through2@4.0.2: resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} dependencies: @@ -6008,16 +6162,23 @@ packages: engines: {node: '>=8'} dev: true - /ts-api-utils@1.0.1(typescript@5.1.6): + /ts-api-utils@1.0.1(typescript@5.4.3): resolution: {integrity: sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==} engines: {node: '>=16.13.0'} peerDependencies: typescript: '>=4.2.0' dependencies: - typescript: 5.1.6 + typescript: 5.4.3 dev: true - /ts-node@10.9.1(@types/node@20.10.0)(typescript@5.1.6): + /ts-morph@22.0.0: + resolution: {integrity: sha512-M9MqFGZREyeb5fTl6gNHKZLqBQA0TjA1lea+CR48R8EBTDuWrNqW6ccC5QvjNR4s6wDumD3LTCjOFSp9iwlzaw==} + dependencies: + '@ts-morph/common': 0.23.0 + code-block-writer: 13.0.1 + dev: true + + /ts-node@10.9.1(@types/node@20.10.0)(typescript@5.4.3): resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true peerDependencies: @@ -6043,7 +6204,7 @@ packages: create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.1.6 + typescript: 5.4.3 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 dev: true @@ -6122,28 +6283,22 @@ packages: is-typed-array: 1.1.10 dev: true - /typedoc@0.25.3(typescript@5.1.6): - resolution: {integrity: sha512-Ow8Bo7uY1Lwy7GTmphRIMEo6IOZ+yYUyrc8n5KXIZg1svpqhZSWgni2ZrDhe+wLosFS8yswowUzljTAV/3jmWw==} + /typedoc@0.25.12(typescript@5.4.3): + resolution: {integrity: sha512-F+qhkK2VoTweDXd1c42GS/By2DvI2uDF4/EpG424dTexSHdtCH52C6IcAvMA6jR3DzAWZjHpUOW+E02kyPNUNw==} engines: {node: '>= 16'} hasBin: true peerDependencies: - typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x + typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x dependencies: lunr: 2.3.9 marked: 4.3.0 minimatch: 9.0.3 - shiki: 0.14.2 - typescript: 5.1.6 + shiki: 0.14.7 + typescript: 5.4.3 dev: true - /typescript@5.1.6: - resolution: {integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==} - engines: {node: '>=14.17'} - hasBin: true - dev: true - - /typescript@5.2.2: - resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} + /typescript@5.4.3: + resolution: {integrity: sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==} engines: {node: '>=14.17'} hasBin: true dev: true diff --git a/scripts/build-package.js b/scripts/build-package.js index 255d23e7..b697cf93 100644 --- a/scripts/build-package.js +++ b/scripts/build-package.js @@ -1,15 +1,22 @@ +/* eslint-disable no-inner-declarations */ const cp = require('child_process') const path = require('path') const fs = require('fs') const glob = require('glob') +const ts = require('typescript') +const stc = require('@teidesu/slow-types-compiler') if (process.argv.length < 3) { console.log('Usage: build-package.js ') process.exit(0) } -const packageDir = path.join(__dirname, '../packages', process.argv[2]) -const outDir = path.join(packageDir, 'dist') +const IS_JSR = process.env.JSR === '1' + +const packagesDir = path.join(__dirname, '../packages') +const packageDir = path.join(packagesDir, process.argv[2]) +let outDir = path.join(packageDir, 'dist') +if (IS_JSR) outDir = path.join(outDir, 'jsr') function exec(cmd, params) { cp.execSync(cmd, { cwd: packageDir, stdio: 'inherit', ...params }) @@ -52,6 +59,7 @@ const buildConfig = { transformFile, packageDir, outDir, + jsr: IS_JSR, }) } @@ -59,6 +67,10 @@ const buildConfig = { })(), } +function getPackageVersion(name) { + return require(path.join(packagesDir, name, 'package.json')).version +} + function buildPackageJson() { const pkgJson = JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf-8')) @@ -84,6 +96,13 @@ function buildPackageJson() { delete pkgJson.distOnlyFields } + if (pkgJson.jsrOnlyFields) { + if (IS_JSR) { + Object.assign(pkgJson, pkgJson.jsrOnlyFields) + } + delete pkgJson.jsrOnlyFields + } + function replaceWorkspaceDependencies(field) { if (!pkgJson[field]) return @@ -103,11 +122,8 @@ function buildPackageJson() { } // note: pnpm replaces workspace:* with the current version, unlike this script - const depVersion = - value === 'workspace:*' ? - '*' : - require(path.join(packageDir, '..', name.slice(8), 'package.json')).version - dependencies[name] = `^${depVersion}` + const depVersion = value === 'workspace:*' ? '*' : `^${getPackageVersion(name.slice(8))}` + dependencies[name] = depVersion } } } @@ -119,17 +135,21 @@ function buildPackageJson() { delete pkgJson.typedoc - function maybeFixPath(p, repl) { - if (!p) return p + if (pkgJson.browser) { + function maybeFixPath(p, repl) { + if (!p) return p - if (p.startsWith('./src/')) { - return repl + p.slice(6) + if (p.startsWith('./src/')) { + return repl + p.slice(6) + } + + if (p.startsWith('./')) { + return repl + p.slice(2) + } + + return p } - return p - } - - if (pkgJson.browser) { for (const key of Object.keys(pkgJson.browser)) { if (!key.startsWith('./src/')) continue @@ -144,24 +164,88 @@ function buildPackageJson() { } } - fs.writeFileSync(path.join(packageDir, 'dist/package.json'), JSON.stringify(pkgJson, null, 2)) + // fix exports + if (pkgJson.exports) { + function maybeFixPath(path, repl) { + if (!path) return path + if (pkgJson.exportsKeepPath?.includes(path)) return path + + if (path.startsWith('./src/')) { + path = repl + path.slice(6) + } else if (path.startsWith('./')) { + path = repl + path.slice(2) + } + + return path.replace(/\.ts$/, '.js') + } + + function fixValue(value) { + if (IS_JSR) { + return maybeFixPath(value, './').replace(/\.js$/, '.ts') + } + + if (buildConfig.buildCjs) { + return { + import: maybeFixPath(value, './esm/'), + require: maybeFixPath(value, './cjs/'), + } + } + + return maybeFixPath(value, './') + } + + if (typeof pkgJson.exports === 'string') { + pkgJson.exports = { + '.': fixValue(pkgJson.exports), + } + } else { + for (const key of Object.keys(pkgJson.exports)) { + const value = pkgJson.exports[key] + + if (typeof value !== 'string') { + throw new Error('Conditional exports are not supported') + } + + pkgJson.exports[key] = fixValue(value) + } + } + + delete pkgJson.exportsKeepPath + } + + if (!IS_JSR) { + fs.writeFileSync(path.join(outDir, 'package.json'), JSON.stringify(pkgJson, null, 2)) + } + + return pkgJson } // clean fs.rmSync(path.join(outDir), { recursive: true, force: true }) fs.mkdirSync(path.join(outDir), { recursive: true }) +// for jsr - copy typescript sources +if (IS_JSR) { + buildConfig.buildCjs = false +} + buildConfig.before() -if (buildConfig.buildTs) { +if (buildConfig.buildTs && !IS_JSR) { console.log('[i] Building typescript...') - fs.cpSync(path.join(packageDir, 'tsconfig.json'), path.join(packageDir, 'tsconfig.backup.json')) + const tsconfigPath = path.join(packageDir, 'tsconfig.json') + fs.cpSync(tsconfigPath, path.join(packageDir, 'tsconfig.backup.json')) - let tsconfig = fs.readFileSync(path.join(packageDir, 'tsconfig.backup.json'), 'utf-8') - // what the fuck - tsconfig = tsconfig.replace(/(?<="extends": "\.\.\/\.\.\/)tsconfig\.json(?=",)/, '.config/tsconfig.build.json') - fs.writeFileSync(path.join(packageDir, 'tsconfig.json'), tsconfig) + const tsconfig = ts.parseConfigFileTextToJson(tsconfigPath, fs.readFileSync(tsconfigPath, 'utf-8')).config + + if (tsconfig.extends === '../../tsconfig.json') { + tsconfig.extends = '../../.config/tsconfig.build.json' + } else { + throw new Error('expected tsconfig to extend base config') + } + + fs.writeFileSync(path.join(packageDir, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2)) const restoreTsconfig = () => { fs.renameSync(path.join(packageDir, 'tsconfig.backup.json'), path.join(packageDir, 'tsconfig.json')) @@ -178,30 +262,69 @@ if (buildConfig.buildTs) { console.log('[i] Building typescript (CJS)...') const originalFiles = {} - // todo - get rid of these, use @esm-replace-import instead - if (buildConfig.esmOnlyDirectives) { - for (const f of glob.sync(path.join(packageDir, '**/*.ts'))) { - const content = fs.readFileSync(f, 'utf8') - if (!content.includes('@only-if-esm')) continue - originalFiles[f] = content + for (const f of glob.sync(path.join(packagesDir, '**/*.ts'))) { + const content = fs.readFileSync(f, 'utf8') + if (!content.includes('@only-if-esm')) continue + originalFiles[f] = content - fs.writeFileSync(f, content.replace(/@only-if-esm.*?@\/only-if-esm/gs, '')) - } + fs.writeFileSync(f, content.replace(/@only-if-esm.*?@\/only-if-esm/gs, '')) } - if (buildConfig.esmImportDirectives) { - for (const f of glob.sync(path.join(packageDir, '**/*.ts'))) { - const content = fs.readFileSync(f, 'utf8') - if (!content.includes('@esm-replace-import')) continue - originalFiles[f] = content + for (const f of glob.sync(path.join(packagesDir, '**/*.ts'))) { + const content = fs.readFileSync(f, 'utf8') + if (!content.includes('@esm-replace-import')) continue + originalFiles[f] = content - fs.writeFileSync(f, content.replace(/@esm-replace-import.*?await import/gs, 'require')) + fs.writeFileSync(f, content.replace(/@esm-replace-import.*?await import/gs, 'require')) + } + + // set type=commonjs in all package.json-s + for (const pkg of fs.readdirSync(packagesDir)) { + const pkgJson = path.join(packagesDir, pkg, 'package.json') + if (!fs.existsSync(pkgJson)) continue + + const orig = fs.readFileSync(pkgJson, 'utf8') + originalFiles[pkgJson] = orig + + fs.writeFileSync( + pkgJson, + JSON.stringify( + { + ...JSON.parse(orig), + type: 'commonjs', + }, + null, + 2, + ), + ) + + // maybe also dist/package.json + const distPkgJson = path.join(packagesDir, pkg, 'dist/package.json') + + if (fs.existsSync(distPkgJson)) { + const orig = fs.readFileSync(distPkgJson, 'utf8') + originalFiles[distPkgJson] = orig + + fs.writeFileSync( + distPkgJson, + JSON.stringify( + { + ...JSON.parse(orig), + type: 'commonjs', + }, + null, + 2, + ), + ) } } let error = false try { - exec('pnpm exec tsc --module commonjs --outDir dist/cjs', { cwd: packageDir, stdio: 'inherit' }) + exec('pnpm exec tsc --outDir dist/cjs', { + cwd: packageDir, + stdio: 'inherit', + }) } catch (e) { error = e } @@ -218,6 +341,7 @@ if (buildConfig.buildTs) { restoreTsconfig() + // todo: can we remove these? console.log('[i] Post-processing...') if (buildConfig.removeReferenceComments) { @@ -252,15 +376,160 @@ if (buildConfig.buildTs) { if (changed) fs.writeFileSync(f, content) } } +} else if (buildConfig.buildTs && IS_JSR) { + console.log('[i] Copying sources...') + fs.cpSync(path.join(packageDir, 'src'), outDir, { recursive: true }) + + const printer = ts.createPrinter() + + for (const f of glob.sync(path.join(outDir, '**/*.ts'))) { + let fileContent = fs.readFileSync(f, 'utf8') + let changed = false + + // replace .js imports with .ts + const file = ts.createSourceFile(f, fileContent, ts.ScriptTarget.ESNext, true) + let changedTs = false + + for (const imp of file.statements) { + if (imp.kind !== ts.SyntaxKind.ImportDeclaration && imp.kind !== ts.SyntaxKind.ExportDeclaration) { + continue + } + if (imp.kind === ts.SyntaxKind.ExportDeclaration && !imp.moduleSpecifier) { + continue + } + const mod = imp.moduleSpecifier.text + + if (mod[0] === '.' && mod.endsWith('.js')) { + changedTs = true + imp.moduleSpecifier = { + kind: ts.SyntaxKind.StringLiteral, + text: mod.slice(0, -3) + '.ts', + } + } + } + + if (changedTs) { + fileContent = printer.printFile(file) + changed = true + } + + // add shims for node-specific APIs and replace NodeJS.* types + // pretty fragile, but it works for now + const typesToReplace = { + 'NodeJS\\.Timeout': 'number', + 'NodeJS\\.Immediate': 'number', + } + const nodeSpecificApis = { + setImmediate: '(cb: (...args: any[]) => void, ...args: any[]) => number', + clearImmediate: '(id: number) => void', + Buffer: + '{ ' + + 'concat: (...args: any[]) => Uint8Array, ' + + 'from: (data: any, encoding?: string) => { toString(encoding?: string): string }, ' + + ' }', + SharedWorker: ['type', 'never'], + process: '{ ' + 'hrtime: { bigint: () => bigint }, ' + '}', + } + + for (const [name, decl_] of Object.entries(nodeSpecificApis)) { + if (fileContent.includes(name)) { + changed = true + const isType = Array.isArray(decl_) && decl_[0] === 'type' + const decl = isType ? decl_[1] : decl_ + + if (isType) { + fileContent = `declare type ${name} = ${decl};\n` + fileContent + } else { + fileContent = `declare const ${name}: ${decl};\n` + fileContent + } + } + } + + for (const [oldType, newType] of Object.entries(typesToReplace)) { + if (fileContent.match(oldType)) { + changed = true + fileContent = fileContent.replace(new RegExp(oldType, 'g'), newType) + } + } + + if (changed) { + fs.writeFileSync(f, fileContent) + } + } } -console.log('[i] Copying files...') +console.log('[i] Copying misc files...') + +const builtPkgJson = buildPackageJson() if (buildConfig.buildCjs) { fs.writeFileSync(path.join(outDir, 'cjs/package.json'), JSON.stringify({ type: 'commonjs' }, null, 2)) } -buildPackageJson() +if (IS_JSR) { + // generate deno.json from package.json + // https://jsr.io/docs/package-configuration + + const importMap = {} + + if (builtPkgJson.dependencies) { + for (const [name, version] of Object.entries(builtPkgJson.dependencies)) { + if (name.startsWith('@mtcute/')) { + importMap[name] = `jsr:${name}@${version}` + } else { + importMap[name] = `npm:${name}@${version}` + } + } + } + + for (const [name, target] of Object.entries(builtPkgJson.exports)) { + // jsr doesn't support wildcards, so we need to flatten those + if (!name.includes('*')) continue + + if (!name.endsWith('*') || !target.endsWith('*')) { + // for simplicity + it's the only one supported in some bundlers + throw new Error(`Invalid wildcard in export map: ${name} -> ${target}`) + } + + const base = name.slice(0, -1) + const targetBase = target.slice(0, -1) + + for (const file of glob.sync(path.join(outDir, base, '**/*'))) { + const newName = (base + path.relative(path.join(outDir, base), file)).replace(/\.ts$/, '.js') + const newTarget = targetBase + path.relative(path.join(outDir, base), file) + builtPkgJson.exports[newName] = newTarget + } + + delete builtPkgJson.exports[name] + } + + const denoJson = path.join(outDir, 'deno.json') + fs.writeFileSync( + denoJson, + JSON.stringify( + { + name: builtPkgJson.name, + version: builtPkgJson.version, + exports: builtPkgJson.exports, + exclude: ['**/*.test.ts', '**/*.test-utils.ts', '**/__fixtures__/**'], + imports: importMap, + ...builtPkgJson.denoJson, + }, + null, + 2, + ), + ) + + console.log('[i] Processing with slow-types-compiler...') + const project = stc.createProject() + stc.processPackage(project, denoJson) + const unsavedSourceFiles = project.getSourceFiles().filter((s) => !s.isSaved()) + + if (unsavedSourceFiles.length > 0) { + console.log('[v] Changed %d files', unsavedSourceFiles.length) + project.saveSync() + } +} try { fs.cpSync(path.join(packageDir, 'README.md'), path.join(outDir, 'README.md')) @@ -270,8 +539,16 @@ try { fs.cpSync(path.join(__dirname, '../LICENSE'), path.join(outDir, 'LICENSE')) -fs.writeFileSync(path.join(outDir, '.npmignore'), '*.tsbuildinfo\n') +if (!IS_JSR) { + fs.writeFileSync(path.join(outDir, '.npmignore'), '*.tsbuildinfo\n') +} Promise.resolve(buildConfig.final()).then(() => { - console.log('[v] Done!') + if (IS_JSR) { + console.log('[i] Trying to publish with --dry-run') + exec('deno publish --dry-run --allow-dirty --quiet', { cwd: outDir }) + console.log('[v] All good!') + } else { + console.log('[v] Done!') + } }) diff --git a/scripts/publish.js b/scripts/publish.js index bd5d3b06..e07c75c2 100644 --- a/scripts/publish.js +++ b/scripts/publish.js @@ -2,28 +2,79 @@ const fs = require('fs') const path = require('path') const cp = require('child_process') -const NPMJS = 'https://registry.npmjs.org' -const REGISTRY = process.env.REGISTRY || NPMJS +const IS_JSR = process.env.JSR === '1' +const MAIN_REGISTRY = IS_JSR ? 'http://jsr.test/' : 'https://registry.npmjs.org' +let REGISTRY = process.env.REGISTRY || MAIN_REGISTRY exports.REGISTRY = REGISTRY +if (!REGISTRY.endsWith('/')) REGISTRY += '/' -async function checkVersion(name, version, retry = 0) { +if (process.env.E2E && IS_JSR) { + // running behind a socat proxy seems to fix some of the docker networking issues (thx kamillaova) + const hostname = new URL(REGISTRY).hostname + const port = new URL(REGISTRY).port || { 'http:': 80, 'https:': 443 }[new URL(REGISTRY).protocol] + cp.spawn('bash', ['-c', `socat TCP-LISTEN:1234,fork,reuseaddr TCP4:${hostname}:${port}`], { stdio: 'ignore' }) + REGISTRY = 'http://localhost:1234/' +} + +if (IS_JSR) { + // for the underlying tools that expect JSR_URL env var + process.env.JSR_URL = REGISTRY +} + +const JSR_EXCEPTIONS = { + bun: 'never', + 'create-bot': 'never', + 'crypto-node': 'never', + node: 'never', + 'http-proxy': 'never', + 'socks-proxy': 'never', + mtproxy: 'never', + test: 'never', +} + +function fetchRetry(url, init, retry = 0) { + return fetch(url, init).catch((err) => { + if (retry >= 5) throw err + + // for whatever reason this request sometimes fails with ECONNRESET + // no idea why, probably some issue in docker networking + console.log('[i] Error fetching %s:', url) + console.log(err) + + return new Promise((resolve) => setTimeout(resolve, 1000)).then(() => fetchRetry(url, init, retry + 1)) + }) +} + +async function checkVersion(name, version) { let registry = REGISTRY - if (!registry.endsWith('/')) registry += '/' - return fetch(`${registry}@mtcute/${name}/${version}`) - .then((r) => r.status === 200) - .catch((err) => { - if (retry >= 5) throw err + const url = IS_JSR ? `${registry}@mtcute/${name}/${version}_meta.json` : `${registry}@mtcute/${name}/${version}` - // for whatever reason this request sometimes fails with ECONNRESET - // no idea why, probably some issue in orbstack networking - console.log('[i] Error checking version:') - console.log(err) + return fetchRetry(url).then((r) => r.status === 200) +} - return new Promise((resolve) => setTimeout(resolve, 1000)).then(() => - checkVersion(name, version, retry + 1), - ) +async function jsrMaybeCreatePackage(name) { + // check if the package even exists + const packageMeta = await fetchRetry(`${REGISTRY}api/scopes/mtcute/packages/${name}`) + + if (packageMeta.status === 404) { + console.error('[i] %s does not exist, creating..', name) + + const create = await fetchRetry(`${REGISTRY}api/scopes/mtcute/packages`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Cookie: `token=${process.env.JSR_TOKEN}`, + }, + body: JSON.stringify({ package: name }), }) + + if (create.status !== 200) { + throw new Error(`Failed to create package: ${create.statusText} ${await create.text()}`) + } + } else if (packageMeta.status !== 200) { + throw new Error(`Failed to check package: ${packageMeta.statusText} ${await packageMeta.text()}`) + } } async function publishSinglePackage(name) { @@ -39,12 +90,14 @@ async function publishSinglePackage(name) { console.log('[i] Publishing %s', name) - const version = require(path.join(packageDir, 'dist/package.json')).version + const version = IS_JSR ? + require(path.join(packageDir, 'dist/jsr/deno.json')).version : + require(path.join(packageDir, 'dist/package.json')).version const exists = await checkVersion(name, version) if (exists) { - if (process.env.E2E) { + if (process.env.E2E && !IS_JSR) { console.log('[i] %s already exists, unpublishing..', name) cp.execSync(`npm unpublish --registry ${REGISTRY} --force @mtcute/${name}`, { cwd: path.join(packageDir, 'dist'), @@ -55,28 +108,59 @@ async function publishSinglePackage(name) { return } + } else if (IS_JSR && process.env.JSR_TOKEN) { + await jsrMaybeCreatePackage(name) } - // publish to npm - const params = REGISTRY === NPMJS ? '--access public' : '--force' - cp.execSync(`npm publish --registry ${REGISTRY} ${params} -q`, { - cwd: path.join(packageDir, 'dist'), - stdio: 'inherit', - }) + if (IS_JSR) { + // publish to jsr + const params = process.env.JSR_TOKEN ? `--token ${process.env.JSR_TOKEN}` : '' + cp.execSync(`deno publish --allow-dirty ${params}`, { + cwd: path.join(packageDir, 'dist/jsr'), + stdio: 'inherit', + }) + } else { + // make sure dist/jsr doesn't exist (it shouldn't, but just in case) + if (fs.existsSync(path.join(packageDir, 'dist/jsr'))) { + fs.rmdirSync(path.join(packageDir, 'dist/jsr'), { recursive: true }) + } + + // publish to npm + const params = REGISTRY === MAIN_REGISTRY ? '--access public' : '--force' + cp.execSync(`npm publish --registry ${REGISTRY} ${params} -q`, { + cwd: path.join(packageDir, 'dist'), + stdio: 'inherit', + }) + } } -const LOCAL = ['crypto'] - function listPackages() { - const packages = [] + let packages = [] for (const f of fs.readdirSync(path.join(__dirname, '../packages'))) { - if (LOCAL.indexOf(f) > -1) continue if (f[0] === '.') continue + if (IS_JSR && JSR_EXCEPTIONS[f] === 'never') continue + if (!IS_JSR && JSR_EXCEPTIONS[f] === 'only') continue packages.push(f) } + if (IS_JSR) { + // we should sort them in a way that dependencies are published first. stc has a util for that + const map = {} + + for (const pkg of packages) { + const deps = require(`../packages/${pkg}/package.json`).dependencies || {} + map[pkg] = Object.keys(deps) + .filter((d) => d.startsWith('@mtcute/')) + .map((d) => d.slice(8)) + } + + const stc = require('@teidesu/slow-types-compiler') + packages = stc.determinePublishOrder(map) + console.log('[i] Publishing order:', packages.join(', ')) + } + return packages } @@ -98,7 +182,7 @@ async function main(arg = process.argv[2]) { const pkgVersion = require(`../packages/${pkg}/package.json`).version const published = await checkVersion(pkg, pkgVersion) - if (published) { + if (published && !process.env.E2E) { console.log('[i] %s is up to date', pkg) continue } @@ -109,6 +193,7 @@ async function main(arg = process.argv[2]) { } catch (e) { console.error('[!] Failed to publish %s:', pkg) console.error(e) + if (IS_JSR || process.env.E2E) throw e failedPkgs.push(pkg) } } @@ -120,6 +205,8 @@ async function main(arg = process.argv[2]) { } catch (e) { console.error('[!] Failed to publish %s:', pkg) console.error(e) + if (IS_JSR || process.env.E2E) throw e + failedPkgs.push(pkg) } } diff --git a/tsconfig.json b/tsconfig.json index d3481db6..92538b93 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "outDir": "./dist", "module": "NodeNext", "moduleResolution": "NodeNext", - "target": "es2020", + "target": "es2022", "allowJs": true, "sourceMap": true, "inlineSources": true, @@ -29,6 +29,7 @@ ], "resolveJsonModule": true, "isolatedModules": true, + "useDefineForClassFields": true, }, "ts-node": { "esm": true,