teleproto 1.225.2 → 1.225.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "1.225.2";
1
+ export declare const version = "1.225.4";
package/Version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.version = void 0;
4
- exports.version = "1.225.2";
4
+ exports.version = "1.225.4";
@@ -85,6 +85,22 @@ export declare class TelegramClient extends TelegramBaseClient {
85
85
  * @return boolean (true of authorized else false)
86
86
  */
87
87
  checkAuthorization(): Promise<boolean>;
88
+ /**
89
+ * Logs out the currently authenticated user/bot. Invalidates the session
90
+ * on Telegram's side (via `auth.LogOut`), disconnects the client and wipes
91
+ * local session data so a subsequent `session.save()` returns nothing.
92
+ *
93
+ * After this call the client is disconnected and event handlers stop
94
+ * firing; reconnecting requires going through `start`/`signInUser` again.
95
+ *
96
+ * @returns `true` if the server-side log out succeeded, `false` if the
97
+ * `auth.LogOut` call failed. Local session data is wiped in both cases.
98
+ * @example
99
+ * ```ts
100
+ * await client.logOut();
101
+ * ```
102
+ */
103
+ logOut(): Promise<boolean>;
88
104
  /**
89
105
  * Logs in as a user. Should only be used when not already logged in.<br/>
90
106
  * This method will send a code when needed.<br/>
@@ -115,11 +131,18 @@ export declare class TelegramClient extends TelegramBaseClient {
115
131
  /**
116
132
  * logs the user using a QR code to be scanned.<br/>
117
133
  * this function generates the QR code that needs to be scanned by mobile.
134
+ *
135
+ * The flow can be cancelled at any time (e.g. the user closed the page) by
136
+ * passing an `abortSignal`; once aborted it stops polling for tokens and
137
+ * rejects with an `AbortError`.
118
138
  * @example
119
139
  * '''ts
120
140
  * await client.connect();
121
- * const user = await client.signInUserWithQrCode({ apiId, apiHash },
122
- * {
141
+ * const controller = new AbortController();
142
+ * // call controller.abort() to cancel the QR login at any time
143
+ * try {
144
+ * const user = await client.signInUserWithQrCode({ apiId, apiHash },
145
+ * {
123
146
  * onError: async function(p1: Error) {
124
147
  * console.log("error", p1);
125
148
  * // true = stop the authentication processes
@@ -134,14 +157,19 @@ export declare class TelegramClient extends TelegramBaseClient {
134
157
  * password: async (hint) => {
135
158
  * // password if needed
136
159
  * return "1111";
137
- * }
160
+ * },
161
+ * abortSignal: controller.signal,
138
162
  * }
139
- * );
140
- * console.log("user is", user);
163
+ * );
164
+ * console.log("user is", user);
165
+ * } catch (err) {
166
+ * if (err.name === "AbortError") console.log("QR login cancelled");
167
+ * else throw err;
168
+ * }
141
169
  *
142
170
  * '''
143
171
  * @param apiCredentials - credentials to be used.
144
- * @param authParams - user auth params.
172
+ * @param authParams - user auth params. Pass `abortSignal` to cancel the flow.
145
173
  */
146
174
  signInUserWithQrCode(apiCredentials: authMethods.ApiCredentials, authParams: authMethods.QrCodeAuthParams): Promise<Api.TypeUser>;
147
175
  /**
@@ -127,6 +127,24 @@ class TelegramClient extends telegramBaseClient_1.TelegramBaseClient {
127
127
  checkAuthorization() {
128
128
  return authMethods.checkAuthorization(this);
129
129
  }
130
+ /**
131
+ * Logs out the currently authenticated user/bot. Invalidates the session
132
+ * on Telegram's side (via `auth.LogOut`), disconnects the client and wipes
133
+ * local session data so a subsequent `session.save()` returns nothing.
134
+ *
135
+ * After this call the client is disconnected and event handlers stop
136
+ * firing; reconnecting requires going through `start`/`signInUser` again.
137
+ *
138
+ * @returns `true` if the server-side log out succeeded, `false` if the
139
+ * `auth.LogOut` call failed. Local session data is wiped in both cases.
140
+ * @example
141
+ * ```ts
142
+ * await client.logOut();
143
+ * ```
144
+ */
145
+ logOut() {
146
+ return authMethods.logOut(this);
147
+ }
130
148
  /**
131
149
  * Logs in as a user. Should only be used when not already logged in.<br/>
132
150
  * This method will send a code when needed.<br/>
@@ -159,11 +177,18 @@ class TelegramClient extends telegramBaseClient_1.TelegramBaseClient {
159
177
  /**
160
178
  * logs the user using a QR code to be scanned.<br/>
161
179
  * this function generates the QR code that needs to be scanned by mobile.
180
+ *
181
+ * The flow can be cancelled at any time (e.g. the user closed the page) by
182
+ * passing an `abortSignal`; once aborted it stops polling for tokens and
183
+ * rejects with an `AbortError`.
162
184
  * @example
163
185
  * '''ts
164
186
  * await client.connect();
165
- * const user = await client.signInUserWithQrCode({ apiId, apiHash },
166
- * {
187
+ * const controller = new AbortController();
188
+ * // call controller.abort() to cancel the QR login at any time
189
+ * try {
190
+ * const user = await client.signInUserWithQrCode({ apiId, apiHash },
191
+ * {
167
192
  * onError: async function(p1: Error) {
168
193
  * console.log("error", p1);
169
194
  * // true = stop the authentication processes
@@ -178,14 +203,19 @@ class TelegramClient extends telegramBaseClient_1.TelegramBaseClient {
178
203
  * password: async (hint) => {
179
204
  * // password if needed
180
205
  * return "1111";
181
- * }
206
+ * },
207
+ * abortSignal: controller.signal,
182
208
  * }
183
- * );
184
- * console.log("user is", user);
209
+ * );
210
+ * console.log("user is", user);
211
+ * } catch (err) {
212
+ * if (err.name === "AbortError") console.log("QR login cancelled");
213
+ * else throw err;
214
+ * }
185
215
  *
186
216
  * '''
187
217
  * @param apiCredentials - credentials to be used.
188
- * @param authParams - user auth params.
218
+ * @param authParams - user auth params. Pass `abortSignal` to cancel the flow.
189
219
  */
190
220
  signInUserWithQrCode(apiCredentials, authParams) {
191
221
  return authMethods.signInUserWithQrCode(this, apiCredentials, authParams);
@@ -14,6 +14,8 @@ export declare class UpdateManager {
14
14
  private globalPtsTimer?;
15
15
  private readonly channels;
16
16
  private readonly pendingSeq;
17
+ private readonly recentMessageKeys;
18
+ private readonly recentMessageQueue;
17
19
  private fetchingDifference;
18
20
  private failTimeoutS;
19
21
  private failRetryTimer?;
@@ -38,6 +40,7 @@ export declare class UpdateManager {
38
40
  private handleShortMessage;
39
41
  private feedUpdate;
40
42
  private dispatch;
43
+ private isDuplicateMessage;
41
44
  private collectEntities;
42
45
  private makeGlobalWaiter;
43
46
  private getOrCreateChannel;
@@ -43,6 +43,7 @@ const NO_UPDATES_TIMEOUT_MS = 15 * 60 * 1000;
43
43
  const FAIL_DIFFERENCE_INITIAL_S = 1;
44
44
  const FAIL_DIFFERENCE_CAP_S = 64;
45
45
  const CHANNEL_DIFFERENCE_LIMIT = 100;
46
+ const RECENT_MESSAGE_BUFFER_SIZE = 1000;
46
47
  function isCommonPtsUpdate(update) {
47
48
  return (update instanceof tl_1.Api.UpdateNewMessage ||
48
49
  update instanceof tl_1.Api.UpdateDeleteMessages ||
@@ -79,6 +80,8 @@ class UpdateManager {
79
80
  this.lastUpdateTime = 0;
80
81
  this.channels = new Map();
81
82
  this.pendingSeq = [];
83
+ this.recentMessageKeys = new Set();
84
+ this.recentMessageQueue = [];
82
85
  this.fetchingDifference = false;
83
86
  this.failTimeoutS = FAIL_DIFFERENCE_INITIAL_S;
84
87
  this.channelFailTimeoutS = new Map();
@@ -306,9 +309,39 @@ class UpdateManager {
306
309
  }
307
310
  dispatch(update, payload) {
308
311
  var _a;
312
+ if (this.isDuplicateMessage(update)) {
313
+ this.client._log.debug("Skip duplicate message update (already dispatched)");
314
+ return;
315
+ }
309
316
  update._entities = (_a = payload.entities) !== null && _a !== void 0 ? _a : new Map();
310
317
  (0, updates_1._dispatchUpdate)(this.client, { update }).catch((e) => this.client._log.error(`Error dispatching update: ${e}`));
311
318
  }
319
+ isDuplicateMessage(update) {
320
+ if (!(update instanceof tl_1.Api.UpdateNewMessage) &&
321
+ !(update instanceof tl_1.Api.UpdateNewChannelMessage)) {
322
+ return false;
323
+ }
324
+ const message = update.message;
325
+ if ((message === null || message === void 0 ? void 0 : message.id) == undefined || message.peerId == undefined)
326
+ return false;
327
+ let peerId;
328
+ try {
329
+ peerId = utils.getPeerId(message.peerId);
330
+ }
331
+ catch (_a) {
332
+ return false;
333
+ }
334
+ const key = `${peerId}:${message.id}`;
335
+ if (this.recentMessageKeys.has(key))
336
+ return true;
337
+ this.recentMessageKeys.add(key);
338
+ this.recentMessageQueue.push(key);
339
+ if (this.recentMessageQueue.length > RECENT_MESSAGE_BUFFER_SIZE) {
340
+ const old = this.recentMessageQueue.shift();
341
+ this.recentMessageKeys.delete(old);
342
+ }
343
+ return false;
344
+ }
312
345
  collectEntities(users, chats) {
313
346
  const entities = new Map();
314
347
  for (const x of [...users, ...chats]) {
package/client/auth.d.ts CHANGED
@@ -86,6 +86,9 @@ export interface QrCodeAuthParams extends UserPasswordAuthParams {
86
86
  /** when an error happens during auth this function will be called with the error.<br/>
87
87
  * if this returns true the auth operation will stop. */
88
88
  onError: (err: Error) => Promise<boolean> | void;
89
+ /** an AbortSignal to cancel the QR login flow (e.g. the user closed the page).<br/>
90
+ * when aborted, the flow stops polling and rejects with an `AbortError`. */
91
+ abortSignal?: AbortSignal;
89
92
  }
90
93
  interface ReturnString {
91
94
  (): string;
@@ -115,6 +118,8 @@ export declare function start(client: TelegramClient, authParams: UserAuthParams
115
118
  /** @hidden */
116
119
  export declare function checkAuthorization(client: TelegramClient): Promise<boolean>;
117
120
  /** @hidden */
121
+ export declare function logOut(client: TelegramClient): Promise<boolean>;
122
+ /** @hidden */
118
123
  export declare function signInUser(client: TelegramClient, apiCredentials: ApiCredentials, authParams: UserAuthParams): Promise<Api.TypeUser>;
119
124
  /** @hidden */
120
125
  export declare function signInUserWithQrCode(client: TelegramClient, apiCredentials: ApiCredentials, authParams: QrCodeAuthParams): Promise<Api.TypeUser>;
package/client/auth.js CHANGED
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.start = start;
37
37
  exports.checkAuthorization = checkAuthorization;
38
+ exports.logOut = logOut;
38
39
  exports.signInUser = signInUser;
39
40
  exports.signInUserWithQrCode = signInUserWithQrCode;
40
41
  exports.sendCode = sendCode;
@@ -75,6 +76,20 @@ async function checkAuthorization(client) {
75
76
  }
76
77
  }
77
78
  /** @hidden */
79
+ async function logOut(client) {
80
+ let success = true;
81
+ try {
82
+ await client.invoke(new tl_1.Api.auth.LogOut());
83
+ }
84
+ catch (e) {
85
+ client._log.warn("auth.LogOut failed: " + e.message);
86
+ success = false;
87
+ }
88
+ await client.disconnect();
89
+ await client.session.delete();
90
+ return success;
91
+ }
92
+ /** @hidden */
78
93
  async function signInUser(client, apiCredentials, authParams) {
79
94
  let phoneNumber = "";
80
95
  let phoneCodeHash = "";
@@ -233,17 +248,23 @@ async function signInUser(client, apiCredentials, authParams) {
233
248
  await authParams.onError(new Error("Auth failed"));
234
249
  return client.signInUser(apiCredentials, authParams);
235
250
  }
251
+ function qrAbortError() {
252
+ const err = new Error("QR login aborted");
253
+ err.name = "AbortError";
254
+ return err;
255
+ }
236
256
  /** @hidden */
237
257
  async function signInUserWithQrCode(client, apiCredentials, authParams) {
238
- let isScanningComplete = false;
239
258
  if (authParams.qrCode == undefined) {
240
259
  throw new Error("qrCode callback not defined");
241
260
  }
261
+ const { abortSignal } = authParams;
262
+ if (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted)
263
+ throw qrAbortError();
264
+ let isScanningComplete = false;
265
+ const stopped = () => isScanningComplete || !!(abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted);
242
266
  const inputPromise = (async () => {
243
- while (1) {
244
- if (isScanningComplete) {
245
- break;
246
- }
267
+ while (!stopped()) {
247
268
  const result = await client.invoke(new tl_1.Api.auth.ExportLoginToken({
248
269
  apiId: Number(apiCredentials.apiId),
249
270
  apiHash: apiCredentials.apiHash,
@@ -260,21 +281,24 @@ async function signInUserWithQrCode(client, apiCredentials, authParams) {
260
281
  await (0, Helpers_1.sleep)(QR_CODE_TIMEOUT);
261
282
  }
262
283
  })();
263
- const updatePromise = new Promise((resolve) => {
264
- client.addEventHandler((update) => {
265
- if (update instanceof tl_1.Api.UpdateLoginToken) {
266
- resolve(undefined);
267
- }
268
- });
269
- });
284
+ const Raw = require("../events/Raw").Raw;
285
+ const rawEvent = new Raw({});
286
+ const onUpdate = (update) => {
287
+ if (update instanceof tl_1.Api.UpdateLoginToken)
288
+ resolveUpdate();
289
+ };
290
+ let resolveUpdate;
291
+ const updatePromise = new Promise((resolve) => (resolveUpdate = resolve));
292
+ client.addEventHandler(onUpdate, rawEvent);
293
+ const abortPromise = new Promise((_, reject) => abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.addEventListener("abort", () => reject(qrAbortError()), {
294
+ once: true,
295
+ }));
270
296
  try {
271
- await Promise.race([updatePromise, inputPromise]);
272
- }
273
- catch (err) {
274
- throw err;
297
+ await Promise.race([updatePromise, inputPromise, abortPromise]);
275
298
  }
276
299
  finally {
277
300
  isScanningComplete = true;
301
+ client.removeEventHandler(onUpdate, rawEvent);
278
302
  }
279
303
  try {
280
304
  const result2 = await client.invoke(new tl_1.Api.auth.ExportLoginToken({
@@ -1,9 +1,6 @@
1
1
  import { ObfuscatedConnection } from "./Connection";
2
2
  import { AbridgedPacketCodec } from "./TCPAbridged";
3
3
  import { Logger, PromisedNetSockets } from "../../extensions";
4
- /**
5
- * Base proxy configuration shared by all proxy types.
6
- */
7
4
  interface BasicProxyInterface {
8
5
  /** Proxy server IP address. */
9
6
  ip: string;
@@ -38,9 +35,13 @@ interface BasicProxyInterface {
38
35
  */
39
36
  export type MTProxyType = BasicProxyInterface & {
40
37
  /**
41
- * The proxy secret (hex or base64 encoded).
42
- * Must decode to exactly 16 bytes. Secrets prefixed with `dd` (fake-TLS)
43
- * are also supported the leading byte is stripped automatically.
38
+ * The proxy secret (hex or base64 encoded). Three formats are accepted:
39
+ * - 16-byte plain secret.
40
+ * - 17 bytes with a leading `dd` byte random-padding mode.
41
+ * - A leading `ee` byte + 16 bytes of secret + UTF-8 SNI domain bytes —
42
+ * fake-TLS mode. The connection is wrapped in a spoofed TLS 1.2
43
+ * handshake and all MTProxy bytes are tunneled inside TLS
44
+ * application_data records.
44
45
  */
45
46
  secret: string;
46
47
  /** Must be `true` to identify this as an MTProxy configuration. */
@@ -50,12 +51,11 @@ export type MTProxyType = BasicProxyInterface & {
50
51
  * Configuration for SOCKS4/SOCKS5 proxy connections.
51
52
  *
52
53
  * SOCKS proxies are handled at the socket level via the `socks` package and
53
- * work transparently with any connection type (Full, Abridged, Obfuscated).
54
- * SOCKS5 supports username/password authentication.
54
+ * work transparently with any connection type. SOCKS5 supports username/
55
+ * password authentication.
55
56
  *
56
57
  * @example
57
58
  * ```ts
58
- * // SOCKS5 with authentication
59
59
  * const client = new TelegramClient(session, apiId, apiHash, {
60
60
  * proxy: {
61
61
  * socksType: 5,
@@ -65,15 +65,6 @@ export type MTProxyType = BasicProxyInterface & {
65
65
  * password: "pass",
66
66
  * },
67
67
  * });
68
- *
69
- * // SOCKS4 (no authentication)
70
- * const client = new TelegramClient(session, apiId, apiHash, {
71
- * proxy: {
72
- * socksType: 4,
73
- * ip: "127.0.0.1",
74
- * port: 1080,
75
- * },
76
- * });
77
68
  * ```
78
69
  */
79
70
  export type SocksProxyType = BasicProxyInterface & {
@@ -81,32 +72,10 @@ export type SocksProxyType = BasicProxyInterface & {
81
72
  socksType: 4 | 5;
82
73
  };
83
74
  /**
84
- * Union type for all supported proxy configurations.
85
- *
86
- * Pass to `TelegramClientParams.proxy` to route all connections through a proxy.
87
- *
88
- * - {@link MTProxyType} — Telegram MTProxy (obfuscated, secret-based)
89
- * - {@link SocksProxyType} — SOCKS4/SOCKS5 (general-purpose, optional auth)
75
+ * Union type for all supported proxy configurations. Pass to
76
+ * `TelegramClientParams.proxy` to route all connections through a proxy.
90
77
  */
91
78
  export type ProxyInterface = MTProxyType | SocksProxyType;
92
- /**
93
- * Handles the MTProxy obfuscation layer: generates the initial handshake header
94
- * and wraps all reads/writes in AES-CTR encryption keyed by the proxy secret.
95
- * @internal
96
- */
97
- declare class MTProxyIO {
98
- header?: Buffer;
99
- private connection;
100
- private _encrypt?;
101
- private _decrypt?;
102
- private _packetClass;
103
- private _secret;
104
- private _dcId;
105
- constructor(connection: TCPMTProxy);
106
- initHeader(): Promise<void>;
107
- read(n: number): Promise<Buffer<any>>;
108
- write(data: Buffer): void;
109
- }
110
79
  interface TCPMTProxyInterfaceParams {
111
80
  ip: string;
112
81
  port: number;
@@ -116,25 +85,47 @@ interface TCPMTProxyInterfaceParams {
116
85
  socket: typeof PromisedNetSockets;
117
86
  }
118
87
  /**
119
- * Connection that routes traffic through a Telegram MTProxy server.
88
+ * Generates the MTProxy obfuscation header and wraps every subsequent
89
+ * read/write in AES-CTR. MTProxy derives mirrored CTR streams from the same
90
+ * 64-byte header — bytes 8..56 supply the client→server key/iv, and the
91
+ * reversed copy supplies the server→client side.
120
92
  *
121
- * Connects to the proxy address (not Telegram directly), performs the
122
- * MTProxy obfuscated handshake using the provided secret, and then tunnels
123
- * all MTProto traffic through the proxy with AES-CTR encryption.
93
+ * @internal
94
+ */
95
+ declare class MTProxyIO {
96
+ header?: Buffer;
97
+ private readonly stream;
98
+ private readonly packetCodec;
99
+ private readonly secret;
100
+ private readonly dcId;
101
+ private encryptor?;
102
+ private decryptor?;
103
+ constructor(connection: TCPMTProxy);
104
+ initHeader(): Promise<void>;
105
+ read(n: number): Promise<Buffer>;
106
+ write(data: Buffer): void;
107
+ }
108
+ /**
109
+ * Connection that routes traffic through a Telegram MTProxy server. Connects
110
+ * to the proxy address (not Telegram directly), performs the MTProxy
111
+ * obfuscated handshake, and tunnels MTProto traffic through with AES-CTR
112
+ * encryption. When the secret carries a fake-TLS prefix the underlying
113
+ * socket is first wrapped in a {@link FakeTlsSocket}.
124
114
  *
125
- * Not intended to be used directly — use {@link ConnectionTCPMTProxyAbridged} instead,
126
- * or simply pass an {@link MTProxyType} config to `TelegramClientParams.proxy`
115
+ * Not intended to be used directly — use {@link ConnectionTCPMTProxyAbridged}
116
+ * instead, or pass an {@link MTProxyType} config to `TelegramClientParams.proxy`
127
117
  * and the client will select the correct connection class automatically.
128
118
  */
129
119
  export declare class TCPMTProxy extends ObfuscatedConnection {
130
120
  ObfuscatedIO: typeof MTProxyIO;
131
121
  _secret: Buffer;
132
- constructor({ ip, port, dcId, loggers, proxy, socket, }: TCPMTProxyInterfaceParams);
122
+ _fakeTlsDomain?: string;
123
+ constructor({ dcId, loggers, proxy, socket, }: TCPMTProxyInterfaceParams);
124
+ _initConn(): Promise<void>;
133
125
  }
134
126
  /**
135
- * MTProxy connection using the Abridged packet codec.
136
- *
137
- * This is the connection class automatically selected when `proxy.MTProxy` is `true`.
127
+ * MTProxy connection using the Abridged packet codec — automatically selected
128
+ * when `proxy.MTProxy` is `true`.
138
129
  */
139
130
  export declare class ConnectionTCPMTProxyAbridged extends TCPMTProxy {
140
131
  PacketCodecClass: typeof AbridgedPacketCodec;
@@ -1,136 +1,403 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ConnectionTCPMTProxyAbridged = exports.TCPMTProxy = void 0;
4
+ const node_crypto_1 = require("node:crypto");
4
5
  const Connection_1 = require("./Connection");
5
6
  const TCPAbridged_1 = require("./TCPAbridged");
6
7
  const Helpers_1 = require("../../Helpers");
7
8
  const CTR_1 = require("../../crypto/CTR");
9
+ const SECRET_LEN = 16;
10
+ const PREFIX_FAKE_TLS = 0xee;
11
+ const PREFIX_DD_PADDING = 0xdd;
12
+ function decodeSecret(input) {
13
+ return /^[0-9a-f]+$/i.test(input)
14
+ ? Buffer.from(input, "hex")
15
+ : Buffer.from(input, "base64");
16
+ }
17
+ function parseProxySecret(input) {
18
+ if (!input) {
19
+ throw new Error("MTProxy: secret is required");
20
+ }
21
+ const raw = decodeSecret(input);
22
+ if (raw.length > 1 + SECRET_LEN && raw[0] === PREFIX_FAKE_TLS) {
23
+ return {
24
+ key: Buffer.from(raw.subarray(1, 1 + SECRET_LEN)),
25
+ fakeTlsDomain: raw.subarray(1 + SECRET_LEN).toString("utf8"),
26
+ };
27
+ }
28
+ if (raw.length === 1 + SECRET_LEN && raw[0] === PREFIX_DD_PADDING) {
29
+ return { key: Buffer.from(raw.subarray(1)) };
30
+ }
31
+ if (raw.length === SECRET_LEN) {
32
+ return { key: raw };
33
+ }
34
+ throw new Error(`MTProxy: secret must be ${SECRET_LEN} bytes, ` +
35
+ `${1 + SECRET_LEN} with 0xdd prefix, ` +
36
+ `or 0xee + ${SECRET_LEN} bytes + domain — got ${raw.length} bytes`);
37
+ }
38
+ const OBF_HEADER_LEN = 64;
39
+ const OBF_TAG_OFFSET = 56;
40
+ const OBF_DC_OFFSET = 60;
41
+ /** Header prefixes that collide with HTTP or other protocol sniffers. */
42
+ const FORBIDDEN_HEADER_PREFIXES = [
43
+ Buffer.from("50567247", "hex"),
44
+ Buffer.from("474554", "hex"),
45
+ Buffer.from("504f5354", "hex"),
46
+ Buffer.from("eeeeeeee", "hex"),
47
+ ];
48
+ function pickObfuscationHeader() {
49
+ while (true) {
50
+ const candidate = (0, Helpers_1.generateRandomBytes)(OBF_HEADER_LEN);
51
+ if (candidate[0] === 0xef)
52
+ continue;
53
+ if (candidate.subarray(4, 8).equals(Buffer.alloc(4)))
54
+ continue;
55
+ const head4 = candidate.subarray(0, 4);
56
+ if (FORBIDDEN_HEADER_PREFIXES.some((p) => p.equals(head4)))
57
+ continue;
58
+ return candidate;
59
+ }
60
+ }
8
61
  /**
9
- * Handles the MTProxy obfuscation layer: generates the initial handshake header
10
- * and wraps all reads/writes in AES-CTR encryption keyed by the proxy secret.
62
+ * Generates the MTProxy obfuscation header and wraps every subsequent
63
+ * read/write in AES-CTR. MTProxy derives mirrored CTR streams from the same
64
+ * 64-byte header — bytes 8..56 supply the client→server key/iv, and the
65
+ * reversed copy supplies the server→client side.
66
+ *
11
67
  * @internal
12
68
  */
13
69
  class MTProxyIO {
14
70
  constructor(connection) {
15
- this.header = undefined;
16
- this.connection = connection.socket;
17
- this._packetClass =
71
+ this.stream = connection.socket;
72
+ this.packetCodec =
18
73
  connection.PacketCodecClass;
19
- this._secret = connection._secret;
20
- this._dcId = connection._dcId;
74
+ this.secret = connection._secret;
75
+ this.dcId = connection._dcId;
21
76
  }
22
77
  async initHeader() {
23
- let secret = this._secret;
24
- const isDD = secret.length == 17 && secret[0] == 0xdd;
25
- secret = isDD ? secret.slice(1) : secret;
26
- if (secret.length != 16) {
27
- throw new Error("MTProxy secret must be a hex-string representing 16 bytes");
28
- }
29
- const keywords = [
30
- Buffer.from("50567247", "hex"),
31
- Buffer.from("474554", "hex"),
32
- Buffer.from("504f5354", "hex"),
33
- Buffer.from("eeeeeeee", "hex"),
34
- ];
35
- let random;
36
- // eslint-disable-next-line no-constant-condition
37
- while (true) {
38
- random = (0, Helpers_1.generateRandomBytes)(64);
39
- if (random[0] !== 0xef &&
40
- !random.slice(4, 8).equals(Buffer.alloc(4))) {
41
- let ok = true;
42
- for (const key of keywords) {
43
- if (key.equals(random.slice(0, 4))) {
44
- ok = false;
45
- break;
46
- }
47
- }
48
- if (ok) {
49
- break;
50
- }
51
- }
78
+ if (this.secret.length !== SECRET_LEN) {
79
+ throw new Error(`MTProxy: secret must be ${SECRET_LEN} bytes, got ${this.secret.length}`);
52
80
  }
53
- random = random.toJSON().data;
54
- const randomReversed = Buffer.from(random.slice(8, 56)).reverse();
55
- // Encryption has "continuous buffer" enabled
56
- const encryptKey = await (0, Helpers_1.sha256)(Buffer.concat([Buffer.from(random.slice(8, 40)), secret]));
57
- const encryptIv = Buffer.from(random.slice(40, 56));
58
- const decryptKey = await (0, Helpers_1.sha256)(Buffer.concat([Buffer.from(randomReversed.slice(0, 32)), secret]));
59
- const decryptIv = Buffer.from(randomReversed.slice(32, 48));
60
- const encryptor = new CTR_1.CTR(encryptKey, encryptIv);
61
- const decryptor = new CTR_1.CTR(decryptKey, decryptIv);
62
- random = Buffer.concat([
63
- Buffer.from(random.slice(0, 56)),
64
- this._packetClass.obfuscateTag,
65
- Buffer.from(random.slice(60)),
66
- ]);
67
- const dcIdBytes = Buffer.alloc(2);
68
- dcIdBytes.writeInt8(this._dcId, 0);
69
- random = Buffer.concat([
70
- Buffer.from(random.slice(0, 60)),
71
- dcIdBytes,
72
- Buffer.from(random.slice(62)),
73
- ]);
74
- random = Buffer.concat([
75
- Buffer.from(random.slice(0, 56)),
76
- Buffer.from(encryptor.encrypt(random).slice(56, 64)),
77
- Buffer.from(random.slice(64)),
78
- ]);
79
- this.header = random;
80
- this._encrypt = encryptor;
81
- this._decrypt = decryptor;
81
+ const header = pickObfuscationHeader();
82
+ const reversed = Buffer.from(header.subarray(8, 56)).reverse();
83
+ const encryptKey = await (0, Helpers_1.sha256)(Buffer.concat([header.subarray(8, 40), this.secret]));
84
+ const encryptIv = Buffer.from(header.subarray(40, 56));
85
+ const decryptKey = await (0, Helpers_1.sha256)(Buffer.concat([reversed.subarray(0, 32), this.secret]));
86
+ const decryptIv = Buffer.from(reversed.subarray(32, 48));
87
+ this.encryptor = new CTR_1.CTR(encryptKey, encryptIv);
88
+ this.decryptor = new CTR_1.CTR(decryptKey, decryptIv);
89
+ // Stamp protocol tag and DC id (low byte first, byte 61 zeroed —
90
+ // matches gramjs / official client behavior, even for negative DC ids).
91
+ this.packetCodec.obfuscateTag.copy(header, OBF_TAG_OFFSET);
92
+ header.writeInt8(this.dcId, OBF_DC_OFFSET);
93
+ header[OBF_DC_OFFSET + 1] = 0;
94
+ // Re-encrypt the stamped tail in place — the CTR counter ends up
95
+ // advanced by all 64 bytes, matching the server's view.
96
+ const encryptedTail = this.encryptor
97
+ .encrypt(header)
98
+ .subarray(OBF_TAG_OFFSET, OBF_HEADER_LEN);
99
+ encryptedTail.copy(header, OBF_TAG_OFFSET);
100
+ this.header = header;
82
101
  }
83
102
  async read(n) {
84
- const data = await this.connection.readExactly(n);
85
- return this._decrypt.encrypt(data);
103
+ const data = await this.stream.readExactly(n);
104
+ return this.decryptor.encrypt(data);
86
105
  }
87
106
  write(data) {
88
- this.connection.write(this._encrypt.encrypt(data));
107
+ this.stream.write(this.encryptor.encrypt(data));
108
+ }
109
+ }
110
+ const TLS_RECORD_HEADER_LEN = 5;
111
+ const TLS_RECORD_MAX_PAYLOAD = 16384;
112
+ const TLS_RECORD_HANDSHAKE = 0x16;
113
+ const TLS_RECORD_CHANGE_CIPHER_SPEC = 0x14;
114
+ const TLS_RECORD_APPLICATION_DATA = 0x17;
115
+ const TLS_APP_DATA_PREFIX = Buffer.from([0x17, 0x03, 0x03]);
116
+ const FAKE_TLS_HELLO_SIZE = 517;
117
+ const FAKE_TLS_HELLO_RANDOM_OFFSET = 11;
118
+ const FAKE_TLS_HELLO_RANDOM_LEN = 32;
119
+ /** 5-byte TLS record + 4-byte handshake header + 2-byte version = 114. */
120
+ const FAKE_TLS_FIXED_HEADER_SIZE = 114;
121
+ const FAKE_TLS_RANDOM_TIMESTAMP_XOR_OFFSET = 28;
122
+ const GREASE = 0x0a;
123
+ async function readTlsRecord(stream) {
124
+ const hdr = await stream.readExactly(TLS_RECORD_HEADER_LEN);
125
+ const type = hdr[0];
126
+ const len = hdr.readUInt16BE(3);
127
+ const payload = await stream.readExactly(len);
128
+ return { type, payload };
129
+ }
130
+ function tlsRecordTypeName(type) {
131
+ switch (type) {
132
+ case TLS_RECORD_HANDSHAKE:
133
+ return "Handshake";
134
+ case TLS_RECORD_CHANGE_CIPHER_SPEC:
135
+ return "ChangeCipherSpec";
136
+ case TLS_RECORD_APPLICATION_DATA:
137
+ return "ApplicationData";
138
+ default:
139
+ return `0x${type.toString(16).padStart(2, "0")}`;
140
+ }
141
+ }
142
+ function tlsExtension(type, body) {
143
+ const content = Buffer.isBuffer(body) ? body : Buffer.from(body);
144
+ const out = Buffer.alloc(4 + content.length);
145
+ out.writeUInt16BE(type, 0);
146
+ out.writeUInt16BE(content.length, 2);
147
+ content.copy(out, 4);
148
+ return out;
149
+ }
150
+ function sniExtension(domain) {
151
+ const dom = Buffer.from(domain, "utf8");
152
+ const body = Buffer.alloc(5 + dom.length);
153
+ body.writeUInt16BE(dom.length + 3, 0);
154
+ body.writeUInt8(0x00, 2);
155
+ body.writeUInt16BE(dom.length, 3);
156
+ dom.copy(body, 5);
157
+ return tlsExtension(0x0000, body);
158
+ }
159
+ function keyShareExtension() {
160
+ const x25519Pub = (0, Helpers_1.generateRandomBytes)(32);
161
+ const entries = Buffer.concat([
162
+ Buffer.from([GREASE, GREASE, 0x00, 0x01, 0x00]),
163
+ Buffer.from([0x00, 0x1d, 0x00, 0x20]),
164
+ x25519Pub,
165
+ ]);
166
+ const body = Buffer.alloc(2 + entries.length);
167
+ body.writeUInt16BE(entries.length, 0);
168
+ entries.copy(body, 2);
169
+ return tlsExtension(0x0033, body);
170
+ }
171
+ /**
172
+ * Input-independent extensions in Chrome 105+ order. SNI and key_share are
173
+ * spliced in at runtime (see `buildClientHello`). Layout is intentionally
174
+ * stable for JA3 fingerprint similarity.
175
+ */
176
+ const STATIC_EXTENSIONS = [
177
+ tlsExtension(0x0a0a, []),
178
+ tlsExtension(0x0017, []),
179
+ tlsExtension(0xff01, [0x00]),
180
+ tlsExtension(0x000a, [
181
+ 0x00, 0x08, GREASE, GREASE, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18,
182
+ ]),
183
+ tlsExtension(0x000b, [0x01, 0x00]),
184
+ tlsExtension(0x0023, []),
185
+ tlsExtension(0x0010, [
186
+ 0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31,
187
+ 0x2e, 0x31,
188
+ ]),
189
+ tlsExtension(0x0005, [0x01, 0x00, 0x00, 0x00, 0x00]),
190
+ tlsExtension(0x000d, [
191
+ 0x00, 0x10, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05,
192
+ 0x05, 0x01, 0x08, 0x06, 0x06, 0x01,
193
+ ]),
194
+ tlsExtension(0x0012, []),
195
+ tlsExtension(0x002d, [0x01, 0x01]),
196
+ tlsExtension(0x002b, [0x06, GREASE, GREASE, 0x03, 0x04, 0x03, 0x03]),
197
+ tlsExtension(0x001b, [0x02, 0x00, 0x02]),
198
+ tlsExtension(0x0a0a, [0x00]),
199
+ ];
200
+ const STATIC_EXTENSIONS_LEN = STATIC_EXTENSIONS.reduce((s, e) => s + e.length, 0);
201
+ const CIPHER_SUITES = Buffer.from([
202
+ GREASE, GREASE,
203
+ 0x13, 0x01, 0x13, 0x02, 0x13, 0x03,
204
+ 0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30,
205
+ 0xcc, 0xa9, 0xcc, 0xa8,
206
+ 0xc0, 0x13, 0xc0, 0x14,
207
+ 0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x35,
208
+ ]);
209
+ /**
210
+ * Builds the 517-byte Chrome-like TLS 1.2 ClientHello. The 32-byte `random`
211
+ * field at offset 11 is left zeroed — the caller stamps it with the
212
+ * HMAC-derived value before sending.
213
+ */
214
+ function buildClientHello(domain, sessionId) {
215
+ const sni = sniExtension(domain);
216
+ const keyShare = keyShareExtension();
217
+ let extensionsLen = STATIC_EXTENSIONS_LEN + sni.length + keyShare.length;
218
+ const paddingPayloadLen = FAKE_TLS_HELLO_SIZE - FAKE_TLS_FIXED_HEADER_SIZE - extensionsLen - 4;
219
+ if (paddingPayloadLen < 0) {
220
+ throw new Error(`FakeTLS: domain too long (${domain.length} bytes); ClientHello overflows by ${-paddingPayloadLen} bytes`);
221
+ }
222
+ const padding = tlsExtension(0x0015, Buffer.alloc(paddingPayloadLen));
223
+ extensionsLen += padding.length;
224
+ // SNI follows the leading GREASE; key_share follows the SCT extension.
225
+ const KEY_SHARE_INSERT_AFTER = 11;
226
+ const extensions = [
227
+ STATIC_EXTENSIONS[0],
228
+ sni,
229
+ ...STATIC_EXTENSIONS.slice(1, KEY_SHARE_INSERT_AFTER + 1),
230
+ keyShare,
231
+ ...STATIC_EXTENSIONS.slice(KEY_SHARE_INSERT_AFTER + 1),
232
+ padding,
233
+ ];
234
+ const hello = Buffer.concat([
235
+ Buffer.from([0x16, 0x03, 0x01, 0x02, 0x00]),
236
+ Buffer.from([0x01, 0x00, 0x01, 0xfc]),
237
+ Buffer.from([0x03, 0x03]),
238
+ Buffer.alloc(FAKE_TLS_HELLO_RANDOM_LEN),
239
+ Buffer.from([sessionId.length]),
240
+ sessionId,
241
+ Buffer.from([
242
+ (CIPHER_SUITES.length >> 8) & 0xff,
243
+ CIPHER_SUITES.length & 0xff,
244
+ ]),
245
+ CIPHER_SUITES,
246
+ Buffer.from([0x01, 0x00]),
247
+ Buffer.from([(extensionsLen >> 8) & 0xff, extensionsLen & 0xff]),
248
+ ...extensions,
249
+ ]);
250
+ if (hello.length !== FAKE_TLS_HELLO_SIZE) {
251
+ throw new Error(`FakeTLS: ClientHello size mismatch — got ${hello.length}, expected ${FAKE_TLS_HELLO_SIZE}`);
89
252
  }
253
+ return hello;
90
254
  }
91
255
  /**
92
- * Connection that routes traffic through a Telegram MTProxy server.
256
+ * Wraps a {@link PromisedNetSockets} in MTProto fake-TLS framing. On
257
+ * {@link handshake}: sends a Chrome-like ClientHello with the random field
258
+ * stamped to `HMAC-SHA256(secret, hello)` (last 4 bytes XOR'd with unix
259
+ * time), then verifies the server's random equals
260
+ * `HMAC-SHA256(secret, client_random || server_response_zeroed)`. Post-
261
+ * handshake, all bytes ride inside TLS application_data records.
93
262
  *
94
- * Connects to the proxy address (not Telegram directly), performs the
95
- * MTProxy obfuscated handshake using the provided secret, and then tunnels
96
- * all MTProto traffic through the proxy with AES-CTR encryption.
263
+ * Exposes only the subset of {@link PromisedNetSockets} consumed by
264
+ * {@link MTProxyIO} and the {@link ObfuscatedConnection} base class so it
265
+ * can be hot-swapped in place of the raw socket.
97
266
  *
98
- * Not intended to be used directly — use {@link ConnectionTCPMTProxyAbridged} instead,
99
- * or simply pass an {@link MTProxyType} config to `TelegramClientParams.proxy`
267
+ * @internal
268
+ */
269
+ class FakeTlsSocket {
270
+ constructor(inner, secret, domain) {
271
+ this.clientRandom = Buffer.alloc(0);
272
+ this.readBuf = Buffer.alloc(0);
273
+ if (secret.length !== SECRET_LEN) {
274
+ throw new Error(`FakeTLS: secret must be ${SECRET_LEN} bytes, got ${secret.length}`);
275
+ }
276
+ this.inner = inner;
277
+ this.secret = secret;
278
+ this.domain = domain;
279
+ }
280
+ async handshake() {
281
+ const hello = this.sendClientHello();
282
+ this.inner.write(hello);
283
+ await this.readAndVerifyServerHandshake();
284
+ }
285
+ write(data) {
286
+ for (let off = 0; off < data.length; off += TLS_RECORD_MAX_PAYLOAD) {
287
+ const chunkLen = Math.min(TLS_RECORD_MAX_PAYLOAD, data.length - off);
288
+ const hdr = Buffer.alloc(TLS_RECORD_HEADER_LEN);
289
+ TLS_APP_DATA_PREFIX.copy(hdr, 0);
290
+ hdr.writeUInt16BE(chunkLen, 3);
291
+ this.inner.write(Buffer.concat([hdr, data.subarray(off, off + chunkLen)]));
292
+ }
293
+ }
294
+ async readExactly(n) {
295
+ while (this.readBuf.length < n) {
296
+ const { type, payload } = await readTlsRecord(this.inner);
297
+ if (type !== TLS_RECORD_APPLICATION_DATA)
298
+ continue;
299
+ this.readBuf = Buffer.concat([this.readBuf, payload]);
300
+ }
301
+ const out = Buffer.from(this.readBuf.subarray(0, n));
302
+ this.readBuf = this.readBuf.subarray(n);
303
+ return out;
304
+ }
305
+ async close() {
306
+ await this.inner.close();
307
+ }
308
+ sendClientHello() {
309
+ const sessionId = (0, Helpers_1.generateRandomBytes)(32);
310
+ const hello = buildClientHello(this.domain, sessionId);
311
+ const stamp = (0, node_crypto_1.createHmac)("sha256", this.secret).update(hello).digest();
312
+ const now = Math.floor(Date.now() / 1000) >>> 0;
313
+ const lastWord = (stamp.readUInt32BE(FAKE_TLS_RANDOM_TIMESTAMP_XOR_OFFSET) ^ now) >>>
314
+ 0;
315
+ stamp.writeUInt32BE(lastWord, FAKE_TLS_RANDOM_TIMESTAMP_XOR_OFFSET);
316
+ stamp.copy(hello, FAKE_TLS_HELLO_RANDOM_OFFSET);
317
+ this.clientRandom = stamp;
318
+ return hello;
319
+ }
320
+ // Expects the canonical 3-record server response. Anything else fails
321
+ // loudly rather than silently corrupting the downstream MTProxy stream.
322
+ async readAndVerifyServerHandshake() {
323
+ const expectedTypes = [
324
+ TLS_RECORD_HANDSHAKE,
325
+ TLS_RECORD_CHANGE_CIPHER_SPEC,
326
+ TLS_RECORD_APPLICATION_DATA,
327
+ ];
328
+ const chunks = [];
329
+ let serverRandomOffset = -1;
330
+ let offset = 0;
331
+ for (let i = 0; i < expectedTypes.length; i++) {
332
+ const { type, payload } = await readTlsRecord(this.inner);
333
+ if (type !== expectedTypes[i]) {
334
+ throw new Error(`FakeTLS: expected ${tlsRecordTypeName(expectedTypes[i])} at record ${i}, got ${tlsRecordTypeName(type)}`);
335
+ }
336
+ if (i === 0) {
337
+ serverRandomOffset = offset + FAKE_TLS_HELLO_RANDOM_OFFSET;
338
+ }
339
+ const hdr = Buffer.alloc(TLS_RECORD_HEADER_LEN);
340
+ hdr[0] = type;
341
+ hdr[1] = 0x03;
342
+ hdr[2] = 0x03;
343
+ hdr.writeUInt16BE(payload.length, 3);
344
+ chunks.push(hdr, payload);
345
+ offset += TLS_RECORD_HEADER_LEN + payload.length;
346
+ }
347
+ const serverResp = Buffer.concat(chunks);
348
+ const serverRandom = Buffer.from(serverResp.subarray(serverRandomOffset, serverRandomOffset + FAKE_TLS_HELLO_RANDOM_LEN));
349
+ serverResp.fill(0, serverRandomOffset, serverRandomOffset + FAKE_TLS_HELLO_RANDOM_LEN);
350
+ const expected = (0, node_crypto_1.createHmac)("sha256", this.secret)
351
+ .update(this.clientRandom)
352
+ .update(serverResp)
353
+ .digest();
354
+ if (!expected.equals(serverRandom)) {
355
+ throw new Error("FakeTLS: server HMAC verification failed");
356
+ }
357
+ }
358
+ }
359
+ /**
360
+ * Connection that routes traffic through a Telegram MTProxy server. Connects
361
+ * to the proxy address (not Telegram directly), performs the MTProxy
362
+ * obfuscated handshake, and tunnels MTProto traffic through with AES-CTR
363
+ * encryption. When the secret carries a fake-TLS prefix the underlying
364
+ * socket is first wrapped in a {@link FakeTlsSocket}.
365
+ *
366
+ * Not intended to be used directly — use {@link ConnectionTCPMTProxyAbridged}
367
+ * instead, or pass an {@link MTProxyType} config to `TelegramClientParams.proxy`
100
368
  * and the client will select the correct connection class automatically.
101
369
  */
102
370
  class TCPMTProxy extends Connection_1.ObfuscatedConnection {
103
- constructor({ ip, port, dcId, loggers, proxy, socket, }) {
371
+ constructor({ dcId, loggers, proxy, socket, }) {
104
372
  super({
105
373
  ip: proxy.ip,
106
374
  port: proxy.port,
107
- dcId: dcId,
108
- loggers: loggers,
109
- socket: socket,
110
- proxy: proxy,
375
+ dcId,
376
+ loggers,
377
+ socket,
378
+ proxy,
111
379
  });
112
380
  this.ObfuscatedIO = MTProxyIO;
113
381
  if (!("MTProxy" in proxy)) {
114
- throw new Error("This connection only supports MPTProxies");
115
- }
116
- if (!proxy.secret) {
117
- throw new Error("You need to provide the secret for the MTProxy");
382
+ throw new Error("This connection only supports MTProxies");
118
383
  }
119
- if (proxy.secret && proxy.secret.match(/^[0-9a-f]+$/i)) {
120
- // probably hex
121
- this._secret = Buffer.from(proxy.secret, "hex");
122
- }
123
- else {
124
- // probably b64
125
- this._secret = Buffer.from(proxy.secret, "base64");
384
+ const parsed = parseProxySecret(proxy.secret);
385
+ this._secret = parsed.key;
386
+ this._fakeTlsDomain = parsed.fakeTlsDomain;
387
+ }
388
+ async _initConn() {
389
+ if (this._fakeTlsDomain) {
390
+ const tls = new FakeTlsSocket(this.socket, this._secret, this._fakeTlsDomain);
391
+ await tls.handshake();
392
+ this.socket = tls;
126
393
  }
394
+ await super._initConn();
127
395
  }
128
396
  }
129
397
  exports.TCPMTProxy = TCPMTProxy;
130
398
  /**
131
- * MTProxy connection using the Abridged packet codec.
132
- *
133
- * This is the connection class automatically selected when `proxy.MTProxy` is `true`.
399
+ * MTProxy connection using the Abridged packet codec — automatically selected
400
+ * when `proxy.MTProxy` is `true`.
134
401
  */
135
402
  class ConnectionTCPMTProxyAbridged extends TCPMTProxy {
136
403
  constructor() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teleproto",
3
- "version": "1.225.2",
3
+ "version": "1.225.4",
4
4
  "description": "Telegram MTProto API client library written in TypeScript",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -63,7 +63,7 @@ export declare abstract class Session {
63
63
  * to suit several purposes (e.g. user only provided its ID or wishes
64
64
  * to use a cached username to avoid extra RPC).
65
65
  */
66
- abstract getInputEntity(key: EntityLike): Api.TypeInputPeer;
66
+ abstract getInputEntity(key: EntityLike): Api.TypeInputPeer | Promise<Api.TypeInputPeer>;
67
67
  /**
68
68
  * Returns an ID of the takeout process initialized for this session,
69
69
  * or `None` if there's no were any unfinished takeout requests.
@@ -97,10 +97,10 @@ export declare abstract class Session {
97
97
  */
98
98
  abstract save(): void;
99
99
  /**
100
- * Called upon client.log_out(). Should delete the stored
100
+ * Called upon client.logOut(). Should delete the stored
101
101
  * information from disk since it's not valid anymore.
102
102
  */
103
- abstract delete(): void;
103
+ abstract delete(): void | Promise<void>;
104
104
  /**
105
105
  * Processes the input ``TLObject`` or ``list`` and saves
106
106
  * whatever information is relevant (e.g., ID or access hash).
@@ -115,7 +115,15 @@ class MemorySession extends Abstract_1.Session {
115
115
  close() { }
116
116
  save() { }
117
117
  async load() { }
118
- delete() { }
118
+ delete() {
119
+ this._authKey = undefined;
120
+ this._dcId = 0;
121
+ this._serverAddress = undefined;
122
+ this._port = undefined;
123
+ this._dcAuthKeys.clear();
124
+ this._entities.clear();
125
+ this._updateStates = {};
126
+ }
119
127
  _entityValuesToRow(id, hash, username, phone, name) {
120
128
  // While this is a simple implementation it might be overrode by,
121
129
  // other classes so they don't need to implement the plural form
@@ -24,6 +24,7 @@ export declare class StoreSession extends MemorySession {
24
24
  get testServers(): boolean;
25
25
  set authKey(value: AuthKey | undefined);
26
26
  get authKey(): AuthKey | undefined;
27
+ delete(): void;
27
28
  processEntities(tlo: any): void;
28
29
  getEntityRowsById(id: string | bigInt.BigInteger, exact?: boolean): any;
29
30
  }
@@ -113,13 +113,16 @@ class StoreSession extends Memory_1.MemorySession {
113
113
  get authKey() {
114
114
  return this._authKey;
115
115
  }
116
+ delete() {
117
+ this.store.clearAll();
118
+ super.delete();
119
+ }
116
120
  processEntities(tlo) {
117
121
  const rows = this._entitiesToRows(tlo);
118
122
  if (!rows) {
119
123
  return;
120
124
  }
121
125
  for (const row of rows) {
122
- row.push(new Date().getTime().toString());
123
126
  this.store.set(this.sessionName + row[0], row);
124
127
  }
125
128
  }
@@ -28,5 +28,6 @@ export declare class StringSession extends MemorySession {
28
28
  */
29
29
  static decode(x: string): Buffer<ArrayBuffer>;
30
30
  load(): Promise<void>;
31
+ delete(): void;
31
32
  save(): string;
32
33
  }
@@ -88,6 +88,10 @@ class StringSession extends Memory_1.MemorySession {
88
88
  await this._authKey.setKey(this._key);
89
89
  }
90
90
  }
91
+ delete() {
92
+ this._key = undefined;
93
+ super.delete();
94
+ }
91
95
  save() {
92
96
  if (!this.authKey || !this.serverAddress || !this.port) {
93
97
  return "";
@@ -10,7 +10,7 @@ export declare class Dialog {
10
10
  folderId?: number;
11
11
  archived: boolean;
12
12
  message?: Api.Message;
13
- date: number;
13
+ date?: number;
14
14
  entity?: Entity;
15
15
  inputEntity: Api.TypeInputPeer;
16
16
  id?: bigInt.BigInteger;
@@ -7,13 +7,14 @@ const draft_1 = require("./draft");
7
7
  const Helpers_1 = require("../../Helpers");
8
8
  class Dialog {
9
9
  constructor(client, dialog, entities, message) {
10
+ var _a;
10
11
  this._client = client;
11
12
  this.dialog = dialog;
12
13
  this.pinned = !!dialog.pinned;
13
14
  this.folderId = dialog.folderId;
14
15
  this.archived = dialog.folderId != undefined;
15
16
  this.message = message;
16
- this.date = this.message.date;
17
+ this.date = (_a = this.message) === null || _a === void 0 ? void 0 : _a.date;
17
18
  this.entity = entities.get((0, Utils_1.getPeerId)(dialog.peer));
18
19
  this.inputEntity = (0, Utils_1.getInputPeer)(this.entity);
19
20
  if (this.entity) {