UNPKG

@tgsnake/core

Version:

Pure Telegram MTProto library for nodejs

280 lines (279 loc) 10.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Socket = void 0; const platform_node_js_1 = require("../platform.node.js"); const Logger_js_1 = require("../Logger.js"); const index_js_1 = require("../errors/index.js"); const mutex = new platform_node_js_1.Mutex(); class Socket { _client; _data; _read; _promisedReading; timeout; _connectionClosed; constructor(timeout) { this._data = platform_node_js_1.Buffer.alloc(0); this._connectionClosed = true; this.timeout = timeout; } async connect(ip, port, proxy) { if (platform_node_js_1.isBrowser) { if (proxy && !('server' in proxy && 'port' in proxy && 'secret' in proxy)) { throw new index_js_1.WSError.ProxyUnsupported(); } if (port === 443) { this._client = new WebSocket(`wss://${ip.replace('$PORT', String(port))}`, 'binary'); } else { this._client = new WebSocket(`ws://${ip.replace('$PORT', String(port))}`, 'binary'); } this._connectionClosed = false; this._read = new Promise((resolve) => { this._promisedReading = resolve; }); return new Promise((resolve, reject) => { this._client.onopen = () => { this.recv(); resolve(this); }; this._client.onerror = (error) => { return 'message' in error ? reject(new index_js_1.WSError.WebSocketError(error.message)) : reject(error); }; this._client.onclose = () => { if (this._client.readyState >= 2) { if (this._promisedReading) this._promisedReading(false); this._connectionClosed = true; } }; globalThis.addEventListener('offline', this.destroy); }); } else { if (proxy && !('server' in proxy && 'port' in proxy && 'secret' in proxy) && 'hostname' in proxy && 'port' in proxy && 'socks' in proxy) { const ws = await platform_node_js_1.SocksClient.createConnection({ proxy: { host: proxy.hostname, port: proxy.port, type: proxy.socks < 4 || proxy.socks > 5 ? 5 : proxy.socks, userId: proxy.username, password: proxy.password, }, command: 'connect', timeout: this.timeout, destination: { host: ip, port: port, }, }); this._client = ws.socket; this._client.setTimeout(this.timeout); this._connectionClosed = false; this._read = new Promise((resolve) => { this._promisedReading = resolve; }); return new Promise((resolve, reject) => { this._client.on('error', (error) => { return error.message ? reject(new index_js_1.WSError.WebSocketError(error.message)) : reject(error); }); this._client.on('close', () => { if (this._client.destroyed) { if (this._promisedReading) this._promisedReading(false); this._connectionClosed = true; } }); this.recv(); resolve(this); }); } else { this._client = new platform_node_js_1.net.Socket(); this._client.setTimeout(this.timeout); this._connectionClosed = false; this._read = new Promise((resolve) => { this._promisedReading = resolve; }); return new Promise((resolve, reject) => { this._client.connect(port, ip, () => { this.recv(); resolve(this); }); this._client.on('error', (error) => { return error.message ? reject(new index_js_1.WSError.WebSocketError(error.message)) : reject(error); }); this._client.on('close', () => { if (this._client.destroyed) { if (this._promisedReading) this._promisedReading(false); this._connectionClosed = true; } }); }); } } } async destroy() { if (this._client && !this._connectionClosed) { this._connectionClosed = true; this._read = new Promise((resolve) => { this._promisedReading = resolve; }); if (platform_node_js_1.isBrowser) { await this._client.close(); } else { await this._client.destroy(); await this._client.unref(); } } return this._connectionClosed; } recv() { if (this._client && !this._connectionClosed) { if (platform_node_js_1.isBrowser) { this._client.onmessage = async (data) => { const _data = platform_node_js_1.Buffer.from(await new Response(data.data).arrayBuffer()); const release = await mutex.acquire(); try { Logger_js_1.Logger.debug(`[3] Receive ${platform_node_js_1.Buffer.byteLength(_data)} bytes data`); this._data = platform_node_js_1.Buffer.concat([ this._data, _data, ]); if (this._promisedReading) this._promisedReading(true); } finally { release(); } }; } else { this._client.on('data', async (data) => { const release = await mutex.acquire(); try { Logger_js_1.Logger.debug(`[3] Receive ${platform_node_js_1.Buffer.byteLength(data)} bytes data`); this._data = platform_node_js_1.Buffer.concat([ this._data, data, ]); if (this._promisedReading) this._promisedReading(true); } finally { release(); } }); } } else { throw new index_js_1.WSError.Disconnected(); } } async send(data) { if (this._client && !this._connectionClosed) { const release = await mutex.acquire(); try { if (platform_node_js_1.isBrowser) { this._client.send(data); } else { this._client.write(data); } } finally { release(); } } else { throw new index_js_1.WSError.Disconnected(); } } async read(length) { if (this._connectionClosed) { throw new index_js_1.WSError.ReadClosed(); } await this._read; if (this._connectionClosed) { throw new index_js_1.WSError.ReadClosed(); } const toRead = this._data.subarray(0, length); this._data = this._data.subarray(length); if (platform_node_js_1.Buffer.byteLength(this._data) <= 0) { this._read = new Promise((resolve) => { this._promisedReading = resolve; }); } return toRead; } async reading(length) { if (this._client && !this._connectionClosed) { let data = platform_node_js_1.Buffer.alloc(0); while (!this._connectionClosed) { const readed = await this.read(length); data = platform_node_js_1.Buffer.concat([data, readed]); length = length - platform_node_js_1.Buffer.byteLength(readed); if (!length) return data; } } else { throw new index_js_1.WSError.ReadClosed(); } } [Symbol.for('nodejs.util.inspect.custom')]() { const toPrint = { _: this.constructor.name, }; for (const key in this) { if (Object.prototype.hasOwnProperty.call(this, key)) { const value = this[key]; if (!key.startsWith('_') && value !== undefined && value !== null) { toPrint[key] = value; } } } return toPrint; } [Symbol.for('Deno.customInspect')]() { return String((0, platform_node_js_1.inspect)(this[Symbol.for('nodejs.util.inspect.custom')](), { colors: true })); } toJSON() { const toPrint = { _: this.constructor.name, }; for (const key in this) { if (Object.prototype.hasOwnProperty.call(this, key)) { const value = this[key]; if (!key.startsWith('_') && value !== undefined && value !== null) { if (typeof value === 'bigint') { toPrint[key] = String(value); } else if (Array.isArray(value)) { toPrint[key] = value.map((v) => (typeof v === 'bigint' ? String(v) : v)); } else { toPrint[key] = value; } } } } return toPrint; } toString() { return `[constructor of ${this.constructor.name}] ${JSON.stringify(this, null, 2)}`; } } exports.Socket = Socket;