chore!: migrated proxies to fuman
breaking: http-proxy, socks-proxy and mtproxy packages are deprecated, proxified implementations are available in runtime-specific packages
This commit is contained in:
parent
d512da6831
commit
baef78403e
52 changed files with 1083 additions and 2080 deletions
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "mtcute-workspace",
|
"name": "mtcute-workspace",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.16.13",
|
"version": "0.17.0-fuman-alpha",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@9.0.6",
|
"packageManager": "pnpm@9.0.6",
|
||||||
"description": "Type-safe library for MTProto (Telegram API) for browser and NodeJS",
|
"description": "Type-safe library for MTProto (Telegram API) for browser and NodeJS",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@mtcute/bun",
|
"name": "@mtcute/bun",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.16.9",
|
"version": "0.17.0-fuman-alpha",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Meta-package for Bun",
|
"description": "Meta-package for Bun",
|
||||||
"author": "alina sireneva <alina@tei.su>",
|
"author": "alina sireneva <alina@tei.su>",
|
||||||
|
@ -20,7 +20,10 @@
|
||||||
"@mtcute/core": "workspace:^",
|
"@mtcute/core": "workspace:^",
|
||||||
"@mtcute/html-parser": "workspace:^",
|
"@mtcute/html-parser": "workspace:^",
|
||||||
"@mtcute/markdown-parser": "workspace:^",
|
"@mtcute/markdown-parser": "workspace:^",
|
||||||
"@mtcute/wasm": "workspace:^"
|
"@mtcute/wasm": "workspace:^",
|
||||||
|
"@fuman/bun-net": "workspace:^",
|
||||||
|
"@fuman/net": "workspace:^",
|
||||||
|
"@fuman/io": "workspace:^"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@mtcute/test": "workspace:^"
|
"@mtcute/test": "workspace:^"
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { downloadAsNodeStream } from './methods/download-node-stream.js'
|
||||||
import { BunPlatform } from './platform.js'
|
import { BunPlatform } from './platform.js'
|
||||||
import { SqliteStorage } from './sqlite/index.js'
|
import { SqliteStorage } from './sqlite/index.js'
|
||||||
import { BunCryptoProvider } from './utils/crypto.js'
|
import { BunCryptoProvider } from './utils/crypto.js'
|
||||||
// import { TcpTransport } from './utils/tcp.js'
|
import { TcpTransport } from './utils/tcp.js'
|
||||||
|
|
||||||
export type { TelegramClientOptions }
|
export type { TelegramClientOptions }
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ export class BaseTelegramClient extends BaseTelegramClientBase {
|
||||||
|
|
||||||
super({
|
super({
|
||||||
crypto: new BunCryptoProvider(),
|
crypto: new BunCryptoProvider(),
|
||||||
transport: {} as any, // todo
|
transport: TcpTransport,
|
||||||
...opts,
|
...opts,
|
||||||
storage:
|
storage:
|
||||||
typeof opts.storage === 'string'
|
typeof opts.storage === 'string'
|
||||||
|
|
74
packages/bun/src/utils/proxies.ts
Normal file
74
packages/bun/src/utils/proxies.ts
Normal file
|
@ -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<ITelegramConnection> {
|
||||||
|
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<ITelegramConnection> {
|
||||||
|
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<ITcpConnection> {
|
||||||
|
return connectTcp(endpoint)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,152 +1,7 @@
|
||||||
// todo
|
import { connectTcp } from '@fuman/bun-net'
|
||||||
// import EventEmitter from 'node:events'
|
import { IntermediatePacketCodec, type TelegramTransport } from '@mtcute/core'
|
||||||
|
|
||||||
// import { IntermediatePacketCodec, IPacketCodec, ITelegramConnection, MtcuteError } from '@mtcute/core'
|
export const TcpTransport: TelegramTransport = {
|
||||||
// import { BasicDcOption, ICryptoProvider, Logger } from '@mtcute/core/utils.js'
|
connect: dc => connectTcp({ address: dc.ipAddress, port: dc.port }),
|
||||||
|
packetCodec: () => new IntermediatePacketCodec(),
|
||||||
// /**
|
}
|
||||||
// * 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<void> {
|
|
||||||
// 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()
|
|
||||||
// }
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@mtcute/convert",
|
"name": "@mtcute/convert",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.16.9",
|
"version": "0.17.0-fuman-alpha",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Cross-library session conversion utilities",
|
"description": "Cross-library session conversion utilities",
|
||||||
"author": "alina sireneva <alina@tei.su>",
|
"author": "alina sireneva <alina@tei.su>",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@mtcute/core",
|
"name": "@mtcute/core",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.16.13",
|
"version": "0.17.0-fuman-alpha",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Type-safe library for MTProto (Telegram API)",
|
"description": "Type-safe library for MTProto (Telegram API)",
|
||||||
"author": "alina sireneva <alina@tei.su>",
|
"author": "alina sireneva <alina@tei.su>",
|
||||||
|
|
|
@ -8,7 +8,6 @@ import type { CurrentUserInfo } from '../storage/service/current-user.js'
|
||||||
|
|
||||||
export interface StringSessionData {
|
export interface StringSessionData {
|
||||||
version: number
|
version: number
|
||||||
testMode: boolean
|
|
||||||
primaryDcs: DcOptions
|
primaryDcs: DcOptions
|
||||||
self?: CurrentUserInfo | null
|
self?: CurrentUserInfo | null
|
||||||
authKey: Uint8Array
|
authKey: Uint8Array
|
||||||
|
@ -54,10 +53,6 @@ export function writeStringSession(data: StringSessionData): string {
|
||||||
flags |= 1
|
flags |= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.testMode) {
|
|
||||||
flags |= 2
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.uint8View[0] = version
|
writer.uint8View[0] = version
|
||||||
writer.pos += 1
|
writer.pos += 1
|
||||||
|
|
||||||
|
@ -104,7 +99,7 @@ export function readStringSession(data: string): StringSessionData {
|
||||||
|
|
||||||
const flags = reader.int()
|
const flags = reader.int()
|
||||||
const hasSelf = flags & 1
|
const hasSelf = flags & 1
|
||||||
const testMode = Boolean(flags & 2)
|
const testModeOld = Boolean(flags & 2)
|
||||||
const hasMedia = version >= 2 && Boolean(flags & 4)
|
const hasMedia = version >= 2 && Boolean(flags & 4)
|
||||||
|
|
||||||
let primaryDc: BasicDcOption
|
let primaryDc: BasicDcOption
|
||||||
|
@ -135,6 +130,13 @@ export function readStringSession(data: string): StringSessionData {
|
||||||
throw new Error('unreachable')
|
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
|
let self: CurrentUserInfo | null = null
|
||||||
|
|
||||||
if (hasSelf) {
|
if (hasSelf) {
|
||||||
|
@ -154,7 +156,6 @@ export function readStringSession(data: string): StringSessionData {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
version,
|
version,
|
||||||
testMode,
|
|
||||||
primaryDcs: {
|
primaryDcs: {
|
||||||
main: primaryDc,
|
main: primaryDc,
|
||||||
media: primaryMediaDc,
|
media: primaryMediaDc,
|
||||||
|
|
|
@ -7,6 +7,6 @@ export type {
|
||||||
RpcCallMiddlewareContext,
|
RpcCallMiddlewareContext,
|
||||||
RpcCallOptions,
|
RpcCallOptions,
|
||||||
} from './network-manager.js'
|
} from './network-manager.js'
|
||||||
// export * from './reconnection.js'
|
export * from './mtproxy/index.js'
|
||||||
export * from './session-connection.js'
|
export * from './session-connection.js'
|
||||||
export * from './transports/index.js'
|
export * from './transports/index.js'
|
||||||
|
|
300
packages/core/src/network/mtproxy/_fake-tls.ts
Normal file
300
packages/core/src/network/mtproxy/_fake-tls.ts
Normal file
|
@ -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<Uint8Array> {
|
||||||
|
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<Uint8Array> {
|
||||||
|
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<Uint8Array> {
|
||||||
|
this._tag = await this._inner.tag()
|
||||||
|
return new Uint8Array(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
async encode(packet: Uint8Array, into: ISyncWritable): Promise<void> {
|
||||||
|
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<Uint8Array | null> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
184
packages/core/src/network/mtproxy/index.ts
Normal file
184
packages/core/src/network/mtproxy/index.ts
Normal file
|
@ -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<ITcpConnection>
|
||||||
|
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<ITelegramConnection> {
|
||||||
|
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<void> {
|
||||||
|
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')
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,18 +30,12 @@ export abstract class PersistentConnection extends EventEmitter {
|
||||||
private _uid = nextConnectionUid++
|
private _uid = nextConnectionUid++
|
||||||
|
|
||||||
readonly params: PersistentConnectionParams
|
readonly params: PersistentConnectionParams
|
||||||
// protected _transport!: ITelegramConnection
|
|
||||||
|
|
||||||
private _sendOnceConnected: Uint8Array[] = []
|
private _sendOnceConnected: Uint8Array[] = []
|
||||||
private _codec: IPacketCodec
|
private _codec: IPacketCodec
|
||||||
private _fuman: FumanPersistentConnection<BasicDcOption, ITelegramConnection>
|
private _fuman: FumanPersistentConnection<BasicDcOption, ITelegramConnection>
|
||||||
|
|
||||||
// reconnection
|
// 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
|
protected _disconnectedManually = false
|
||||||
|
|
||||||
// inactivity timeout
|
// inactivity timeout
|
||||||
|
@ -64,19 +58,41 @@ export abstract class PersistentConnection extends EventEmitter {
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.params = params
|
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._codec.setup?.(this.params.crypto, this.log)
|
||||||
|
|
||||||
this._onInactivityTimeout = this._onInactivityTimeout.bind(this)
|
this._onInactivityTimeout = this._onInactivityTimeout.bind(this)
|
||||||
this._fuman = new FumanPersistentConnection({
|
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),
|
onOpen: this._onOpen.bind(this),
|
||||||
onClose: this._onClose.bind(this),
|
onClose: this._onClose.bind(this),
|
||||||
onError: this._onError.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 {
|
get isConnected(): boolean {
|
||||||
|
@ -86,7 +102,13 @@ export abstract class PersistentConnection extends EventEmitter {
|
||||||
private _writer?: FramedWriter
|
private _writer?: FramedWriter
|
||||||
|
|
||||||
private async _onOpen(conn: ITelegramConnection) {
|
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)
|
const reader = new FramedReader(conn, this._codec)
|
||||||
this._writer = new FramedWriter(conn, this._codec)
|
this._writer = new FramedWriter(conn, this._codec)
|
||||||
|
@ -116,181 +138,45 @@ export abstract class PersistentConnection extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _onClose() {
|
private async _onClose() {
|
||||||
|
this.log.debug('connection closed')
|
||||||
|
this._updateLogPrefix()
|
||||||
|
|
||||||
this._writer = undefined
|
this._writer = undefined
|
||||||
this._codec.reset()
|
this._codec.reset()
|
||||||
this.onClosed()
|
this.onClosed()
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _onError(err: Error) {
|
private async _onError(err: Error) {
|
||||||
this._lastError = err
|
this._updateLogPrefix()
|
||||||
this.onError(err)
|
this.onError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
async changeTransport(transport: TelegramTransport): Promise<void> {
|
async changeTransport(transport: TelegramTransport): Promise<void> {
|
||||||
await this._fuman.close()
|
await this._fuman.close()
|
||||||
|
|
||||||
this._codec = transport.packetCodec()
|
this._codec = transport.packetCodec(this.params.dc)
|
||||||
this._codec.setup?.(this.params.crypto, this.log)
|
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)
|
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 {
|
connect(): void {
|
||||||
this._fuman.connect(this.params.dc)
|
this._fuman.connect(this.params.dc)
|
||||||
|
|
||||||
this._inactive = false
|
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 {
|
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)
|
this._fuman.reconnect(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
async disconnectManual(): Promise<void> {
|
async disconnectManual(): Promise<void> {
|
||||||
// this._disconnectedManually = true
|
|
||||||
// await this._transport.close()
|
|
||||||
await this._fuman.close()
|
await this._fuman.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
async destroy(): Promise<void> {
|
async destroy(): Promise<void> {
|
||||||
// if (this._reconnectionTimeout != null) {
|
|
||||||
// clearTimeout(this._reconnectionTimeout)
|
|
||||||
// }
|
|
||||||
// if (this._inactivityTimeout != null) {
|
|
||||||
// clearTimeout(this._inactivityTimeout)
|
|
||||||
// }
|
|
||||||
|
|
||||||
await this._fuman.close()
|
await this._fuman.close()
|
||||||
// this._transport.removeAllListeners()
|
|
||||||
// this._destroyed = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _rescheduleInactivity(): void {
|
protected _rescheduleInactivity(): void {
|
||||||
|
|
|
@ -21,8 +21,9 @@ export interface ITelegramConnection extends IConnection<any, any> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TelegramTransport {
|
export interface TelegramTransport {
|
||||||
|
setup?(crypto: ICryptoProvider, log: Logger): void
|
||||||
connect: (dc: BasicDcOption, testMode: boolean) => Promise<ITelegramConnection>
|
connect: (dc: BasicDcOption, testMode: boolean) => Promise<ITelegramConnection>
|
||||||
packetCodec: () => IPacketCodec
|
packetCodec: (dc: BasicDcOption) => IPacketCodec
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -33,7 +33,7 @@ export class IntermediatePacketCodec implements IPacketCodec {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reader.available < length) {
|
if (reader.available < length) {
|
||||||
reader.unread(4)
|
reader.rewind(4)
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,6 @@ export class ObfuscatedPacketCodec implements IPacketCodec {
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(inner: IPacketCodec, proxy?: MtProxyInfo) {
|
constructor(inner: IPacketCodec, proxy?: MtProxyInfo) {
|
||||||
// super(inner)
|
|
||||||
this._inner = inner
|
this._inner = inner
|
||||||
this._proxy = proxy
|
this._proxy = proxy
|
||||||
}
|
}
|
||||||
|
@ -99,19 +98,25 @@ export class ObfuscatedPacketCodec implements IPacketCodec {
|
||||||
|
|
||||||
async encode(packet: Uint8Array, into: ISyncWritable): Promise<void> {
|
async encode(packet: Uint8Array, into: ISyncWritable): Promise<void> {
|
||||||
const temp = Bytes.alloc(packet.length)
|
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()))
|
write.bytes(into, this._encryptor!.process(temp.result()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _decodeBuf = Bytes.alloc()
|
||||||
async decode(reader: Bytes, eof: boolean): Promise<Uint8Array | null> {
|
async decode(reader: Bytes, eof: boolean): Promise<Uint8Array | null> {
|
||||||
const inner = await this._inner.decode(reader, eof)
|
if (eof) return null
|
||||||
if (!inner) 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 {
|
reset(): void {
|
||||||
this._inner.reset()
|
this._inner.reset()
|
||||||
|
this._decodeBuf.reset()
|
||||||
this._encryptor?.close?.()
|
this._encryptor?.close?.()
|
||||||
this._decryptor?.close?.()
|
this._decryptor?.close?.()
|
||||||
|
|
||||||
|
|
|
@ -6,15 +6,18 @@ export interface BasicDcOption {
|
||||||
id: number
|
id: number
|
||||||
ipv6?: boolean
|
ipv6?: boolean
|
||||||
mediaOnly?: boolean
|
mediaOnly?: boolean
|
||||||
|
testMode?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function serializeBasicDcOption(dc: BasicDcOption): Uint8Array {
|
export function serializeBasicDcOption(dc: BasicDcOption): Uint8Array {
|
||||||
const writer = TlBinaryWriter.manual(64)
|
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(
|
writer.raw(
|
||||||
new Uint8Array([
|
new Uint8Array([
|
||||||
1, // version
|
2, // version
|
||||||
dc.id,
|
dc.id,
|
||||||
flags,
|
flags,
|
||||||
]),
|
]),
|
||||||
|
@ -30,7 +33,7 @@ export function parseBasicDcOption(data: Uint8Array): BasicDcOption | null {
|
||||||
const reader = TlBinaryReader.manual(data)
|
const reader = TlBinaryReader.manual(data)
|
||||||
|
|
||||||
const [version, id, flags] = reader.raw(3)
|
const [version, id, flags] = reader.raw(3)
|
||||||
if (version !== 1) return null
|
if (version !== 1 && version !== 2) return null
|
||||||
|
|
||||||
const ipAddress = reader.string()
|
const ipAddress = reader.string()
|
||||||
const port = reader.int()
|
const port = reader.int()
|
||||||
|
@ -41,6 +44,7 @@ export function parseBasicDcOption(data: Uint8Array): BasicDcOption | null {
|
||||||
port,
|
port,
|
||||||
ipv6: (flags & 1) !== 0,
|
ipv6: (flags & 1) !== 0,
|
||||||
mediaOnly: (flags & 2) !== 0,
|
mediaOnly: (flags & 2) !== 0,
|
||||||
|
testMode: version === 2 && (flags & 4) !== 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,12 +88,14 @@ export const defaultTestDc: DcOptions = {
|
||||||
ipAddress: '149.154.167.40',
|
ipAddress: '149.154.167.40',
|
||||||
port: 443,
|
port: 443,
|
||||||
id: 2,
|
id: 2,
|
||||||
|
testMode: true,
|
||||||
},
|
},
|
||||||
media: {
|
media: {
|
||||||
ipAddress: '149.154.167.40',
|
ipAddress: '149.154.167.40',
|
||||||
port: 443,
|
port: 443,
|
||||||
id: 2,
|
id: 2,
|
||||||
mediaOnly: true,
|
mediaOnly: true,
|
||||||
|
testMode: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,6 +105,7 @@ export const defaultTestIpv6Dc: DcOptions = {
|
||||||
port: 443,
|
port: 443,
|
||||||
ipv6: true,
|
ipv6: true,
|
||||||
id: 2,
|
id: 2,
|
||||||
|
testMode: true,
|
||||||
},
|
},
|
||||||
media: {
|
media: {
|
||||||
ipAddress: '2001:67c:4e8:f002::e',
|
ipAddress: '2001:67c:4e8:f002::e',
|
||||||
|
@ -106,5 +113,6 @@ export const defaultTestIpv6Dc: DcOptions = {
|
||||||
ipv6: true,
|
ipv6: true,
|
||||||
id: 2,
|
id: 2,
|
||||||
mediaOnly: true,
|
mediaOnly: true,
|
||||||
|
testMode: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@mtcute/create-bot",
|
"name": "@mtcute/create-bot",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.16.13",
|
"version": "0.17.0-fuman-alpha",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Bot starter kit for mtcute",
|
"description": "Bot starter kit for mtcute",
|
||||||
"author": "alina sireneva <alina@tei.su>",
|
"author": "alina sireneva <alina@tei.su>",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@mtcute/crypto-node",
|
"name": "@mtcute/crypto-node",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.16.9",
|
"version": "0.17.0-fuman-alpha",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Native crypto implementation for NodeJS",
|
"description": "Native crypto implementation for NodeJS",
|
||||||
"author": "alina sireneva <alina@tei.su>",
|
"author": "alina sireneva <alina@tei.su>",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@mtcute/deno",
|
"name": "@mtcute/deno",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.16.9",
|
"version": "0.17.0-fuman-alpha",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Meta-package for Deno",
|
"description": "Meta-package for Deno",
|
||||||
"author": "alina sireneva <alina@tei.su>",
|
"author": "alina sireneva <alina@tei.su>",
|
||||||
|
@ -19,6 +19,9 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@db/sqlite": "npm:@jsr/db__sqlite@0.12.0",
|
"@db/sqlite": "npm:@jsr/db__sqlite@0.12.0",
|
||||||
|
"@fuman/deno-net": "workspace:^",
|
||||||
|
"@fuman/net": "workspace:^",
|
||||||
|
"@fuman/io": "workspace:^",
|
||||||
"@mtcute/core": "workspace:^",
|
"@mtcute/core": "workspace:^",
|
||||||
"@mtcute/html-parser": "workspace:^",
|
"@mtcute/html-parser": "workspace:^",
|
||||||
"@mtcute/markdown-parser": "workspace:^",
|
"@mtcute/markdown-parser": "workspace:^",
|
||||||
|
|
|
@ -2,7 +2,8 @@ export * from './client.js'
|
||||||
export * from './platform.js'
|
export * from './platform.js'
|
||||||
export * from './sqlite/index.js'
|
export * from './sqlite/index.js'
|
||||||
export * from './utils/crypto.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 './worker.js'
|
||||||
export * from '@mtcute/core'
|
export * from '@mtcute/core'
|
||||||
export * from '@mtcute/html-parser'
|
export * from '@mtcute/html-parser'
|
||||||
|
|
74
packages/deno/src/utils/proxies.ts
Normal file
74
packages/deno/src/utils/proxies.ts
Normal file
|
@ -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<ITelegramConnection> {
|
||||||
|
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<ITelegramConnection> {
|
||||||
|
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<ITcpConnection> {
|
||||||
|
return connectTcp(endpoint)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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'
|
export const TcpTransport: TelegramTransport = {
|
||||||
// import { BasicDcOption, ICryptoProvider, Logger } from '@mtcute/core/utils.js'
|
connect: dc => connectTcp({ address: dc.ipAddress, port: dc.port }),
|
||||||
|
packetCodec: () => new IntermediatePacketCodec(),
|
||||||
// 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<void> {
|
|
||||||
// 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<void> {
|
|
||||||
// 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()
|
|
||||||
// }
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@mtcute/dispatcher",
|
"name": "@mtcute/dispatcher",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.16.13",
|
"version": "0.17.0-fuman-alpha",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Updates dispatcher and bot framework for @mtcute/client",
|
"description": "Updates dispatcher and bot framework for @mtcute/client",
|
||||||
"author": "alina sireneva <alina@tei.su>",
|
"author": "alina sireneva <alina@tei.su>",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@mtcute/file-id",
|
"name": "@mtcute/file-id",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.16.12",
|
"version": "0.17.0-fuman-alpha",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Support for TDLib and Bot API file ID for mtcute",
|
"description": "Support for TDLib and Bot API file ID for mtcute",
|
||||||
"author": "alina sireneva <alina@tei.su>",
|
"author": "alina sireneva <alina@tei.su>",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@mtcute/html-parser",
|
"name": "@mtcute/html-parser",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.16.9",
|
"version": "0.17.0-fuman-alpha",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "HTML entities parser for mtcute",
|
"description": "HTML entities parser for mtcute",
|
||||||
"author": "alina sireneva <alina@tei.su>",
|
"author": "alina sireneva <alina@tei.su>",
|
||||||
|
|
|
@ -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,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
```
|
|
|
@ -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<string, string>
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * 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()
|
|
||||||
// }
|
|
|
@ -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 <alina@tei.su>",
|
|
||||||
"license": "MIT",
|
|
||||||
"sideEffects": false,
|
|
||||||
"exports": "./index.ts",
|
|
||||||
"scripts": {
|
|
||||||
"docs": "typedoc",
|
|
||||||
"build": "pnpm run -w build-package http-proxy"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@mtcute/node": "workspace:^"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"extends": "../../tsconfig.json",
|
|
||||||
"include": [
|
|
||||||
"./index.ts"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
extends: ['../../.config/typedoc/config.base.cjs'],
|
|
||||||
entryPoints: ['./index.ts'],
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@mtcute/i18n",
|
"name": "@mtcute/i18n",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.16.9",
|
"version": "0.17.0-fuman-alpha",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "I18n for mtcute",
|
"description": "I18n for mtcute",
|
||||||
"author": "alina sireneva <alina@tei.su>",
|
"author": "alina sireneva <alina@tei.su>",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@mtcute/markdown-parser",
|
"name": "@mtcute/markdown-parser",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.16.9",
|
"version": "0.17.0-fuman-alpha",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Markdown entities parser for mtcute",
|
"description": "Markdown entities parser for mtcute",
|
||||||
"author": "alina sireneva <alina@tei.su>",
|
"author": "alina sireneva <alina@tei.su>",
|
||||||
|
|
|
@ -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'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
```
|
|
|
@ -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<Buffer> {
|
|
||||||
// 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<Buffer> {
|
|
||||||
// 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<Buffer> {
|
|
||||||
// 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<Buffer> {
|
|
||||||
// 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
|
|
||||||
// }
|
|
||||||
// }
|
|
|
@ -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<MtProxyTcpTransport>)._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<void> {
|
|
||||||
// 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<boolean> => {
|
|
||||||
// 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)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
|
@ -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 <alina@tei.su>",
|
|
||||||
"license": "MIT",
|
|
||||||
"sideEffects": false,
|
|
||||||
"exports": "./index.ts",
|
|
||||||
"scripts": {
|
|
||||||
"docs": "typedoc",
|
|
||||||
"build": "pnpm run -w build-package mtproxy"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@mtcute/node": "workspace:^"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"extends": "../../tsconfig.json",
|
|
||||||
"include": [
|
|
||||||
"./index.ts",
|
|
||||||
"./fake-tls.ts"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
extends: ['../../.config/typedoc/config.base.cjs'],
|
|
||||||
entryPoints: ['./index.ts'],
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@mtcute/node",
|
"name": "@mtcute/node",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.16.13",
|
"version": "0.17.0-fuman-alpha",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Meta-package for Node.js",
|
"description": "Meta-package for Node.js",
|
||||||
"author": "alina sireneva <alina@tei.su>",
|
"author": "alina sireneva <alina@tei.su>",
|
||||||
|
@ -21,6 +21,7 @@
|
||||||
"@mtcute/html-parser": "workspace:^",
|
"@mtcute/html-parser": "workspace:^",
|
||||||
"@mtcute/markdown-parser": "workspace:^",
|
"@mtcute/markdown-parser": "workspace:^",
|
||||||
"@mtcute/wasm": "workspace:^",
|
"@mtcute/wasm": "workspace:^",
|
||||||
|
"@fuman/net": "workspace:^",
|
||||||
"@fuman/node-net": "workspace:^",
|
"@fuman/node-net": "workspace:^",
|
||||||
"better-sqlite3": "11.3.0"
|
"better-sqlite3": "11.3.0"
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,6 +2,7 @@ export * from './client.js'
|
||||||
export * from './common-internals-node/platform.js'
|
export * from './common-internals-node/platform.js'
|
||||||
export * from './sqlite/index.js'
|
export * from './sqlite/index.js'
|
||||||
export * from './utils/tcp.js'
|
export * from './utils/tcp.js'
|
||||||
|
export * from './utils/proxies.js'
|
||||||
export * from './worker.js'
|
export * from './worker.js'
|
||||||
export * from '@mtcute/core'
|
export * from '@mtcute/core'
|
||||||
export * from '@mtcute/html-parser'
|
export * from '@mtcute/html-parser'
|
||||||
|
|
84
packages/node/src/utils/proxies.ts
Normal file
84
packages/node/src/utils/proxies.ts
Normal file
|
@ -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<ITelegramConnection> {
|
||||||
|
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<ITelegramConnection> {
|
||||||
|
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<ITcpConnection> {
|
||||||
|
return connectTcp(endpoint)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
```
|
|
|
@ -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<number, string> = {
|
|
||||||
// 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<number, string> = {
|
|
||||||
// 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()
|
|
||||||
// }
|
|
|
@ -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 <alina@tei.su>",
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"extends": "../../tsconfig.json",
|
|
||||||
"include": [
|
|
||||||
"./index.ts"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
extends: ['../../.config/typedoc/config.base.cjs'],
|
|
||||||
entryPoints: ['./index.ts'],
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@mtcute/test",
|
"name": "@mtcute/test",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.16.13",
|
"version": "0.17.0-fuman-alpha",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Test utilities for mtcute",
|
"description": "Test utilities for mtcute",
|
||||||
"author": "alina sireneva <alina@tei.su>",
|
"author": "alina sireneva <alina@tei.su>",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@mtcute/tl-runtime",
|
"name": "@mtcute/tl-runtime",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.16.9",
|
"version": "0.17.0-fuman-alpha",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Runtime for TL",
|
"description": "Runtime for TL",
|
||||||
"author": "alina sireneva <alina@tei.su>",
|
"author": "alina sireneva <alina@tei.su>",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@mtcute/tl-utils",
|
"name": "@mtcute/tl-utils",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.16.9",
|
"version": "0.17.0-fuman-alpha",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Utils for working with TL schema",
|
"description": "Utils for working with TL schema",
|
||||||
"author": "alina sireneva <alina@tei.su>",
|
"author": "alina sireneva <alina@tei.su>",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@mtcute/wasm",
|
"name": "@mtcute/wasm",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.16.11",
|
"version": "0.17.0-fuman-alpha",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "WASM implementation of common algorithms used in Telegram",
|
"description": "WASM implementation of common algorithms used in Telegram",
|
||||||
"author": "alina sireneva <alina@tei.su>",
|
"author": "alina sireneva <alina@tei.su>",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@mtcute/web",
|
"name": "@mtcute/web",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.16.13",
|
"version": "0.17.0-fuman-alpha",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Meta-package for the web platform",
|
"description": "Meta-package for the web platform",
|
||||||
"author": "alina sireneva <alina@tei.su>",
|
"author": "alina sireneva <alina@tei.su>",
|
||||||
|
|
|
@ -101,6 +101,15 @@ importers:
|
||||||
|
|
||||||
packages/bun:
|
packages/bun:
|
||||||
dependencies:
|
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':
|
'@mtcute/core':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../core
|
version: link:../core
|
||||||
|
@ -217,6 +226,15 @@ importers:
|
||||||
'@db/sqlite':
|
'@db/sqlite':
|
||||||
specifier: npm:@jsr/db__sqlite@0.12.0
|
specifier: npm:@jsr/db__sqlite@0.12.0
|
||||||
version: '@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':
|
'@mtcute/core':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../core
|
version: link:../core
|
||||||
|
@ -275,12 +293,6 @@ importers:
|
||||||
specifier: 5.2.3
|
specifier: 5.2.3
|
||||||
version: 5.2.3
|
version: 5.2.3
|
||||||
|
|
||||||
packages/http-proxy:
|
|
||||||
dependencies:
|
|
||||||
'@mtcute/node':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../node
|
|
||||||
|
|
||||||
packages/i18n:
|
packages/i18n:
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@mtcute/core':
|
'@mtcute/core':
|
||||||
|
@ -299,14 +311,11 @@ importers:
|
||||||
specifier: 5.2.3
|
specifier: 5.2.3
|
||||||
version: 5.2.3
|
version: 5.2.3
|
||||||
|
|
||||||
packages/mtproxy:
|
|
||||||
dependencies:
|
|
||||||
'@mtcute/node':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../node
|
|
||||||
|
|
||||||
packages/node:
|
packages/node:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@fuman/net':
|
||||||
|
specifier: workspace:^
|
||||||
|
version: link:../../private/fuman/packages/net
|
||||||
'@fuman/node-net':
|
'@fuman/node-net':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../../private/fuman/packages/node-net
|
version: link:../../private/fuman/packages/node-net
|
||||||
|
@ -333,15 +342,6 @@ importers:
|
||||||
specifier: 7.6.4
|
specifier: 7.6.4
|
||||||
version: 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:
|
packages/test:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@mtcute/core':
|
'@mtcute/core':
|
||||||
|
@ -2778,10 +2778,6 @@ packages:
|
||||||
resolution: {integrity: sha512-B2LafrnnhbRzCWfAdOXisUzL89Kg8cVJlYmhqoi3flSiV/TveO+nsXwgKr9h9PIo+J1hz7nBSk6gegRIMBBf7g==}
|
resolution: {integrity: sha512-B2LafrnnhbRzCWfAdOXisUzL89Kg8cVJlYmhqoi3flSiV/TveO+nsXwgKr9h9PIo+J1hz7nBSk6gegRIMBBf7g==}
|
||||||
engines: {node: '>=14.18.0'}
|
engines: {node: '>=14.18.0'}
|
||||||
|
|
||||||
ip6@0.2.7:
|
|
||||||
resolution: {integrity: sha512-zEzGsxn4Uw33TByv0DdX/RRh+VsGfEctOp7CvJq/b4JEjY9OvPB58dsMYiEwIVLsIWHZSJPn3XG8mP9Qv3TG3g==}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
is-alphabetical@1.0.4:
|
is-alphabetical@1.0.4:
|
||||||
resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==}
|
resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==}
|
||||||
|
|
||||||
|
@ -6680,8 +6676,6 @@ snapshots:
|
||||||
strip-ansi: 6.0.1
|
strip-ansi: 6.0.1
|
||||||
wrap-ansi: 6.2.0
|
wrap-ansi: 6.2.0
|
||||||
|
|
||||||
ip6@0.2.7: {}
|
|
||||||
|
|
||||||
is-alphabetical@1.0.4: {}
|
is-alphabetical@1.0.4: {}
|
||||||
|
|
||||||
is-alphanumerical@1.0.4:
|
is-alphanumerical@1.0.4:
|
||||||
|
|
Loading…
Reference in a new issue