@mtcute/core 0.2.2 → 0.3.0

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 (103) hide show
  1. package/cjs/base-client.d.ts +33 -166
  2. package/cjs/base-client.js +70 -48
  3. package/cjs/base-client.js.map +1 -1
  4. package/cjs/base-client.types.d.ts +153 -0
  5. package/cjs/base-client.types.js +3 -0
  6. package/cjs/base-client.types.js.map +1 -0
  7. package/cjs/index.d.ts +1 -0
  8. package/cjs/index.js +1 -0
  9. package/cjs/index.js.map +1 -1
  10. package/cjs/network/mtproto-session.d.ts +10 -2
  11. package/cjs/network/mtproto-session.js +33 -3
  12. package/cjs/network/mtproto-session.js.map +1 -1
  13. package/cjs/network/multi-session-connection.d.ts +1 -1
  14. package/cjs/network/multi-session-connection.js +4 -3
  15. package/cjs/network/multi-session-connection.js.map +1 -1
  16. package/cjs/network/network-manager.d.ts +23 -0
  17. package/cjs/network/network-manager.js +28 -8
  18. package/cjs/network/network-manager.js.map +1 -1
  19. package/cjs/network/server-salt.d.ts +12 -0
  20. package/cjs/network/server-salt.js +45 -0
  21. package/cjs/network/server-salt.js.map +1 -0
  22. package/cjs/network/session-connection.d.ts +6 -1
  23. package/cjs/network/session-connection.js +179 -54
  24. package/cjs/network/session-connection.js.map +1 -1
  25. package/cjs/storage/abstract.d.ts +12 -1
  26. package/cjs/storage/abstract.js.map +1 -1
  27. package/cjs/storage/idb.d.ts +3 -1
  28. package/cjs/storage/idb.js +17 -0
  29. package/cjs/storage/idb.js.map +1 -1
  30. package/cjs/storage/index.d.ts +1 -0
  31. package/cjs/storage/index.js +1 -0
  32. package/cjs/storage/index.js.map +1 -1
  33. package/cjs/storage/json.js +1 -0
  34. package/cjs/storage/json.js.map +1 -1
  35. package/cjs/storage/memory.d.ts +5 -2
  36. package/cjs/storage/memory.js +16 -2
  37. package/cjs/storage/memory.js.map +1 -1
  38. package/cjs/storage/memory.test.js +2 -1
  39. package/cjs/storage/memory.test.js.map +1 -1
  40. package/cjs/utils/logger.js +8 -2
  41. package/cjs/utils/logger.js.map +1 -1
  42. package/cjs/utils/logger.test.js +12 -0
  43. package/cjs/utils/logger.test.js.map +1 -1
  44. package/cjs/utils/long-utils.d.ts +5 -0
  45. package/cjs/utils/long-utils.js +13 -1
  46. package/cjs/utils/long-utils.js.map +1 -1
  47. package/cjs/utils/sorted-array.js +1 -0
  48. package/cjs/utils/sorted-array.js.map +1 -1
  49. package/cjs/utils/type-assertions.d.ts +1 -0
  50. package/cjs/utils/type-assertions.js +7 -1
  51. package/cjs/utils/type-assertions.js.map +1 -1
  52. package/esm/base-client.d.ts +33 -166
  53. package/esm/base-client.js +70 -48
  54. package/esm/base-client.js.map +1 -1
  55. package/esm/base-client.types.d.ts +153 -0
  56. package/esm/base-client.types.js +2 -0
  57. package/esm/base-client.types.js.map +1 -0
  58. package/esm/index.d.ts +1 -0
  59. package/esm/index.js +1 -0
  60. package/esm/index.js.map +1 -1
  61. package/esm/network/mtproto-session.d.ts +10 -2
  62. package/esm/network/mtproto-session.js +34 -4
  63. package/esm/network/mtproto-session.js.map +1 -1
  64. package/esm/network/multi-session-connection.d.ts +1 -1
  65. package/esm/network/multi-session-connection.js +4 -3
  66. package/esm/network/multi-session-connection.js.map +1 -1
  67. package/esm/network/network-manager.d.ts +23 -0
  68. package/esm/network/network-manager.js +29 -9
  69. package/esm/network/network-manager.js.map +1 -1
  70. package/esm/network/server-salt.d.ts +12 -0
  71. package/esm/network/server-salt.js +38 -0
  72. package/esm/network/server-salt.js.map +1 -0
  73. package/esm/network/session-connection.d.ts +6 -1
  74. package/esm/network/session-connection.js +179 -54
  75. package/esm/network/session-connection.js.map +1 -1
  76. package/esm/storage/abstract.d.ts +12 -1
  77. package/esm/storage/abstract.js.map +1 -1
  78. package/esm/storage/idb.d.ts +3 -1
  79. package/esm/storage/idb.js +17 -0
  80. package/esm/storage/idb.js.map +1 -1
  81. package/esm/storage/index.d.ts +1 -0
  82. package/esm/storage/index.js +1 -0
  83. package/esm/storage/index.js.map +1 -1
  84. package/esm/storage/json.js +1 -0
  85. package/esm/storage/json.js.map +1 -1
  86. package/esm/storage/memory.d.ts +5 -2
  87. package/esm/storage/memory.js +16 -2
  88. package/esm/storage/memory.js.map +1 -1
  89. package/esm/storage/memory.test.js +2 -1
  90. package/esm/storage/memory.test.js.map +1 -1
  91. package/esm/utils/logger.js +8 -2
  92. package/esm/utils/logger.js.map +1 -1
  93. package/esm/utils/logger.test.js +12 -0
  94. package/esm/utils/logger.test.js.map +1 -1
  95. package/esm/utils/long-utils.d.ts +5 -0
  96. package/esm/utils/long-utils.js +11 -0
  97. package/esm/utils/long-utils.js.map +1 -1
  98. package/esm/utils/sorted-array.js +1 -0
  99. package/esm/utils/sorted-array.js.map +1 -1
  100. package/esm/utils/type-assertions.d.ts +1 -0
  101. package/esm/utils/type-assertions.js +5 -0
  102. package/esm/utils/type-assertions.js.map +1 -1
  103. package/package.json +4 -4
