@tgsnake/core
Version:
Pure Telegram MTProto library for nodejs
280 lines (279 loc) • 10.7 kB
JavaScript
"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;