From fb72d3194d38899b59a95283ce23d3ca35a4ca48 Mon Sep 17 00:00:00 2001 From: Alina Sireneva Date: Fri, 1 Mar 2024 01:52:17 +0300 Subject: [PATCH] fix: properly handle file uploads + downloading as node stream --- .config/vite.mts | 3 + .config/vitest.setup.mts | 9 +++ packages/core/src/highlevel/client.ts | 14 +++- .../src/highlevel/methods/files/_platform.ts | 32 -------- .../highlevel/methods/files/_platform.web.ts | 23 ------ .../highlevel/methods/files/download-file.ts | 2 +- .../methods/files/download-node-stream.ts | 16 ++++ .../highlevel/methods/files/upload-file.ts | 22 ++++- .../src/highlevel/utils/stream-utils.test.ts | 26 +----- .../core/src/highlevel/utils/stream-utils.ts | 33 -------- packages/core/src/platform.ts | 30 ++++++- packages/core/src/utils/crypto/keys.test.ts | 6 +- packages/dispatcher/tests/dispatcher.test.ts | 6 +- packages/node/src/client.ts | 9 ++- packages/node/src/index.ts | 2 +- packages/node/src/methods.ts | 2 +- .../node/src/methods/download-node-stream.ts | 19 +++++ packages/node/src/platform.ts | 3 + packages/node/src/utils.ts | 1 + .../normalize-file.ts} | 23 ++---- packages/node/src/utils/stream-utils.test.ts | 59 ++++++++++++++ packages/node/src/utils/stream-utils.ts | 68 ++++++++++++++-- packages/node/src/utils/tcp.test.ts | 2 +- packages/node/src/utils/version.ts | 14 ++++ packages/test/src/index.ts | 1 - packages/tl-runtime/src/writer.test.ts | 4 + packages/wasm/package.json | 4 +- packages/wasm/tests/ctr.test.ts | 81 ++++++++++--------- packages/wasm/tests/gunzip.test.ts | 19 +++-- packages/wasm/tests/hash.test.ts | 21 ++--- packages/wasm/tests/ige.test.ts | 21 ++--- packages/wasm/tests/init.ts | 1 - packages/wasm/tests/zlib.test.ts | 21 ++--- packages/web/src/crypto.test.ts | 21 ----- packages/web/src/index.ts | 1 + .../transports => web/src}/websocket.test.ts | 6 +- .../transports => web/src}/websocket.ts | 15 ++-- packages/web/utils.ts | 1 - pnpm-lock.yaml | 10 ++- 39 files changed, 388 insertions(+), 263 deletions(-) create mode 100644 .config/vitest.setup.mts delete mode 100644 packages/core/src/highlevel/methods/files/_platform.ts delete mode 100644 packages/core/src/highlevel/methods/files/_platform.web.ts create mode 100644 packages/core/src/highlevel/methods/files/download-node-stream.ts create mode 100644 packages/node/src/methods/download-node-stream.ts rename packages/node/src/{methods/upload-file.ts => utils/normalize-file.ts} (58%) create mode 100644 packages/node/src/utils/stream-utils.test.ts create mode 100644 packages/node/src/utils/version.ts delete mode 100644 packages/web/src/crypto.test.ts rename packages/{core/src/network/transports => web/src}/websocket.test.ts (96%) rename packages/{core/src/network/transports => web/src}/websocket.ts (93%) delete mode 100644 packages/web/utils.ts diff --git a/.config/vite.mts b/.config/vite.mts index aa150bb8..52164131 100644 --- a/.config/vite.mts +++ b/.config/vite.mts @@ -11,6 +11,9 @@ export default defineConfig({ 'packages/**/*.test-d.ts', ], }, + setupFiles: [ + './.config/vitest.setup.mts' + ] }, define: { 'import.meta.env.TEST_ENV': '"node"' diff --git a/.config/vitest.setup.mts b/.config/vitest.setup.mts new file mode 100644 index 00000000..ca0294fd --- /dev/null +++ b/.config/vitest.setup.mts @@ -0,0 +1,9 @@ +import { setPlatform } from '../packages/core/src/platform.js' + +// @ts-expect-error no .env here +const TEST_ENV = import.meta.env.TEST_ENV +if (TEST_ENV === 'browser') { + setPlatform(new (await import('../packages/web/src/platform.js')).WebPlatform()) +} else { + setPlatform(new (await import('../packages/node/src/platform.js')).NodePlatform()) +} \ No newline at end of file diff --git a/packages/core/src/highlevel/client.ts b/packages/core/src/highlevel/client.ts index c4db21fe..ba50150e 100644 --- a/packages/core/src/highlevel/client.ts +++ b/packages/core/src/highlevel/client.ts @@ -2248,7 +2248,7 @@ export interface TelegramClient extends ITelegramClient { downloadAsBuffer(location: FileDownloadLocation, params?: FileDownloadParameters): Promise /** - * Download a remote file to a local file (only for NodeJS). + * Download a remote file to a local file (only for Node.js). * Promise will resolve once the download is complete. * * **Available**: ✅ both users and bots @@ -2267,6 +2267,15 @@ export interface TelegramClient extends ITelegramClient { * @param params Download parameters */ downloadAsIterable(input: FileDownloadLocation, params?: FileDownloadParameters): AsyncIterableIterator + + /** + * Download a remote file as a Node.js Readable stream. + * + * **Available**: ✅ both users and bots + * + * @param params File download parameters + */ + downloadAsNodeStream(location: FileDownloadLocation, params?: FileDownloadParameters): import('stream').Readable /** * Download a file and return it as a readable stream, * streaming file contents. @@ -2320,9 +2329,6 @@ export interface TelegramClient extends ITelegramClient { uploadFile(params: { /** * Upload file source. - * - * > **Note**: `fs.ReadStream` is a subclass of `stream.Readable` and contains - * > info about file name, thus you don't need to pass them explicitly. */ file: UploadFileLike diff --git a/packages/core/src/highlevel/methods/files/_platform.ts b/packages/core/src/highlevel/methods/files/_platform.ts deleted file mode 100644 index 9097634c..00000000 --- a/packages/core/src/highlevel/methods/files/_platform.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { createReadStream, promises, ReadStream } from 'node:fs' -import { basename } from 'node:path' -import { Readable } from 'node:stream' - -import { nodeReadableToWeb } from '../../utils/stream-utils.js' - -/** @internal */ -export function _createFileStream(path: string): ReadStream { - return createReadStream(path) -} - -/** @internal */ -export function _isFileStream(stream: unknown): stream is ReadStream { - return stream instanceof ReadStream -} - -/** @internal */ -export async function _extractFileStreamMeta(stream: ReadStream): Promise<[string, number]> { - const fileName = basename(stream.path.toString()) - const fileSize = await promises.stat(stream.path.toString()).then((stat) => stat.size) - - return [fileName, fileSize] -} - -/** @internal */ -export function _handleNodeStream(val: T | Readable): T | ReadableStream { - if (val instanceof Readable) { - return nodeReadableToWeb(val) - } - - return val -} diff --git a/packages/core/src/highlevel/methods/files/_platform.web.ts b/packages/core/src/highlevel/methods/files/_platform.web.ts deleted file mode 100644 index 600b1cad..00000000 --- a/packages/core/src/highlevel/methods/files/_platform.web.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { MtArgumentError } from '../../../types/errors.js' - -/** @internal */ -export function _createFileStream(): never { - throw new MtArgumentError('Cannot create file stream on web platform') -} - -/** @internal */ -export function _isFileStream() { - return false -} - -/** @internal */ -export function _extractFileStreamMeta(): never { - throw new Error('UNREACHABLE') -} - -/** @internal */ -export function _handleNodeStream(val: unknown) { - return val -} - -// all the above functions shall be inlined by terser diff --git a/packages/core/src/highlevel/methods/files/download-file.ts b/packages/core/src/highlevel/methods/files/download-file.ts index 9be869ce..b2169553 100644 --- a/packages/core/src/highlevel/methods/files/download-file.ts +++ b/packages/core/src/highlevel/methods/files/download-file.ts @@ -5,7 +5,7 @@ import { FileDownloadLocation, FileDownloadParameters } from '../../types/index. // @available=both /** - * Download a remote file to a local file (only for NodeJS). + * Download a remote file to a local file (only for Node.js). * Promise will resolve once the download is complete. * * @param filename Local file name to which the remote file will be downloaded diff --git a/packages/core/src/highlevel/methods/files/download-node-stream.ts b/packages/core/src/highlevel/methods/files/download-node-stream.ts new file mode 100644 index 00000000..d105922b --- /dev/null +++ b/packages/core/src/highlevel/methods/files/download-node-stream.ts @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import { ITelegramClient } from '../../client.types.js' +import { FileDownloadLocation, FileDownloadParameters } from '../../types/index.js' + +// @available=both +/** + * Download a remote file as a Node.js Readable stream. + * + * @param params File download parameters + */ +declare function downloadAsNodeStream( + client: ITelegramClient, + location: FileDownloadLocation, + params?: FileDownloadParameters, +): import('stream').Readable diff --git a/packages/core/src/highlevel/methods/files/upload-file.ts b/packages/core/src/highlevel/methods/files/upload-file.ts index 1c92c532..8800f7c7 100644 --- a/packages/core/src/highlevel/methods/files/upload-file.ts +++ b/packages/core/src/highlevel/methods/files/upload-file.ts @@ -1,5 +1,6 @@ import { tl } from '@mtcute/tl' +import { getPlatform } from '../../../platform.js' import { MtArgumentError } from '../../../types/errors.js' import { randomLong } from '../../../utils/long-utils.js' import { ITelegramClient } from '../../client.types.js' @@ -21,6 +22,10 @@ const REQUESTS_PER_CONNECTION = 3 const MAX_PART_COUNT = 4000 // 512 kb * 4000 = 2000 MiB const MAX_PART_COUNT_PREMIUM = 8000 // 512 kb * 8000 = 4000 MiB +// platform-specific +const HAS_FILE = typeof File !== 'undefined' +const HAS_RESPONSE = typeof Response !== 'undefined' + // @available=both /** * Upload a file to Telegram servers, without actually @@ -101,19 +106,30 @@ export async function uploadFile( let fileName = DEFAULT_FILE_NAME let fileMime = params.fileMime + const platform = getPlatform() + + if (platform.normalizeFile) { + const res = await platform.normalizeFile(file) + + if (res?.file) { + file = res.file + if (res.fileSize) fileSize = res.fileSize + if (res.fileName) fileName = res.fileName + } + } + if (ArrayBuffer.isView(file)) { fileSize = file.length file = bufferToStream(file) } - if (typeof File !== 'undefined' && file instanceof File) { + if (HAS_FILE && file instanceof File) { fileName = file.name fileSize = file.size file = file.stream() } - if (typeof file === 'object' && 'headers' in file && 'body' in file && 'url' in file) { - // fetch() response + if (HAS_RESPONSE && file instanceof Response) { const length = parseInt(file.headers.get('content-length') || '0') if (!isNaN(length) && length) fileSize = length diff --git a/packages/core/src/highlevel/utils/stream-utils.test.ts b/packages/core/src/highlevel/utils/stream-utils.test.ts index a4536753..515e733c 100644 --- a/packages/core/src/highlevel/utils/stream-utils.test.ts +++ b/packages/core/src/highlevel/utils/stream-utils.test.ts @@ -1,7 +1,6 @@ -import { Readable } from 'node:stream' import { describe, expect, it } from 'vitest' -import { createChunkedReader, nodeReadableToWeb } from './stream-utils.js' +import { createChunkedReader } from './stream-utils.js' describe('createChunkedReader', () => { it('should correctly handle chunks smaller than chunkSize', async () => { @@ -82,26 +81,3 @@ describe('createChunkedReader', () => { expect(await reader.read()).to.be.null }) }) - -if (import.meta.env.TEST_ENV === 'node' || import.meta.env.TEST_ENV === 'bun') { - describe('nodeReadableToWeb', () => { - it('should correctly convert a readable stream', async () => { - const stream = new Readable({ - read() { - // eslint-disable-next-line no-restricted-globals - this.push(Buffer.from([1, 2, 3])) - // eslint-disable-next-line no-restricted-globals - this.push(Buffer.from([4, 5, 6])) - this.push(null) - }, - }) - - const webStream = nodeReadableToWeb(stream) - const reader = webStream.getReader() - - expect(await reader.read()).to.deep.equal({ value: new Uint8Array([1, 2, 3]), done: false }) - expect(await reader.read()).to.deep.equal({ value: new Uint8Array([4, 5, 6]), done: false }) - expect(await reader.read()).to.deep.equal({ value: undefined, done: true }) - }) - }) -} diff --git a/packages/core/src/highlevel/utils/stream-utils.ts b/packages/core/src/highlevel/utils/stream-utils.ts index a600adc5..2299e0fb 100644 --- a/packages/core/src/highlevel/utils/stream-utils.ts +++ b/packages/core/src/highlevel/utils/stream-utils.ts @@ -135,36 +135,3 @@ export function createChunkedReader(stream: ReadableStream, chunkSiz read: readLocked, } } - -export function nodeReadableToWeb(stream: NodeJS.ReadableStream): ReadableStream { - // using .constructor here to avoid import hacks - const ctor = stream.constructor as { - toWeb?: (stream: NodeJS.ReadableStream) => ReadableStream - } - - if (ctor.toWeb) { - // use `Readable.toWeb` if available - return ctor.toWeb(stream) - } - - // otherwise, use a silly little adapter - - stream.pause() - - return new ReadableStream({ - start(c) { - stream.on('data', (chunk) => { - c.enqueue(chunk) - }) - stream.on('end', () => { - c.close() - }) - stream.on('error', (err) => { - c.error(err) - }) - }, - pull() { - stream.resume() - }, - }) -} diff --git a/packages/core/src/platform.ts b/packages/core/src/platform.ts index 03ef4867..a92363d2 100644 --- a/packages/core/src/platform.ts +++ b/packages/core/src/platform.ts @@ -1,15 +1,39 @@ import { ITlPlatform, TlBinaryReader, TlBinaryWriter } from '@mtcute/tl-runtime' +import { UploadFileLike } from './highlevel/types/files/utils.js' import { MtUnsupportedError } from './types/errors.js' +import { MaybePromise } from './types/index.js' export interface ICorePlatform extends ITlPlatform { beforeExit(fn: () => void): () => void log(color: number, level: number, tag: string, fmt: string, args: unknown[]): void getDefaultLogLevel(): number | null getDeviceModel(): string + normalizeFile?(file: UploadFileLike): MaybePromise<{ + file?: UploadFileLike + fileSize?: number + fileName?: string + } | null> } -let _platform: ICorePlatform | null = null +// eslint-disable-next-line @typescript-eslint/no-explicit-any +let globalObject: any + +if (typeof globalThis !== 'undefined') { + globalObject = globalThis +} else if (typeof global !== 'undefined') { + globalObject = global +} else if (typeof self !== 'undefined') { + globalObject = self +} else if (typeof window !== 'undefined') { + globalObject = window +} + +// NB: when using with some bundlers (e.g. vite) re-importing this module will not return the same object +// so we need to store the platform in a global object to be able to survive hot-reloads etc. + +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment +let _platform: ICorePlatform | null = globalObject?.__MTCUTE_PLATFORM__ ?? null export function setPlatform(platform: ICorePlatform): void { if (_platform) { @@ -23,6 +47,10 @@ export function setPlatform(platform: ICorePlatform): void { _platform = platform TlBinaryReader.platform = platform TlBinaryWriter.platform = platform + + if (globalObject) { + globalObject.__MTCUTE_PLATFORM__ = platform + } } export function getPlatform(): ICorePlatform { diff --git a/packages/core/src/utils/crypto/keys.test.ts b/packages/core/src/utils/crypto/keys.test.ts index 40c00dc1..8aa6f680 100644 --- a/packages/core/src/utils/crypto/keys.test.ts +++ b/packages/core/src/utils/crypto/keys.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from 'vitest' +import { beforeAll, describe, expect, it } from 'vitest' import { defaultCryptoProvider } from '@mtcute/test' @@ -6,6 +6,10 @@ import { findKeyByFingerprints, parsePublicKey } from '../index.js' const crypto = defaultCryptoProvider +beforeAll(async () => { + await crypto.initialize() +}) + describe('parsePublicKey', () => { it('should parse telegram public keys', () => { expect( diff --git a/packages/dispatcher/tests/dispatcher.test.ts b/packages/dispatcher/tests/dispatcher.test.ts index 5299b10e..e218c6ff 100644 --- a/packages/dispatcher/tests/dispatcher.test.ts +++ b/packages/dispatcher/tests/dispatcher.test.ts @@ -1,12 +1,14 @@ import { describe, expect, it } from 'vitest' -import { PeersIndex, TelegramClient } from '@mtcute/core' +import { PeersIndex } from '@mtcute/core' +import { TelegramClient } from '@mtcute/core/client.js' +import { StubTelegramClient } from '@mtcute/test' import { Dispatcher, PropagationAction } from '../src/index.js' describe('Dispatcher', () => { // todo: replace with proper mocked TelegramClient - const client = new TelegramClient({ apiId: 0, apiHash: '' }) + const client = new TelegramClient({ client: new StubTelegramClient({ disableUpdates: false }) }) const emptyPeers = new PeersIndex() describe('Raw updates', () => { diff --git a/packages/node/src/client.ts b/packages/node/src/client.ts index 66d2b048..a863047f 100644 --- a/packages/node/src/client.ts +++ b/packages/node/src/client.ts @@ -7,7 +7,7 @@ import { setPlatform } from '@mtcute/core/platform.js' import { SqliteStorage } from '@mtcute/sqlite' import { downloadToFile } from './methods/download-file.js' -import { uploadFile } from './methods/upload-file.js' +import { downloadAsNodeStream } from './methods/download-node-stream.js' import { NodePlatform } from './platform.js' import { NodeCryptoProvider } from './utils/crypto.js' import { TcpTransport } from './utils/tcp.js' @@ -120,7 +120,10 @@ export class TelegramClient extends TelegramClientBase { return downloadToFile(this, filename, location, params) } - uploadFile(params: Parameters[1]) { - return uploadFile(this, params) + downloadAsNodeStream( + location: FileDownloadLocation, + params?: FileDownloadParameters | undefined, + ) { + return downloadAsNodeStream(this, location, params) } } diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 243dda6a..3b411bd2 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -1,7 +1,7 @@ export * from './client.js' export * from './platform.js' -export * from './utils/tcp.js' export * from './utils/crypto.js' +export * from './utils/tcp.js' export * from '@mtcute/core' export * from '@mtcute/html-parser' export * from '@mtcute/markdown-parser' diff --git a/packages/node/src/methods.ts b/packages/node/src/methods.ts index e9a3f076..dd3f229e 100644 --- a/packages/node/src/methods.ts +++ b/packages/node/src/methods.ts @@ -2,4 +2,4 @@ export * from '@mtcute/core/methods.js' export { downloadToFile } from './methods/download-file.js' -export { uploadFile } from './methods/upload-file.js' +export { downloadAsNodeStream } from './methods/download-node-stream.js' diff --git a/packages/node/src/methods/download-node-stream.ts b/packages/node/src/methods/download-node-stream.ts new file mode 100644 index 00000000..255131c0 --- /dev/null +++ b/packages/node/src/methods/download-node-stream.ts @@ -0,0 +1,19 @@ +import { Readable } from 'stream' + +import { FileDownloadLocation, FileDownloadParameters, ITelegramClient } from '@mtcute/core' +import { downloadAsStream } from '@mtcute/core/methods.js' + +import { webStreamToNode } from '../utils/stream-utils.js' + +/** + * Download a remote file as a Node.js Readable stream. + * + * @param params File download parameters + */ +export function downloadAsNodeStream( + client: ITelegramClient, + location: FileDownloadLocation, + params?: FileDownloadParameters, +): Readable { + return webStreamToNode(downloadAsStream(client, location, params)) +} diff --git a/packages/node/src/platform.ts b/packages/node/src/platform.ts index 80800b75..60dd7307 100644 --- a/packages/node/src/platform.ts +++ b/packages/node/src/platform.ts @@ -4,6 +4,7 @@ import { ICorePlatform } from '@mtcute/core/platform.js' import { beforeExit } from './utils/exit-hook.js' import { defaultLoggingHandler } from './utils/logging.js' +import { normalizeFile } from './utils/normalize-file.js' const BUFFER_BASE64_URL_AVAILABLE = typeof Buffer.isEncoding === 'function' && Buffer.isEncoding('base64url') @@ -17,6 +18,7 @@ export class NodePlatform implements ICorePlatform { // ICorePlatform log!: typeof defaultLoggingHandler beforeExit!: typeof beforeExit + normalizeFile!: typeof normalizeFile getDeviceModel(): string { return `${os.type()} ${os.arch()} ${os.release()}` @@ -76,3 +78,4 @@ export class NodePlatform implements ICorePlatform { NodePlatform.prototype.log = defaultLoggingHandler NodePlatform.prototype.beforeExit = beforeExit +NodePlatform.prototype.normalizeFile = normalizeFile diff --git a/packages/node/src/utils.ts b/packages/node/src/utils.ts index 3356b98c..2cbae9a9 100644 --- a/packages/node/src/utils.ts +++ b/packages/node/src/utils.ts @@ -1 +1,2 @@ +export * from './utils/stream-utils.js' export * from '@mtcute/core/utils.js' diff --git a/packages/node/src/methods/upload-file.ts b/packages/node/src/utils/normalize-file.ts similarity index 58% rename from packages/node/src/methods/upload-file.ts rename to packages/node/src/utils/normalize-file.ts index 7fe71c6c..5414ae21 100644 --- a/packages/node/src/methods/upload-file.ts +++ b/packages/node/src/utils/normalize-file.ts @@ -3,17 +3,11 @@ import { stat } from 'fs/promises' import { basename } from 'path' import { Readable } from 'stream' -import { ITelegramClient } from '@mtcute/core' -import { uploadFile as uploadFileCore } from '@mtcute/core/methods.js' +import { UploadFileLike } from '@mtcute/core' import { nodeStreamToWeb } from '../utils/stream-utils.js' -export async function uploadFile( - client: ITelegramClient, - params: Parameters[1], -) { - let file = params.file - +export async function normalizeFile(file: UploadFileLike) { if (typeof file === 'string') { file = createReadStream(file) } @@ -22,20 +16,19 @@ export async function uploadFile( const fileName = basename(file.path.toString()) const fileSize = await stat(file.path.toString()).then((stat) => stat.size) - return uploadFileCore(client, { - ...params, + return { file: nodeStreamToWeb(file), fileName, fileSize, - }) + } } if (file instanceof Readable) { - return uploadFileCore(client, { - ...params, + return { file: nodeStreamToWeb(file), - }) + } } - return uploadFileCore(client, params) + // string -> ReadStream, thus already handled + return null } diff --git a/packages/node/src/utils/stream-utils.test.ts b/packages/node/src/utils/stream-utils.test.ts new file mode 100644 index 00000000..20a747ea --- /dev/null +++ b/packages/node/src/utils/stream-utils.test.ts @@ -0,0 +1,59 @@ +import { Readable } from 'stream' +import { describe, expect, it } from 'vitest' + +import { nodeStreamToWeb, webStreamToNode } from './stream-utils.js' + +if (import.meta.env.TEST_ENV === 'node' || import.meta.env.TEST_ENV === 'bun') { + describe('nodeStreamToWeb', () => { + it('should correctly convert a readable stream', async () => { + const stream = new Readable({ + read() { + // eslint-disable-next-line no-restricted-globals + this.push(Buffer.from([1, 2, 3])) + // eslint-disable-next-line no-restricted-globals + this.push(Buffer.from([4, 5, 6])) + this.push(null) + }, + }) + + const webStream = nodeStreamToWeb(stream) + const reader = webStream.getReader() + + expect(await reader.read()).to.deep.equal({ value: new Uint8Array([1, 2, 3]), done: false }) + expect(await reader.read()).to.deep.equal({ value: new Uint8Array([4, 5, 6]), done: false }) + expect(await reader.read()).to.deep.equal({ value: undefined, done: true }) + }) + }) + + describe('webStreamToNode', () => { + it('should correctly convert a readable stream', async () => { + const stream = new ReadableStream({ + start(controller) { + controller.enqueue(new Uint8Array([1, 2, 3])) + controller.enqueue(new Uint8Array([4, 5, 6])) + controller.close() + }, + }) + + const nodeStream = webStreamToNode(stream) + const chunks: Buffer[] = [] + + nodeStream.on('data', (chunk) => { + chunks.push(chunk as Buffer) + }) + + await new Promise((resolve, reject) => { + nodeStream.on('end', () => { + try { + expect(chunks).to.deep.equal([Buffer.from([1, 2, 3]), Buffer.from([4, 5, 6])]) + resolve() + } catch (err) { + reject(err) + } + }) + }) + }) + }) +} else { + describe.skip('node stream utils', () => {}) +} diff --git a/packages/node/src/utils/stream-utils.ts b/packages/node/src/utils/stream-utils.ts index 8a450f5e..20ac1b8b 100644 --- a/packages/node/src/utils/stream-utils.ts +++ b/packages/node/src/utils/stream-utils.ts @@ -1,26 +1,78 @@ import { Readable } from 'stream' +import { isNodeVersionAfter } from './version.js' + export function nodeStreamToWeb(stream: Readable): ReadableStream { if (typeof Readable.toWeb === 'function') { return Readable.toWeb(stream) } + // otherwise, use a silly little adapter + + stream.pause() + return new ReadableStream({ - start(controller) { + start(c) { stream.on('data', (chunk) => { - controller.enqueue(chunk) + c.enqueue(chunk as Uint8Array) }) stream.on('end', () => { - controller.close() + c.close() }) stream.on('error', (err) => { - controller.error(err) + c.error(err) }) }, - cancel() { - if (typeof stream.destroy === 'function') { - stream.destroy() - } + pull() { + stream.resume() }, }) } + +export function webStreamToNode(stream: ReadableStream): Readable { + if ( + typeof Readable.fromWeb === 'function' && + isNodeVersionAfter(18, 13, 0) // https://github.com/nodejs/node/issues/42694 + ) { + // @ts-expect-error node typings are wrong lmao + return Readable.fromWeb(stream) + } + + const reader = stream.getReader() + let ended = false + + const readable = new Readable({ + async read() { + try { + const { done, value } = await reader.read() + + if (done) { + this.push(null) + } else { + this.push(Buffer.from(value.buffer, value.byteOffset, value.byteLength)) + } + } catch (err) { + this.destroy(err as Error) + } + }, + destroy(error, cb) { + if (!ended) { + void reader.cancel(error).catch(() => {}).then(() => { + cb(error) + }) + + return + } + + cb(error) + }, + }) + + reader.closed.then(() => { + ended = true + }).catch((err) => { + readable.destroy(err as Error) + }) + + return readable +} diff --git a/packages/node/src/utils/tcp.test.ts b/packages/node/src/utils/tcp.test.ts index d446b923..da70f7b9 100644 --- a/packages/node/src/utils/tcp.test.ts +++ b/packages/node/src/utils/tcp.test.ts @@ -4,7 +4,6 @@ import { describe, expect, it, MockedObject, vi } from 'vitest' import { TransportState } from '@mtcute/core' import { getPlatform } from '@mtcute/core/platform.js' import { defaultProductionDc, LogManager } from '@mtcute/core/utils.js' -import { defaultTestCryptoProvider, u8HexDecode } from '@mtcute/test' if (import.meta.env.TEST_ENV === 'node' || import.meta.env.TEST_ENV === 'bun') { vi.doMock('net', () => ({ @@ -27,6 +26,7 @@ if (import.meta.env.TEST_ENV === 'node' || import.meta.env.TEST_ENV === 'bun') { const connect = vi.mocked(net.connect) const { TcpTransport } = await import('./tcp.js') + const { defaultTestCryptoProvider, u8HexDecode } = await import('@mtcute/test') describe('TcpTransport', () => { const getLastSocket = () => { diff --git a/packages/node/src/utils/version.ts b/packages/node/src/utils/version.ts new file mode 100644 index 00000000..1e987b06 --- /dev/null +++ b/packages/node/src/utils/version.ts @@ -0,0 +1,14 @@ +export const NODE_VERSION = typeof process !== 'undefined' && 'node' in process.versions ? process.versions.node : null +export const NODE_VERSION_TUPLE = NODE_VERSION ? NODE_VERSION.split('.').map(Number) : null + +export function isNodeVersionAfter(major: number, minor: number, patch: number): boolean { + if (!NODE_VERSION_TUPLE) return true // assume non-node environment is always "after" + + const [a, b, c] = NODE_VERSION_TUPLE + if (a > major) return true + if (a < major) return false + if (b > minor) return true + if (b < minor) return false + + return c >= patch +} diff --git a/packages/test/src/index.ts b/packages/test/src/index.ts index 3399704a..b3755c23 100644 --- a/packages/test/src/index.ts +++ b/packages/test/src/index.ts @@ -1,7 +1,6 @@ export * from './client.js' export * from './crypto.js' export * from './platform.js' -export * from './platform.js' export * from './storage.js' export * from './storage/index.js' export * from './stub.js' diff --git a/packages/tl-runtime/src/writer.test.ts b/packages/tl-runtime/src/writer.test.ts index 58d3cd79..e1b6e71a 100644 --- a/packages/tl-runtime/src/writer.test.ts +++ b/packages/tl-runtime/src/writer.test.ts @@ -94,6 +94,10 @@ describe('TlBinaryWriter', () => { expect(testSingleMethod(1004, (w) => w.bytes(random1000bytes))).toEqual(hexEncode(buffer)) }) + it('should write tg-encoded string', () => { + expect(testSingleMethod(8, (w) => w.string('test'))).toEqual('0474657374000000') + }) + const stubObjectsMap: TlWriterMap = { deadbeef: function (w, obj) { w.uint(0xdeadbeef) diff --git a/packages/wasm/package.json b/packages/wasm/package.json index b0c72335..ba52e239 100644 --- a/packages/wasm/package.json +++ b/packages/wasm/package.json @@ -26,6 +26,8 @@ } }, "devDependencies": { - "@mtcute/tl-runtime": "workspace:^" + "@mtcute/core": "workspace:^", + "@mtcute/web": "workspace:^", + "@mtcute/node": "workspace:^" } } diff --git a/packages/wasm/tests/ctr.test.ts b/packages/wasm/tests/ctr.test.ts index ebe38127..5fcb9db5 100644 --- a/packages/wasm/tests/ctr.test.ts +++ b/packages/wasm/tests/ctr.test.ts @@ -1,26 +1,29 @@ import { beforeAll, describe, expect, it } from 'vitest' -import { hexDecodeToBuffer, hexEncode } from '@mtcute/tl-runtime' +import { getPlatform } from '@mtcute/core/platform.js' -import { __getWasm, createCtr256, ctr256, freeCtr256, initAsync } from '../src/index.js' +import { __getWasm, createCtr256, ctr256, freeCtr256 } from '../src/index.js' +import { initWasm } from './init.js' + +const p = getPlatform() beforeAll(async () => { - await initAsync() + await initWasm() }) describe('aes-ctr', () => { - const key = hexDecodeToBuffer('603DEB1015CA71BE2B73AEF0857D77811F352C073B6108D72D9810A30914DFF4') - const iv = hexDecodeToBuffer('F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF') + const key = p.hexDecode('603DEB1015CA71BE2B73AEF0857D77811F352C073B6108D72D9810A30914DFF4') + const iv = p.hexDecode('F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF') describe('NIST', () => { // https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/AES_CTR.pdf - const data = hexDecodeToBuffer( + const data = p.hexDecode( `6BC1BEE2 2E409F96 E93D7E11 7393172A AE2D8A57 1E03AC9C 9EB76FAC 45AF8E51 30C81C46 A35CE411 E5FBC119 1A0A52EF F69F2445 DF4F9B17 AD2B417B E66C3710`.replace(/\s/g, ''), ) - const dataEnc = hexDecodeToBuffer( + const dataEnc = p.hexDecode( `601EC313 775789A5 B7A7F504 BBF3D228 F443E3CA 4D62B59A CA84E990 CACAF5C5 2B0930DA A23DE94C E87017BA 2D84988D @@ -32,7 +35,7 @@ describe('aes-ctr', () => { const res = ctr256(ctr, data) freeCtr256(ctr) - expect(hexEncode(res)).toEqual(hexEncode(dataEnc)) + expect(p.hexEncode(res)).toEqual(p.hexEncode(dataEnc)) }) it('should correctly decrypt', () => { @@ -40,15 +43,15 @@ describe('aes-ctr', () => { const res = ctr256(ctr, dataEnc) freeCtr256(ctr) - expect(hexEncode(res)).toEqual(hexEncode(data)) + expect(p.hexEncode(res)).toEqual(p.hexEncode(data)) }) }) describe('stream', () => { - const data = hexDecodeToBuffer('6BC1BEE22E409F96E93D7E117393172A') - const dataEnc1 = hexDecodeToBuffer('601ec313775789a5b7a7f504bbf3d228') - const dataEnc2 = hexDecodeToBuffer('31afd77f7d218690bd0ef82dfcf66cbe') - const dataEnc3 = hexDecodeToBuffer('7000927e2f2192cbe4b6a8b2441ddd48') + const data = p.hexDecode('6BC1BEE22E409F96E93D7E117393172A') + const dataEnc1 = p.hexDecode('601ec313775789a5b7a7f504bbf3d228') + const dataEnc2 = p.hexDecode('31afd77f7d218690bd0ef82dfcf66cbe') + const dataEnc3 = p.hexDecode('7000927e2f2192cbe4b6a8b2441ddd48') it('should correctly encrypt', () => { const ctr = createCtr256(key, iv) @@ -58,9 +61,9 @@ describe('aes-ctr', () => { freeCtr256(ctr) - expect(hexEncode(res1)).toEqual(hexEncode(dataEnc1)) - expect(hexEncode(res2)).toEqual(hexEncode(dataEnc2)) - expect(hexEncode(res3)).toEqual(hexEncode(dataEnc3)) + expect(p.hexEncode(res1)).toEqual(p.hexEncode(dataEnc1)) + expect(p.hexEncode(res2)).toEqual(p.hexEncode(dataEnc2)) + expect(p.hexEncode(res3)).toEqual(p.hexEncode(dataEnc3)) }) it('should correctly decrypt', () => { @@ -71,20 +74,20 @@ describe('aes-ctr', () => { freeCtr256(ctr) - expect(hexEncode(res1)).toEqual(hexEncode(data)) - expect(hexEncode(res2)).toEqual(hexEncode(data)) - expect(hexEncode(res3)).toEqual(hexEncode(data)) + expect(p.hexEncode(res1)).toEqual(p.hexEncode(data)) + expect(p.hexEncode(res2)).toEqual(p.hexEncode(data)) + expect(p.hexEncode(res3)).toEqual(p.hexEncode(data)) }) }) describe('stream (unaligned)', () => { - const data = hexDecodeToBuffer('6BC1BEE22E40') - const dataEnc1 = hexDecodeToBuffer('601ec3137757') - const dataEnc2 = hexDecodeToBuffer('7df2e078a555') - const dataEnc3 = hexDecodeToBuffer('a3a17be0742e') - const dataEnc4 = hexDecodeToBuffer('025ced833746') - const dataEnc5 = hexDecodeToBuffer('3ff238dea125') - const dataEnc6 = hexDecodeToBuffer('1055a52302dc') + const data = p.hexDecode('6BC1BEE22E40') + const dataEnc1 = p.hexDecode('601ec3137757') + const dataEnc2 = p.hexDecode('7df2e078a555') + const dataEnc3 = p.hexDecode('a3a17be0742e') + const dataEnc4 = p.hexDecode('025ced833746') + const dataEnc5 = p.hexDecode('3ff238dea125') + const dataEnc6 = p.hexDecode('1055a52302dc') it('should correctly encrypt', () => { const ctr = createCtr256(key, iv) @@ -97,12 +100,12 @@ describe('aes-ctr', () => { freeCtr256(ctr) - expect(hexEncode(res1)).toEqual(hexEncode(dataEnc1)) - expect(hexEncode(res2)).toEqual(hexEncode(dataEnc2)) - expect(hexEncode(res3)).toEqual(hexEncode(dataEnc3)) - expect(hexEncode(res4)).toEqual(hexEncode(dataEnc4)) - expect(hexEncode(res5)).toEqual(hexEncode(dataEnc5)) - expect(hexEncode(res6)).toEqual(hexEncode(dataEnc6)) + expect(p.hexEncode(res1)).toEqual(p.hexEncode(dataEnc1)) + expect(p.hexEncode(res2)).toEqual(p.hexEncode(dataEnc2)) + expect(p.hexEncode(res3)).toEqual(p.hexEncode(dataEnc3)) + expect(p.hexEncode(res4)).toEqual(p.hexEncode(dataEnc4)) + expect(p.hexEncode(res5)).toEqual(p.hexEncode(dataEnc5)) + expect(p.hexEncode(res6)).toEqual(p.hexEncode(dataEnc6)) }) it('should correctly decrypt', () => { @@ -116,17 +119,17 @@ describe('aes-ctr', () => { freeCtr256(ctr) - expect(hexEncode(res1)).toEqual(hexEncode(data)) - expect(hexEncode(res2)).toEqual(hexEncode(data)) - expect(hexEncode(res3)).toEqual(hexEncode(data)) - expect(hexEncode(res4)).toEqual(hexEncode(data)) - expect(hexEncode(res5)).toEqual(hexEncode(data)) - expect(hexEncode(res6)).toEqual(hexEncode(data)) + expect(p.hexEncode(res1)).toEqual(p.hexEncode(data)) + expect(p.hexEncode(res2)).toEqual(p.hexEncode(data)) + expect(p.hexEncode(res3)).toEqual(p.hexEncode(data)) + expect(p.hexEncode(res4)).toEqual(p.hexEncode(data)) + expect(p.hexEncode(res5)).toEqual(p.hexEncode(data)) + expect(p.hexEncode(res6)).toEqual(p.hexEncode(data)) }) }) it('should not leak memory', () => { - const data = hexDecodeToBuffer('6BC1BEE22E409F96E93D7E117393172A') + const data = p.hexDecode('6BC1BEE22E409F96E93D7E117393172A') const mem = __getWasm().memory.buffer const memSize = mem.byteLength diff --git a/packages/wasm/tests/gunzip.test.ts b/packages/wasm/tests/gunzip.test.ts index 9a004a6c..79aa59f2 100644 --- a/packages/wasm/tests/gunzip.test.ts +++ b/packages/wasm/tests/gunzip.test.ts @@ -1,14 +1,17 @@ import { beforeAll, describe, expect, it } from 'vitest' import { gzipSync } from 'zlib' -import { utf8Decode, utf8EncodeToBuffer } from '@mtcute/tl-runtime' +import { getPlatform } from '@mtcute/core/platform.js' -import { __getWasm, gunzip, initAsync } from '../src/index.js' +import { __getWasm, gunzip } from '../src/index.js' +import { initWasm } from './init.js' beforeAll(async () => { - await initAsync() + await initWasm() }) +const p = getPlatform() + function gzipSyncWrap(data: Uint8Array) { if (import.meta.env.TEST_ENV === 'browser') { // @ts-expect-error fucking crutch because @jspm/core uses Buffer.isBuffer for some reason @@ -23,7 +26,7 @@ function gzipSyncWrap(data: Uint8Array) { describe('gunzip', () => { it('should correctly read zlib headers', () => { const wasm = __getWasm() - const data = gzipSyncWrap(utf8EncodeToBuffer('hello world')) + const data = gzipSyncWrap(p.utf8Encode('hello world')) const inputPtr = wasm.__malloc(data.length) new Uint8Array(wasm.memory.buffer).set(data, inputPtr) @@ -33,11 +36,11 @@ describe('gunzip', () => { it('should correctly inflate', () => { const data = Array.from({ length: 1000 }, () => 'a').join('') - const res = gzipSyncWrap(utf8EncodeToBuffer(data)) + const res = gzipSyncWrap(p.utf8Encode(data)) expect(res).not.toBeNull() expect(res.length).toBeLessThan(100) - expect(gunzip(res)).toEqual(new Uint8Array(utf8EncodeToBuffer(data))) + expect(gunzip(res)).toEqual(new Uint8Array(p.utf8Encode(data))) }) it('should not leak memory', () => { @@ -45,11 +48,11 @@ describe('gunzip', () => { for (let i = 0; i < 100; i++) { const data = Array.from({ length: 1000 }, () => 'a').join('') - const deflated = gzipSyncWrap(utf8EncodeToBuffer(data)) + const deflated = gzipSyncWrap(p.utf8Encode(data)) const res = gunzip(deflated) - expect(utf8Decode(res)).toEqual(data) + expect(p.utf8Decode(res)).toEqual(data) } expect(__getWasm().memory.buffer.byteLength).toEqual(memSize) diff --git a/packages/wasm/tests/hash.test.ts b/packages/wasm/tests/hash.test.ts index 2f85f1bb..7d0b5597 100644 --- a/packages/wasm/tests/hash.test.ts +++ b/packages/wasm/tests/hash.test.ts @@ -1,18 +1,21 @@ import { beforeAll, describe, expect, it } from 'vitest' -import { hexEncode, utf8EncodeToBuffer } from '@mtcute/tl-runtime' +import { getPlatform } from '@mtcute/core/platform.js' -import { __getWasm, initAsync, sha1, sha256 } from '../src/index.js' +import { __getWasm, sha1, sha256 } from '../src/index.js' +import { initWasm } from './init.js' beforeAll(async () => { - await initAsync() + await initWasm() }) +const p = getPlatform() + describe('sha256', () => { it('should correctly calculate sha-256 hash', () => { - const hash = sha256(utf8EncodeToBuffer('abc')) + const hash = sha256(p.utf8Encode('abc')) - expect(hexEncode(hash)).toEqual('ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad') + expect(p.hexEncode(hash)).toEqual('ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad') }) it('should not leak memory', () => { @@ -20,7 +23,7 @@ describe('sha256', () => { const memSize = mem.byteLength for (let i = 0; i < 100; i++) { - sha256(utf8EncodeToBuffer('abc')) + sha256(p.utf8Encode('abc')) } expect(mem.byteLength).toEqual(memSize) @@ -29,9 +32,9 @@ describe('sha256', () => { describe('sha1', () => { it('should correctly calculate sha-1 hash', () => { - const hash = sha1(utf8EncodeToBuffer('abc')) + const hash = sha1(p.utf8Encode('abc')) - expect(hexEncode(hash)).toEqual('a9993e364706816aba3e25717850c26c9cd0d89d') + expect(p.hexEncode(hash)).toEqual('a9993e364706816aba3e25717850c26c9cd0d89d') }) it('should not leak memory', () => { @@ -39,7 +42,7 @@ describe('sha1', () => { const memSize = mem.byteLength for (let i = 0; i < 100; i++) { - sha1(utf8EncodeToBuffer('abc')) + sha1(p.utf8Encode('abc')) } expect(mem.byteLength).toEqual(memSize) diff --git a/packages/wasm/tests/ige.test.ts b/packages/wasm/tests/ige.test.ts index 1edca67e..6e568842 100644 --- a/packages/wasm/tests/ige.test.ts +++ b/packages/wasm/tests/ige.test.ts @@ -1,31 +1,34 @@ /* eslint-disable no-restricted-globals */ import { beforeAll, describe, expect, it } from 'vitest' -import { hexDecodeToBuffer, hexEncode } from '@mtcute/tl-runtime' +import { getPlatform } from '@mtcute/core/platform.js' -import { __getWasm, ige256Decrypt, ige256Encrypt, initAsync } from '../src/index.js' +import { __getWasm, ige256Decrypt, ige256Encrypt } from '../src/index.js' +import { initWasm } from './init.js' + +const p = getPlatform() beforeAll(async () => { - await initAsync() + await initWasm() }) describe('aes-ige', () => { - const key = hexDecodeToBuffer('5468697320697320616E20696D706C655468697320697320616E20696D706C65') - const iv = hexDecodeToBuffer('6D656E746174696F6E206F6620494745206D6F646520666F72204F70656E5353') + const key = p.hexDecode('5468697320697320616E20696D706C655468697320697320616E20696D706C65') + const iv = p.hexDecode('6D656E746174696F6E206F6620494745206D6F646520666F72204F70656E5353') - const data = hexDecodeToBuffer('99706487a1cde613bc6de0b6f24b1c7aa448c8b9c3403e3467a8cad89340f53b') - const dataEnc = hexDecodeToBuffer('792ea8ae577b1a66cb3bd92679b8030ca54ee631976bd3a04547fdcb4639fa69') + const data = p.hexDecode('99706487a1cde613bc6de0b6f24b1c7aa448c8b9c3403e3467a8cad89340f53b') + const dataEnc = p.hexDecode('792ea8ae577b1a66cb3bd92679b8030ca54ee631976bd3a04547fdcb4639fa69') it('should correctly encrypt', () => { const aes = ige256Encrypt(data, key, iv) - expect(hexEncode(aes)).toEqual(hexEncode(dataEnc)) + expect(p.hexEncode(aes)).toEqual(p.hexEncode(dataEnc)) }) it('should correctly decrypt', () => { const aes = ige256Decrypt(dataEnc, key, iv) - expect(hexEncode(aes)).toEqual(hexEncode(data)) + expect(p.hexEncode(aes)).toEqual(p.hexEncode(data)) }) it('should not leak memory', () => { diff --git a/packages/wasm/tests/init.ts b/packages/wasm/tests/init.ts index 07cd12e7..6665faa1 100644 --- a/packages/wasm/tests/init.ts +++ b/packages/wasm/tests/init.ts @@ -1,6 +1,5 @@ import { initSync } from '../src/index.js' -// todo: use platform-specific packages export async function initWasm() { const url = new URL('../lib/mtcute.wasm', import.meta.url) diff --git a/packages/wasm/tests/zlib.test.ts b/packages/wasm/tests/zlib.test.ts index 4f67a514..848c4871 100644 --- a/packages/wasm/tests/zlib.test.ts +++ b/packages/wasm/tests/zlib.test.ts @@ -1,14 +1,17 @@ import { beforeAll, describe, expect, it } from 'vitest' import { inflateSync } from 'zlib' -import { utf8Decode, utf8EncodeToBuffer } from '@mtcute/tl-runtime' +import { getPlatform } from '@mtcute/core/platform.js' -import { __getWasm, deflateMaxSize, initAsync } from '../src/index.js' +import { __getWasm, deflateMaxSize } from '../src/index.js' +import { initWasm } from './init.js' beforeAll(async () => { - await initAsync() + await initWasm() }) +const p = getPlatform() + function inflateSyncWrap(data: Uint8Array) { if (import.meta.env.TEST_ENV === 'browser') { // @ts-expect-error fucking crutch because @jspm/core uses Buffer.isBuffer for some reason @@ -22,25 +25,25 @@ function inflateSyncWrap(data: Uint8Array) { describe('zlib deflate', () => { it('should add zlib headers', () => { - const res = deflateMaxSize(utf8EncodeToBuffer('hello world'), 100) + const res = deflateMaxSize(p.utf8Encode('hello world'), 100) expect(res).not.toBeNull() expect(res!.slice(0, 2)).toEqual(new Uint8Array([0x78, 0x9c])) }) it('should return null if compressed data is larger than size', () => { - const res = deflateMaxSize(utf8EncodeToBuffer('hello world'), 1) + const res = deflateMaxSize(p.utf8Encode('hello world'), 1) expect(res).toBeNull() }) it('should correctly deflate', () => { const data = Array.from({ length: 1000 }, () => 'a').join('') - const res = deflateMaxSize(utf8EncodeToBuffer(data), 100) + const res = deflateMaxSize(p.utf8Encode(data), 100) expect(res).not.toBeNull() expect(res!.length).toBeLessThan(100) - expect(inflateSyncWrap(res!)).toEqual(utf8EncodeToBuffer(data)) + expect(inflateSyncWrap(res!)).toEqual(p.utf8Encode(data)) }) it('should not leak memory', () => { @@ -48,11 +51,11 @@ describe('zlib deflate', () => { for (let i = 0; i < 100; i++) { const data = Array.from({ length: 1000 }, () => 'a').join('') - const deflated = deflateMaxSize(utf8EncodeToBuffer(data), 100) + const deflated = deflateMaxSize(p.utf8Encode(data), 100) const res = inflateSyncWrap(deflated!) - expect(utf8Decode(res)).toEqual(data) + expect(p.utf8Decode(res)).toEqual(data) } expect(__getWasm().memory.buffer.byteLength).toEqual(memSize) diff --git a/packages/web/src/crypto.test.ts b/packages/web/src/crypto.test.ts deleted file mode 100644 index c5d65b28..00000000 --- a/packages/web/src/crypto.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { describe } from 'vitest' - -import { testCryptoProvider } from '@mtcute/test' - -import { WebCryptoProvider } from './crypto.js' - -describe('WebCryptoProvider', async () => { - let crypto = globalThis.crypto - - if (!crypto && typeof process !== 'undefined') { - crypto = await import('crypto').then((m) => m.webcrypto as Crypto) - } - - if (!crypto) { - console.warn('Skipping WebCryptoProvider tests (no webcrypto)') - - return - } - - testCryptoProvider(new WebCryptoProvider({ crypto })) -}) diff --git a/packages/web/src/index.ts b/packages/web/src/index.ts index 31f603e6..cab1978e 100644 --- a/packages/web/src/index.ts +++ b/packages/web/src/index.ts @@ -1,2 +1,3 @@ export * from './crypto.js' +export * from './idb/index.js' export * from './platform.js' diff --git a/packages/core/src/network/transports/websocket.test.ts b/packages/web/src/websocket.test.ts similarity index 96% rename from packages/core/src/network/transports/websocket.test.ts rename to packages/web/src/websocket.test.ts index b583568e..91630352 100644 --- a/packages/core/src/network/transports/websocket.test.ts +++ b/packages/web/src/websocket.test.ts @@ -1,10 +1,10 @@ import { describe, expect, it, Mock, MockedObject, vi } from 'vitest' +import { TransportState } from '@mtcute/core' +import { getPlatform } from '@mtcute/core/platform.js' +import { defaultProductionDc, LogManager } from '@mtcute/core/utils.js' import { defaultTestCryptoProvider, u8HexDecode } from '@mtcute/test' -import { getPlatform } from '../../platform.js' -import { defaultProductionDc, LogManager } from '../../utils/index.js' -import { TransportState } from './abstract.js' import { WebSocketTransport } from './websocket.js' const p = getPlatform() diff --git a/packages/core/src/network/transports/websocket.ts b/packages/web/src/websocket.ts similarity index 93% rename from packages/core/src/network/transports/websocket.ts rename to packages/web/src/websocket.ts index 20609a31..4135ed8a 100644 --- a/packages/core/src/network/transports/websocket.ts +++ b/packages/web/src/websocket.ts @@ -1,10 +1,15 @@ import EventEmitter from 'events' -import { MtcuteError, MtUnsupportedError } from '../../types/errors.js' -import { BasicDcOption, ICryptoProvider, Logger } from '../../utils/index.js' -import { IPacketCodec, ITelegramTransport, TransportState } from './abstract.js' -import { IntermediatePacketCodec } from './intermediate.js' -import { ObfuscatedPacketCodec } from './obfuscated.js' +import { + IntermediatePacketCodec, + IPacketCodec, + ITelegramTransport, + MtcuteError, + MtUnsupportedError, + ObfuscatedPacketCodec, + TransportState, +} from '@mtcute/core' +import { BasicDcOption, ICryptoProvider, Logger } from '@mtcute/core/utils.js' export type WebSocketConstructor = { new (address: string, protocol?: string): WebSocket diff --git a/packages/web/utils.ts b/packages/web/utils.ts deleted file mode 100644 index 3356b98c..00000000 --- a/packages/web/utils.ts +++ /dev/null @@ -1 +0,0 @@ -export * from '@mtcute/core/utils.js' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 50e5c5f3..ea545077 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -390,9 +390,15 @@ importers: packages/wasm: devDependencies: - '@mtcute/tl-runtime': + '@mtcute/core': specifier: workspace:^ - version: link:../tl-runtime + version: link:../core + '@mtcute/node': + specifier: workspace:^ + version: link:../node + '@mtcute/web': + specifier: workspace:^ + version: link:../web packages/web: dependencies: