diff --git a/package.json b/package.json index b209d98c..c8005bfa 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mtcute-workspace", "type": "module", - "version": "0.16.13", + "version": "0.17.0-fuman-alpha", "private": true, "packageManager": "pnpm@9.0.6", "description": "Type-safe library for MTProto (Telegram API) for browser and NodeJS", diff --git a/packages/bun/package.json b/packages/bun/package.json index 04951b63..f3f5edae 100644 --- a/packages/bun/package.json +++ b/packages/bun/package.json @@ -1,7 +1,7 @@ { "name": "@mtcute/bun", "type": "module", - "version": "0.16.9", + "version": "0.17.0-fuman-alpha", "private": true, "description": "Meta-package for Bun", "author": "alina sireneva ", @@ -20,7 +20,10 @@ "@mtcute/core": "workspace:^", "@mtcute/html-parser": "workspace:^", "@mtcute/markdown-parser": "workspace:^", - "@mtcute/wasm": "workspace:^" + "@mtcute/wasm": "workspace:^", + "@fuman/bun-net": "workspace:^", + "@fuman/net": "workspace:^", + "@fuman/io": "workspace:^" }, "devDependencies": { "@mtcute/test": "workspace:^" diff --git a/packages/bun/src/client.ts b/packages/bun/src/client.ts index 3aa53c20..90d83193 100644 --- a/packages/bun/src/client.ts +++ b/packages/bun/src/client.ts @@ -18,7 +18,7 @@ import { downloadAsNodeStream } from './methods/download-node-stream.js' import { BunPlatform } from './platform.js' import { SqliteStorage } from './sqlite/index.js' import { BunCryptoProvider } from './utils/crypto.js' -// import { TcpTransport } from './utils/tcp.js' +import { TcpTransport } from './utils/tcp.js' export type { TelegramClientOptions } @@ -49,7 +49,7 @@ export class BaseTelegramClient extends BaseTelegramClientBase { super({ crypto: new BunCryptoProvider(), - transport: {} as any, // todo + transport: TcpTransport, ...opts, storage: typeof opts.storage === 'string' diff --git a/packages/bun/src/utils/proxies.ts b/packages/bun/src/utils/proxies.ts new file mode 100644 index 00000000..e522458c --- /dev/null +++ b/packages/bun/src/utils/proxies.ts @@ -0,0 +1,74 @@ +import type { HttpProxySettings as FumanHttpProxySettings, ITcpConnection, SocksProxySettings, TcpEndpoint } from '@fuman/net' +import { performHttpProxyHandshake, performSocksHandshake } from '@fuman/net' +import { connectTcp, connectTls } from '@fuman/bun-net' +import { BaseMtProxyTransport, type ITelegramConnection, IntermediatePacketCodec, type TelegramTransport } from '@mtcute/core' +import type { BasicDcOption } from '@mtcute/core/utils.js' + +export type { SocksProxySettings } from '@fuman/net' +export { SocksProxyConnectionError, HttpProxyConnectionError } from '@fuman/net' +export type { MtProxySettings } from '@mtcute/core' + +export interface HttpProxySettings extends FumanHttpProxySettings { + /** + * Whether this is a HTTPS proxy (by default it is regular HTTP). + */ + tls?: boolean +} + +export class HttpProxyTcpTransport implements TelegramTransport { + constructor(readonly proxy: HttpProxySettings) {} + + async connect(dc: BasicDcOption): Promise { + let conn + if (this.proxy.tls) { + conn = await connectTls({ + address: this.proxy.host, + port: this.proxy.port, + }) + } else { + conn = await connectTcp({ + address: this.proxy.host, + port: this.proxy.port, + }) + } + + await performHttpProxyHandshake(conn, conn, this.proxy, { + address: dc.ipAddress, + port: dc.port, + }) + + return conn + } + + packetCodec(): IntermediatePacketCodec { + return new IntermediatePacketCodec() + } +} + +export class SocksProxyTcpTransport implements TelegramTransport { + constructor(readonly proxy: SocksProxySettings) {} + + async connect(dc: BasicDcOption): Promise { + const conn = await connectTcp({ + address: this.proxy.host, + port: this.proxy.port, + }) + + await performSocksHandshake(conn, conn, this.proxy, { + address: dc.ipAddress, + port: dc.port, + }) + + return conn + } + + packetCodec(): IntermediatePacketCodec { + return new IntermediatePacketCodec() + } +} + +export class MtProxyTcpTransport extends BaseMtProxyTransport { + async _connectTcp(endpoint: TcpEndpoint): Promise { + return connectTcp(endpoint) + } +} diff --git a/packages/bun/src/utils/tcp.ts b/packages/bun/src/utils/tcp.ts index b12952a2..5583a1e2 100644 --- a/packages/bun/src/utils/tcp.ts +++ b/packages/bun/src/utils/tcp.ts @@ -1,152 +1,7 @@ -// todo -// import EventEmitter from 'node:events' +import { connectTcp } from '@fuman/bun-net' +import { IntermediatePacketCodec, type TelegramTransport } from '@mtcute/core' -// import { IntermediatePacketCodec, IPacketCodec, ITelegramConnection, MtcuteError } from '@mtcute/core' -// import { BasicDcOption, ICryptoProvider, Logger } from '@mtcute/core/utils.js' - -// /** -// * Base for TCP transports. -// * Subclasses must provide packet codec in `_packetCodec` property -// */ -// export abstract class BaseTcpTransport extends EventEmitter implements ITelegramConnection { -// protected _currentDc: BasicDcOption | null = null -// protected _state: TransportState = TransportState.Idle -// protected _socket: Socket | null = null - -// abstract _packetCodec: IPacketCodec -// protected _crypto!: ICryptoProvider -// protected log!: Logger - -// packetCodecInitialized = false - -// private _updateLogPrefix() { -// if (this._currentDc) { -// this.log.prefix = `[TCP:${this._currentDc.ipAddress}:${this._currentDc.port}] ` -// } else { -// this.log.prefix = '[TCP:disconnected] ' -// } -// } - -// setup(crypto: ICryptoProvider, log: Logger): void { -// this._crypto = crypto -// this.log = log.create('tcp') -// this._updateLogPrefix() -// } - -// state(): TransportState { -// return this._state -// } - -// currentDc(): BasicDcOption | null { -// return this._currentDc -// } - -// // eslint-disable-next-line unused-imports/no-unused-vars -// connect(dc: BasicDcOption, testMode: boolean): void { -// if (this._state !== TransportState.Idle) { -// throw new MtcuteError('Transport is not IDLE') -// } - -// if (!this.packetCodecInitialized) { -// this._packetCodec.setup?.(this._crypto, this.log) -// this._packetCodec.on('error', err => this.emit('error', err)) -// this._packetCodec.on('packet', buf => this.emit('message', buf)) -// this.packetCodecInitialized = true -// } - -// this._state = TransportState.Connecting -// this._currentDc = dc -// this._updateLogPrefix() - -// this.log.debug('connecting to %j', dc) - -// Bun.connect({ -// hostname: dc.ipAddress, -// port: dc.port, -// socket: { -// open: this.handleConnect.bind(this), -// error: this.handleError.bind(this), -// data: (socket, data) => this._packetCodec.feed(data), -// close: this.close.bind(this), -// drain: this.handleDrained.bind(this), -// }, -// }).catch((err) => { -// this.handleError(null, err as Error) -// this.close() -// }) -// } - -// close(): void { -// if (this._state === TransportState.Idle) return -// this.log.info('connection closed') - -// this._state = TransportState.Idle -// this._socket?.end() -// this._socket = null -// this._currentDc = null -// this._packetCodec.reset() -// this._sendOnceDrained = [] -// this.emit('close') -// } - -// handleError(socket: unknown, error: Error): void { -// this.log.error('error: %s', error.stack) - -// if (this.listenerCount('error') > 0) { -// this.emit('error', error) -// } -// } - -// handleConnect(socket: Socket): void { -// this._socket = socket -// this.log.info('connected') - -// Promise.resolve(this._packetCodec.tag()) -// .then((initialMessage) => { -// if (initialMessage.length) { -// this._socket!.write(initialMessage) -// this._state = TransportState.Ready -// this.emit('ready') -// } else { -// this._state = TransportState.Ready -// this.emit('ready') -// } -// }) -// .catch((err) => { -// if (this.listenerCount('error') > 0) { -// this.emit('error', err) -// } -// }) -// } - -// async send(bytes: Uint8Array): Promise { -// const framed = await this._packetCodec.encode(bytes) - -// if (this._state !== TransportState.Ready) { -// throw new MtcuteError('Transport is not READY') -// } - -// const written = this._socket!.write(framed) - -// if (written < framed.length) { -// this._sendOnceDrained.push(framed.subarray(written)) -// } -// } - -// private _sendOnceDrained: Uint8Array[] = [] -// private handleDrained(): void { -// while (this._sendOnceDrained.length) { -// const data = this._sendOnceDrained.shift()! -// const written = this._socket!.write(data) - -// if (written < data.length) { -// this._sendOnceDrained.unshift(data.subarray(written)) -// break -// } -// } -// } -// } - -// export class TcpTransport extends BaseTcpTransport { -// _packetCodec: IntermediatePacketCodec = new IntermediatePacketCodec() -// } +export const TcpTransport: TelegramTransport = { + connect: dc => connectTcp({ address: dc.ipAddress, port: dc.port }), + packetCodec: () => new IntermediatePacketCodec(), +} diff --git a/packages/convert/package.json b/packages/convert/package.json index 36650ed3..c7b20b84 100644 --- a/packages/convert/package.json +++ b/packages/convert/package.json @@ -1,20 +1,20 @@ { - "name": "@mtcute/convert", - "type": "module", - "version": "0.16.9", - "private": true, - "description": "Cross-library session conversion utilities", - "author": "alina sireneva ", - "license": "MIT", - "sideEffects": false, - "exports": "./src/index.ts", - "scripts": { - "build": "pnpm run -w build-package convert" - }, - "dependencies": { - "@mtcute/core": "workspace:^" - }, - "devDependencies": { - "@mtcute/test": "workspace:^" - } + "name": "@mtcute/convert", + "type": "module", + "version": "0.17.0-fuman-alpha", + "private": true, + "description": "Cross-library session conversion utilities", + "author": "alina sireneva ", + "license": "MIT", + "sideEffects": false, + "exports": "./src/index.ts", + "scripts": { + "build": "pnpm run -w build-package convert" + }, + "dependencies": { + "@mtcute/core": "workspace:^" + }, + "devDependencies": { + "@mtcute/test": "workspace:^" + } } diff --git a/packages/core/package.json b/packages/core/package.json index 3b730197..aa532dcb 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@mtcute/core", "type": "module", - "version": "0.16.13", + "version": "0.17.0-fuman-alpha", "private": true, "description": "Type-safe library for MTProto (Telegram API)", "author": "alina sireneva ", diff --git a/packages/core/src/highlevel/utils/string-session.ts b/packages/core/src/highlevel/utils/string-session.ts index 424f8a64..c1fc1415 100644 --- a/packages/core/src/highlevel/utils/string-session.ts +++ b/packages/core/src/highlevel/utils/string-session.ts @@ -8,7 +8,6 @@ import type { CurrentUserInfo } from '../storage/service/current-user.js' export interface StringSessionData { version: number - testMode: boolean primaryDcs: DcOptions self?: CurrentUserInfo | null authKey: Uint8Array @@ -54,10 +53,6 @@ export function writeStringSession(data: StringSessionData): string { flags |= 1 } - if (data.testMode) { - flags |= 2 - } - writer.uint8View[0] = version writer.pos += 1 @@ -104,7 +99,7 @@ export function readStringSession(data: string): StringSessionData { const flags = reader.int() const hasSelf = flags & 1 - const testMode = Boolean(flags & 2) + const testModeOld = Boolean(flags & 2) const hasMedia = version >= 2 && Boolean(flags & 4) let primaryDc: BasicDcOption @@ -135,6 +130,13 @@ export function readStringSession(data: string): StringSessionData { throw new Error('unreachable') } + if (testModeOld) { + primaryDc.testMode = true + primaryMediaDc.testMode = true + } else if (primaryDc.testMode !== primaryMediaDc.testMode) { + throw new MtArgumentError('Primary DC and primary media DC must have the same test mode flag') + } + let self: CurrentUserInfo | null = null if (hasSelf) { @@ -154,7 +156,6 @@ export function readStringSession(data: string): StringSessionData { return { version, - testMode, primaryDcs: { main: primaryDc, media: primaryMediaDc, diff --git a/packages/core/src/network/index.ts b/packages/core/src/network/index.ts index dfb492c4..b0229968 100644 --- a/packages/core/src/network/index.ts +++ b/packages/core/src/network/index.ts @@ -7,6 +7,6 @@ export type { RpcCallMiddlewareContext, RpcCallOptions, } from './network-manager.js' -// export * from './reconnection.js' +export * from './mtproxy/index.js' export * from './session-connection.js' export * from './transports/index.js' diff --git a/packages/core/src/network/mtproxy/_fake-tls.ts b/packages/core/src/network/mtproxy/_fake-tls.ts new file mode 100644 index 00000000..c720ab15 --- /dev/null +++ b/packages/core/src/network/mtproxy/_fake-tls.ts @@ -0,0 +1,300 @@ +import { Bytes, type ISyncWritable, read } from '@fuman/io' + +import type { Logger } from '../../utils' +import { concatBuffers, dataViewFromBuffer } from '../../utils' +import { bigIntModInv, bigIntModPow, bigIntToBuffer, bufferToBigInt } from '../../utils/bigint-utils' +import type { ICryptoProvider } from '../../utils/crypto/abstract' +import type { IPacketCodec } from '../transports' + +const MAX_TLS_PACKET_LENGTH = 2878 + +// ref: https://github.com/tdlib/td/blob/master/td/mtproto/TlsInit.cpp +const KEY_MOD = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEDn +// 2^255 - 19 +const QUAD_RES_MOD = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEDn +// (mod - 1) / 2 = 2^254 - 10 +const QUAD_RES_POW = 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6n + +function _getY2(x: bigint, mod: bigint): bigint { + // returns y = x^3 + x^2 * 486662 + x + let y = x + y = (y + 486662n) % mod + y = (y * x) % mod + y = (y + 1n) % mod + y = (y * x) % mod + + return y +} + +function _getDoubleX(x: bigint, mod: bigint): bigint { + // returns x_2 = (x^2 - 1)^2/(4*y^2) + let denominator = _getY2(x, mod) + denominator = (denominator * 4n) % mod + + let numerator = (x * x) % mod + numerator = (numerator - 1n) % mod + numerator = (numerator * numerator) % mod + + denominator = bigIntModInv(denominator, mod) + numerator = (numerator * denominator) % mod + + return numerator +} + +function _isQuadraticResidue(a: bigint): boolean { + const r = bigIntModPow(a, QUAD_RES_POW, QUAD_RES_MOD) + + return r === 1n +} + +function executeTlsOperations(h: TlsHelloWriter): void { + h.string(new Uint8Array([0x16, 0x03, 0x01, 0x02, 0x00, 0x01, 0x00, 0x01, 0xFC, 0x03, 0x03])) + h.zero(32) + h.string(new Uint8Array([0x20])) + h.random(32) + h.string(new Uint8Array([0x00, 0x20])) + h.grease(0) + /* eslint-disable antfu/consistent-list-newline */ + h.string(new Uint8Array([ + 0x13, 0x01, 0x13, 0x02, 0x13, 0x03, 0xC0, 0x2B, 0xC0, 0x2F, 0xC0, 0x2C, + 0xC0, 0x30, 0xCC, 0xA9, 0xCC, 0xA8, 0xC0, 0x13, 0xC0, 0x14, 0x00, 0x9C, + 0x00, 0x9D, 0x00, 0x2F, 0x00, 0x35, 0x01, 0x00, 0x01, 0x93, + ])) + h.grease(2) + h.string(new Uint8Array([0x00, 0x00, 0x00, 0x00])) + h.beginScope() + h.beginScope() + h.string(new Uint8Array([0x00])) + h.beginScope() + h.domain() + h.endScope() + h.endScope() + h.endScope() + h.string(new Uint8Array([0x00, 0x17, 0x00, 0x00, 0xFF, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x08])) + h.grease(4) + h.string( + new Uint8Array([ + 0x00, 0x1D, 0x00, 0x17, 0x00, 0x18, 0x00, 0x0B, 0x00, 0x02, 0x01, 0x00, + 0x00, 0x23, 0x00, 0x00, 0x00, 0x10, 0x00, 0x0E, 0x00, 0x0C, 0x02, 0x68, + 0x32, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2F, 0x31, 0x2E, 0x31, 0x00, 0x05, + 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x12, 0x00, + 0x10, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 0x05, + 0x01, 0x08, 0x06, 0x06, 0x01, 0x00, 0x12, 0x00, 0x00, 0x00, 0x33, 0x00, + 0x2B, 0x00, 0x29, + ]), + ) + h.grease(4) + h.string(new Uint8Array([0x00, 0x01, 0x00, 0x00, 0x1D, 0x00, 0x20])) + h.key() + h.string(new Uint8Array([0x00, 0x2D, 0x00, 0x02, 0x01, 0x01, 0x00, 0x2B, 0x00, 0x0B, 0x0A])) + h.grease(6) + h.string(new Uint8Array([0x03, 0x04, 0x03, 0x03, 0x03, 0x02, 0x03, 0x01, 0x00, 0x1B, 0x00, 0x03, 0x02, 0x00, 0x02])) + h.grease(3) + h.string(new Uint8Array([0x00, 0x01, 0x00, 0x00, 0x15])) + /* eslint-enable */ +} + +function initGrease(crypto: ICryptoProvider, size: number): Uint8Array { + const buf = crypto.randomBytes(size) + + for (let i = 0; i < size; i++) { + buf[i] = (buf[i] & 0xF0) + 0x0A + } + + for (let i = 1; i < size; i += 2) { + if (buf[i] === buf[i - 1]) { + buf[i] ^= 0x10 + } + } + + return buf +} + +class TlsHelloWriter { + buf: Uint8Array + dv: DataView + pos = 0 + + private _domain: Uint8Array + private _grease + private _scopes: number[] = [] + + constructor( + readonly crypto: ICryptoProvider, + size: number, + domain: Uint8Array, + ) { + this._domain = domain + this.buf = new Uint8Array(size) + this.dv = dataViewFromBuffer(this.buf) + this._grease = initGrease(this.crypto, 7) + } + + string(buf: Uint8Array) { + this.buf.set(buf, this.pos) + this.pos += buf.length + } + + random(size: number) { + this.string(this.crypto.randomBytes(size)) + } + + zero(size: number) { + // since the Uint8Array is initialized with zeros, we can just skip + this.pos += size + } + + domain() { + this.string(this._domain) + } + + grease(seed: number) { + this.buf[this.pos] = this.buf[this.pos + 1] = this._grease[seed] + this.pos += 2 + } + + key() { + for (;;) { + const key = this.crypto.randomBytes(32) + key[31] &= 127 + + let x = bufferToBigInt(key) + const y = _getY2(x, KEY_MOD) + + if (_isQuadraticResidue(y)) { + for (let i = 0; i < 3; i++) { + x = _getDoubleX(x, KEY_MOD) + } + + const key = bigIntToBuffer(x, 32, true) + this.string(key) + + return + } + } + } + + beginScope() { + this._scopes.push(this.pos) + this.pos += 2 + } + + endScope() { + const begin = this._scopes.pop() + + if (begin === undefined) { + throw new Error('endScope called without beginScope') + } + + const end = this.pos + const size = end - begin - 2 + + this.dv.setUint16(begin, size) + } + + async finish(secret: Uint8Array): Promise { + const padSize = 515 - this.pos + const unixTime = ~~(Date.now() / 1000) + + this.beginScope() + this.zero(padSize) + this.endScope() + + const hash = await this.crypto.hmacSha256(this.buf, secret) + const dv = dataViewFromBuffer(hash) + + const old = dv.getInt32(28, true) + dv.setInt32(28, old ^ unixTime, true) + + this.buf.set(hash, 11) + + return this.buf + } +} + +export async function generateFakeTlsHeader( + domain: Uint8Array, + secret: Uint8Array, + crypto: ICryptoProvider, +): Promise { + const writer = new TlsHelloWriter(crypto, 517, domain) + executeTlsOperations(writer) + + return writer.finish(secret) +} + +/** + * Fake TLS packet codec, used for some MTProxies. + * + * Must only be used inside {@link MtProxyTcpTransport} + * @internal + */ +export class FakeTlsPacketCodec implements IPacketCodec { + // protected _stream: Buffer = Buffer.alloc(0) + constructor(readonly _inner: IPacketCodec) {} + + private _isFirstTls = true + + setup?(crypto: ICryptoProvider, log: Logger): void { + this._inner.setup?.(crypto, log) + } + + private _tag!: Uint8Array + async tag(): Promise { + this._tag = await this._inner.tag() + return new Uint8Array(0) + } + + async encode(packet: Uint8Array, into: ISyncWritable): Promise { + const tmp = Bytes.alloc(packet.length) + await this._inner.encode(packet, tmp) + + while (tmp.available > 0) { + const header = new Uint8Array([0x17, 0x03, 0x03, 0x00, 0x00]) + + let packet + if (this._isFirstTls) { + this._isFirstTls = false + packet = concatBuffers([this._tag, tmp.readSync(MAX_TLS_PACKET_LENGTH - this._tag.length)]) + } else { + packet = tmp.readSync(MAX_TLS_PACKET_LENGTH) + } + + dataViewFromBuffer(header).setUint16(3, packet.length) + + into.writeSync(header.length).set(header) + into.writeSync(packet.length).set(packet) + into.disposeWriteSync() + } + } + + async decode(reader: Bytes, eof: boolean): Promise { + if (eof) return null + if (reader.available < 5) return null + + const header = reader.readSync(3) + if (header[0] !== 0x17 || header[1] !== 0x03 || header[2] !== 0x03) { + throw new Error('Invalid TLS header') + } + + const length = read.uint16be(reader) + if (length < reader.available - 5) { + reader.rewind(5) + return null + } + + const packet = reader.readSync(length) + const inner = await this._inner.decode(Bytes.from(packet), eof) + + if (!inner) { + reader.rewind(5 + length) + return null + } + + return inner + } + + reset(): void { + this._isFirstTls = true + } +} diff --git a/packages/core/src/network/mtproxy/index.ts b/packages/core/src/network/mtproxy/index.ts new file mode 100644 index 00000000..00d0cc21 --- /dev/null +++ b/packages/core/src/network/mtproxy/index.ts @@ -0,0 +1,184 @@ +import type { tl } from '@mtcute/tl' +import type { ITcpConnection, TcpEndpoint } from '@fuman/net' +import { Bytes, read, write } from '@fuman/io' +import { base64, hex } from '@fuman/utils' + +import type { IPacketCodec, ITelegramConnection, MtProxyInfo, TelegramTransport } from '../transports/index.js' +import { IntermediatePacketCodec, ObfuscatedPacketCodec, PaddedIntermediatePacketCodec } from '../transports/index.js' +import { type BasicDcOption, type ICryptoProvider, type Logger, buffersEqual, concatBuffers, dataViewFromBuffer } from '../../utils/index.js' +import { MtSecurityError, MtUnsupportedError } from '../../types/errors.js' + +import { FakeTlsPacketCodec, generateFakeTlsHeader } from './_fake-tls.js' + +/** + * MTProto proxy settings + */ +export interface MtProxySettings { + /** + * Host or IP of the proxy (e.g. `proxy.example.com`, `1.2.3.4`) + */ + host: string + + /** + * Port of the proxy (e.g. `8888`) + */ + port: number + + /** + * Secret of the proxy, optionally encoded either as hex or base64 + */ + secret: string | Uint8Array +} + +const MAX_DOMAIN_LENGTH = 182 // must be small enough not to overflow TLS-hello length +const TLS_START_SERVER = [ + new Uint8Array([0x16, 0x03, 0x03]), + new Uint8Array([0x14, 0x03, 0x03, 0x00, 0x01, 0x01, 0x17, 0x03, 0x03]), +] +const TLS_START_CLIENT = new Uint8Array([0x14, 0x03, 0x03, 0x00, 0x01, 0x01]) + +/** + * Base for a TCP transport that connects via an MTProxy + */ +export abstract class BaseMtProxyTransport implements TelegramTransport { + abstract _connectTcp(endpoint: TcpEndpoint): Promise + readonly _proxy: MtProxySettings + + private _rawSecret: Uint8Array + private _randomPadding = false + private _fakeTlsDomain: Uint8Array | null = null + private _crypto!: ICryptoProvider + private _log!: Logger + + setup(crypto: ICryptoProvider, log: Logger): void { + this._crypto = crypto + this._log = log.create('mtproxy') + } + + /** + * @param proxy Information about the proxy + */ + constructor(proxy: MtProxySettings) { + this._proxy = proxy + + // validate and parse secret + let secret: Uint8Array + + if (ArrayBuffer.isView(proxy.secret)) { + secret = proxy.secret + } else if (proxy.secret.match(/^[0-9a-f]+$/i)) { + secret = hex.decode(proxy.secret) + } else { + secret = base64.decode(proxy.secret, true) + } + + if (secret.length > 17 + MAX_DOMAIN_LENGTH) { + throw new MtSecurityError('Invalid secret: too long') + } + + if (secret.length < 16) { + throw new MtSecurityError('Invalid secret: too short') + } + + if (secret.length === 16) { + this._rawSecret = secret + } else if (secret.length === 17 && secret[0] === 0xDD) { + this._rawSecret = secret.slice(1) + this._randomPadding = true + } else if (secret.length >= 18 && secret[0] === 0xEE) { + this._rawSecret = secret.slice(1, 17) + this._fakeTlsDomain = secret.slice(17) + } else { + throw new MtUnsupportedError('Unsupported secret') + } + } + + getMtproxyInfo(): tl.RawInputClientProxy { + return { + _: 'inputClientProxy', + address: this._proxy.host, + port: this._proxy.port, + } + } + + packetCodec(dc: BasicDcOption): IPacketCodec { + const proxy: MtProxyInfo = { + dcId: dc.id, + media: Boolean(dc.mediaOnly), + test: Boolean(dc.testMode), + secret: this._rawSecret, + } + + if (!this._fakeTlsDomain) { + let inner: IPacketCodec + + if (this._randomPadding) { + inner = new PaddedIntermediatePacketCodec() + } else { + inner = new IntermediatePacketCodec() + } + + return new ObfuscatedPacketCodec(inner, proxy) + } + + return new FakeTlsPacketCodec( + new ObfuscatedPacketCodec(new PaddedIntermediatePacketCodec(), proxy), + ) + } + + async connect(): Promise { + const conn = await this._connectTcp({ + address: this._proxy.host, + port: this._proxy.port, + }) + + if (this._fakeTlsDomain) { + await this._handleConnectFakeTls(conn) + } + + return conn + } + + private async _handleConnectFakeTls(conn: ITcpConnection): Promise { + this._log.debug('performing initial faketls handshake') + const hello = await generateFakeTlsHeader(this._fakeTlsDomain!, this._rawSecret, this._crypto) + const helloRand = hello.slice(11, 11 + 32) + + await conn.write(hello) + + const resp = Bytes.alloc(0) + + for (const first of TLS_START_SERVER) { + const buf = await read.async.exactly(conn, first.length + 2) + write.bytes(resp, buf) + + if (!buffersEqual(buf.slice(0, first.length), first)) { + throw new MtSecurityError('Server hello is invalid') + } + + const skipSize = dataViewFromBuffer(buf).getUint16(first.length) + + write.bytes(resp, await read.async.exactly(conn, skipSize)) + } + + const respBuf = resp.result() + const respRand = respBuf.slice(11, 11 + 32) + const hash = await this._crypto.hmacSha256( + concatBuffers([ + helloRand, + respBuf.slice(0, 11), + new Uint8Array(32), + respBuf.slice(11 + 32), + ]), + this._rawSecret, + ) + + if (!buffersEqual(hash, respRand)) { + throw new MtSecurityError('Response hash is invalid') + } + + await conn.write(TLS_START_CLIENT) + + this._log.debug('faketls handshake done') + } +} diff --git a/packages/core/src/network/persistent-connection.ts b/packages/core/src/network/persistent-connection.ts index 44d2ea8d..6f008bce 100644 --- a/packages/core/src/network/persistent-connection.ts +++ b/packages/core/src/network/persistent-connection.ts @@ -30,18 +30,12 @@ export abstract class PersistentConnection extends EventEmitter { private _uid = nextConnectionUid++ readonly params: PersistentConnectionParams - // protected _transport!: ITelegramConnection private _sendOnceConnected: Uint8Array[] = [] private _codec: IPacketCodec private _fuman: FumanPersistentConnection // reconnection - private _lastError: Error | null = null - private _consequentFails = 0 - private _previousWait: number | null = null - private _reconnectionTimeout: timers.Timer | null = null - private _shouldReconnectImmediately = false protected _disconnectedManually = false // inactivity timeout @@ -64,19 +58,41 @@ export abstract class PersistentConnection extends EventEmitter { ) { super() this.params = params - this.log.prefix = `[UID ${this._uid}] ` - this._codec = this.params.transport.packetCodec() + this.params.transport.setup?.(this.params.crypto, log) + this._codec = this.params.transport.packetCodec(params.dc) this._codec.setup?.(this.params.crypto, this.log) this._onInactivityTimeout = this._onInactivityTimeout.bind(this) this._fuman = new FumanPersistentConnection({ - connect: dc => params.transport.connect(dc, params.testMode), + connect: (dc) => { + this._updateLogPrefix() + this.log.debug('connecting to %j', dc) + return params.transport.connect(dc, params.testMode) + }, onOpen: this._onOpen.bind(this), onClose: this._onClose.bind(this), onError: this._onError.bind(this), - onWait: wait => this.emit('wait', wait), + onWait: (wait) => { + this._updateLogPrefix() + this.log.debug('waiting for %d ms before reconnecting', wait) + this.emit('wait', wait) + }, }) + + this._updateLogPrefix() + } + + private _updateLogPrefix() { + const uidPrefix = `[UID ${this._uid}] ` + if (this._fuman.isConnected) { + const dc = this.params.dc + this.log.prefix = `${uidPrefix}[DC ${dc.id}:${dc.ipAddress}:${dc.port}] ` + } else if (this._fuman.isConnecting) { + this.log.prefix = `${uidPrefix}[connecting] ` + } else { + this.log.prefix = `${uidPrefix}[disconnected] ` + } } get isConnected(): boolean { @@ -86,7 +102,13 @@ export abstract class PersistentConnection extends EventEmitter { private _writer?: FramedWriter private async _onOpen(conn: ITelegramConnection) { - await conn.write(await this._codec.tag()) + this._updateLogPrefix() + this.log.debug('connected') + + const tag = await this._codec.tag() + if (tag) { + await conn.write(tag) + } const reader = new FramedReader(conn, this._codec) this._writer = new FramedWriter(conn, this._codec) @@ -116,181 +138,45 @@ export abstract class PersistentConnection extends EventEmitter { } private async _onClose() { + this.log.debug('connection closed') + this._updateLogPrefix() + this._writer = undefined this._codec.reset() this.onClosed() } private async _onError(err: Error) { - this._lastError = err + this._updateLogPrefix() this.onError(err) } async changeTransport(transport: TelegramTransport): Promise { await this._fuman.close() - this._codec = transport.packetCodec() + this._codec = transport.packetCodec(this.params.dc) this._codec.setup?.(this.params.crypto, this.log) - await this._fuman.changeTransport(() => transport.connect(this.params.dc, this.params.testMode)) + await this._fuman.changeTransport(dc => transport.connect(dc, this.params.testMode)) this._fuman.connect(this.params.dc) - // if (this._transport) { - // Promise.resolve(this._transport.close()).catch((err) => { - // this.log.warn('error closing previous transport: %e', err) - // }) - // } - - // this._transport = conn() - // this._transport.setup?.(this.params.crypto, this.log) - - // this._transport.on('ready', this.onTransportReady.bind(this)) - // this._transport.on('message', this.onMessage.bind(this)) - // this._transport.on('error', this.onTransportError.bind(this)) - // this._transport.on('close', this.onTransportClose.bind(this)) } - // onTransportReady(): void { - // // transport ready does not mean actual mtproto is ready - // if (this._sendOnceConnected.length) { - // const sendNext = () => { - // if (!this._sendOnceConnected.length) { - // this.onConnected() - - // return - // } - - // const data = this._sendOnceConnected.shift()! - // this._transport - // .send(data) - // .then(sendNext) - // .catch((err) => { - // this.log.error('error sending queued data: %e', err) - // this._sendOnceConnected.unshift(data) - // }) - // } - - // sendNext() - - // return - // } - - // this.onConnected() - // } - - // protected onConnectionUsable(): void { - // const isReconnection = this._consequentFails > 0 - - // // reset reconnection related state - // this._lastError = null - // this._consequentFails = 0 - // this._previousWait = null - // this._usable = true - // this.emit('usable', isReconnection) - // this._rescheduleInactivity() - // } - - // onTransportError(err: Error): void { - - // // transport is expected to emit `close` after `error` - // } - - // onTransportClose(): void { - // // transport closed because of inactivity - // // obviously we dont want to reconnect then - // if (this._inactive || this._disconnectedManually) return - - // if (this._shouldReconnectImmediately) { - // this._shouldReconnectImmediately = false - // this.connect() - - // return - // } - - // this._consequentFails += 1 - - // const wait = this.params.reconnectionStrategy( - // this.params, - // this._lastError, - // this._consequentFails, - // this._previousWait, - // ) - - // if (wait === false) { - // this.destroy().catch((err) => { - // this.log.warn('error destroying connection: %e', err) - // }) - - // return - // } - - // this._previousWait = wait - - // if (this._reconnectionTimeout != null) { - // timers.clearTimeout(this._reconnectionTimeout) - // } - // this._reconnectionTimeout = timers.setTimeout(() => { - // if (this._destroyed) return - // this._reconnectionTimeout = null - // this.connect() - // }, wait) - // } - connect(): void { this._fuman.connect(this.params.dc) this._inactive = false - // if (this.isConnected) { - // throw new MtcuteError('Connection is already opened!') - // } - // if (this._destroyed) { - // throw new MtcuteError('Connection is already destroyed!') - // } - - // if (this._reconnectionTimeout != null) { - // clearTimeout(this._reconnectionTimeout) - // this._reconnectionTimeout = null - // } - - // this._inactive = false - // this._disconnectedManually = false - // this._transport.connect(this.params.dc, this.params.testMode) } reconnect(): void { - // if (this._inactive) return - - // // if we are already connected - // if (this.isConnected) { - // this._shouldReconnectImmediately = true - // Promise.resolve(this._transport.close()).catch((err) => { - // this.log.error('error closing transport: %e', err) - // }) - - // return - // } - - // // if reconnection timeout is pending, it will be cancelled in connect() - // this.connect() this._fuman.reconnect(true) } async disconnectManual(): Promise { - // this._disconnectedManually = true - // await this._transport.close() await this._fuman.close() } async destroy(): Promise { - // if (this._reconnectionTimeout != null) { - // clearTimeout(this._reconnectionTimeout) - // } - // if (this._inactivityTimeout != null) { - // clearTimeout(this._inactivityTimeout) - // } - await this._fuman.close() - // this._transport.removeAllListeners() - // this._destroyed = true } protected _rescheduleInactivity(): void { diff --git a/packages/core/src/network/transports/abstract.ts b/packages/core/src/network/transports/abstract.ts index 6485fe47..cac84000 100644 --- a/packages/core/src/network/transports/abstract.ts +++ b/packages/core/src/network/transports/abstract.ts @@ -21,8 +21,9 @@ export interface ITelegramConnection extends IConnection { } export interface TelegramTransport { + setup?(crypto: ICryptoProvider, log: Logger): void connect: (dc: BasicDcOption, testMode: boolean) => Promise - packetCodec: () => IPacketCodec + packetCodec: (dc: BasicDcOption) => IPacketCodec } /** diff --git a/packages/core/src/network/transports/intermediate.ts b/packages/core/src/network/transports/intermediate.ts index efc83aee..f34febaa 100644 --- a/packages/core/src/network/transports/intermediate.ts +++ b/packages/core/src/network/transports/intermediate.ts @@ -33,7 +33,7 @@ export class IntermediatePacketCodec implements IPacketCodec { } if (reader.available < length) { - reader.unread(4) + reader.rewind(4) return null } diff --git a/packages/core/src/network/transports/obfuscated.ts b/packages/core/src/network/transports/obfuscated.ts index d9b9d427..4dd34229 100644 --- a/packages/core/src/network/transports/obfuscated.ts +++ b/packages/core/src/network/transports/obfuscated.ts @@ -27,7 +27,6 @@ export class ObfuscatedPacketCodec implements IPacketCodec { } constructor(inner: IPacketCodec, proxy?: MtProxyInfo) { - // super(inner) this._inner = inner this._proxy = proxy } @@ -99,19 +98,25 @@ export class ObfuscatedPacketCodec implements IPacketCodec { async encode(packet: Uint8Array, into: ISyncWritable): Promise { const temp = Bytes.alloc(packet.length) - await this._inner.encode(packet, into) + await this._inner.encode(packet, temp) write.bytes(into, this._encryptor!.process(temp.result())) } + private _decodeBuf = Bytes.alloc() async decode(reader: Bytes, eof: boolean): Promise { - const inner = await this._inner.decode(reader, eof) - if (!inner) return null + if (eof) return null - return this._decryptor!.process(inner) + if (reader.available > 0) { + const into = this._decodeBuf.writeSync(reader.available) + into.set(this._decryptor!.process(reader.readSync(reader.available))) + } + + return this._inner.decode(this._decodeBuf, eof) } reset(): void { this._inner.reset() + this._decodeBuf.reset() this._encryptor?.close?.() this._decryptor?.close?.() diff --git a/packages/core/src/utils/dcs.ts b/packages/core/src/utils/dcs.ts index c8928906..05502881 100644 --- a/packages/core/src/utils/dcs.ts +++ b/packages/core/src/utils/dcs.ts @@ -6,15 +6,18 @@ export interface BasicDcOption { id: number ipv6?: boolean mediaOnly?: boolean + testMode?: boolean } export function serializeBasicDcOption(dc: BasicDcOption): Uint8Array { const writer = TlBinaryWriter.manual(64) - const flags = (dc.ipv6 ? 1 : 0) | (dc.mediaOnly ? 2 : 0) + const flags = (dc.ipv6 ? 1 : 0) + | (dc.mediaOnly ? 2 : 0) + | (dc.testMode ? 4 : 0) writer.raw( new Uint8Array([ - 1, // version + 2, // version dc.id, flags, ]), @@ -30,7 +33,7 @@ export function parseBasicDcOption(data: Uint8Array): BasicDcOption | null { const reader = TlBinaryReader.manual(data) const [version, id, flags] = reader.raw(3) - if (version !== 1) return null + if (version !== 1 && version !== 2) return null const ipAddress = reader.string() const port = reader.int() @@ -41,6 +44,7 @@ export function parseBasicDcOption(data: Uint8Array): BasicDcOption | null { port, ipv6: (flags & 1) !== 0, mediaOnly: (flags & 2) !== 0, + testMode: version === 2 && (flags & 4) !== 0, } } @@ -84,12 +88,14 @@ export const defaultTestDc: DcOptions = { ipAddress: '149.154.167.40', port: 443, id: 2, + testMode: true, }, media: { ipAddress: '149.154.167.40', port: 443, id: 2, mediaOnly: true, + testMode: true, }, } @@ -99,6 +105,7 @@ export const defaultTestIpv6Dc: DcOptions = { port: 443, ipv6: true, id: 2, + testMode: true, }, media: { ipAddress: '2001:67c:4e8:f002::e', @@ -106,5 +113,6 @@ export const defaultTestIpv6Dc: DcOptions = { ipv6: true, id: 2, mediaOnly: true, + testMode: true, }, } diff --git a/packages/create-bot/package.json b/packages/create-bot/package.json index fd90e6ea..1293a480 100644 --- a/packages/create-bot/package.json +++ b/packages/create-bot/package.json @@ -1,7 +1,7 @@ { "name": "@mtcute/create-bot", "type": "module", - "version": "0.16.13", + "version": "0.17.0-fuman-alpha", "private": true, "description": "Bot starter kit for mtcute", "author": "alina sireneva ", diff --git a/packages/crypto-node/package.json b/packages/crypto-node/package.json index 849f20ac..6f351c59 100644 --- a/packages/crypto-node/package.json +++ b/packages/crypto-node/package.json @@ -1,30 +1,30 @@ { - "name": "@mtcute/crypto-node", - "type": "module", - "version": "0.16.9", - "private": true, - "description": "Native crypto implementation for NodeJS", - "author": "alina sireneva ", - "license": "MIT", - "sideEffects": false, - "exports": { - ".": "./src/index.ts", - "./native.js": "./src/native.cjs" - }, - "scripts": { - "build": "pnpm run -w build-package crypto-node", - "install": "node-gyp-build", - "rebuild": "node-gyp configure && node-gyp -j 16 rebuild", - "clean": "node-gyp clean" - }, - "keepScripts": [ - "install" - ], - "dependencies": { - "@mtcute/node": "workspace:^", - "node-gyp-build": "4.8.1" - }, - "devDependencies": { - "@mtcute/test": "workspace:^" - } + "name": "@mtcute/crypto-node", + "type": "module", + "version": "0.17.0-fuman-alpha", + "private": true, + "description": "Native crypto implementation for NodeJS", + "author": "alina sireneva ", + "license": "MIT", + "sideEffects": false, + "exports": { + ".": "./src/index.ts", + "./native.js": "./src/native.cjs" + }, + "scripts": { + "build": "pnpm run -w build-package crypto-node", + "install": "node-gyp-build", + "rebuild": "node-gyp configure && node-gyp -j 16 rebuild", + "clean": "node-gyp clean" + }, + "keepScripts": [ + "install" + ], + "dependencies": { + "@mtcute/node": "workspace:^", + "node-gyp-build": "4.8.1" + }, + "devDependencies": { + "@mtcute/test": "workspace:^" + } } diff --git a/packages/deno/package.json b/packages/deno/package.json index 67ea6845..79ff005b 100644 --- a/packages/deno/package.json +++ b/packages/deno/package.json @@ -1,31 +1,34 @@ { - "name": "@mtcute/deno", - "type": "module", - "version": "0.16.9", - "private": true, - "description": "Meta-package for Deno", - "author": "alina sireneva ", - "license": "MIT", - "sideEffects": false, - "exports": { - ".": "./src/index.ts", - "./utils.js": "./src/utils.ts", - "./methods.js": "./src/methods.ts" - }, - "main": "src/index.ts", - "scripts": { - "docs": "typedoc", - "build": "pnpm run -w build-package deno" - }, - "dependencies": { - "@db/sqlite": "npm:@jsr/db__sqlite@0.12.0", - "@mtcute/core": "workspace:^", - "@mtcute/html-parser": "workspace:^", - "@mtcute/markdown-parser": "workspace:^", - "@mtcute/wasm": "workspace:^", - "@std/io": "npm:@jsr/std__io@0.223.0" - }, - "devDependencies": { - "@mtcute/test": "workspace:^" - } + "name": "@mtcute/deno", + "type": "module", + "version": "0.17.0-fuman-alpha", + "private": true, + "description": "Meta-package for Deno", + "author": "alina sireneva ", + "license": "MIT", + "sideEffects": false, + "exports": { + ".": "./src/index.ts", + "./utils.js": "./src/utils.ts", + "./methods.js": "./src/methods.ts" + }, + "main": "src/index.ts", + "scripts": { + "docs": "typedoc", + "build": "pnpm run -w build-package deno" + }, + "dependencies": { + "@db/sqlite": "npm:@jsr/db__sqlite@0.12.0", + "@fuman/deno-net": "workspace:^", + "@fuman/net": "workspace:^", + "@fuman/io": "workspace:^", + "@mtcute/core": "workspace:^", + "@mtcute/html-parser": "workspace:^", + "@mtcute/markdown-parser": "workspace:^", + "@mtcute/wasm": "workspace:^", + "@std/io": "npm:@jsr/std__io@0.223.0" + }, + "devDependencies": { + "@mtcute/test": "workspace:^" + } } diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index 1b2031f3..2d557549 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -2,7 +2,8 @@ export * from './client.js' export * from './platform.js' export * from './sqlite/index.js' export * from './utils/crypto.js' -// export * from './utils/tcp.js' +export * from './utils/tcp.js' +export * from './utils/proxies.js' export * from './worker.js' export * from '@mtcute/core' export * from '@mtcute/html-parser' diff --git a/packages/deno/src/utils/proxies.ts b/packages/deno/src/utils/proxies.ts new file mode 100644 index 00000000..95ce291e --- /dev/null +++ b/packages/deno/src/utils/proxies.ts @@ -0,0 +1,74 @@ +import type { HttpProxySettings as FumanHttpProxySettings, ITcpConnection, SocksProxySettings, TcpEndpoint } from '@fuman/net' +import { performHttpProxyHandshake, performSocksHandshake } from '@fuman/net' +import { connectTcp, connectTls } from '@fuman/deno-net' +import { BaseMtProxyTransport, type ITelegramConnection, IntermediatePacketCodec, type TelegramTransport } from '@mtcute/core' +import type { BasicDcOption } from '@mtcute/core/utils.js' + +export type { SocksProxySettings } from '@fuman/net' +export { SocksProxyConnectionError, HttpProxyConnectionError } from '@fuman/net' +export type { MtProxySettings } from '@mtcute/core' + +export interface HttpProxySettings extends FumanHttpProxySettings { + /** + * Whether this is a HTTPS proxy (by default it is regular HTTP). + */ + tls?: boolean +} + +export class HttpProxyTcpTransport implements TelegramTransport { + constructor(readonly proxy: HttpProxySettings) {} + + async connect(dc: BasicDcOption): Promise { + let conn + if (this.proxy.tls) { + conn = await connectTls({ + address: this.proxy.host, + port: this.proxy.port, + }) + } else { + conn = await connectTcp({ + address: this.proxy.host, + port: this.proxy.port, + }) + } + + await performHttpProxyHandshake(conn, conn, this.proxy, { + address: dc.ipAddress, + port: dc.port, + }) + + return conn + } + + packetCodec(): IntermediatePacketCodec { + return new IntermediatePacketCodec() + } +} + +export class SocksProxyTcpTransport implements TelegramTransport { + constructor(readonly proxy: SocksProxySettings) {} + + async connect(dc: BasicDcOption): Promise { + const conn = await connectTcp({ + address: this.proxy.host, + port: this.proxy.port, + }) + + await performSocksHandshake(conn, conn, this.proxy, { + address: dc.ipAddress, + port: dc.port, + }) + + return conn + } + + packetCodec(): IntermediatePacketCodec { + return new IntermediatePacketCodec() + } +} + +export class MtProxyTcpTransport extends BaseMtProxyTransport { + async _connectTcp(endpoint: TcpEndpoint): Promise { + return connectTcp(endpoint) + } +} diff --git a/packages/deno/src/utils/tcp.ts b/packages/deno/src/utils/tcp.ts index a91c8b43..2923b852 100644 --- a/packages/deno/src/utils/tcp.ts +++ b/packages/deno/src/utils/tcp.ts @@ -1,146 +1,7 @@ -// import EventEmitter from 'node:events' +import { connectTcp } from '@fuman/deno-net' +import { IntermediatePacketCodec, type TelegramTransport } from '@mtcute/core' -// import { IntermediatePacketCodec, IPacketCodec, ITelegramTransport, MtcuteError, TransportState } from '@mtcute/core' -// import { BasicDcOption, ICryptoProvider, Logger } from '@mtcute/core/utils.js' - -// import { writeAll } from '@std/io/write-all' - -// /** -// * Base for TCP transports. -// * Subclasses must provide packet codec in `_packetCodec` property -// */ -// export abstract class BaseTcpTransport extends EventEmitter implements ITelegramConnection { -// protected _currentDc: BasicDcOption | null = null -// protected _state: TransportState = TransportState.Idle -// protected _socket: Deno.TcpConn | null = null - -// abstract _packetCodec: IPacketCodec -// protected _crypto!: ICryptoProvider -// protected log!: Logger - -// packetCodecInitialized = false - -// private _updateLogPrefix() { -// if (this._currentDc) { -// this.log.prefix = `[TCP:${this._currentDc.ipAddress}:${this._currentDc.port}] ` -// } else { -// this.log.prefix = '[TCP:disconnected] ' -// } -// } - -// setup(crypto: ICryptoProvider, log: Logger): void { -// this._crypto = crypto -// this.log = log.create('tcp') -// this._updateLogPrefix() -// } - -// state(): TransportState { -// return this._state -// } - -// currentDc(): BasicDcOption | null { -// return this._currentDc -// } - -// // eslint-disable-next-line unused-imports/no-unused-vars -// connect(dc: BasicDcOption, testMode: boolean): void { -// if (this._state !== TransportState.Idle) { -// throw new MtcuteError('Transport is not IDLE') -// } - -// if (!this.packetCodecInitialized) { -// this._packetCodec.setup?.(this._crypto, this.log) -// this._packetCodec.on('error', err => this.emit('error', err)) -// this._packetCodec.on('packet', buf => this.emit('message', buf)) -// this.packetCodecInitialized = true -// } - -// this._state = TransportState.Connecting -// this._currentDc = dc -// this._updateLogPrefix() - -// this.log.debug('connecting to %j', dc) - -// Deno.connect({ -// hostname: dc.ipAddress, -// port: dc.port, -// transport: 'tcp', -// }) -// .then(this.handleConnect.bind(this)) -// .catch((err) => { -// this.handleError(err) -// this.close() -// }) -// } - -// close(): void { -// if (this._state === TransportState.Idle) return -// this.log.info('connection closed') - -// this._state = TransportState.Idle - -// try { -// this._socket?.close() -// } catch (e) { -// if (!(e instanceof Deno.errors.BadResource)) { -// this.handleError(e) -// } -// } - -// this._socket = null -// this._currentDc = null -// this._packetCodec.reset() -// this.emit('close') -// } - -// handleError(error: unknown): void { -// this.log.error('error: %s', error) - -// if (this.listenerCount('error') > 0) { -// this.emit('error', error) -// } -// } - -// async handleConnect(socket: Deno.TcpConn): Promise { -// this._socket = socket -// this.log.info('connected') - -// try { -// const packet = await this._packetCodec.tag() - -// if (packet.length) { -// await writeAll(this._socket, packet) -// } - -// this._state = TransportState.Ready -// this.emit('ready') - -// const reader = this._socket.readable.getReader() - -// while (true) { -// const { done, value } = await reader.read() -// if (done) break - -// this._packetCodec.feed(value) -// } -// } catch (e) { -// this.handleError(e) -// } - -// this.close() -// } - -// async send(bytes: Uint8Array): Promise { -// const framed = await this._packetCodec.encode(bytes) - -// if (this._state !== TransportState.Ready) { -// throw new MtcuteError('Transport is not READY') -// } - -// await writeAll(this._socket!, framed) -// } -// } - -// export class TcpTransport extends BaseTcpTransport { -// _packetCodec: IntermediatePacketCodec = new IntermediatePacketCodec() -// } +export const TcpTransport: TelegramTransport = { + connect: dc => connectTcp({ address: dc.ipAddress, port: dc.port }), + packetCodec: () => new IntermediatePacketCodec(), +} diff --git a/packages/dispatcher/package.json b/packages/dispatcher/package.json index 5396f47b..b9176187 100644 --- a/packages/dispatcher/package.json +++ b/packages/dispatcher/package.json @@ -1,22 +1,22 @@ { - "name": "@mtcute/dispatcher", - "type": "module", - "version": "0.16.13", - "private": true, - "description": "Updates dispatcher and bot framework for @mtcute/client", - "author": "alina sireneva ", - "license": "MIT", - "sideEffects": false, - "exports": "./src/index.ts", - "scripts": { - "build": "pnpm run -w build-package dispatcher", - "gen-updates": "node ./scripts/generate.cjs" - }, - "dependencies": { - "@mtcute/core": "workspace:^", - "events": "3.2.0" - }, - "devDependencies": { - "@mtcute/test": "workspace:^" - } + "name": "@mtcute/dispatcher", + "type": "module", + "version": "0.17.0-fuman-alpha", + "private": true, + "description": "Updates dispatcher and bot framework for @mtcute/client", + "author": "alina sireneva ", + "license": "MIT", + "sideEffects": false, + "exports": "./src/index.ts", + "scripts": { + "build": "pnpm run -w build-package dispatcher", + "gen-updates": "node ./scripts/generate.cjs" + }, + "dependencies": { + "@mtcute/core": "workspace:^", + "events": "3.2.0" + }, + "devDependencies": { + "@mtcute/test": "workspace:^" + } } diff --git a/packages/file-id/package.json b/packages/file-id/package.json index b581b22d..b08a50f8 100644 --- a/packages/file-id/package.json +++ b/packages/file-id/package.json @@ -1,23 +1,23 @@ { - "name": "@mtcute/file-id", - "type": "module", - "version": "0.16.12", - "private": true, - "description": "Support for TDLib and Bot API file ID for mtcute", - "author": "alina sireneva ", - "license": "MIT", - "sideEffects": false, - "exports": { - ".": "./src/index.ts" - }, - "scripts": { - "build": "pnpm run -w build-package file-id" - }, - "dependencies": { - "@mtcute/tl-runtime": "workspace:^", - "long": "5.2.3" - }, - "devDependencies": { - "@mtcute/test": "workspace:^" - } + "name": "@mtcute/file-id", + "type": "module", + "version": "0.17.0-fuman-alpha", + "private": true, + "description": "Support for TDLib and Bot API file ID for mtcute", + "author": "alina sireneva ", + "license": "MIT", + "sideEffects": false, + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "build": "pnpm run -w build-package file-id" + }, + "dependencies": { + "@mtcute/tl-runtime": "workspace:^", + "long": "5.2.3" + }, + "devDependencies": { + "@mtcute/test": "workspace:^" + } } diff --git a/packages/html-parser/package.json b/packages/html-parser/package.json index 992dbcfd..c34e5869 100644 --- a/packages/html-parser/package.json +++ b/packages/html-parser/package.json @@ -1,19 +1,19 @@ { - "name": "@mtcute/html-parser", - "type": "module", - "version": "0.16.9", - "private": true, - "description": "HTML entities parser for mtcute", - "author": "alina sireneva ", - "license": "MIT", - "sideEffects": false, - "exports": "./src/index.ts", - "scripts": { - "build": "pnpm run -w build-package html-parser" - }, - "dependencies": { - "@mtcute/core": "workspace:^", - "htmlparser2": "^6.0.1", - "long": "5.2.3" - } + "name": "@mtcute/html-parser", + "type": "module", + "version": "0.17.0-fuman-alpha", + "private": true, + "description": "HTML entities parser for mtcute", + "author": "alina sireneva ", + "license": "MIT", + "sideEffects": false, + "exports": "./src/index.ts", + "scripts": { + "build": "pnpm run -w build-package html-parser" + }, + "dependencies": { + "@mtcute/core": "workspace:^", + "htmlparser2": "^6.0.1", + "long": "5.2.3" + } } diff --git a/packages/http-proxy/README.md b/packages/http-proxy/README.md deleted file mode 100644 index 3d475499..00000000 --- a/packages/http-proxy/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# @mtcute/http-proxy - -📖 [API Reference](https://ref.mtcute.dev/modules/_mtcute_http_proxy.html) - -HTTP(s) proxy transport for mtcute. - -## Usage - -```typescript -import { HttpProxyTcpTransport } from '@mtcute/socks-proxy' - -const tg = new TelegramClient({ - // ... - transport: () => new HttpProxyTcpTransport({ - host: 'localhost', - port: 1080, - }) -}) -``` diff --git a/packages/http-proxy/index.ts b/packages/http-proxy/index.ts deleted file mode 100644 index a97f01ad..00000000 --- a/packages/http-proxy/index.ts +++ /dev/null @@ -1,165 +0,0 @@ -// todo: move to fuman -// import { connect as connectTcp } from 'node:net' -// import type { SecureContextOptions } from 'node:tls' -// import { connect as connectTls } from 'node:tls' - -// import type { tl } from '@mtcute/node' -// import { BaseTcpTransport, IntermediatePacketCodec, MtcuteError, NodePlatform, TransportState } from '@mtcute/node' - -// /** -// * An error has occurred while connecting to an HTTP(s) proxy -// */ -// export class HttpProxyConnectionError extends Error { -// readonly proxy: HttpProxySettings - -// constructor(proxy: HttpProxySettings, message: string) { -// super(`Error while connecting to ${proxy.host}:${proxy.port}: ${message}`) -// this.proxy = proxy -// } -// } - -// /** -// * HTTP(s) proxy settings -// */ -// export interface HttpProxySettings { -// /** -// * Host or IP of the proxy (e.g. `proxy.example.com`, `1.2.3.4`) -// */ -// host: string - -// /** -// * Port of the proxy (e.g. `8888`) -// */ -// port: number - -// /** -// * Proxy authorization username, if needed -// */ -// user?: string - -// /** -// * Proxy authorization password, if needed -// */ -// password?: string - -// /** -// * Proxy connection headers, if needed -// */ -// headers?: Record - -// /** -// * Whether this is a HTTPS proxy (i.e. the client -// * should connect to the proxy server via TLS) -// */ -// tls?: boolean - -// /** -// * Additional TLS options, used if `tls = true`. -// * Can contain stuff like custom certificate, host, -// * or whatever. -// */ -// tlsOptions?: SecureContextOptions -// } - -// /** -// * TCP transport that connects via an HTTP(S) proxy. -// */ -// export abstract class BaseHttpProxyTcpTransport extends BaseTcpTransport { -// readonly _proxy: HttpProxySettings - -// constructor(proxy: HttpProxySettings) { -// super() -// this._proxy = proxy -// } - -// private _platform = new NodePlatform() - -// connect(dc: tl.RawDcOption): void { -// if (this._state !== TransportState.Idle) { -// throw new MtcuteError('Transport is not IDLE') -// } - -// if (!this.packetCodecInitialized) { -// this._packetCodec.on('error', err => this.emit('error', err)) -// this._packetCodec.on('packet', buf => this.emit('message', buf)) -// this.packetCodecInitialized = true -// } - -// this._state = TransportState.Connecting -// this._currentDc = dc - -// this._socket = this._proxy.tls -// ? connectTls(this._proxy.port, this._proxy.host, this._proxy.tlsOptions, this._onProxyConnected.bind(this)) -// : connectTcp(this._proxy.port, this._proxy.host, this._onProxyConnected.bind(this)) - -// this._socket.on('error', this.handleError.bind(this)) -// this._socket.on('close', this.close.bind(this)) -// } - -// private _onProxyConnected() { -// this.log.debug('[%s:%d] connected to proxy, sending CONNECT', this._proxy.host, this._proxy.port) - -// let ip = `${this._currentDc!.ipAddress}:${this._currentDc!.port}` -// if (this._currentDc!.ipv6) ip = `[${ip}]` - -// const headers = { -// ...(this._proxy.headers ?? {}), -// } -// headers.Host = ip - -// if (this._proxy.user) { -// let auth = this._proxy.user - -// if (this._proxy.password) { -// auth += `:${this._proxy.password}` -// } -// headers['Proxy-Authorization'] = `Basic ${this._platform.base64Encode(this._platform.utf8Encode(auth))}` -// } -// headers['Proxy-Connection'] = 'Keep-Alive' - -// const headersStr = Object.keys(headers) -// .map(k => `\r\n${k}: ${headers[k]}`) -// .join('') -// const packet = `CONNECT ${ip} HTTP/1.1${headersStr}\r\n\r\n` - -// this._socket!.write(packet) -// this._socket!.once('data', (msg) => { -// this.log.debug('[%s:%d] CONNECT resulted in: %s', this._proxy.host, this._proxy.port, msg) - -// const [proto, code, name] = msg.toString().split(' ') - -// if (!proto.match(/^HTTP\/1.[01]$/i)) { -// // wtf? -// this._socket!.emit( -// 'error', -// new HttpProxyConnectionError(this._proxy, `Server returned invalid protocol: ${proto}`), -// ) - -// return -// } - -// if (code[0] !== '2') { -// this._socket!.emit( -// 'error', -// new HttpProxyConnectionError(this._proxy, `Server returned error: ${code} ${name}`), -// ) - -// return -// } - -// // all ok, connection established, can now call handleConnect -// this._socket!.on('data', data => this._packetCodec.feed(data)) -// this.handleConnect() -// }) -// } -// } - -// /** -// * HTTP(s) TCP transport using an intermediate packet codec. -// * -// * Should be the one passed as `transport` to `TelegramClient` constructor -// * (unless you want to use a custom codec). -// */ -// export class HttpProxyTcpTransport extends BaseHttpProxyTcpTransport { -// _packetCodec: IntermediatePacketCodec = new IntermediatePacketCodec() -// } diff --git a/packages/http-proxy/package.json b/packages/http-proxy/package.json deleted file mode 100644 index 1ddf6596..00000000 --- a/packages/http-proxy/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "@mtcute/http-proxy", - "type": "module", - "version": "0.16.9", - "private": true, - "description": "HTTP(S) proxy support for mtcute", - "author": "alina sireneva ", - "license": "MIT", - "sideEffects": false, - "exports": "./index.ts", - "scripts": { - "docs": "typedoc", - "build": "pnpm run -w build-package http-proxy" - }, - "dependencies": { - "@mtcute/node": "workspace:^" - } -} diff --git a/packages/http-proxy/tsconfig.json b/packages/http-proxy/tsconfig.json deleted file mode 100644 index f149f686..00000000 --- a/packages/http-proxy/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "include": [ - "./index.ts" - ] -} diff --git a/packages/http-proxy/typedoc.cjs b/packages/http-proxy/typedoc.cjs deleted file mode 100644 index 7082ca6f..00000000 --- a/packages/http-proxy/typedoc.cjs +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - extends: ['../../.config/typedoc/config.base.cjs'], - entryPoints: ['./index.ts'], -} diff --git a/packages/i18n/package.json b/packages/i18n/package.json index d4e087f1..5b59963f 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,24 +1,24 @@ { - "name": "@mtcute/i18n", - "type": "module", - "version": "0.16.9", - "private": true, - "description": "I18n for mtcute", - "author": "alina sireneva ", - "license": "MIT", - "sideEffects": false, - "exports": "./src/index.ts", - "scripts": { - "build": "pnpm run -w build-package i18n" - }, - "devDependencies": { - "@mtcute/core": "workspace:^", - "@mtcute/dispatcher": "workspace:^" - }, - "jsrOnlyFields": { - "dependencies": { - "@mtcute/core": "workspace:^", - "@mtcute/dispatcher": "workspace:^" + "name": "@mtcute/i18n", + "type": "module", + "version": "0.17.0-fuman-alpha", + "private": true, + "description": "I18n for mtcute", + "author": "alina sireneva ", + "license": "MIT", + "sideEffects": false, + "exports": "./src/index.ts", + "scripts": { + "build": "pnpm run -w build-package i18n" + }, + "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 4ea88c77..c4f70f7e 100644 --- a/packages/markdown-parser/package.json +++ b/packages/markdown-parser/package.json @@ -1,18 +1,18 @@ { - "name": "@mtcute/markdown-parser", - "type": "module", - "version": "0.16.9", - "private": true, - "description": "Markdown entities parser for mtcute", - "author": "alina sireneva ", - "license": "MIT", - "sideEffects": false, - "exports": "./src/index.ts", - "scripts": { - "build": "pnpm run -w build-package markdown-parser" - }, - "dependencies": { - "@mtcute/core": "workspace:^", - "long": "5.2.3" - } + "name": "@mtcute/markdown-parser", + "type": "module", + "version": "0.17.0-fuman-alpha", + "private": true, + "description": "Markdown entities parser for mtcute", + "author": "alina sireneva ", + "license": "MIT", + "sideEffects": false, + "exports": "./src/index.ts", + "scripts": { + "build": "pnpm run -w build-package markdown-parser" + }, + "dependencies": { + "@mtcute/core": "workspace:^", + "long": "5.2.3" + } } diff --git a/packages/mtproxy/README.md b/packages/mtproxy/README.md deleted file mode 100644 index 16407331..00000000 --- a/packages/mtproxy/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# @mtcute/mtproxy - -📖 [API Reference](https://ref.mtcute.dev/modules/_mtcute_mtproxy.html) - -MTProto proxy (MTProxy) transport for mtcute. - -Supports all kinds of MTProto proxies, including obfuscated and fake TLS. - -## Usage - -```typescript -import { MtProxyTcpTransport } from '@mtcute/mtproxy' - -const tg = new TelegramClient({ - // ... - transport: () => new MtProxyTcpTransport({ - host: 'localhost', - port: 443, - secret: 'secret' - }) -}) -``` diff --git a/packages/mtproxy/fake-tls.ts b/packages/mtproxy/fake-tls.ts deleted file mode 100644 index df0618ba..00000000 --- a/packages/mtproxy/fake-tls.ts +++ /dev/null @@ -1,352 +0,0 @@ -// /* eslint-disable no-restricted-globals */ -// todo fixme -// import type { IPacketCodec } from '@mtcute/node' -// import { WrappedCodec } from '@mtcute/node' -// import type { ICryptoProvider } from '@mtcute/node/utils.js' -// import { bigIntModInv, bigIntModPow, bigIntToBuffer, bufferToBigInt } from '@mtcute/node/utils.js' - -// const MAX_TLS_PACKET_LENGTH = 2878 -// const TLS_FIRST_PREFIX = Buffer.from('140303000101', 'hex') - -// // ref: https://github.com/tdlib/td/blob/master/td/mtproto/TlsInit.cpp -// const KEY_MOD = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEDn -// // 2^255 - 19 -// const QUAD_RES_MOD = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEDn -// // (mod - 1) / 2 = 2^254 - 10 -// const QUAD_RES_POW = 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6n - -// function _getY2(x: bigint, mod: bigint): bigint { -// // returns y = x^3 + x^2 * 486662 + x -// let y = x -// y = (y + 486662n) % mod -// y = (y * x) % mod -// y = (y + 1n) % mod -// y = (y * x) % mod - -// return y -// } - -// function _getDoubleX(x: bigint, mod: bigint): bigint { -// // returns x_2 = (x^2 - 1)^2/(4*y^2) -// let denominator = _getY2(x, mod) -// denominator = (denominator * 4n) % mod - -// let numerator = (x * x) % mod -// numerator = (numerator - 1n) % mod -// numerator = (numerator * numerator) % mod - -// denominator = bigIntModInv(denominator, mod) -// numerator = (numerator * denominator) % mod - -// return numerator -// } - -// function _isQuadraticResidue(a: bigint): boolean { -// const r = bigIntModPow(a, QUAD_RES_POW, QUAD_RES_MOD) - -// return r === 1n -// } - -// interface TlsOperationHandler { -// string: (buf: Buffer) => void -// zero: (size: number) => void -// random: (size: number) => void -// domain: () => void -// grease: (seed: number) => void -// beginScope: () => void -// endScope: () => void -// key: () => void -// } - -// function executeTlsOperations(h: TlsOperationHandler): void { -// h.string(Buffer.from('1603010200010001fc0303', 'hex')) -// h.zero(32) -// h.string(Buffer.from('20', 'hex')) -// h.random(32) -// h.string(Buffer.from('0020', 'hex')) -// h.grease(0) -// h.string(Buffer.from('130113021303c02bc02fc02cc030cca9cca8c013c014009c009d002f003501000193', 'hex')) -// h.grease(2) -// h.string(Buffer.from('00000000', 'hex')) -// h.beginScope() -// h.beginScope() -// h.string(Buffer.from('00', 'hex')) -// h.beginScope() -// h.domain() -// h.endScope() -// h.endScope() -// h.endScope() -// h.string(Buffer.from('00170000ff01000100000a000a0008', 'hex')) -// h.grease(4) -// h.string( -// Buffer.from( -// '001d00170018000b00020100002300000010000e000c02683208687474702f312e31000500050100000000000d0012001004030804040105030805050108060601001200000033002b0029', -// 'hex', -// ), -// ) -// h.grease(4) -// h.string(Buffer.from('000100001d0020', 'hex')) -// h.key() -// h.string(Buffer.from('002d00020101002b000b0a', 'hex')) -// h.grease(6) -// h.string(Buffer.from('0304030303020301001b0003020002', 'hex')) -// h.grease(3) -// h.string(Buffer.from('0001000015', 'hex')) -// } - -// // i dont know why is this needed, since it is always padded to 517 bytes -// // this was in tdlib sources, so whatever. not used here though, and works just fine -// // class TlsHelloCounter implements TlsOperationHandler { -// // size = 0 -// // -// // private _domain: Buffer -// // -// // constructor(domain: Buffer) { -// // this._domain = domain -// // } -// // -// // string(buf: Buffer) { -// // this.size += buf.length -// // } -// // -// // random(size: number) { -// // this.size += size -// // } -// // -// // zero(size: number) { -// // this.size += size -// // } -// // -// // domain() { -// // this.size += this._domain.length -// // } -// // -// // grease() { -// // this.size += 2 -// // } -// // -// // key() { -// // this.size += 32 -// // } -// // -// // beginScope() { -// // this.size += 2 -// // } -// // -// // endScope() { -// // // no-op, since this does not affect size -// // } -// // -// // finish(): number { -// // const zeroPad = 515 - this.size -// // this.beginScope() -// // this.zero(zeroPad) -// // this.endScope() -// // -// // return this.size -// // } -// // } - -// function initGrease(crypto: ICryptoProvider, size: number): Buffer { -// const buf = crypto.randomBytes(size) - -// for (let i = 0; i < size; i++) { -// buf[i] = (buf[i] & 0xF0) + 0x0A -// } - -// for (let i = 1; i < size; i += 2) { -// if (buf[i] === buf[i - 1]) { -// buf[i] ^= 0x10 -// } -// } - -// return Buffer.from(buf) -// } - -// class TlsHelloWriter implements TlsOperationHandler { -// buf: Buffer -// pos = 0 - -// private _domain: Buffer -// private _grease -// private _scopes: number[] = [] - -// constructor( -// readonly crypto: ICryptoProvider, -// size: number, -// domain: Buffer, -// ) { -// this._domain = domain -// this.buf = Buffer.allocUnsafe(size) -// this._grease = initGrease(this.crypto, 7) -// } - -// string(buf: Buffer) { -// buf.copy(this.buf, this.pos) -// this.pos += buf.length -// } - -// random(size: number) { -// this.string(Buffer.from(this.crypto.randomBytes(size))) -// } - -// zero(size: number) { -// this.string(Buffer.alloc(size, 0)) -// } - -// domain() { -// this.string(this._domain) -// } - -// grease(seed: number) { -// this.buf[this.pos] = this.buf[this.pos + 1] = this._grease[seed] -// this.pos += 2 -// } - -// key() { -// for (;;) { -// const key = this.crypto.randomBytes(32) -// key[31] &= 127 - -// let x = bufferToBigInt(key) -// const y = _getY2(x, KEY_MOD) - -// if (_isQuadraticResidue(y)) { -// for (let i = 0; i < 3; i++) { -// x = _getDoubleX(x, KEY_MOD) -// } - -// const key = bigIntToBuffer(x, 32, true) -// this.string(Buffer.from(key)) - -// return -// } -// } -// } - -// beginScope() { -// this._scopes.push(this.pos) -// this.pos += 2 -// } - -// endScope() { -// const begin = this._scopes.pop() - -// if (begin === undefined) { -// throw new Error('endScope called without beginScope') -// } - -// const end = this.pos -// const size = end - begin - 2 - -// this.buf.writeUInt16BE(size, begin) -// } - -// async finish(secret: Buffer): Promise { -// const padSize = 515 - this.pos -// const unixTime = ~~(Date.now() / 1000) - -// this.beginScope() -// this.zero(padSize) -// this.endScope() - -// const hash = Buffer.from(await this.crypto.hmacSha256(this.buf, secret)) - -// const old = hash.readInt32LE(28) -// hash.writeInt32LE(old ^ unixTime, 28) - -// hash.copy(this.buf, 11) - -// return this.buf -// } -// } - -// /** @internal */ -// export async function generateFakeTlsHeader(domain: string, secret: Buffer, crypto: ICryptoProvider): Promise { -// const domainBuf = Buffer.from(domain) - -// const writer = new TlsHelloWriter(crypto, 517, domainBuf) -// executeTlsOperations(writer) - -// return writer.finish(secret) -// } - -// /** -// * Fake TLS packet codec, used for some MTProxies. -// * -// * Must only be used inside {@link MtProxyTcpTransport} -// * @internal -// */ -// export class FakeTlsPacketCodec extends WrappedCodec implements IPacketCodec { -// protected _stream: Buffer = Buffer.alloc(0) - -// private _header!: Buffer -// private _isFirstTls = true - -// async tag(): Promise { -// this._header = Buffer.from(await this._inner.tag()) - -// return Buffer.alloc(0) -// } - -// private _encodeTls(packet: Buffer): Buffer { -// if (this._header.length) { -// packet = Buffer.concat([this._header, packet]) -// this._header = Buffer.alloc(0) -// } - -// const header = Buffer.from([0x17, 0x03, 0x03, 0x00, 0x00]) -// header.writeUInt16BE(packet.length, 3) - -// if (this._isFirstTls) { -// this._isFirstTls = false - -// return Buffer.concat([TLS_FIRST_PREFIX, header, packet]) -// } - -// return Buffer.concat([header, packet]) -// } - -// async encode(packet: Buffer): Promise { -// packet = Buffer.from(await this._inner.encode(packet)) - -// if (packet.length + this._header.length > MAX_TLS_PACKET_LENGTH) { -// const ret: Buffer[] = [] - -// while (packet.length) { -// const buf = packet.slice(0, MAX_TLS_PACKET_LENGTH - this._header.length) -// packet = packet.slice(buf.length) -// ret.push(this._encodeTls(buf)) -// } - -// return Buffer.concat(ret) -// } - -// return this._encodeTls(packet) -// } - -// feed(data: Buffer): void { -// this._stream = Buffer.concat([this._stream, data]) - -// for (;;) { -// if (this._stream.length < 5) return - -// if (!(this._stream[0] === 0x17 && this._stream[1] === 0x03 && this._stream[2] === 0x03)) { -// this.emit('error', new Error('Invalid TLS header')) - -// return -// } - -// const length = this._stream.readUInt16BE(3) -// if (length < this._stream.length - 5) return - -// this._inner.feed(this._stream.slice(5, length + 5)) -// this._stream = this._stream.slice(length + 5) -// } -// } - -// reset(): void { -// this._stream = Buffer.alloc(0) -// this._isFirstTls = true -// } -// } diff --git a/packages/mtproxy/index.ts b/packages/mtproxy/index.ts deleted file mode 100644 index 99d70912..00000000 --- a/packages/mtproxy/index.ts +++ /dev/null @@ -1,250 +0,0 @@ -// /* eslint-disable no-restricted-globals */ -// todo fixme - -// import { connect } from 'node:net' - -// import type { -// IPacketCodec, -// tl, -// } from '@mtcute/node' -// import { -// BaseTcpTransport, -// IntermediatePacketCodec, -// MtSecurityError, -// MtUnsupportedError, -// MtcuteError, -// ObfuscatedPacketCodec, -// PaddedIntermediatePacketCodec, -// TransportState, -// } from '@mtcute/node' -// import { buffersEqual } from '@mtcute/node/utils.js' - -// import { FakeTlsPacketCodec, generateFakeTlsHeader } from './fake-tls.js' - -// /** -// * MTProto proxy settings -// */ -// export interface MtProxySettings { -// /** -// * Host or IP of the proxy (e.g. `proxy.example.com`, `1.2.3.4`) -// */ -// host: string - -// /** -// * Port of the proxy (e.g. `8888`) -// */ -// port: number - -// /** -// * Secret of the proxy, optionally encoded either as hex or base64 -// */ -// secret: string | Buffer -// } - -// const MAX_DOMAIN_LENGTH = 182 // must be small enough not to overflow TLS-hello length -// const TLS_START = [Buffer.from('160303', 'hex'), Buffer.from('140303000101170303', 'hex')] - -// /** -// * TCP transport that connects via an MTProxy -// */ -// export class MtProxyTcpTransport extends BaseTcpTransport { -// readonly _proxy: MtProxySettings - -// private _rawSecret: Buffer -// private _randomPadding = false -// private _fakeTlsDomain: string | null = null - -// /** -// * @param proxy Information about the proxy -// */ -// constructor(proxy: MtProxySettings) { -// super() - -// this._proxy = proxy - -// // validate and parse secret -// let secret: Buffer - -// if (Buffer.isBuffer(proxy.secret)) { -// secret = proxy.secret -// } else if (proxy.secret.match(/^[0-9a-f]+$/i)) { -// secret = Buffer.from(proxy.secret, 'hex') -// } else { -// secret = Buffer.from(proxy.secret, 'base64url') -// } - -// if (secret.length > 17 + MAX_DOMAIN_LENGTH) { -// throw new MtSecurityError('Invalid secret: too long') -// } - -// if (secret.length < 16) { -// throw new MtSecurityError('Invalid secret: too short') -// } - -// if (secret.length === 16) { -// this._rawSecret = secret -// } else if (secret.length === 17 && secret[0] === 0xDD) { -// this._rawSecret = secret.slice(1) -// this._randomPadding = true -// } else if (secret.length >= 18 && secret[0] === 0xEE) { -// this._rawSecret = secret.slice(1, 17) -// this._fakeTlsDomain = secret.slice(17).toString() -// } else { -// throw new MtUnsupportedError('Unsupported secret') -// } -// } - -// getMtproxyInfo(): tl.RawInputClientProxy { -// return { -// _: 'inputClientProxy', -// address: this._proxy.host, -// port: this._proxy.port, -// } -// } - -// _packetCodec!: IPacketCodec - -// connect(dc: tl.RawDcOption, testMode: boolean): void { -// if (this._state !== TransportState.Idle) { -// throw new MtcuteError('Transport is not IDLE') -// } - -// if (this._packetCodec && this._currentDc?.id !== dc.id) { -// // dc changed, thus the codec's init will change too -// // clean up to avoid memory leaks -// this.packetCodecInitialized = false -// this._packetCodec.reset() -// this._packetCodec.removeAllListeners() -// delete (this as Partial)._packetCodec -// } - -// if (!this._packetCodec) { -// const proxy = { -// dcId: dc.id, -// media: dc.mediaOnly!, -// test: testMode, -// secret: this._rawSecret, -// } - -// if (!this._fakeTlsDomain) { -// let inner: IPacketCodec - -// if (this._randomPadding) { -// inner = new PaddedIntermediatePacketCodec() -// } else { -// inner = new IntermediatePacketCodec() -// } - -// this._packetCodec = new ObfuscatedPacketCodec(inner, proxy) -// } else { -// this._packetCodec = new FakeTlsPacketCodec( -// new ObfuscatedPacketCodec(new PaddedIntermediatePacketCodec(), proxy), -// ) -// } - -// this._packetCodec.setup?.(this._crypto, this.log) -// this._packetCodec.on('error', err => this.emit('error', err)) -// this._packetCodec.on('packet', buf => this.emit('message', buf)) -// } - -// this._state = TransportState.Connecting -// this._currentDc = dc - -// if (this._fakeTlsDomain) { -// this._socket = connect( -// this._proxy.port, -// this._proxy.host, -// // MTQ-55 -// // eslint-disable-next-line ts/no-misused-promises -// this._handleConnectFakeTls.bind(this), -// ) -// } else { -// this._socket = connect( -// this._proxy.port, -// this._proxy.host, -// // MTQ-55 - -// this.handleConnect.bind(this), -// ) -// this._socket.on('data', data => this._packetCodec.feed(data)) -// } -// this._socket.on('error', this.handleError.bind(this)) -// this._socket.on('close', this.close.bind(this)) -// } - -// private async _handleConnectFakeTls(): Promise { -// try { -// const hello = await generateFakeTlsHeader(this._fakeTlsDomain!, this._rawSecret, this._crypto) -// const helloRand = hello.slice(11, 11 + 32) - -// let serverHelloBuffer: Buffer | null = null - -// const checkHelloResponse = async (buf: Buffer): Promise => { -// if (serverHelloBuffer) { -// buf = Buffer.concat([serverHelloBuffer, buf]) -// } - -// const resp = buf - -// for (const first of TLS_START) { -// if (buf.length < first.length + 2) { -// throw new MtSecurityError('Server hello is too short') -// } - -// if (!buffersEqual(buf.slice(0, first.length), first)) { -// throw new MtSecurityError('Server hello is invalid') -// } -// buf = buf.slice(first.length) - -// const skipSize = buf.readUInt16BE() -// buf = buf.slice(2) - -// if (buf.length < skipSize) { -// // likely got split into multiple packets -// if (serverHelloBuffer) { -// throw new MtSecurityError('Server hello is too short') -// } - -// serverHelloBuffer = resp - -// return false -// } - -// buf = buf.slice(skipSize) -// } - -// const respRand = resp.slice(11, 11 + 32) -// const hash = await this._crypto.hmacSha256( -// Buffer.concat([helloRand, resp.slice(0, 11), Buffer.alloc(32, 0), resp.slice(11 + 32)]), -// this._rawSecret, -// ) - -// if (!buffersEqual(hash, respRand)) { -// throw new MtSecurityError('Response hash is invalid') -// } - -// return true -// } - -// const packetHandler = (buf: Buffer): void => { -// checkHelloResponse(buf) -// .then((done) => { -// if (!done) return - -// this._socket!.off('data', packetHandler) -// this._socket!.on('data', (data) => { -// this._packetCodec.feed(data) -// }) - -// return this.handleConnect() -// }) -// .catch(err => this._socket!.emit('error', err)) -// } - -// this._socket!.write(hello) -// this._socket!.on('data', packetHandler) -// } catch (e) { -// this._socket!.emit('error', e) -// } -// } -// } diff --git a/packages/mtproxy/package.json b/packages/mtproxy/package.json deleted file mode 100644 index d9985671..00000000 --- a/packages/mtproxy/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "@mtcute/mtproxy", - "type": "module", - "version": "0.16.9", - "private": true, - "description": "MTProto proxy (MTProxy) support for mtcute", - "author": "alina sireneva ", - "license": "MIT", - "sideEffects": false, - "exports": "./index.ts", - "scripts": { - "docs": "typedoc", - "build": "pnpm run -w build-package mtproxy" - }, - "dependencies": { - "@mtcute/node": "workspace:^" - } -} diff --git a/packages/mtproxy/tsconfig.json b/packages/mtproxy/tsconfig.json deleted file mode 100644 index da82202a..00000000 --- a/packages/mtproxy/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "include": [ - "./index.ts", - "./fake-tls.ts" - ] -} diff --git a/packages/mtproxy/typedoc.cjs b/packages/mtproxy/typedoc.cjs deleted file mode 100644 index 7082ca6f..00000000 --- a/packages/mtproxy/typedoc.cjs +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - extends: ['../../.config/typedoc/config.base.cjs'], - entryPoints: ['./index.ts'], -} diff --git a/packages/node/package.json b/packages/node/package.json index bcf9d515..01f62945 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -1,7 +1,7 @@ { "name": "@mtcute/node", "type": "module", - "version": "0.16.13", + "version": "0.17.0-fuman-alpha", "private": true, "description": "Meta-package for Node.js", "author": "alina sireneva ", @@ -21,6 +21,7 @@ "@mtcute/html-parser": "workspace:^", "@mtcute/markdown-parser": "workspace:^", "@mtcute/wasm": "workspace:^", + "@fuman/net": "workspace:^", "@fuman/node-net": "workspace:^", "better-sqlite3": "11.3.0" }, diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index bb9a6310..d801a59d 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -2,6 +2,7 @@ export * from './client.js' export * from './common-internals-node/platform.js' export * from './sqlite/index.js' export * from './utils/tcp.js' +export * from './utils/proxies.js' export * from './worker.js' export * from '@mtcute/core' export * from '@mtcute/html-parser' diff --git a/packages/node/src/utils/proxies.ts b/packages/node/src/utils/proxies.ts new file mode 100644 index 00000000..519408f7 --- /dev/null +++ b/packages/node/src/utils/proxies.ts @@ -0,0 +1,84 @@ +import type { SecureContextOptions } from 'node:tls' + +import type { HttpProxySettings as FumanHttpProxySettings, ITcpConnection, SocksProxySettings, TcpEndpoint } from '@fuman/net' +import { performHttpProxyHandshake, performSocksHandshake } from '@fuman/net' +import { connectTcp, connectTls } from '@fuman/node-net' +import { BaseMtProxyTransport, type ITelegramConnection, IntermediatePacketCodec, type TelegramTransport } from '@mtcute/core' +import type { BasicDcOption } from '@mtcute/core/utils.js' + +export type { SocksProxySettings } from '@fuman/net' +export { SocksProxyConnectionError, HttpProxyConnectionError } from '@fuman/net' +export type { MtProxySettings } from '@mtcute/core' + +export interface HttpProxySettings extends FumanHttpProxySettings { + /** + * Whether this is a HTTPS proxy (by default it is regular HTTP). + */ + tls?: boolean + + /** + * Additional TLS options, used if `tls = true`. + * Can contain stuff like custom certificate, host, + * or whatever. + */ + tlsOptions?: SecureContextOptions +} + +export class HttpProxyTcpTransport implements TelegramTransport { + constructor(readonly proxy: HttpProxySettings) {} + + async connect(dc: BasicDcOption): Promise { + let conn + if (this.proxy.tls) { + conn = await connectTls({ + address: this.proxy.host, + port: this.proxy.port, + extraOptions: this.proxy.tlsOptions, + }) + } else { + conn = await connectTcp({ + address: this.proxy.host, + port: this.proxy.port, + }) + } + + await performHttpProxyHandshake(conn, conn, this.proxy, { + address: dc.ipAddress, + port: dc.port, + }) + + return conn + } + + packetCodec(): IntermediatePacketCodec { + return new IntermediatePacketCodec() + } +} + +export class SocksProxyTcpTransport implements TelegramTransport { + constructor(readonly proxy: SocksProxySettings) {} + + async connect(dc: BasicDcOption): Promise { + const conn = await connectTcp({ + address: this.proxy.host, + port: this.proxy.port, + }) + + await performSocksHandshake(conn, conn, this.proxy, { + address: dc.ipAddress, + port: dc.port, + }) + + return conn + } + + packetCodec(): IntermediatePacketCodec { + return new IntermediatePacketCodec() + } +} + +export class MtProxyTcpTransport extends BaseMtProxyTransport { + async _connectTcp(endpoint: TcpEndpoint): Promise { + return connectTcp(endpoint) + } +} diff --git a/packages/socks-proxy/README.md b/packages/socks-proxy/README.md deleted file mode 100644 index a883e3bc..00000000 --- a/packages/socks-proxy/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# @mtcute/socks-proxy - -📖 [API Reference](https://ref.mtcute.dev/modules/_mtcute_socks_proxy.html) - -Socks4/5 proxy transport for mtcute. - -## Usage - -```typescript -import { SocksTcpTransport } from '@mtcute/socks-proxy' - -const tg = new TelegramClient({ - // ... - transport: () => new SocksTcpTransport({ - host: 'localhost', - port: 1080, - }) -}) -``` diff --git a/packages/socks-proxy/index.ts b/packages/socks-proxy/index.ts deleted file mode 100644 index 5cc286e1..00000000 --- a/packages/socks-proxy/index.ts +++ /dev/null @@ -1,420 +0,0 @@ -// todo: move to fuman -// import { connect } from 'node:net' - -// // @ts-expect-error no typings -// import { normalize } from 'ip6' -// import type { tl } from '@mtcute/node' -// import { BaseTcpTransport, IntermediatePacketCodec, MtArgumentError, NodePlatform, TransportState, assertNever } from '@mtcute/node' -// import { dataViewFromBuffer } from '@mtcute/node/utils.js' - -// const p = new NodePlatform() - -// /** -// * An error has occurred while connecting to an SOCKS proxy -// */ -// export class SocksProxyConnectionError extends Error { -// readonly proxy: SocksProxySettings - -// constructor(proxy: SocksProxySettings, message: string) { -// super(`Error while connecting to ${proxy.host}:${proxy.port}: ${message}`) -// this.proxy = proxy -// } -// } - -// /** -// * Settings for a SOCKS4/5 proxy -// */ -// export interface SocksProxySettings { -// /** -// * Host or IP of the proxy (e.g. `proxy.example.com`, `1.2.3.4`) -// */ -// host: string - -// /** -// * Port of the proxy (e.g. `8888`) -// */ -// port: number - -// /** -// * Proxy authorization username, if needed -// */ -// user?: string - -// /** -// * Proxy authorization password, if needed -// */ -// password?: string - -// /** -// * Version of the SOCKS proxy (4 or 5) -// * -// * @default `5` -// */ -// version?: 4 | 5 -// } - -// function writeIpv4(ip: string, buf: Uint8Array, offset: number): void { -// const parts = ip.split('.') - -// if (parts.length !== 4) { -// throw new MtArgumentError('Invalid IPv4 address') -// } -// for (let i = 0; i < 4; i++) { -// const n = Number.parseInt(parts[i]) - -// if (Number.isNaN(n) || n < 0 || n > 255) { -// throw new MtArgumentError('Invalid IPv4 address') -// } - -// buf[offset + i] = n -// } -// } - -// function buildSocks4ConnectRequest(ip: string, port: number, username = ''): Uint8Array { -// const userId = p.utf8Encode(username) -// const buf = new Uint8Array(9 + userId.length) - -// buf[0] = 0x04 // VER -// buf[1] = 0x01 // CMD = establish a TCP/IP stream connection -// dataViewFromBuffer(buf).setUint16(2, port, false) -// writeIpv4(ip, buf, 4) // DSTIP -// buf.set(userId, 8) -// buf[8 + userId.length] = 0x00 // ID (null-termination) - -// return buf -// } - -// function buildSocks5Greeting(authAvailable: boolean): Uint8Array { -// const buf = new Uint8Array(authAvailable ? 4 : 3) - -// buf[0] = 0x05 // VER - -// if (authAvailable) { -// buf[1] = 0x02 // NAUTH -// buf[2] = 0x00 // AUTH[0] = No authentication -// buf[3] = 0x02 // AUTH[1] = Username/password -// } else { -// buf[1] = 0x01 // NAUTH -// buf[2] = 0x00 // AUTH[0] = No authentication -// } - -// return buf -// } - -// function buildSocks5Auth(username: string, password: string) { -// const usernameBuf = p.utf8Encode(username) -// const passwordBuf = p.utf8Encode(password) - -// if (usernameBuf.length > 255) { -// throw new MtArgumentError(`Too long username (${usernameBuf.length} > 255)`) -// } -// if (passwordBuf.length > 255) { -// throw new MtArgumentError(`Too long password (${passwordBuf.length} > 255)`) -// } - -// const buf = new Uint8Array(3 + usernameBuf.length + passwordBuf.length) -// buf[0] = 0x01 // VER of auth -// buf[1] = usernameBuf.length -// buf.set(usernameBuf, 2) -// buf[2 + usernameBuf.length] = passwordBuf.length -// buf.set(passwordBuf, 3 + usernameBuf.length) - -// return buf -// } - -// function writeIpv6(ip: string, buf: Uint8Array, offset: number): void { -// // eslint-disable-next-line ts/no-unsafe-call -// ip = normalize(ip) as string -// const parts = ip.split(':') - -// if (parts.length !== 8) { -// throw new MtArgumentError('Invalid IPv6 address') -// } - -// const dv = dataViewFromBuffer(buf) - -// for (let i = 0, j = offset; i < 8; i++, j += 2) { -// const n = Number.parseInt(parts[i]) - -// if (Number.isNaN(n) || n < 0 || n > 0xFFFF) { -// throw new MtArgumentError('Invalid IPv6 address') -// } - -// dv.setUint16(j, n, false) -// } -// } - -// function buildSocks5Connect(ip: string, port: number, ipv6 = false): Uint8Array { -// const buf = new Uint8Array(ipv6 ? 22 : 10) -// const dv = dataViewFromBuffer(buf) - -// buf[0] = 0x05 // VER -// buf[1] = 0x01 // CMD = establish a TCP/IP stream connection -// buf[2] = 0x00 // RSV - -// if (ipv6) { -// buf[3] = 0x04 // TYPE = IPv6 -// writeIpv6(ip, buf, 4) // ADDR -// dv.setUint16(20, port, false) -// } else { -// buf[3] = 0x01 // TYPE = IPv4 -// writeIpv4(ip, buf, 4) // ADDR -// dv.setUint16(8, port, false) -// } - -// return buf -// } - -// const SOCKS4_ERRORS: Record = { -// 91: 'Request rejected or failed', -// 92: 'Request failed because client is not running identd', -// 93: "Request failed because client's identd could not confirm the user ID in the request", -// } - -// const SOCKS5_ERRORS: Record = { -// 1: 'General failure', -// 2: 'Connection not allowed by ruleset', -// 3: 'Network unreachable', -// 4: 'Host unreachable', -// 5: 'Connection refused by destination host', -// 6: 'TTL expired', -// 7: 'Command not supported / protocol error', -// 8: 'Address type not supported', -// } - -// /** -// * TCP transport that connects via a SOCKS4/5 proxy. -// */ -// export abstract class BaseSocksTcpTransport extends BaseTcpTransport { -// readonly _proxy: SocksProxySettings - -// constructor(proxy: SocksProxySettings) { -// super() - -// if (proxy.version != null && proxy.version !== 4 && proxy.version !== 5) { -// throw new SocksProxyConnectionError( -// proxy, -// `Invalid SOCKS version: ${proxy.version}`, -// ) -// } - -// this._proxy = proxy -// } - -// connect(dc: tl.RawDcOption): void { -// if (this._state !== TransportState.Idle) { -// throw new MtArgumentError('Transport is not IDLE') -// } - -// if (!this.packetCodecInitialized) { -// this._packetCodec.on('error', err => this.emit('error', err)) -// this._packetCodec.on('packet', buf => this.emit('message', buf)) -// this.packetCodecInitialized = true -// } - -// this._state = TransportState.Connecting -// this._currentDc = dc - -// this._socket = connect(this._proxy.port, this._proxy.host, this._onProxyConnected.bind(this)) - -// this._socket.on('error', this.handleError.bind(this)) -// this._socket.on('close', this.close.bind(this)) -// } - -// private _onProxyConnected() { -// let packetHandler: (msg: Uint8Array) => void - -// if (this._proxy.version === 4) { -// packetHandler = (msg) => { -// if (msg[0] !== 0x04) { -// // VER, must be 4 -// this._socket!.emit( -// 'error', -// new SocksProxyConnectionError(this._proxy, `Server returned version ${msg[0]}`), -// ) - -// return -// } -// const code = msg[1] - -// this.log.debug('[%s:%d] CONNECT returned code %d', this._proxy.host, this._proxy.port, code) - -// if (code === 0x5A) { -// this._socket!.off('data', packetHandler) -// this._socket!.on('data', data => this._packetCodec.feed(data)) -// this.handleConnect() -// } else { -// const msg -// = code in SOCKS4_ERRORS ? SOCKS4_ERRORS[code] : `Unknown error code: 0x${code.toString(16)}` -// this._socket!.emit('error', new SocksProxyConnectionError(this._proxy, msg)) -// } -// } - -// this.log.debug('[%s:%d] connected to proxy, sending CONNECT', this._proxy.host, this._proxy.port) - -// try { -// this._socket!.write( -// buildSocks4ConnectRequest(this._currentDc!.ipAddress, this._currentDc!.port, this._proxy.user), -// ) -// } catch (e) { -// this._socket!.emit('error', e) -// } -// } else { -// let state: 'greeting' | 'auth' | 'connect' = 'greeting' - -// const sendConnect = () => { -// this.log.debug('[%s:%d] sending CONNECT', this._proxy.host, this._proxy.port) - -// try { -// this._socket!.write( -// buildSocks5Connect(this._currentDc!.ipAddress, this._currentDc!.port, this._currentDc!.ipv6), -// ) -// state = 'connect' -// } catch (e) { -// this._socket!.emit('error', e) -// } -// } - -// packetHandler = (msg) => { -// switch (state) { -// case 'greeting': { -// if (msg[0] !== 0x05) { -// // VER, must be 5 -// this._socket!.emit( -// 'error', -// new SocksProxyConnectionError(this._proxy, `Server returned version ${msg[0]}`), -// ) - -// return -// } - -// const chosen = msg[1] - -// this.log.debug( -// '[%s:%d] GREETING returned auth method %d', -// this._proxy.host, -// this._proxy.port, -// chosen, -// ) - -// switch (chosen) { -// case 0x00: -// // "No authentication" -// sendConnect() -// break -// case 0x02: -// // Username/password -// if (!this._proxy.user || !this._proxy.password) { -// // should not happen -// this._socket!.emit( -// 'error', -// new SocksProxyConnectionError( -// this._proxy, -// 'Authentication is required, but not provided', -// ), -// ) -// break -// } - -// try { -// this._socket!.write(buildSocks5Auth(this._proxy.user, this._proxy.password)) -// state = 'auth' -// } catch (e) { -// this._socket!.emit('error', e) -// } -// break -// case 0xFF: -// default: -// // "no acceptable methods were offered" -// this._socket!.emit( -// 'error', -// new SocksProxyConnectionError( -// this._proxy, -// 'Authentication is required, but not provided/supported', -// ), -// ) -// break -// } - -// break -// } -// case 'auth': -// if (msg[0] !== 0x01) { -// // VER of auth, must be 1 -// this._socket!.emit( -// 'error', -// new SocksProxyConnectionError(this._proxy, `Server returned version ${msg[0]}`), -// ) - -// return -// } - -// this.log.debug('[%s:%d] AUTH returned code %d', this._proxy.host, this._proxy.port, msg[1]) - -// if (msg[1] === 0x00) { -// // success -// sendConnect() -// } else { -// this._socket!.emit( -// 'error', -// new SocksProxyConnectionError(this._proxy, 'Authentication failure'), -// ) -// } -// break - -// case 'connect': { -// if (msg[0] !== 0x05) { -// // VER, must be 5 -// this._socket!.emit( -// 'error', -// new SocksProxyConnectionError(this._proxy, `Server returned version ${msg[0]}`), -// ) - -// return -// } - -// const code = msg[1] - -// this.log.debug('[%s:%d] CONNECT returned code %d', this._proxy.host, this._proxy.port, code) - -// if (code === 0x00) { -// // Request granted -// this._socket!.off('data', packetHandler) -// this._socket!.on('data', data => this._packetCodec.feed(data)) -// this.handleConnect() -// } else { -// const msg -// = code in SOCKS5_ERRORS -// ? SOCKS5_ERRORS[code] -// : `Unknown error code: 0x${code.toString(16)}` -// this._socket!.emit('error', new SocksProxyConnectionError(this._proxy, msg)) -// } -// break -// } -// default: -// assertNever(state) -// } -// } - -// this.log.debug('[%s:%d] connected to proxy, sending GREETING', this._proxy.host, this._proxy.port) - -// try { -// this._socket!.write(buildSocks5Greeting(Boolean(this._proxy.user && this._proxy.password))) -// } catch (e) { -// this._socket!.emit('error', e) -// } -// } - -// this._socket!.on('data', packetHandler) -// } -// } - -// /** -// * Socks TCP transport using an intermediate packet codec. -// * -// * Should be the one passed as `transport` to `TelegramClient` constructor -// * (unless you want to use a custom codec). -// */ -// export class SocksTcpTransport extends BaseSocksTcpTransport { -// _packetCodec: IntermediatePacketCodec = new IntermediatePacketCodec() -// } diff --git a/packages/socks-proxy/package.json b/packages/socks-proxy/package.json deleted file mode 100644 index 55cc233d..00000000 --- a/packages/socks-proxy/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "@mtcute/socks-proxy", - "type": "module", - "version": "0.16.9", - "private": true, - "description": "SOCKS4/5 proxy support for mtcute", - "author": "alina sireneva ", - "license": "MIT", - "sideEffects": false, - "exports": "./index.ts", - "scripts": { - "docs": "typedoc", - "build": "pnpm run -w build-package socks-proxy" - }, - "dependencies": { - "@mtcute/node": "workspace:^", - "ip6": "0.2.7" - } -} diff --git a/packages/socks-proxy/tsconfig.json b/packages/socks-proxy/tsconfig.json deleted file mode 100644 index f149f686..00000000 --- a/packages/socks-proxy/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "include": [ - "./index.ts" - ] -} diff --git a/packages/socks-proxy/typedoc.cjs b/packages/socks-proxy/typedoc.cjs deleted file mode 100644 index 7082ca6f..00000000 --- a/packages/socks-proxy/typedoc.cjs +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - extends: ['../../.config/typedoc/config.base.cjs'], - entryPoints: ['./index.ts'], -} diff --git a/packages/test/package.json b/packages/test/package.json index 216980da..9e30c076 100644 --- a/packages/test/package.json +++ b/packages/test/package.json @@ -1,38 +1,38 @@ { - "name": "@mtcute/test", - "type": "module", - "version": "0.16.13", - "private": true, - "description": "Test utilities for mtcute", - "author": "alina sireneva ", - "license": "MIT", - "sideEffects": false, - "exports": "./src/index.ts", - "scripts": { - "build": "pnpm run -w build-package test" - }, - "peerDependencies": { - "@mtcute/core": "workspace:^", - "@mtcute/node": "workspace:^", - "@mtcute/tl": "workspace:*", - "@mtcute/web": "workspace:^", - "vitest": "*" - }, - "peerDependenciesMeta": { - "@mtcute/node": { - "optional": true + "name": "@mtcute/test", + "type": "module", + "version": "0.17.0-fuman-alpha", + "private": true, + "description": "Test utilities for mtcute", + "author": "alina sireneva ", + "license": "MIT", + "sideEffects": false, + "exports": "./src/index.ts", + "scripts": { + "build": "pnpm run -w build-package test" }, - "@mtcute/web": { - "optional": true + "peerDependencies": { + "@mtcute/core": "workspace:^", + "@mtcute/node": "workspace:^", + "@mtcute/tl": "workspace:*", + "@mtcute/web": "workspace:^", + "vitest": "*" + }, + "peerDependenciesMeta": { + "@mtcute/node": { + "optional": true + }, + "@mtcute/web": { + "optional": true + } + }, + "dependencies": { + "long": "5.2.3" + }, + "devDependencies": { + "@mtcute/tl-utils": "workspace:^" + }, + "browser": { + "./src/platform.js": "./src/platform.web.js" } - }, - "dependencies": { - "long": "5.2.3" - }, - "devDependencies": { - "@mtcute/tl-utils": "workspace:^" - }, - "browser": { - "./src/platform.js": "./src/platform.web.js" - } } diff --git a/packages/tl-runtime/package.json b/packages/tl-runtime/package.json index 46792edf..ff7b9e99 100644 --- a/packages/tl-runtime/package.json +++ b/packages/tl-runtime/package.json @@ -1,20 +1,20 @@ { - "name": "@mtcute/tl-runtime", - "type": "module", - "version": "0.16.9", - "private": true, - "description": "Runtime for TL", - "author": "alina sireneva ", - "license": "MIT", - "sideEffects": false, - "exports": { - ".": "./src/index.ts" - }, - "scripts": { - "docs": "typedoc", - "build": "pnpm run -w build-package tl-runtime" - }, - "dependencies": { - "long": "5.2.3" - } + "name": "@mtcute/tl-runtime", + "type": "module", + "version": "0.17.0-fuman-alpha", + "private": true, + "description": "Runtime for TL", + "author": "alina sireneva ", + "license": "MIT", + "sideEffects": false, + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "docs": "typedoc", + "build": "pnpm run -w build-package tl-runtime" + }, + "dependencies": { + "long": "5.2.3" + } } diff --git a/packages/tl-utils/package.json b/packages/tl-utils/package.json index a965d742..3e120ea9 100644 --- a/packages/tl-utils/package.json +++ b/packages/tl-utils/package.json @@ -1,22 +1,22 @@ { - "name": "@mtcute/tl-utils", - "type": "module", - "version": "0.16.9", - "private": true, - "description": "Utils for working with TL schema", - "author": "alina sireneva ", - "license": "MIT", - "sideEffects": false, - "exports": { - ".": "./src/index.ts", - "./json.js": "./src/json/index.ts" - }, - "scripts": { - "docs": "typedoc", - "build": "pnpm run -w build-package tl-utils" - }, - "dependencies": { - "@mtcute/tl-runtime": "workspace:^", - "crc-32": "1.2.0" - } + "name": "@mtcute/tl-utils", + "type": "module", + "version": "0.17.0-fuman-alpha", + "private": true, + "description": "Utils for working with TL schema", + "author": "alina sireneva ", + "license": "MIT", + "sideEffects": false, + "exports": { + ".": "./src/index.ts", + "./json.js": "./src/json/index.ts" + }, + "scripts": { + "docs": "typedoc", + "build": "pnpm run -w build-package tl-utils" + }, + "dependencies": { + "@mtcute/tl-runtime": "workspace:^", + "crc-32": "1.2.0" + } } diff --git a/packages/wasm/package.json b/packages/wasm/package.json index 4ae04e24..0d011121 100644 --- a/packages/wasm/package.json +++ b/packages/wasm/package.json @@ -1,7 +1,7 @@ { "name": "@mtcute/wasm", "type": "module", - "version": "0.16.11", + "version": "0.17.0-fuman-alpha", "private": true, "description": "WASM implementation of common algorithms used in Telegram", "author": "alina sireneva ", diff --git a/packages/web/package.json b/packages/web/package.json index 2edb218f..61a4c5ce 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,7 +1,7 @@ { "name": "@mtcute/web", "type": "module", - "version": "0.16.13", + "version": "0.17.0-fuman-alpha", "private": true, "description": "Meta-package for the web platform", "author": "alina sireneva ", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4cc6eb76..51e4f8f9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -101,6 +101,15 @@ importers: packages/bun: dependencies: + '@fuman/bun-net': + specifier: workspace:^ + version: link:../../private/fuman/packages/bun-net + '@fuman/io': + specifier: workspace:^ + version: link:../../private/fuman/packages/io + '@fuman/net': + specifier: workspace:^ + version: link:../../private/fuman/packages/net '@mtcute/core': specifier: workspace:^ version: link:../core @@ -217,6 +226,15 @@ importers: '@db/sqlite': specifier: npm:@jsr/db__sqlite@0.12.0 version: '@jsr/db__sqlite@0.12.0' + '@fuman/deno-net': + specifier: workspace:^ + version: link:../../private/fuman/packages/deno-net + '@fuman/io': + specifier: workspace:^ + version: link:../../private/fuman/packages/io + '@fuman/net': + specifier: workspace:^ + version: link:../../private/fuman/packages/net '@mtcute/core': specifier: workspace:^ version: link:../core @@ -275,12 +293,6 @@ importers: specifier: 5.2.3 version: 5.2.3 - packages/http-proxy: - dependencies: - '@mtcute/node': - specifier: workspace:^ - version: link:../node - packages/i18n: devDependencies: '@mtcute/core': @@ -299,14 +311,11 @@ importers: specifier: 5.2.3 version: 5.2.3 - packages/mtproxy: - dependencies: - '@mtcute/node': - specifier: workspace:^ - version: link:../node - packages/node: dependencies: + '@fuman/net': + specifier: workspace:^ + version: link:../../private/fuman/packages/net '@fuman/node-net': specifier: workspace:^ version: link:../../private/fuman/packages/node-net @@ -333,15 +342,6 @@ importers: specifier: 7.6.4 version: 7.6.4 - packages/socks-proxy: - dependencies: - '@mtcute/node': - specifier: workspace:^ - version: link:../node - ip6: - specifier: 0.2.7 - version: 0.2.7 - packages/test: dependencies: '@mtcute/core': @@ -2778,10 +2778,6 @@ packages: resolution: {integrity: sha512-B2LafrnnhbRzCWfAdOXisUzL89Kg8cVJlYmhqoi3flSiV/TveO+nsXwgKr9h9PIo+J1hz7nBSk6gegRIMBBf7g==} engines: {node: '>=14.18.0'} - ip6@0.2.7: - resolution: {integrity: sha512-zEzGsxn4Uw33TByv0DdX/RRh+VsGfEctOp7CvJq/b4JEjY9OvPB58dsMYiEwIVLsIWHZSJPn3XG8mP9Qv3TG3g==} - hasBin: true - is-alphabetical@1.0.4: resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} @@ -6680,8 +6676,6 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 6.2.0 - ip6@0.2.7: {} - is-alphabetical@1.0.4: {} is-alphanumerical@1.0.4: