@mtcute/core 0.30.1 → 0.30.2

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.
Files changed (83) hide show
  1. package/highlevel/methods/files/download-iterable.cjs +80 -45
  2. package/highlevel/methods/files/download-iterable.js +80 -45
  3. package/highlevel/methods/files/normalize-input-media.cjs +1 -1
  4. package/highlevel/methods/files/normalize-input-media.js +1 -1
  5. package/highlevel/methods/files/upload-file.cjs +6 -1
  6. package/highlevel/methods/files/upload-file.js +6 -1
  7. package/highlevel/methods/messages/normalize-rich-message.cjs +12 -1
  8. package/highlevel/methods/messages/normalize-rich-message.js +12 -1
  9. package/highlevel/methods/messages/send-common.cjs +3 -2
  10. package/highlevel/methods/messages/send-common.d.cts +4 -1
  11. package/highlevel/methods/messages/send-common.d.ts +4 -1
  12. package/highlevel/methods/messages/send-common.js +3 -2
  13. package/highlevel/types/files/utils.d.cts +9 -0
  14. package/highlevel/types/files/utils.d.ts +9 -0
  15. package/highlevel/updates/manager.cjs +6 -169
  16. package/highlevel/updates/manager.js +6 -169
  17. package/highlevel/utils/index.d.cts +1 -0
  18. package/highlevel/utils/index.d.ts +1 -0
  19. package/highlevel/utils/walk-page-blocks.cjs +164 -0
  20. package/highlevel/utils/walk-page-blocks.d.cts +9 -0
  21. package/highlevel/utils/walk-page-blocks.d.ts +9 -0
  22. package/highlevel/utils/walk-page-blocks.js +159 -0
  23. package/network/authorization.cjs +1 -1
  24. package/network/authorization.js +1 -1
  25. package/network/delay-gate.cjs +29 -0
  26. package/network/delay-gate.d.cts +5 -0
  27. package/network/delay-gate.d.ts +5 -0
  28. package/network/delay-gate.js +24 -0
  29. package/network/delay-gate.test.d.cts +1 -0
  30. package/network/delay-gate.test.d.ts +1 -0
  31. package/network/message-id-store.cjs +30 -0
  32. package/network/message-id-store.d.cts +7 -0
  33. package/network/message-id-store.d.ts +7 -0
  34. package/network/message-id-store.js +25 -0
  35. package/network/message-id-store.test.d.cts +1 -0
  36. package/network/message-id-store.test.d.ts +1 -0
  37. package/network/middlewares/bundle.cjs +2 -0
  38. package/network/middlewares/bundle.d.cts +1 -0
  39. package/network/middlewares/bundle.d.ts +1 -0
  40. package/network/middlewares/bundle.js +2 -0
  41. package/network/middlewares/default.cjs +6 -1
  42. package/network/middlewares/default.js +6 -1
  43. package/network/middlewares/flood-waiter.test.d.cts +1 -0
  44. package/network/middlewares/flood-waiter.test.d.ts +1 -0
  45. package/network/middlewares/internal-errors.test.d.cts +1 -0
  46. package/network/middlewares/internal-errors.test.d.ts +1 -0
  47. package/network/middlewares/media-throttle.cjs +65 -0
  48. package/network/middlewares/media-throttle.d.cts +5 -0
  49. package/network/middlewares/media-throttle.d.ts +5 -0
  50. package/network/middlewares/media-throttle.js +60 -0
  51. package/network/middlewares/media-throttle.test.d.cts +1 -0
  52. package/network/middlewares/media-throttle.test.d.ts +1 -0
  53. package/network/middlewares/on-error.test.d.cts +1 -0
  54. package/network/middlewares/on-error.test.d.ts +1 -0
  55. package/network/middlewares/on-method.test.d.cts +1 -0
  56. package/network/middlewares/on-method.test.d.ts +1 -0
  57. package/network/mtproto-session.cjs +9 -2
  58. package/network/mtproto-session.d.cts +5 -2
  59. package/network/mtproto-session.d.ts +5 -2
  60. package/network/mtproto-session.js +9 -2
  61. package/network/multi-session-connection.cjs +7 -0
  62. package/network/multi-session-connection.d.cts +2 -0
  63. package/network/multi-session-connection.d.ts +2 -0
  64. package/network/multi-session-connection.js +7 -0
  65. package/network/network-manager.cjs +7 -1
  66. package/network/network-manager.js +7 -1
  67. package/network/resource-limiter.cjs +60 -0
  68. package/network/resource-limiter.d.cts +11 -0
  69. package/network/resource-limiter.d.ts +11 -0
  70. package/network/resource-limiter.js +55 -0
  71. package/network/resource-limiter.test.d.cts +1 -0
  72. package/network/resource-limiter.test.d.ts +1 -0
  73. package/network/server-salt.cjs +7 -3
  74. package/network/server-salt.d.cts +2 -0
  75. package/network/server-salt.d.ts +2 -0
  76. package/network/server-salt.js +7 -3
  77. package/network/session-connection.cjs +106 -20
  78. package/network/session-connection.d.cts +11 -0
  79. package/network/session-connection.d.ts +11 -0
  80. package/network/session-connection.js +106 -20
  81. package/package.json +1 -1
  82. package/utils.cjs +2 -0
  83. package/utils.js +2 -0
@@ -11,6 +11,7 @@ const Long = require("long");
11
11
  const mtproto = require("../utils/crypto/mtproto.cjs");
