dograma
Version:
NodeJS/Browser MTProto API Telegram client library,
254 lines (224 loc) • 7.16 kB
text/typescript
import {
CancelHelper,
Logger,
PromisedNetSockets,
PromisedWebSockets,
} from "../../extensions";
import { AsyncQueue } from "../../extensions";
import { AbridgedPacketCodec } from "./TCPAbridged";
import { FullPacketCodec } from "./TCPFull";
import { ProxyInterface } from "./TCPMTProxy";
interface ConnectionInterfaceParams {
ip: string;
port: number;
dcId: number;
loggers: Logger;
proxy?: ProxyInterface;
socket: typeof PromisedNetSockets | typeof PromisedWebSockets;
testServers: boolean;
}
/**
* The `Connection` class is a wrapper around ``asyncio.open_connection``.
*
* Subclasses will implement different transport modes as atomic operations,
* which this class eases doing since the exposed interface simply puts and
* gets complete data payloads to and from queues.
*
* The only error that will raise from send and receive methods is
* ``ConnectionError``, which will raise when attempting to send if
* the client is disconnected (includes remote disconnections).
*/
class Connection {
PacketCodecClass?: typeof AbridgedPacketCodec | typeof FullPacketCodec;
readonly _ip: string;
readonly _port: number;
_dcId: number;
_log: Logger;
_proxy?: ProxyInterface;
_connected: boolean;
private _sendTask?: Promise<void>;
private _recvTask?: Promise<void>;
protected _codec: any;
protected _obfuscation: any;
_sendArray: AsyncQueue;
_recvArray: AsyncQueue;
private _recvCancelPromise: Promise<CancelHelper>;
private _recvCancelResolve?: (value: CancelHelper) => void;
private _sendCancelPromise: Promise<CancelHelper>;
private _sendCancelResolve?: (value: CancelHelper) => void;
socket: PromisedNetSockets | PromisedWebSockets;
public _testServers: boolean;
constructor({
ip,
port,
dcId,
loggers,
proxy,
socket,
testServers,
}: ConnectionInterfaceParams) {
this._ip = ip;
this._port = port;
this._dcId = dcId;
this._log = loggers;
this._proxy = proxy;
this._connected = false;
this._sendTask = undefined;
this._recvTask = undefined;
this._codec = undefined;
this._obfuscation = undefined; // TcpObfuscated and MTProxy
this._sendArray = new AsyncQueue();
this._recvArray = new AsyncQueue();
this.socket = new socket(proxy);
this._testServers = testServers;
this._recvCancelPromise = new Promise((resolve) => {
this._recvCancelResolve = resolve;
});
this._sendCancelPromise = new Promise((resolve) => {
this._sendCancelResolve = resolve;
});
}
async _connect() {
this._log.debug("Connecting");
this._codec = new this.PacketCodecClass!(this);
await this.socket.connect(this._port, this._ip, this._testServers);
this._log.debug("Finished connecting");
// await this.socket.connect({host: this._ip, port: this._port});
await this._initConn();
}
async connect() {
await this._connect();
this._connected = true;
this._sendTask = this._sendLoop();
this._recvTask = this._recvLoop();
}
_cancelLoops() {
this._recvCancelResolve!(new CancelHelper());
this._sendCancelResolve!(new CancelHelper());
this._recvCancelPromise = new Promise((resolve) => {
this._recvCancelResolve = resolve;
});
this._sendCancelPromise = new Promise((resolve) => {
this._sendCancelResolve = resolve;
});
}
async disconnect() {
this._connected = false;
this._cancelLoops();
try {
await this.socket.close();
} catch (e) {
this._log.error("error while closing socket connection");
}
}
async send(data: Buffer) {
if (!this._connected) {
throw new Error("Not connected");
}
await this._sendArray.push(data);
}
async recv() {
while (this._connected) {
const result = await this._recvArray.pop();
if (result && result.length) {
return result;
}
}
throw new Error("Not connected");
}
async _sendLoop() {
try {
while (this._connected) {
const data = await Promise.race([
this._sendCancelPromise,
this._sendArray.pop(),
]);
if (data instanceof CancelHelper) {
break;
}
if (!data) {
continue;
}
await this._send(data);
}
} catch (e: any) {
this._log.info("The server closed the connection while sending");
await this.disconnect();
}
}
async _recvLoop() {
let data;
while (this._connected) {
try {
data = await Promise.race([
this._recvCancelPromise,
await this._recv(),
]);
if (data instanceof CancelHelper) {
return;
}
} catch (e: any) {
this._log.info("The server closed the connection");
await this.disconnect();
if (!this._recvArray._queue.length) {
await this._recvArray.push(undefined);
}
break;
}
try {
await this._recvArray.push(data);
} catch (e) {
break;
}
}
}
async _initConn() {
if (this._codec.tag) {
await this.socket.write(this._codec.tag);
}
}
async _send(data: Buffer) {
const encodedPacket = this._codec.encodePacket(data);
this.socket.write(encodedPacket);
}
async _recv() {
return await this._codec.readPacket(this.socket);
}
toString() {
return `${this._ip}:${this._port}/${this.constructor.name.replace(
"Connection",
""
)}`;
}
}
class ObfuscatedConnection extends Connection {
ObfuscatedIO: any = undefined;
async _initConn() {
this._obfuscation = new this.ObfuscatedIO(this);
await this._obfuscation.initHeader();
this.socket.write(this._obfuscation.header);
}
async _send(data: Buffer) {
this._obfuscation.write(this._codec.encodePacket(data));
}
async _recv() {
return await this._codec.readPacket(this._obfuscation);
}
}
class PacketCodec {
private _conn: Buffer;
constructor(connection: Buffer) {
this._conn = connection;
}
encodePacket(data: Buffer) {
throw new Error("Not Implemented");
// Override
}
async readPacket(
reader: PromisedNetSockets | PromisedWebSockets
): Promise<Buffer> {
// override
throw new Error("Not Implemented");
}
}
export { Connection, PacketCodec, ObfuscatedConnection };