@@ -16,7 +16,9 @@ const error_reporting_js_1 = require("../utils/platform/error-reporting.js");
16
16
  const authorization_js_1 = require("./authorization.js");
17
17
  const persistent_connection_js_1 = require("./persistent-connection.js");
18
18
  const abstract_js_1 = require("./transports/abstract.js");
19
- const TEMP_AUTH_KEY_EXPIRY = 86400;
19
+ const TEMP_AUTH_KEY_EXPIRY = 86400; // 24 hours
20
+ const PING_INTERVAL = 60000; // 1 minute
21
+ const GET_STATE_INTERVAL = 1500; // 1.5 seconds
20
22
  // destroy_auth_key#d1435160 = DestroyAuthKeyRes;
21
23
  // const DESTROY_AUTH_KEY = Buffer.from('605134d1', 'hex')
22
24
  // gzip_packed#3072cfa1 packed_data:string = Object;
@@ -25,6 +27,9 @@ const GZIP_PACKED_ID = 0x3072cfa1;
25
27
  const MSG_CONTAINER_ID = 0x73f1f8dc;
26
28
  // rpc_result#f35c6d01 req_msg_id:long result:Object = RpcResult;
27
29
  const RPC_RESULT_ID = 0xf35c6d01;
30
+ // invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
31
+ const INVOKE_AFTER_MSG_ID = 0xcb9f372d;
32
+ const INVOKE_AFTER_MSG_SIZE = 12; // 8 (invokeAfterMsg) + 4 (msg_id)
28
33
  function makeNiceStack(error, stack, method) {
29
34
  error.stack = `RpcError (${error.code} ${error.text}): ${error.message}\n at ${method}\n${stack
30
35
  .split('\n')
@@ -50,6 +55,7 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
50
55
  this._readerMap = params.readerMap;
51
56
  this._writerMap = params.writerMap;
52
57
  this._crypto = params.crypto;
58
+ this._salts = params.salts;
53
59
  this._handleRawMessage = this._handleRawMessage.bind(this);
54
60
  }
55
61
  getAuthKey(temp = false) {
@@ -87,6 +93,7 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
87
93
  reset(forever = false) {
88
94
  this._session.initConnectionCalled = false;
89
95
  this._flushTimer.reset();
96
+ this._salts.isFetching = false;
90
97
  if (forever) {
91
98
  this.removeAllListeners();
92
99
  }
@@ -188,7 +195,7 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
188
195
  (0, authorization_js_1.doAuthorization)(this, this._crypto)
189
196
  .then(([authKey, serverSalt, timeOffset]) => {
190
197
  this._session._authKey.setup(authKey);
191
- this._session.serverSalt = serverSalt;
198
+ this._salts.currentSalt = serverSalt;
192
199
  this._session._timeOffset = timeOffset;
193
200
  this._session.authorizationPending = false;
194
201
  this.emit('key-change', authKey);
@@ -306,7 +313,7 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
306
313
  // and primary is not immediately forgot because messages using it may still be in flight)
307
314
  this._session._authKeyTempSecondary = this._session._authKeyTemp;
308
315
  this._session._authKeyTemp = tempKey;
309
- this._session.serverSalt = tempServerSalt;
316
+ this._salts.currentSalt = tempServerSalt;
310
317
  this.log.debug('temp key has been bound, exp = %d', inner.expiresAt);
311
318
  this._isPfsBindingPending = false;
312
319
  this._isPfsBindingPendingInBackground = false;
@@ -445,7 +452,7 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
445
452
  this._onMsgsStateInfo(message);
446
453
  break;
447
454
  case 'mt_future_salts':
448
- // todo
455
+ this._onFutureSalts(message);
449
456
  break;
450
457
  case 'mt_msgs_state_req':
451
458
  case 'mt_msg_resend_req':
@@ -526,7 +533,7 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
526
533
  this.log.debug('failed to parse rpc_result for cancel request %l, ignoring', reqMsgId);
527
534
  return;
528
535
  }
529
- this.log.debug('received %s for cancelled request %l: %j', result._, reqMsgId);
536
+ this.log.debug('received %s for cancelled request %l: %j', result._, reqMsgId, result);
530
537
  this._onMessageAcked(reqMsgId);
531
538
  return;
532
539
  }
@@ -552,6 +559,7 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
552
559
  }
553
560
  // initConnection call was definitely received and
554
561
  // processed by the server, so we no longer need to use it
562
+ // todo: is this the case with failed invokeAfterMsg(s) as well?
555
563
  if (rpc.initConn) {
556
564
  this._session.initConnectionCalled = true;
557
565
  }
@@ -560,37 +568,61 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
560
568
  if (result._ === 'mt_rpc_error') {
561
569
  const res = result;
562
570
  this.log.debug('received rpc_error [%d:%s] for %l (%s)', res.errorCode, res.errorMessage, reqMsgId, rpc.method);
563
- if (res.errorMessage === 'AUTH_KEY_PERM_EMPTY') {
564
- // happens when temp auth key is not yet bound
565
- // this shouldn't happen as we block any outbound communications
566
- // until the temp key is derived and bound.
567
- //
568
- // i think it is also possible for the error to be returned
569
- // when the temp key has expired, but this still shouldn't happen
570
- // but this is tg, so something may go wrong, and we will receive this as an error
571
- // (for god's sake why is this not in mtproto and instead hacked into the app layer)
572
- this._authorizePfs();
573
- this._onMessageFailed(reqMsgId, 'AUTH_KEY_PERM_EMPTY', true);
574
- return;
575
- }
576
- if (res.errorMessage === 'CONNECTION_NOT_INITED') {
577
- // this seems to sometimes happen when using pfs
578
- // no idea why, but tdlib also seems to handle these, so whatever
579
- this._session.initConnectionCalled = false;
580
- this._onMessageFailed(reqMsgId, res.errorMessage, true);
581
- // just setting this flag is not enough because the message
582
- // is already serialized, so we do this awesome hack
583
- this.sendRpc({ _: 'help.getNearestDc' })
584
- .then(() => {
585
- this.log.debug('additional help.getNearestDc for initConnection ok');
586
- })
587
- .catch((err) => {
588
- this.log.debug('additional help.getNearestDc for initConnection error: %s', err);
589
- });
590
- return;
591
- }
592
571
  if (rpc.cancelled)
593
572
  return;
573
+ switch (res.errorMessage) {
574
+ case 'AUTH_KEY_PERM_EMPTY':
575
+ // happens when temp auth key is not yet bound
576
+ // this shouldn't happen as we block any outbound communications
577
+ // until the temp key is derived and bound.
578
+ //
579
+ // i think it is also possible for the error to be returned
580
+ // when the temp key has expired, but this still shouldn't happen
581
+ // but this is tg, so something may go wrong, and we will receive this as an error
582
+ // (for god's sake why is this not in mtproto and instead hacked into the app layer)
583
+ this._authorizePfs();
584
+ this._onMessageFailed(reqMsgId, 'AUTH_KEY_PERM_EMPTY', true);
585
+ return;
586
+ case 'CONNECTION_NOT_INITED': {
587
+ // this seems to sometimes happen when using pfs
588
+ // no idea why, but tdlib also seems to handle these, so whatever
589
+ this._session.initConnectionCalled = false;
590
+ this._onMessageFailed(reqMsgId, res.errorMessage, true);
591
+ // just setting this flag is not enough because the message
592
+ // is already serialized, so we do this awesome hack
593
+ this.sendRpc({ _: 'help.getNearestDc' })
594
+ .then(() => {
595
+ this.log.debug('additional help.getNearestDc for initConnection ok');
596
+ })
597
+ .catch((err) => {
598
+ this.log.debug('additional help.getNearestDc for initConnection error: %s', err);
599
+ });
600
+ return;
601
+ }
602
+ case 'MSG_WAIT_TIMEOUT':
603
+ case 'MSG_WAIT_FAILED': {
604
+ if (!rpc.invokeAfter) {
605
+ this.log.warn('received %s for non-chained request %l', res.errorMessage, reqMsgId);
606
+ break;
607
+ }
608
+ // in some cases, MSG_WAIT_TIMEOUT is returned instead of MSG_WAIT_FAILED when one of the deps
609
+ // failed with MSG_WAIT_TIMEOUT. i have no clue why, this makes zero sense, but what fucking ever
610
+ //
611
+ // this basically means we can't handle a timeout any different than a general failure,
612
+ // because the timeout might not refer to the immediate `.invokeAfter` message, but to
613
+ // its arbitrary-depth dependency, so we indeed have to wait for the message ourselves...
614
+ if (this._session.pendingMessages.has(rpc.invokeAfter)) {
615
+ // the dependency is still pending, postpone the processing
616
+ this.log.debug('chain %s: waiting for %l before processing %l', rpc.chainId, rpc.invokeAfter, reqMsgId);
617
+ this._session.getPendingChainedFails(rpc.chainId).insert(rpc);
618
+ }
619
+ else {
620
+ this._session.chains.delete(rpc.chainId);
621
+ this._onMessageFailed(reqMsgId, 'MSG_WAIT_FAILED', true);
622
+ }
623
+ return;
624
+ }
625
+ }
594
626
  const error = tl_1.tl.RpcError.fromTl(res);
595
627
  if (this.params.niceStacks !== false) {
596
628
  makeNiceStack(error, rpc.stack, rpc.method);
@@ -606,9 +638,31 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
606
638
  return;
607
639
  rpc.promise.resolve(result);
608
640
  }
641
+ if (rpc.chainId) {
642
+ this._processPendingChainedFails(rpc.chainId, reqMsgId);
643
+ }
609
644
  this._onMessageAcked(reqMsgId);
610
645
  this._session.pendingMessages.delete(reqMsgId);
611
646
  }
647
+ _processPendingChainedFails(chainId, sinceMsgId) {
648
+ // sinceMsgId was already definitely received and contained an error.
649
+ // we should now re-send all the pending MSG_WAIT_FAILED after it
650
+ this._session.removeFromChain(chainId, sinceMsgId);
651
+ const oldPending = this._session.chainsPendingFails.get(chainId);
652
+ if (!oldPending?.length) {
653
+ return;
654
+ }
655
+ const idx = oldPending.index({ invokeAfter: sinceMsgId }, true);
656
+ if (idx === -1)
657
+ return;
658
+ const toFail = oldPending.raw.splice(idx);
659
+ this.log.debug('chain %s: failing %d dependant messages: %L', chainId, toFail.length, toFail);
660
+ // we're failing the rest of the chain, including the last message
661
+ this._session.chains.delete(chainId);
662
+ for (const rpc of toFail) {
663
+ this._onMessageFailed(rpc.msgId, 'MSG_WAIT_FAILED', true);
664
+ }
665
+ }
612
666
  _onMessageAcked(msgId, inContainer = false) {
613
667
  const msg = this._session.pendingMessages.get(msgId);
614
668
  if (!msg) {
@@ -640,6 +694,7 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
640
694
  break;
641
695
  }
642
696
  case 'bind':
697
+ case 'cancel':
643
698
  break; // do nothing, wait for the result
644
699
  default:
645
700
  if (!inContainer) {
@@ -714,6 +769,12 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
714
769
  case 'bind':
715
770
  this.log.debug('temp key binding request %l failed because of %s, retrying', msgId, reason);
716
771
  msgInfo.promise.reject(Error(reason));
772
+ break;
773
+ case 'future_salts':
774
+ this.log.debug('future_salts request %l failed because of %s, will retry', msgId, reason);
775
+ this._salts.isFetching = false;
776
+ // this is enough to make it retry on the next flush.
777
+ break;
717
778
  }
718
779
  this._session.pendingMessages.delete(msgId);
719
780
  }
@@ -743,7 +804,7 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
743
804
  this._session.resetLastPing();
744
805
  }
745
806
  _onBadServerSalt(msg) {
746
- this._session.serverSalt = msg.newServerSalt;
807
+ this._salts.currentSalt = msg.newServerSalt;
747
808
  this._onMessageFailed(msg.badMsgId, 'bad_server_salt');
748
809
  }
749
810
  _onBadMsgNotification(msgId, msg) {
@@ -781,7 +842,7 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
781
842
  // just been established, and the client will fetch them anyways
782
843
  this.emit('update', { _: 'updatesTooLong' });
783
844
  }
784
- this._session.serverSalt = serverSalt;
845
+ this._salts.currentSalt = serverSalt;
785
846
  this.log.debug('received new_session_created, uid = %l, first msg_id = %l', uniqueId, firstMsgId);
786
847
  for (const msgId of this._session.pendingMessages.keys()) {
787
848
  const val = this._session.pendingMessages.get(msgId);
@@ -820,7 +881,7 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
820
881
  case 2:
821
882
  case 3:
822
883
  // message wasn't received by the server
823
- this._onMessageFailed(msgId, `message info state ${status}`);
884
+ return this._onMessageFailed(msgId, `message info state ${status}`);
824
885
  break;
825
886
  case 0:
826
887
  if (!answerMsgId.isZero()) {
@@ -839,6 +900,10 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
839
900
  this._flushTimer.emitWhenIdle();
840
901
  return;
841
902
  }
903
+ if (answerMsgId.isZero()) {
904
+ this.log.debug('received message info for %l: message is still pending (status = %d)', msgId, status);
905
+ return;
906
+ }
842
907
  this.log.debug('received message info for %l, and answer (%l) was already received', msgId, answerMsgId);
843
908
  }
844
909
  _onMessagesInfo(msgIds, info) {
@@ -861,6 +926,21 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
861
926
  }
862
927
  this._onMessagesInfo(info.msgIds, msg.info);
863
928
  }
929
+ _onFutureSalts(msg) {
930
+ const info = this._session.pendingMessages.get(msg.reqMsgId);
931
+ if (!info) {
932
+ this.log.warn('received future_salts to unknown request %l', msg.reqMsgId);
933
+ return;
934
+ }
935
+ if (info._ !== 'future_salts') {
936
+ this.log.warn('received future_salts to %s query %l', info._, msg.reqMsgId);
937
+ return;
938
+ }
939
+ this.log.debug('received future_salts: %d salts', msg.salts.length);
940
+ this._salts.isFetching = false;
941
+ this._salts.setFutureSalts(msg.salts.slice());
942
+ this.emit('future-salts', msg.salts);
943
+ }
864
944
  _onDestroySessionResult(msg) {
865
945
  const reqMsgId = this._session.destroySessionIdToMsgId.get(msg.sessionId);
866
946
  if (!reqMsgId) {
@@ -892,7 +972,7 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
892
972
  this._flushTimer.emitWhenIdle();
893
973
  }
894
974
  }
895
- sendRpc(request, stack, timeout, abortSignal) {
975
+ sendRpc(request, stack, timeout, abortSignal, chainId) {
896
976
  if (this._usable && this.params.inactivityTimeout) {
897
977
  this._rescheduleInactivity();
898
978
  }
@@ -963,7 +1043,9 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
963
1043
  // we will need to know size of gzip_packed overhead in _flush()
964
1044
  gzipOverhead: shouldGzip ? 4 + tl_runtime_1.TlSerializationCounter.countBytesOverhead(content.length) : 0,
965
1045
  initConn,
1046
+ chainId,
966
1047
  // setting them as well so jit can optimize stuff
1048
+ invokeAfter: undefined,
967
1049
  sent: undefined,
968
1050
  done: undefined,
969
1051
  getState: undefined,
@@ -1010,6 +1092,7 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
1010
1092
  rpc.cancelled = true;
1011
1093
  if (rpc.msgId) {
1012
1094
  this._session.queuedCancelReq.push(rpc.msgId);
1095
+ this._session.getStateSchedule.remove(rpc);
1013
1096
  this._flushTimer.emitWhenIdle();
1014
1097
  }
1015
1098
  else {
@@ -1053,14 +1136,10 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
1053
1136
  // between multiple connections using the same session
1054
1137
  this._flushTimer.emitWhenIdle();
1055
1138
  }
1056
- else if (this._inactivityPendingFlush) {
1057
- this.log.debug('pending messages sent, closing connection');
1058
- this._flushTimer.reset();
1059
- this._inactivityPendingFlush = false;
1060
- super._onInactivityTimeout();
1061
- }
1062
1139
  else {
1063
- this._flushTimer.emitBefore(this._session.lastPingTime + 60000);
1140
+ const nextPingTime = this._session.lastPingTime + PING_INTERVAL;
1141
+ const nextGetScheduleTime = this._session.getStateSchedule.raw[0]?.getState || Infinity;
1142
+ this._flushTimer.emitBefore(Math.min(nextPingTime, nextGetScheduleTime));
1064
1143
  }
1065
1144
  }
1066
1145
  _doFlush() {
@@ -1075,6 +1154,8 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
1075
1154
  let containerSize = 0;
1076
1155
  let ackRequest = null;
1077
1156
  let ackMsgIds = null;
1157
+ let getFutureSaltsRequest = null;
1158
+ let getFutureSaltsMsgId = null;
1078
1159
  let pingRequest = null;
1079
1160
  let pingId = null;
1080
1161
  let pingMsgId = null;
@@ -1106,16 +1187,16 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
1106
1187
  packetSize += ackRequest.length + 16;
1107
1188
  messageCount += 1;
1108
1189
  }
1109
- const getStateTime = now + 1500;
1110
- if (now - this._session.lastPingTime > 60000) {
1190
+ if (now - this._session.lastPingTime > PING_INTERVAL) {
1111
1191
  if (!this._session.lastPingMsgId.isZero()) {
1112
1192
  this.log.warn("didn't receive pong for previous ping (msg_id = %l)", this._session.lastPingMsgId);
1113
1193
  this._session.pendingMessages.delete(this._session.lastPingMsgId);
1114
1194
  }
1115
1195
  pingId = (0, index_js_2.randomLong)();
1116
1196
  const obj = {
1117
- _: 'mt_ping',
1197
+ _: 'mt_ping_delay_disconnect',
1118
1198
  pingId,
1199
+ disconnectDelay: 75,
1119
1200
  };
1120
1201
  this._session.lastPingTime = Date.now();
1121
1202
  pingRequest = tl_runtime_1.TlBinaryWriter.serializeObject(this._writerMap, obj);
@@ -1180,6 +1261,16 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
1180
1261
  destroySessions = this._queuedDestroySession;
1181
1262
  this._queuedDestroySession = [];
1182
1263
  }
1264
+ if (this._salts.shouldFetchSalts()) {
1265
+ const obj = {
1266
+ _: 'mt_get_future_salts',
1267
+ num: 64,
1268
+ };
1269
+ getFutureSaltsRequest = tl_runtime_1.TlBinaryWriter.serializeObject(this._writerMap, obj);
1270
+ containerSize += getFutureSaltsRequest.length + 16;
1271
+ containerMessageCount += 1;
1272
+ this._salts.isFetching = true;
1273
+ }
1183
1274
  let forceContainer = false;
1184
1275
  const rpcToSend = [];
1185
1276
  while (this._session.queuedRpc.length &&
@@ -1195,6 +1286,9 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
1195
1286
  containerSize += msg.data.length + 16;
1196
1287
  if (msg.gzipOverhead)
1197
1288
  containerSize += msg.gzipOverhead;
1289
+ if (msg.chainId) {
1290
+ containerSize += INVOKE_AFTER_MSG_SIZE;
1291
+ }
1198
1292
  // if message was already assigned a msg_id,
1199
1293
  // we must wrap it in a container with a newer msg_id
1200
1294
  if (msg.msgId)
@@ -1287,6 +1381,16 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
1287
1381
  otherPendings.push(pending);
1288
1382
  });
1289
1383
  }
1384
+ if (getFutureSaltsRequest) {
1385
+ getFutureSaltsMsgId = this._registerOutgoingMsgId(this._session.writeMessage(writer, getFutureSaltsRequest));
1386
+ const pending = {
1387
+ _: 'future_salts',
1388
+ containerId: getFutureSaltsMsgId,
1389
+ };
1390
+ this._session.pendingMessages.set(getFutureSaltsMsgId, pending);
1391
+ otherPendings.push(pending);
1392
+ }
1393
+ const getStateTime = now + GET_STATE_INTERVAL;
1290
1394
  for (let i = 0; i < rpcToSend.length; i++) {
1291
1395
  const msg = rpcToSend[i];
1292
1396
  // not using writeMessage here because we also need seqNo, and
@@ -1302,6 +1406,10 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
1302
1406
  _: 'rpc',
1303
1407
  rpc: msg,
1304
1408
  });
1409
+ if (msg.chainId) {
1410
+ msg.invokeAfter = this._session.addToChain(msg.chainId, msgId);
1411
+ this.log.debug('chain %s: invoke %l after %l', msg.chainId, msg.msgId, msg.invokeAfter);
1412
+ }
1305
1413
  }
1306
1414
  else {
1307
1415
  this.log.debug('%s: msg_id already assigned, reusing %l, seqno: %d', msg.method, msg.msgId, msg.seqNo);
@@ -1318,13 +1426,22 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
1318
1426
  rootMsgId = msg.msgId;
1319
1427
  writer.long(this._registerOutgoingMsgId(msg.msgId));
1320
1428
  writer.uint(msg.seqNo);
1429
+ const invokeAfterSize = msg.invokeAfter ? INVOKE_AFTER_MSG_SIZE : 0;
1430
+ const writeInvokeAfter = () => {
1431
+ if (!msg.invokeAfter)
1432
+ return;
1433
+ writer.uint(INVOKE_AFTER_MSG_ID);
1434
+ writer.long(msg.invokeAfter);
1435
+ };
1321
1436
  if (msg.gzipOverhead) {
1322
- writer.uint(msg.data.length + msg.gzipOverhead);
1323
- writer.uint(0x3072cfa1); // gzip_packed#3072cfa1
1437
+ writer.uint(msg.data.length + msg.gzipOverhead + invokeAfterSize);
1438
+ writeInvokeAfter();
1439
+ writer.uint(GZIP_PACKED_ID);
1324
1440
  writer.bytes(msg.data);
1325
1441
  }
1326
1442
  else {
1327
- writer.uint(msg.data.length);
1443
+ writer.uint(msg.data.length + invokeAfterSize);
1444
+ writeInvokeAfter();
1328
1445
  writer.raw(msg.data);
1329
1446
  }
1330
1447
  msg.sent = true;
@@ -1333,7 +1450,7 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
1333
1450
  // we now need to assign the container msg_id and seqno
1334
1451
  // we couldn't have assigned them earlier because mtproto
1335
1452
  // requires them to be >= than the contained messages
1336
- // writer.pos is expected to be packetSize
1453
+ packetSize = writer.pos;
1337
1454
  const containerId = this._session.getMessageId();
1338
1455
  writer.pos = 0;
1339
1456
  rootMsgId = containerId;
@@ -1359,9 +1476,9 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
1359
1476
  });
1360
1477
  }
1361
1478
  const result = writer.result();
1362
- this.log.debug('sending %d messages: size = %db, acks = %d (msg_id = %s), ping = %s (msg_id = %s), state_req = %s (msg_id = %s), resend = %s (msg_id = %s), cancels = %s (msg_id = %s), rpc = %s, container = %s, root msg_id = %l', messageCount, packetSize, ackMsgIds?.length || 'false', ackMsgIds?.map((it) => it.toString()), Boolean(pingRequest), pingMsgId, getStateMsgIds?.map((it) => it.toString()) || 'false', getStateMsgId, resendMsgIds?.map((it) => it.toString()) || 'false', cancelRpcs, cancelRpcs?.map((it) => it.toString()) || 'false', resendMsgId, rpcToSend.map((it) => it.method), useContainer, rootMsgId);
1479
+ this.log.debug('sending %d messages: size = %db, acks = %L, ping = %b (msg_id = %l), state_req = %L (msg_id = %l), resend = %L (msg_id = %l), cancels = %L (msg_id = %l), salts_req = %b (msg_id = %l), rpc = %s, container = %b, root msg_id = %l', messageCount, packetSize, ackMsgIds, pingRequest, pingMsgId, getStateMsgIds, getStateMsgId, resendMsgIds, cancelRpcs, cancelRpcs, resendMsgId, getFutureSaltsRequest, getFutureSaltsMsgId, rpcToSend.map((it) => it.method), useContainer, rootMsgId);
1363
1480
  const enc = this._session.encryptMessage(result);
1364
- this.send(enc).catch((err) => {
1481
+ const promise = this.send(enc).catch((err) => {
1365
1482
  this.log.error('error while sending pending messages (root msg_id = %l): %s', rootMsgId, err.stack);
1366
1483
  // put acks in the front so they are the first to be sent
1367
1484
  if (ackMsgIds) {
@@ -1371,6 +1488,14 @@ class SessionConnection extends persistent_connection_js_1.PersistentConnection
1371
1488
  this._onMessageFailed(rootMsgId, 'unknown error');
1372
1489
  }
1373
1490
  });
1491
+ if (this._inactivityPendingFlush && !this._session.hasPendingMessages) {
1492
+ void promise.then(() => {
1493
+ this.log.debug('pending messages sent, closing connection');
1494
+ this._flushTimer.reset();
1495
+ this._inactivityPendingFlush = false;
1496
+ super._onInactivityTimeout();
1497
+ });
1498
+ }
1374
1499
  }
1375
1500
  }
1376
1501
  exports.SessionConnection = SessionConnection;