12
12
  const earlyTimer = require("../utils/early-timer.cjs");
13
13
  const longUtils = require("../utils/long-utils.cjs");
14
+ const miscUtils = require("../utils/misc-utils.cjs");
14
15
  const authorization = require("./authorization.cjs");
15
16
  const mtprotoSession = require("./mtproto-session.cjs");
16
17
  const persistentConnection = require("./persistent-connection.cjs");
@@ -18,6 +19,8 @@ const abstract = require("./transports/abstract.cjs");
18
19
  const errors = require("../types/errors.cjs");
19
20
  const innerTl = require("../tl/inner-tl.cjs");
20
21
  const TEMP_AUTH_KEY_EXPIRY = 86400;
22
+ const TEMP_AUTH_KEY_REFRESH_MARGIN = 3600;
23
+ const TEMP_AUTH_KEY_REFRESH_JITTER = 600;
21
24
  const GET_STATE_INTERVAL = 1500;
22
25
  const GET_STATE_TIMEOUT = 2500;
23
26
  const GZIP_PACKED_ID = 812830625;
@@ -29,6 +32,8 @@ const INVOKE_AFTER_MSG_SIZE = 12;
29
32
  class SessionConnection extends persistentConnection.PersistentConnection {
30
33
  _flushTimer = new earlyTimer.EarlyTimer();
31
34
  _queuedDestroySession = [];
35
+ _needDestroyAuthKey = false;
36
+ _sentDestroyAuthKey = false;
32
37
  // waitForMessage
33
38
  _pendingWaitForUnencrypted = [];
34
39
  _usePfs;
@@ -41,9 +46,8 @@ class SessionConnection extends persistentConnection.PersistentConnection {
41
46
  _crypto;
42
47
  _salts;
43
48
  _session;
44
- // todo: we should probably do adaptive ping interval based on rtt like tdlib:
45
- // https://github.com/tdlib/td/blob/91aa6c9e4d0774eabf4f8d7f3aa51239032059a6/td/mtproto/SessionConnection.h
46
49
  _pingInterval;
50
+ _randomDelay = miscUtils.getRandomInt(5e3);
47
51
  // NB: dont forget to update .reset()
48
52
  onDisconnect = new utils.Emitter();
49
53
  onKeyChange = new utils.Emitter();
@@ -73,6 +77,7 @@ class SessionConnection extends persistentConnection.PersistentConnection {
73
77
  this._online = params.platform.isOnline?.() ?? true;
74
78
  }
75
79
  _online;
80
+ _active = false;
76
81
  getAuthKey(temp = false) {
77
82
  const key = temp ? this._session._authKeyTemp : this._session._authKey;
78
83
  if (!key.ready) return null;
@@ -98,6 +103,9 @@ class SessionConnection extends persistentConnection.PersistentConnection {
98
103
  for (const msgId of this._session.pendingGetStateTimeouts.keys()) {
99
104
  this._onMessageFailed(msgId, "connection loss", true);
100
105
  }
106
+ for (const rpc of this._session.getStateSchedule.raw) {
107
+ rpc.getState = 0;
108
+ }
101
109
  this.onDisconnect.emit();
102
110
  this.reset();
103
111
  }
@@ -105,6 +113,12 @@ class SessionConnection extends persistentConnection.PersistentConnection {
105
113
  await super.destroy();
106
114
  this.reset(true);
107
115
  }
116
+ destroyAuthKey() {
117
+ if (!this._session._authKey.ready || this._needDestroyAuthKey) return;
118
+ this._needDestroyAuthKey = true;
119
+ this._sentDestroyAuthKey = false;
120
+ this._flushTimer.emitNow();
121
+ }
108
122
  reset(forever = false) {
109
123
  this._session.initConnectionCalled = false;
110
124
  this._flushTimer.reset();
@@ -114,6 +128,7 @@ class SessionConnection extends persistentConnection.PersistentConnection {
114
128
  utils.timers.clearTimeout(timeout);
115
129
  }
116
130
  this._session.pendingGetStateTimeouts.clear();
131
+ if (this._needDestroyAuthKey) this._sentDestroyAuthKey = false;
117
132
  if (forever) {
118
133
  utils.timers.clearTimeout(this._pfsUpdateTimeout);
119
134
  this.onDisconnect.clear();
@@ -189,6 +204,8 @@ class SessionConnection extends persistentConnection.PersistentConnection {
189
204
  this.onError.emit(error);
190
205
  }
191
206
  onConnectionUsable() {
207
+ this._session.lastActivityTime = performance.now();
208
+ this._active = false;
192
209
  if (this.params.withUpdates) {
193
210
  this.sendRpc({ _: "updates.getState" }).catch((err) => {
194
211
  if (this._destroyed || innerTl.RpcError.is(err, "AUTH_KEY_UNREGISTERED")) return;
@@ -212,7 +229,7 @@ class SessionConnection extends persistentConnection.PersistentConnection {
212
229
  authorization.doAuthorization(this, this._crypto).then(([authKey, serverSalt, timeOffset]) => {
213
230
  this._session._authKey.setup(authKey);
214
231
  this._salts.currentSalt = serverSalt;
215
- this._session.updateTimeOffset(timeOffset);
232
+ this._session.updateTimeOffset(timeOffset, true);
216
233
  this._session.authorizationPending = false;
217
234
  this.onKeyChange.emit(authKey);
218
235
  if (this._usePfs) {
@@ -328,7 +345,7 @@ class SessionConnection extends persistentConnection.PersistentConnection {
328
345
  this.log.debug("temp key is expiring soon");
329
346
  this._authorizePfs(true);
330
347
  },
331
- (TEMP_AUTH_KEY_EXPIRY - 60) * 1e3
348
+ (TEMP_AUTH_KEY_EXPIRY - TEMP_AUTH_KEY_REFRESH_MARGIN - miscUtils.getRandomInt(TEMP_AUTH_KEY_REFRESH_JITTER)) * 1e3
332
349
  );
333
350
  }).catch((err) => {
334
351
  if (this._destroyed) return;
@@ -356,6 +373,7 @@ class SessionConnection extends persistentConnection.PersistentConnection {
356
373
  }
357
374
  _warnedAboutUpdates = false;
358
375
  onMessage(data) {
376
+ this._session.lastActivityTime = performance.now();
359
377
  if (this._pendingWaitForUnencrypted.length) {
360
378
  const int32 = new Int32Array(data.buffer, data.byteOffset, 2);
361
379
  if (int32[0] === 0 && int32[1] === 0) {
@@ -410,13 +428,12 @@ class SessionConnection extends persistentConnection.PersistentConnection {
410
428
  this.log.warn("warn: ignoring message with invalid messageId = %s (is even)", messageId);
411
429
  return;
412
430
  }
413
- if (this._session.recentIncomingMsgIds.has(messageId)) {
414
- this.log.debug("ignoring duplicate message %s", messageId);
431
+ if (!this._session.recentIncomingMsgIds.add(messageId)) {
432
+ this.log.debug("ignoring duplicate or too old message %s", messageId);
415
433
  return;
416
434
  }
417
435
  const message = message_;
418
436
  this.log.debug("received %s (msg_id: %l)", message._, messageId);
419
- this._session.recentIncomingMsgIds.add(messageId);
420
437
  switch (message._) {
421
438
  case "mt_msgs_ack":
422
439
  case "mt_http_wait":
@@ -469,6 +486,16 @@ class SessionConnection extends persistentConnection.PersistentConnection {
469
486
  case "mt_destroy_session_none":
470
487
  this.log.debug("received %s (msg_id = %l): %j", message._, messageId, message);
471
488
  break;
489
+ case "mt_destroy_auth_key_ok":
490
+ case "mt_destroy_auth_key_none":
491
+ this.log.info("auth key destroyed: %s", message._);
492
+ this._needDestroyAuthKey = false;
493
+ this._sentDestroyAuthKey = false;
494
+ break;
495
+ case "mt_destroy_auth_key_fail":
496
+ this.log.warn("auth key destruction failed");
497
+ this._sentDestroyAuthKey = false;
498
+ break;
472
499
  default:
473
500
  if (innerTl.isAnyUpdates(message)) {
474
501
  if (this._usable && this.params.inactivityTimeout) {
@@ -791,6 +818,7 @@ class SessionConnection extends persistentConnection.PersistentConnection {
791
818
  }
792
819
  this.log.debug("received pong: msg_id %l, ping_id %l, rtt = %dms", msgId, pingId, rtt);
793
820
  this._session.resetLastPing();
821
+ this._flushTimer.emitBefore(this._session.lastPingTime + this._pingMustDelay());
794
822
  }
795
823
  _onBadServerSalt(msg) {
796
824
  this._salts.currentSalt = msg.newServerSalt;
@@ -798,8 +826,19 @@ class SessionConnection extends persistentConnection.PersistentConnection {
798
826
  }
799
827
  _onBadMsgNotification(msgId, msg) {
800
828
  switch (msg.errorCode) {
829
+ case 17: {
830
+ const serverTime = msgId.high >>> 0;
831
+ const timeOffset = serverTime - Math.floor(performance.now() / 1e3);
832
+ this._session.updateTimeOffset(timeOffset, true);
833
+ this.log.warn(
834
+ "msg_id too high, server time: %d, corrected offset to %d, resetting session",
835
+ serverTime,
836
+ timeOffset
837
+ );
838
+ this._resetSession("msg_id too high");
839
+ break;
840
+ }
801
841
  case 16:
802
- case 17:
803
842
  case 20: {
804
843
  if (msg.errorCode !== 20) {
805
844
  const serverTime = msgId.high >>> 0;
@@ -1120,6 +1159,41 @@ class SessionConnection extends persistentConnection.PersistentConnection {
1120
1159
  }
1121
1160
  this._onMessageFailed(msgId, "timeout");
1122
1161
  };
1162
+ // https://github.com/tdlib/td/blob/master/td/mtproto/SessionConnection.h
1163
+ _rtt() {
1164
+ const rtt = this._session.lastPingRtt;
1165
+ if (Number.isNaN(rtt)) return 2e3;
1166
+ return Math.max(2e3, rtt * 1.5 + 1e3);
1167
+ }
1168
+ _readDisconnectDelay() {
1169
+ return this._active ? this._rtt() * 3.5 : 135e3 + this._randomDelay;
1170
+ }
1171
+ _pingDisconnectDelay() {
1172
+ return this._active && this.params.isMainConnection ? this._rtt() * 2.5 : 135e3 + this._randomDelay;
1173
+ }
1174
+ _pingMustDelay() {
1175
+ return this._active ? this._rtt() : this._pingInterval + this._randomDelay;
1176
+ }
1177
+ _isActive() {
1178
+ if (this._session.queuedRpc.length > 0) return true;
1179
+ for (const msg of this._session.pendingMessages.values()) {
1180
+ if (msg._ === "rpc") return true;
1181
+ }
1182
+ return false;
1183
+ }
1184
+ _checkTimeouts(now) {
1185
+ if (!this._session.lastPingMsgId.isZero() && now - this._session.lastPingTime > this._pingDisconnectDelay()) {
1186
+ this.log.warn("ping timeout, no pong for %dms", Math.round(now - this._session.lastPingTime));
1187
+ this._resetSession("ping timeout");
1188
+ return true;
1189
+ }
1190
+ if (now - this._session.lastActivityTime > this._readDisconnectDelay()) {
1191
+ this.log.warn("read timeout, last read was %dms ago", Math.round(now - this._session.lastActivityTime));
1192
+ this._resetSession("read timeout");
1193
+ return true;
1194
+ }
1195
+ return false;
1196
+ }
1123
1197
  _flush() {
1124
1198
  if (this._disconnectedManually || !this._session._authKey.ready || this._isPfsBindingPending) {
1125
1199
  this.log.debug(
@@ -1130,6 +1204,14 @@ class SessionConnection extends persistentConnection.PersistentConnection {
1130
1204
  );
1131
1205
  return;
1132
1206
  }
1207
+ const now = performance.now();
1208
+ const active = this._online && this._isActive();
1209
+ if (active && !this._active) {
1210
+ this._session.lastActivityTime = now;
1211
+ this._session.resetLastPing(true);
1212
+ }
1213
+ this._active = active;
1214
+ if (this._checkTimeouts(now)) return;
1133
1215
  try {
1134
1216
  this._doFlush();
1135
1217
  } catch (e) {
@@ -1140,9 +1222,11 @@ class SessionConnection extends persistentConnection.PersistentConnection {
1140
1222
  if (this._session.hasPendingMessages) {
1141
1223
  this._flushTimer.emitWhenIdle();
1142
1224
  } else {
1143
- const nextPingTime = this._session.lastPingTime + this._pingInterval;
1225
+ const nextPingTime = this._session.lastPingMsgId.isZero() ? this._session.lastPingTime + this._pingMustDelay() : Infinity;
1144
1226
  const nextGetScheduleTime = this._session.getStateSchedule.raw[0]?.getState || Infinity;
1145
- this._flushTimer.emitBefore(Math.min(nextPingTime, nextGetScheduleTime));
1227
+ const readDeadline = this._session.lastActivityTime + this._readDisconnectDelay();
1228
+ const pingDeadline = this._session.lastPingMsgId.isZero() ? Infinity : this._session.lastPingTime + this._pingDisconnectDelay();
1229
+ this._flushTimer.emitBefore(Math.min(nextPingTime, nextGetScheduleTime, readDeadline, pingDeadline));
1146
1230
  }
1147
1231
  }
1148
1232
  _doFlush() {
@@ -1166,6 +1250,7 @@ class SessionConnection extends persistentConnection.PersistentConnection {
1166
1250
  let resendMsgIds = null;
1167
1251
  let cancelRpcs = null;
1168
1252
  let destroySessions = null;
1253
+ let destroyAuthKey = false;
1169
1254
  let rootMsgId = null;
1170
1255
  const now = performance.now();
1171
1256
  if (this._session.queuedAcks.length) {
@@ -1185,20 +1270,12 @@ class SessionConnection extends persistentConnection.PersistentConnection {
1185
1270
  packetSize += ackRequest.length + 16;
1186
1271
  messageCount += 1;
1187
1272
  }
1188
- if (now - this._session.lastPingTime > this._pingInterval) {
1189
- if (!this._session.lastPingMsgId.isZero()) {
1190
- this.log.warn("didn't receive pong for previous ping (msg_id = %l). are we offline?", this._session.lastPingMsgId);
1191
- if (!this._disconnectedManually) {
1192
- this._resetSession("ping timeout");
1193
- }
1194
- return;
1195
- }
1273
+ if (this._session.lastPingMsgId.isZero() && now - this._session.lastPingTime > this._pingMustDelay()) {
1196
1274
  pingId = longUtils.randomLong();
1197
1275
  const obj = {
1198
1276
  _: "mt_ping_delay_disconnect",
1199
1277
  pingId,
1200
- // ÷ 1000 * 1.25
1201
- disconnectDelay: Math.round(this._pingInterval * 125e-5)
1278
+ disconnectDelay: Math.round(this._pingDisconnectDelay() / 1e3) + 2
1202
1279
  };
1203
1280
  this._session.lastPingTime = now;
1204
1281
  pingRequest = tlRuntime.TlBinaryWriter.serializeObject(this._writerMap, obj);
@@ -1260,6 +1337,11 @@ class SessionConnection extends persistentConnection.PersistentConnection {
1260
1337
  destroySessions = this._queuedDestroySession;
1261
1338
  this._queuedDestroySession = [];
1262
1339
  }
1340
+ if (this._needDestroyAuthKey && !this._sentDestroyAuthKey) {
1341
+ destroyAuthKey = true;
1342
+ containerMessageCount += 1;
1343
+ containerSize += 20;
1344
+ }
1263
1345
  if (this._salts.shouldFetchSalts()) {
1264
1346
  const obj = {
1265
1347
  _: "mt_get_future_salts",
@@ -1370,6 +1452,10 @@ class SessionConnection extends persistentConnection.PersistentConnection {
1370
1452
  otherPendings.push(pending);
1371
1453
  });
1372
1454
  }
1455
+ if (destroyAuthKey) {
1456
+ this._registerOutgoingMsgId(this._session.writeMessage(writer, { _: "mt_destroy_auth_key" }));
1457
+ this._sentDestroyAuthKey = true;
1458
+ }
1373
1459
  if (getFutureSaltsRequest) {
1374
1460
  getFutureSaltsMsgId = this._registerOutgoingMsgId(this._session.writeMessage(writer, getFutureSaltsRequest));
1375
1461
  const pending = {
@@ -28,6 +28,8 @@ export declare class SessionConnection extends PersistentConnection {
28
28
  readonly params: SessionConnectionParams;
29
29
  private _flushTimer;
30
30
  private _queuedDestroySession;
31
+ private _needDestroyAuthKey;
32
+ private _sentDestroyAuthKey;
31
33
  private _pendingWaitForUnencrypted;
32
34
  private _usePfs;
33
35
  private _isPfsBindingPending;
@@ -40,6 +42,7 @@ export declare class SessionConnection extends PersistentConnection {
40
42
  private _salts;
41
43
  readonly _session: MtprotoSession;
42
44
  private _pingInterval;
45
+ private _randomDelay;
43
46
  readonly onDisconnect: Emitter<void>;
44
47
  readonly onKeyChange: Emitter<Uint8Array | null>;
45
48
  readonly onTmpKeyChange: Emitter<[Uint8Array, number] | null>;
@@ -50,10 +53,12 @@ export declare class SessionConnection extends PersistentConnection {
50
53
  private _triedReconnectingOn404;
51
54
  constructor(params: SessionConnectionParams, log: Logger);
52
55
  private _online;
56
+ private _active;
53
57
  getAuthKey(temp?: boolean): Uint8Array | null;
54
58
  setUsePfs(usePfs: boolean): void;
55
59
  onClosed(): void;
56
60
  destroy(): Promise<void>;
61
+ destroyAuthKey(): void;
57
62
  reset(forever?: boolean): void;
58
63
  onConnected(): void;
59
64
  protected handleError(error: Error): void;
@@ -87,6 +92,12 @@ export declare class SessionConnection extends PersistentConnection {
87
92
  private _cancelRpc;
88
93
  protected _onInactivityTimeout(): void;
89
94
  private _handleGetStateTimeout;
95
+ private _rtt;
96
+ private _readDisconnectDelay;
97
+ private _pingDisconnectDelay;
98
+ private _pingMustDelay;
99
+ private _isActive;
100
+ private _checkTimeouts;
90
101
  private _flush;
91
102
  private _doFlush;
92
103
  }
@@ -28,6 +28,8 @@ export declare class SessionConnection extends PersistentConnection {
28
28
  readonly params: SessionConnectionParams;
29
29
  private _flushTimer;
30
30
  private _queuedDestroySession;
31
+ private _needDestroyAuthKey;
32
+ private _sentDestroyAuthKey;
31
33
  private _pendingWaitForUnencrypted;
32
34
  private _usePfs;
33
35
  private _isPfsBindingPending;
@@ -40,6 +42,7 @@ export declare class SessionConnection extends PersistentConnection {
40
42
  private _salts;
41
43
  readonly _session: MtprotoSession;
42
44
  private _pingInterval;
45
+ private _randomDelay;
43
46
  readonly onDisconnect: Emitter<void>;
44
47
  readonly onKeyChange: Emitter<Uint8Array | null>;
45
48
  readonly onTmpKeyChange: Emitter<[Uint8Array, number] | null>;
@@ -50,10 +53,12 @@ export declare class SessionConnection extends PersistentConnection {
50
53
  private _triedReconnectingOn404;
51
54
  constructor(params: SessionConnectionParams, log: Logger);
52
55
  private _online;
56
+ private _active;
53
57
  getAuthKey(temp?: boolean): Uint8Array | null;
54
58
  setUsePfs(usePfs: boolean): void;
55
59
  onClosed(): void;
56
60
  destroy(): Promise<void>;
61
+ destroyAuthKey(): void;
57
62
  reset(forever?: boolean): void;
58
63
  onConnected(): void;
59
64
  protected handleError(error: Error): void;
@@ -87,6 +92,12 @@ export declare class SessionConnection extends PersistentConnection {
87
92
  private _cancelRpc;
88
93
  protected _onInactivityTimeout(): void;
89
94
  private _handleGetStateTimeout;
95
+ private _rtt;
96
+ private _readDisconnectDelay;
97
+ private _pingDisconnectDelay;
98
+ private _pingMustDelay;
99
+ private _isActive;
100
+ private _checkTimeouts;
90
101
  private _flush;
91
102
  private _doFlush;
92
103
  }
@@ -4,6 +4,7 @@ import Long from "long";
4
4
  import { createAesIgeForMessageOld } from "../utils/crypto/mtproto.js";
5
5
  import { EarlyTimer } from "../utils/early-timer.js";
6
6
  import { longFromBuffer, randomLong, removeFromLongArray } from "../utils/long-utils.js";
7
+ import { getRandomInt } from "../utils/misc-utils.js";
7
8
  import { doAuthorization } from "./authorization.js";
8
9
  import { MtprotoSession } from "./mtproto-session.js";
9
10
  import { PersistentConnection } from "./persistent-connection.js";
@@ -11,6 +12,8 @@ import { TransportError } from "./transports/abstract.js";
11
12
  import { MtcuteError, MtTimeoutError, MtArgumentError } from "../types/errors.js";
12
13
  import { RpcError, isAnyUpdates } from "../tl/inner-tl.js";
13
14
  const TEMP_AUTH_KEY_EXPIRY = 86400;
15
+ const TEMP_AUTH_KEY_REFRESH_MARGIN = 3600;
16
+ const TEMP_AUTH_KEY_REFRESH_JITTER = 600;
14
17
  const GET_STATE_INTERVAL = 1500;
15
18
  const GET_STATE_TIMEOUT = 2500;
16
19
  const GZIP_PACKED_ID = 812830625;
@@ -22,6 +25,8 @@ const INVOKE_AFTER_MSG_SIZE = 12;
22
25
  class SessionConnection extends PersistentConnection {
23
26
  _flushTimer = new EarlyTimer();
24
27
  _queuedDestroySession = [];
28
+ _needDestroyAuthKey = false;
29
+ _sentDestroyAuthKey = false;
25
30
  // waitForMessage
26
31
  _pendingWaitForUnencrypted = [];
27
32
  _usePfs;
@@ -34,9 +39,8 @@ class SessionConnection extends PersistentConnection {
34
39
  _crypto;
35
40
  _salts;
36
41
  _session;
37
- // todo: we should probably do adaptive ping interval based on rtt like tdlib:
38
- // https://github.com/tdlib/td/blob/91aa6c9e4d0774eabf4f8d7f3aa51239032059a6/td/mtproto/SessionConnection.h
39
42
  _pingInterval;
43
+ _randomDelay = getRandomInt(5e3);
40
44
  // NB: dont forget to update .reset()
41
45
  onDisconnect = new Emitter();
42
46
  onKeyChange = new Emitter();
@@ -66,6 +70,7 @@ class SessionConnection extends PersistentConnection {
66
70
  this._online = params.platform.isOnline?.() ?? true;
67
71
  }
68
72
  _online;
73
+ _active = false;
69
74
  getAuthKey(temp = false) {
70
75
  const key = temp ? this._session._authKeyTemp : this._session._authKey;
71
76
  if (!key.ready) return null;
@@ -91,6 +96,9 @@ class SessionConnection extends PersistentConnection {
91
96
  for (const msgId of this._session.pendingGetStateTimeouts.keys()) {
92
97
  this._onMessageFailed(msgId, "connection loss", true);
93
98
  }
99
+ for (const rpc of this._session.getStateSchedule.raw) {
100
+ rpc.getState = 0;
101
+ }
94
102
  this.onDisconnect.emit();
95
103
  this.reset();
96
104
  }
@@ -98,6 +106,12 @@ class SessionConnection extends PersistentConnection {
98
106
  await super.destroy();
99
107
  this.reset(true);
100
108
  }
109
+ destroyAuthKey() {
110
+ if (!this._session._authKey.ready || this._needDestroyAuthKey) return;
111
+ this._needDestroyAuthKey = true;
112
+ this._sentDestroyAuthKey = false;
113
+ this._flushTimer.emitNow();
114
+ }
101
115
  reset(forever = false) {
102
116
  this._session.initConnectionCalled = false;
103
117
  this._flushTimer.reset();
@@ -107,6 +121,7 @@ class SessionConnection extends PersistentConnection {
107
121
  timers.clearTimeout(timeout);
108
122
  }
109
123
  this._session.pendingGetStateTimeouts.clear();
124
+ if (this._needDestroyAuthKey) this._sentDestroyAuthKey = false;
110
125
  if (forever) {
111
126
  timers.clearTimeout(this._pfsUpdateTimeout);
112
127
  this.onDisconnect.clear();
@@ -182,6 +197,8 @@ class SessionConnection extends PersistentConnection {
182
197
  this.onError.emit(error);
183
198
  }
184
199
  onConnectionUsable() {
200
+ this._session.lastActivityTime = performance.now();
201
+ this._active = false;
185
202
  if (this.params.withUpdates) {
186
203
  this.sendRpc({ _: "updates.getState" }).catch((err) => {
187
204
  if (this._destroyed || RpcError.is(err, "AUTH_KEY_UNREGISTERED")) return;
@@ -205,7 +222,7 @@ class SessionConnection extends PersistentConnection {
205
222
  doAuthorization(this, this._crypto).then(([authKey, serverSalt, timeOffset]) => {
206
223
  this._session._authKey.setup(authKey);
207
224
  this._salts.currentSalt = serverSalt;
208
- this._session.updateTimeOffset(timeOffset);
225
+ this._session.updateTimeOffset(timeOffset, true);
209
226
  this._session.authorizationPending = false;
210
227
  this.onKeyChange.emit(authKey);
211
228
  if (this._usePfs) {
@@ -321,7 +338,7 @@ class SessionConnection extends PersistentConnection {
321
338
  this.log.debug("temp key is expiring soon");
322
339
  this._authorizePfs(true);
323
340
  },
324
- (TEMP_AUTH_KEY_EXPIRY - 60) * 1e3
341
+ (TEMP_AUTH_KEY_EXPIRY - TEMP_AUTH_KEY_REFRESH_MARGIN - getRandomInt(TEMP_AUTH_KEY_REFRESH_JITTER)) * 1e3
325
342
  );
326
343
  }).catch((err) => {
327
344
  if (this._destroyed) return;
@@ -349,6 +366,7 @@ class SessionConnection extends PersistentConnection {
349
366
  }
350
367
  _warnedAboutUpdates = false;
351
368
  onMessage(data) {
369
+ this._session.lastActivityTime = performance.now();
352
370
  if (this._pendingWaitForUnencrypted.length) {
353
371
  const int32 = new Int32Array(data.buffer, data.byteOffset, 2);
354
372
  if (int32[0] === 0 && int32[1] === 0) {
@@ -403,13 +421,12 @@ class SessionConnection extends PersistentConnection {
403
421
  this.log.warn("warn: ignoring message with invalid messageId = %s (is even)", messageId);
404
422
  return;
405
423
  }
406
- if (this._session.recentIncomingMsgIds.has(messageId)) {
407
- this.log.debug("ignoring duplicate message %s", messageId);
424
+ if (!this._session.recentIncomingMsgIds.add(messageId)) {
425
+ this.log.debug("ignoring duplicate or too old message %s", messageId);
408
426
  return;
409
427
  }
410
428
  const message = message_;
411
429
  this.log.debug("received %s (msg_id: %l)", message._, messageId);
412
- this._session.recentIncomingMsgIds.add(messageId);
413
430
  switch (message._) {
414
431
  case "mt_msgs_ack":
415
432
  case "mt_http_wait":
@@ -462,6 +479,16 @@ class SessionConnection extends PersistentConnection {
462
479
  case "mt_destroy_session_none":
463
480
  this.log.debug("received %s (msg_id = %l): %j", message._, messageId, message);
464
481
  break;
482
+ case "mt_destroy_auth_key_ok":
483
+ case "mt_destroy_auth_key_none":
484
+ this.log.info("auth key destroyed: %s", message._);
485
+ this._needDestroyAuthKey = false;
486
+ this._sentDestroyAuthKey = false;
487
+ break;
488
+ case "mt_destroy_auth_key_fail":
489
+ this.log.warn("auth key destruction failed");
490
+ this._sentDestroyAuthKey = false;
491
+ break;
465
492
  default:
466
493
  if (isAnyUpdates(message)) {
467
494
  if (this._usable && this.params.inactivityTimeout) {
@@ -784,6 +811,7 @@ class SessionConnection extends PersistentConnection {
784
811
  }
785
812
  this.log.debug("received pong: msg_id %l, ping_id %l, rtt = %dms", msgId, pingId, rtt);
786
813
  this._session.resetLastPing();
814
+ this._flushTimer.emitBefore(this._session.lastPingTime + this._pingMustDelay());
787
815
  }
788
816
  _onBadServerSalt(msg) {
789
817
  this._salts.currentSalt = msg.newServerSalt;
@@ -791,8 +819,19 @@ class SessionConnection extends PersistentConnection {
791
819
  }
792
820
  _onBadMsgNotification(msgId, msg) {
793
821
  switch (msg.errorCode) {
822
+ case 17: {
823
+ const serverTime = msgId.high >>> 0;
824
+ const timeOffset = serverTime - Math.floor(performance.now() / 1e3);
825
+ this._session.updateTimeOffset(timeOffset, true);
826
+ this.log.warn(
827
+ "msg_id too high, server time: %d, corrected offset to %d, resetting session",
828
+ serverTime,
829
+ timeOffset
830
+ );
831
+ this._resetSession("msg_id too high");
832
+ break;
833
+ }
794
834
  case 16:
795
- case 17:
796
835
  case 20: {
797
836
  if (msg.errorCode !== 20) {
798
837
  const serverTime = msgId.high >>> 0;
@@ -1113,6 +1152,41 @@ class SessionConnection extends PersistentConnection {
1113
1152
  }
1114
1153
  this._onMessageFailed(msgId, "timeout");
1115
1154
  };
1155
+ // https://github.com/tdlib/td/blob/master/td/mtproto/SessionConnection.h
1156
+ _rtt() {
1157
+ const rtt = this._session.lastPingRtt;
1158
+ if (Number.isNaN(rtt)) return 2e3;
1159
+ return Math.max(2e3, rtt * 1.5 + 1e3);
1160
+ }
1161
+ _readDisconnectDelay() {
1162
+ return this._active ? this._rtt() * 3.5 : 135e3 + this._randomDelay;
1163
+ }
1164
+ _pingDisconnectDelay() {
1165
+ return this._active && this.params.isMainConnection ? this._rtt() * 2.5 : 135e3 + this._randomDelay;
1166
+ }
1167
+ _pingMustDelay() {
1168
+ return this._active ? this._rtt() : this._pingInterval + this._randomDelay;
1169
+ }
1170
+ _isActive() {
1171
+ if (this._session.queuedRpc.length > 0) return true;
1172
+ for (const msg of this._session.pendingMessages.values()) {
1173
+ if (msg._ === "rpc") return true;
1174
+ }
1175
+ return false;
1176
+ }
1177
+ _checkTimeouts(now) {
1178
+ if (!this._session.lastPingMsgId.isZero() && now - this._session.lastPingTime > this._pingDisconnectDelay()) {
1179
+ this.log.warn("ping timeout, no pong for %dms", Math.round(now - this._session.lastPingTime));
1180
+ this._resetSession("ping timeout");
1181
+ return true;
1182
+ }
1183
+ if (now - this._session.lastActivityTime > this._readDisconnectDelay()) {
1184
+ this.log.warn("read timeout, last read was %dms ago", Math.round(now - this._session.lastActivityTime));
1185
+ this._resetSession("read timeout");
1186
+ return true;
1187
+ }
1188
+ return false;
1189
+ }
1116
1190
  _flush() {
1117
1191
  if (this._disconnectedManually || !this._session._authKey.ready || this._isPfsBindingPending) {
1118
1192
  this.log.debug(
@@ -1123,6 +1197,14 @@ class SessionConnection extends PersistentConnection {
1123
1197
  );
1124
1198
  return;
1125
1199
  }
1200
+ const now = performance.now();
1201
+ const active = this._online && this._isActive();
1202
+ if (active && !this._active) {
1203
+ this._session.lastActivityTime = now;
1204
+ this._session.resetLastPing(true);
1205
+ }
1206
+ this._active = active;
1207
+ if (this._checkTimeouts(now)) return;
1126
1208
  try {
1127
1209
  this._doFlush();
1128
1210
  } catch (e) {
@@ -1133,9 +1215,11 @@ class SessionConnection extends PersistentConnection {
1133
1215
  if (this._session.hasPendingMessages) {
1134
1216
  this._flushTimer.emitWhenIdle();
1135
1217
  } else {
1136
- const nextPingTime = this._session.lastPingTime + this._pingInterval;
1218
+ const nextPingTime = this._session.lastPingMsgId.isZero() ? this._session.lastPingTime + this._pingMustDelay() : Infinity;
1137
1219
  const nextGetScheduleTime = this._session.getStateSchedule.raw[0]?.getState || Infinity;
1138
- this._flushTimer.emitBefore(Math.min(nextPingTime, nextGetScheduleTime));
1220
+ const readDeadline = this._session.lastActivityTime + this._readDisconnectDelay();
1221
+ const pingDeadline = this._session.lastPingMsgId.isZero() ? Infinity : this._session.lastPingTime + this._pingDisconnectDelay();
1222
+ this._flushTimer.emitBefore(Math.min(nextPingTime, nextGetScheduleTime, readDeadline, pingDeadline));
1139
1223
  }
1140
1224
  }
1141
1225
  _doFlush() {
@@ -1159,6 +1243,7 @@ class SessionConnection extends PersistentConnection {
1159
1243
  let resendMsgIds = null;
1160
1244
  let cancelRpcs = null;
1161
1245
  let destroySessions = null;
1246
+ let destroyAuthKey = false;
1162
1247
  let rootMsgId = null;
1163
1248
  const now = performance.now();
1164
1249
  if (this._session.queuedAcks.length) {
@@ -1178,20 +1263,12 @@ class SessionConnection extends PersistentConnection {
1178
1263
  packetSize += ackRequest.length + 16;
1179
1264
  messageCount += 1;
1180
1265
  }
1181
- if (now - this._session.lastPingTime > this._pingInterval) {
1182
- if (!this._session.lastPingMsgId.isZero()) {
1183
- this.log.warn("didn't receive pong for previous ping (msg_id = %l). are we offline?", this._session.lastPingMsgId);
1184
- if (!this._disconnectedManually) {
1185
- this._resetSession("ping timeout");
1186
- }
1187
- return;
1188
- }
1266
+ if (this._session.lastPingMsgId.isZero() && now - this._session.lastPingTime > this._pingMustDelay()) {
1189
1267
  pingId = randomLong();
1190
1268
  const obj = {
1191
1269
  _: "mt_ping_delay_disconnect",
1192
1270
  pingId,
1193
- // ÷ 1000 * 1.25
1194
- disconnectDelay: Math.round(this._pingInterval * 125e-5)
1271
+ disconnectDelay: Math.round(this._pingDisconnectDelay() / 1e3) + 2
1195
1272
  };
1196
1273
  this._session.lastPingTime = now;
1197
1274
  pingRequest = TlBinaryWriter.serializeObject(this._writerMap, obj);
@@ -1253,6 +1330,11 @@ class SessionConnection extends PersistentConnection {
1253
1330
  destroySessions = this._queuedDestroySession;
1254
1331
  this._queuedDestroySession = [];
1255
1332
  }
1333
+ if (this._needDestroyAuthKey && !this._sentDestroyAuthKey) {
1334
+ destroyAuthKey = true;
1335
+ containerMessageCount += 1;
1336
+ containerSize += 20;
1337
+ }
1256
1338
  if (this._salts.shouldFetchSalts()) {
1257
1339
  const obj = {
1258
1340
  _: "mt_get_future_salts",
@@ -1363,6 +1445,10 @@ class SessionConnection extends PersistentConnection {
1363
1445
  otherPendings.push(pending);
1364
1446
  });
1365
1447
  }
1448
+ if (destroyAuthKey) {
1449
+ this._registerOutgoingMsgId(this._session.writeMessage(writer, { _: "mt_destroy_auth_key" }));
1450
+ this._sentDestroyAuthKey = true;
1451
+ }
1366
1452
  if (getFutureSaltsRequest) {
1367
1453
  getFutureSaltsMsgId = this._registerOutgoingMsgId(this._session.writeMessage(writer, getFutureSaltsRequest));
1368
1454
  const pending = {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mtcute/core",
3
3
  "type": "module",
4
- "version": "0.30.1",
4
+ "version": "0.30.2",
5
5
  "description": "Type-safe library for MTProto (Telegram API)",
6
6
  "license": "MIT",
7
7
  "scripts": {},