@mtcute/core 0.2.3 → 0.4.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 (127) hide show
  1. package/cjs/base-client.d.ts +33 -166
  2. package/cjs/base-client.js +69 -45
  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 +35 -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 +193 -54
  24. package/cjs/network/session-connection.js.map +1 -1
  25. package/cjs/storage/abstract.d.ts +13 -2
  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-file.d.ts +1 -2
  34. package/cjs/storage/json-file.js +4 -12
  35. package/cjs/storage/json-file.js.map +1 -1
  36. package/cjs/storage/json.js +1 -0
  37. package/cjs/storage/json.js.map +1 -1
  38. package/cjs/storage/memory.d.ts +5 -2
  39. package/cjs/storage/memory.js +16 -2
  40. package/cjs/storage/memory.js.map +1 -1
  41. package/cjs/storage/memory.test.js +2 -1
  42. package/cjs/storage/memory.test.js.map +1 -1
  43. package/cjs/utils/index.d.ts +1 -0
  44. package/cjs/utils/index.js +1 -0
  45. package/cjs/utils/index.js.map +1 -1
  46. package/cjs/utils/logger.js +8 -2
  47. package/cjs/utils/logger.js.map +1 -1
  48. package/cjs/utils/logger.test.js +12 -0
  49. package/cjs/utils/logger.test.js.map +1 -1
  50. package/cjs/utils/long-utils.d.ts +5 -0
  51. package/cjs/utils/long-utils.js +13 -1
  52. package/cjs/utils/long-utils.js.map +1 -1
  53. package/cjs/utils/platform/exit-hook.d.ts +1 -0
  54. package/cjs/utils/platform/exit-hook.js +45 -0
  55. package/cjs/utils/platform/exit-hook.js.map +1 -0
  56. package/cjs/utils/platform/exit-hook.web.d.ts +1 -0
  57. package/cjs/utils/platform/exit-hook.web.js +21 -0
  58. package/cjs/utils/platform/exit-hook.web.js.map +1 -0
  59. package/cjs/utils/sorted-array.js +1 -0
  60. package/cjs/utils/sorted-array.js.map +1 -1
  61. package/cjs/utils/type-assertions.d.ts +1 -0
  62. package/cjs/utils/type-assertions.js +7 -1
  63. package/cjs/utils/type-assertions.js.map +1 -1
  64. package/esm/base-client.d.ts +33 -166
  65. package/esm/base-client.js +69 -45
  66. package/esm/base-client.js.map +1 -1
  67. package/esm/base-client.types.d.ts +153 -0
  68. package/esm/base-client.types.js +2 -0
  69. package/esm/base-client.types.js.map +1 -0
  70. package/esm/index.d.ts +1 -0
  71. package/esm/index.js +1 -0
  72. package/esm/index.js.map +1 -1
  73. package/esm/network/mtproto-session.d.ts +10 -2
  74. package/esm/network/mtproto-session.js +36 -4
  75. package/esm/network/mtproto-session.js.map +1 -1
  76. package/esm/network/multi-session-connection.d.ts +1 -1
  77. package/esm/network/multi-session-connection.js +4 -3
  78. package/esm/network/multi-session-connection.js.map +1 -1
  79. package/esm/network/network-manager.d.ts +23 -0
  80. package/esm/network/network-manager.js +29 -9
  81. package/esm/network/network-manager.js.map +1 -1
  82. package/esm/network/server-salt.d.ts +12 -0
  83. package/esm/network/server-salt.js +38 -0
  84. package/esm/network/server-salt.js.map +1 -0
  85. package/esm/network/session-connection.d.ts +6 -1
  86. package/esm/network/session-connection.js +193 -54
  87. package/esm/network/session-connection.js.map +1 -1
  88. package/esm/storage/abstract.d.ts +13 -2
  89. package/esm/storage/abstract.js.map +1 -1
  90. package/esm/storage/idb.d.ts +3 -1
  91. package/esm/storage/idb.js +17 -0
  92. package/esm/storage/idb.js.map +1 -1
  93. package/esm/storage/index.d.ts +1 -0
  94. package/esm/storage/index.js +1 -0
  95. package/esm/storage/index.js.map +1 -1
  96. package/esm/storage/json-file.d.ts +1 -2
  97. package/esm/storage/json-file.js +4 -12
  98. package/esm/storage/json-file.js.map +1 -1
  99. package/esm/storage/json.js +1 -0
  100. package/esm/storage/json.js.map +1 -1
  101. package/esm/storage/memory.d.ts +5 -2
  102. package/esm/storage/memory.js +16 -2
  103. package/esm/storage/memory.js.map +1 -1
  104. package/esm/storage/memory.test.js +2 -1
  105. package/esm/storage/memory.test.js.map +1 -1
  106. package/esm/utils/index.d.ts +1 -0
  107. package/esm/utils/index.js +1 -0
  108. package/esm/utils/index.js.map +1 -1
  109. package/esm/utils/logger.js +8 -2
  110. package/esm/utils/logger.js.map +1 -1
  111. package/esm/utils/logger.test.js +12 -0
  112. package/esm/utils/logger.test.js.map +1 -1
  113. package/esm/utils/long-utils.d.ts +5 -0
  114. package/esm/utils/long-utils.js +11 -0
  115. package/esm/utils/long-utils.js.map +1 -1
  116. package/esm/utils/platform/exit-hook.d.ts +1 -0
  117. package/esm/utils/platform/exit-hook.js +41 -0
  118. package/esm/utils/platform/exit-hook.js.map +1 -0
  119. package/esm/utils/platform/exit-hook.web.d.ts +1 -0
  120. package/esm/utils/platform/exit-hook.web.js +17 -0
  121. package/esm/utils/platform/exit-hook.web.js.map +1 -0
  122. package/esm/utils/sorted-array.js +1 -0
  123. package/esm/utils/sorted-array.js.map +1 -1
  124. package/esm/utils/type-assertions.d.ts +1 -0
  125. package/esm/utils/type-assertions.js +5 -0
  126. package/esm/utils/type-assertions.js.map +1 -1
  127. package/package.json +6 -4
@@ -10,7 +10,9 @@ import { reportUnknownError } from '../utils/platform/error-reporting.js';
10
10
  import { doAuthorization } from './authorization.js';
11
11
  import { PersistentConnection } from './persistent-connection.js';
12
12
  import { TransportError } from './transports/abstract.js';
13
- const TEMP_AUTH_KEY_EXPIRY = 86400;
13
+ const TEMP_AUTH_KEY_EXPIRY = 86400; // 24 hours
14
+ const PING_INTERVAL = 60000; // 1 minute
15
+ const GET_STATE_INTERVAL = 1500; // 1.5 seconds
14
16
  // destroy_auth_key#d1435160 = DestroyAuthKeyRes;
15
17
  // const DESTROY_AUTH_KEY = Buffer.from('605134d1', 'hex')
16
18
  // gzip_packed#3072cfa1 packed_data:string = Object;
@@ -19,6 +21,9 @@ const GZIP_PACKED_ID = 0x3072cfa1;
19
21
  const MSG_CONTAINER_ID = 0x73f1f8dc;
20
22
  // rpc_result#f35c6d01 req_msg_id:long result:Object = RpcResult;
21
23
  const RPC_RESULT_ID = 0xf35c6d01;
24
+ // invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
25
+ const INVOKE_AFTER_MSG_ID = 0xcb9f372d;
26
+ const INVOKE_AFTER_MSG_SIZE = 12; // 8 (invokeAfterMsg) + 4 (msg_id)
22
27
  function makeNiceStack(error, stack, method) {
23
28
  error.stack = `RpcError (${error.code} ${error.text}): ${error.message}\n at ${method}\n${stack
24
29
  .split('\n')
@@ -44,6 +49,7 @@ export class SessionConnection extends PersistentConnection {
44
49
  this._readerMap = params.readerMap;
45
50
  this._writerMap = params.writerMap;
46
51
  this._crypto = params.crypto;
52
+ this._salts = params.salts;
47
53
  this._handleRawMessage = this._handleRawMessage.bind(this);
48
54
  }
49
55
  getAuthKey(temp = false) {
@@ -81,6 +87,7 @@ export class SessionConnection extends PersistentConnection {
81
87
  reset(forever = false) {
82
88
  this._session.initConnectionCalled = false;
83
89
  this._flushTimer.reset();
90
+ this._salts.isFetching = false;
84
91
  if (forever) {
85
92
  this.removeAllListeners();
86
93
  }
@@ -182,7 +189,7 @@ export class SessionConnection extends PersistentConnection {
182
189
  doAuthorization(this, this._crypto)
183
190
  .then(([authKey, serverSalt, timeOffset]) => {
184
191
  this._session._authKey.setup(authKey);
185
- this._session.serverSalt = serverSalt;
192
+ this._salts.currentSalt = serverSalt;
186
193
  this._session._timeOffset = timeOffset;
187
194
  this._session.authorizationPending = false;
188
195
  this.emit('key-change', authKey);
@@ -300,7 +307,7 @@ export class SessionConnection extends PersistentConnection {
300
307
  // and primary is not immediately forgot because messages using it may still be in flight)
301
308
  this._session._authKeyTempSecondary = this._session._authKeyTemp;
302
309
  this._session._authKeyTemp = tempKey;
303
- this._session.serverSalt = tempServerSalt;
310
+ this._salts.currentSalt = tempServerSalt;
304
311
  this.log.debug('temp key has been bound, exp = %d', inner.expiresAt);
305
312
  this._isPfsBindingPending = false;
306
313
  this._isPfsBindingPendingInBackground = false;
@@ -439,7 +446,7 @@ export class SessionConnection extends PersistentConnection {
439
446
  this._onMsgsStateInfo(message);
440
447
  break;
441
448
  case 'mt_future_salts':
442
- // todo
449
+ this._onFutureSalts(message);
443
450
  break;
444
451
  case 'mt_msgs_state_req':
445
452
  case 'mt_msg_resend_req':
@@ -520,7 +527,7 @@ export class SessionConnection extends PersistentConnection {
520
527
  this.log.debug('failed to parse rpc_result for cancel request %l, ignoring', reqMsgId);
521
528
  return;
522
529
  }
523
- this.log.debug('received %s for cancelled request %l: %j', result._, reqMsgId);
530
+ this.log.debug('received %s for cancelled request %l: %j', result._, reqMsgId, result);
524
531
  this._onMessageAcked(reqMsgId);
525
532
  return;
526
533
  }
@@ -546,6 +553,7 @@ export class SessionConnection extends PersistentConnection {
546
553
  }
547
554
  // initConnection call was definitely received and
548
555
  // processed by the server, so we no longer need to use it
556
+ // todo: is this the case with failed invokeAfterMsg(s) as well?
549
557
  if (rpc.initConn) {
550
558
  this._session.initConnectionCalled = true;
551
559
  }
@@ -554,37 +562,61 @@ export class SessionConnection extends PersistentConnection {
554
562
  if (result._ === 'mt_rpc_error') {
555
563
  const res = result;
556
564
  this.log.debug('received rpc_error [%d:%s] for %l (%s)', res.errorCode, res.errorMessage, reqMsgId, rpc.method);
557
- if (res.errorMessage === 'AUTH_KEY_PERM_EMPTY') {
558
- // happens when temp auth key is not yet bound
559
- // this shouldn't happen as we block any outbound communications
560
- // until the temp key is derived and bound.
561
- //
562
- // i think it is also possible for the error to be returned
563
- // when the temp key has expired, but this still shouldn't happen
564
- // but this is tg, so something may go wrong, and we will receive this as an error
565
- // (for god's sake why is this not in mtproto and instead hacked into the app layer)
566
- this._authorizePfs();
567
- this._onMessageFailed(reqMsgId, 'AUTH_KEY_PERM_EMPTY', true);
568
- return;
569
- }
570
- if (res.errorMessage === 'CONNECTION_NOT_INITED') {
571
- // this seems to sometimes happen when using pfs
572
- // no idea why, but tdlib also seems to handle these, so whatever
573
- this._session.initConnectionCalled = false;
574
- this._onMessageFailed(reqMsgId, res.errorMessage, true);
575
- // just setting this flag is not enough because the message
576
- // is already serialized, so we do this awesome hack
577
- this.sendRpc({ _: 'help.getNearestDc' })
578
- .then(() => {
579
- this.log.debug('additional help.getNearestDc for initConnection ok');
580
- })
581
- .catch((err) => {
582
- this.log.debug('additional help.getNearestDc for initConnection error: %s', err);
583
- });
584
- return;
585
- }
586
565
  if (rpc.cancelled)
587
566
  return;
567
+ switch (res.errorMessage) {
568
+ case 'AUTH_KEY_PERM_EMPTY':
569
+ // happens when temp auth key is not yet bound
570
+ // this shouldn't happen as we block any outbound communications
571
+ // until the temp key is derived and bound.
572
+ //
573
+ // i think it is also possible for the error to be returned
574
+ // when the temp key has expired, but this still shouldn't happen
575
+ // but this is tg, so something may go wrong, and we will receive this as an error
576
+ // (for god's sake why is this not in mtproto and instead hacked into the app layer)
577
+ this._authorizePfs();
578
+ this._onMessageFailed(reqMsgId, 'AUTH_KEY_PERM_EMPTY', true);
579
+ return;
580
+ case 'CONNECTION_NOT_INITED': {
581
+ // this seems to sometimes happen when using pfs
582
+ // no idea why, but tdlib also seems to handle these, so whatever
583
+ this._session.initConnectionCalled = false;
584
+ this._onMessageFailed(reqMsgId, res.errorMessage, true);
585
+ // just setting this flag is not enough because the message
586
+ // is already serialized, so we do this awesome hack
587
+ this.sendRpc({ _: 'help.getNearestDc' })
588
+ .then(() => {
589
+ this.log.debug('additional help.getNearestDc for initConnection ok');
590
+ })
591
+ .catch((err) => {
592
+ this.log.debug('additional help.getNearestDc for initConnection error: %s', err);
593
+ });
594
+ return;
595
+ }
596
+ case 'MSG_WAIT_TIMEOUT':
597
+ case 'MSG_WAIT_FAILED': {
598
+ if (!rpc.invokeAfter) {
599
+ this.log.warn('received %s for non-chained request %l', res.errorMessage, reqMsgId);
600
+ break;
601
+ }
602
+ // in some cases, MSG_WAIT_TIMEOUT is returned instead of MSG_WAIT_FAILED when one of the deps
603
+ // failed with MSG_WAIT_TIMEOUT. i have no clue why, this makes zero sense, but what fucking ever
604
+ //
605
+ // this basically means we can't handle a timeout any different than a general failure,
606
+ // because the timeout might not refer to the immediate `.invokeAfter` message, but to
607
+ // its arbitrary-depth dependency, so we indeed have to wait for the message ourselves...
608
+ if (this._session.pendingMessages.has(rpc.invokeAfter)) {
609
+ // the dependency is still pending, postpone the processing
610
+ this.log.debug('chain %s: waiting for %l before processing %l', rpc.chainId, rpc.invokeAfter, reqMsgId);
611
+ this._session.getPendingChainedFails(rpc.chainId).insert(rpc);
612
+ }
613
+ else {
614
+ this._session.chains.delete(rpc.chainId);
615
+ this._onMessageFailed(reqMsgId, 'MSG_WAIT_FAILED', true);
616
+ }
617
+ return;
618
+ }
619
+ }
588
620
  const error = tl.RpcError.fromTl(res);
589
621
  if (this.params.niceStacks !== false) {
590
622
  makeNiceStack(error, rpc.stack, rpc.method);
@@ -600,9 +632,31 @@ export class SessionConnection extends PersistentConnection {
600
632
  return;
601
633
  rpc.promise.resolve(result);
602
634
  }
635
+ if (rpc.chainId) {
636
+ this._processPendingChainedFails(rpc.chainId, reqMsgId);
637
+ }
603
638
  this._onMessageAcked(reqMsgId);
604
639
  this._session.pendingMessages.delete(reqMsgId);
605
640
  }
641
+ _processPendingChainedFails(chainId, sinceMsgId) {
642
+ // sinceMsgId was already definitely received and contained an error.
643
+ // we should now re-send all the pending MSG_WAIT_FAILED after it
644
+ this._session.removeFromChain(chainId, sinceMsgId);
645
+ const oldPending = this._session.chainsPendingFails.get(chainId);
646
+ if (!oldPending?.length) {
647
+ return;
648
+ }
649
+ const idx = oldPending.index({ invokeAfter: sinceMsgId }, true);
650
+ if (idx === -1)
651
+ return;
652
+ const toFail = oldPending.raw.splice(idx);
653
+ this.log.debug('chain %s: failing %d dependant messages: %L', chainId, toFail.length, toFail);
654
+ // we're failing the rest of the chain, including the last message
655
+ this._session.chains.delete(chainId);
656
+ for (const rpc of toFail) {
657
+ this._onMessageFailed(rpc.msgId, 'MSG_WAIT_FAILED', true);
658
+ }
659
+ }
606
660
  _onMessageAcked(msgId, inContainer = false) {
607
661
  const msg = this._session.pendingMessages.get(msgId);
608
662
  if (!msg) {
@@ -634,6 +688,7 @@ export class SessionConnection extends PersistentConnection {
634
688
  break;
635
689
  }
636
690
  case 'bind':
691
+ case 'cancel':
637
692
  break; // do nothing, wait for the result
638
693
  default:
639
694
  if (!inContainer) {
@@ -708,6 +763,12 @@ export class SessionConnection extends PersistentConnection {
708
763
  case 'bind':
709
764
  this.log.debug('temp key binding request %l failed because of %s, retrying', msgId, reason);
710
765
  msgInfo.promise.reject(Error(reason));
766
+ break;
767
+ case 'future_salts':
768
+ this.log.debug('future_salts request %l failed because of %s, will retry', msgId, reason);
769
+ this._salts.isFetching = false;
770
+ // this is enough to make it retry on the next flush.
771
+ break;
711
772
  }
712
773
  this._session.pendingMessages.delete(msgId);
713
774
  }
@@ -737,7 +798,7 @@ export class SessionConnection extends PersistentConnection {
737
798
  this._session.resetLastPing();
738
799
  }
739
800
  _onBadServerSalt(msg) {
740
- this._session.serverSalt = msg.newServerSalt;
801
+ this._salts.currentSalt = msg.newServerSalt;
741
802
  this._onMessageFailed(msg.badMsgId, 'bad_server_salt');
742
803
  }
743
804
  _onBadMsgNotification(msgId, msg) {
@@ -775,7 +836,7 @@ export class SessionConnection extends PersistentConnection {
775
836
  // just been established, and the client will fetch them anyways
776
837
  this.emit('update', { _: 'updatesTooLong' });
777
838
  }
778
- this._session.serverSalt = serverSalt;
839
+ this._salts.currentSalt = serverSalt;
779
840
  this.log.debug('received new_session_created, uid = %l, first msg_id = %l', uniqueId, firstMsgId);
780
841
  for (const msgId of this._session.pendingMessages.keys()) {
781
842
  const val = this._session.pendingMessages.get(msgId);
@@ -814,7 +875,7 @@ export class SessionConnection extends PersistentConnection {
814
875
  case 2:
815
876
  case 3:
816
877
  // message wasn't received by the server
817
- this._onMessageFailed(msgId, `message info state ${status}`);
878
+ return this._onMessageFailed(msgId, `message info state ${status}`);
818
879
  break;
819
880
  case 0:
820
881
  if (!answerMsgId.isZero()) {
@@ -833,6 +894,10 @@ export class SessionConnection extends PersistentConnection {
833
894
  this._flushTimer.emitWhenIdle();
834
895
  return;
835
896
  }
897
+ if (answerMsgId.isZero()) {
898
+ this.log.debug('received message info for %l: message is still pending (status = %d)', msgId, status);
899
+ return;
900
+ }
836
901
  this.log.debug('received message info for %l, and answer (%l) was already received', msgId, answerMsgId);
837
902
  }
838
903
  _onMessagesInfo(msgIds, info) {
@@ -855,6 +920,21 @@ export class SessionConnection extends PersistentConnection {
855
920
  }
856
921
  this._onMessagesInfo(info.msgIds, msg.info);
857
922
  }
923
+ _onFutureSalts(msg) {
924
+ const info = this._session.pendingMessages.get(msg.reqMsgId);
925
+ if (!info) {
926
+ this.log.warn('received future_salts to unknown request %l', msg.reqMsgId);
927
+ return;
928
+ }
929
+ if (info._ !== 'future_salts') {
930
+ this.log.warn('received future_salts to %s query %l', info._, msg.reqMsgId);
931
+ return;
932
+ }
933
+ this.log.debug('received future_salts: %d salts', msg.salts.length);
934
+ this._salts.isFetching = false;
935
+ this._salts.setFutureSalts(msg.salts.slice());
936
+ this.emit('future-salts', msg.salts);
937
+ }
858
938
  _onDestroySessionResult(msg) {
859
939
  const reqMsgId = this._session.destroySessionIdToMsgId.get(msg.sessionId);
860
940
  if (!reqMsgId) {
@@ -886,7 +966,7 @@ export class SessionConnection extends PersistentConnection {
886
966
  this._flushTimer.emitWhenIdle();
887
967
  }
888
968
  }
889
- sendRpc(request, stack, timeout, abortSignal) {
969
+ sendRpc(request, stack, timeout, abortSignal, chainId) {
890
970
  if (this._usable && this.params.inactivityTimeout) {
891
971
  this._rescheduleInactivity();
892
972
  }
@@ -957,7 +1037,9 @@ export class SessionConnection extends PersistentConnection {
957
1037
  // we will need to know size of gzip_packed overhead in _flush()
958
1038
  gzipOverhead: shouldGzip ? 4 + TlSerializationCounter.countBytesOverhead(content.length) : 0,
959
1039
  initConn,
1040
+ chainId,
960
1041
  // setting them as well so jit can optimize stuff
1042
+ invokeAfter: undefined,
961
1043
  sent: undefined,
962
1044
  done: undefined,
963
1045
  getState: undefined,
@@ -1004,6 +1086,7 @@ export class SessionConnection extends PersistentConnection {
1004
1086
  rpc.cancelled = true;
1005
1087
  if (rpc.msgId) {
1006
1088
  this._session.queuedCancelReq.push(rpc.msgId);
1089
+ this._session.getStateSchedule.remove(rpc);
1007
1090
  this._flushTimer.emitWhenIdle();
1008
1091
  }
1009
1092
  else {
@@ -1015,6 +1098,20 @@ export class SessionConnection extends PersistentConnection {
1015
1098
  _onInactivityTimeout() {
1016
1099
  // we should send all pending acks and other service messages
1017
1100
  // before dropping the connection
1101
+ // additionally, if we are still waiting for some rpc results,
1102
+ // we should wait for them first
1103
+ let hasPendingRpc = false;
1104
+ for (const it of this._session.pendingMessages.values()) {
1105
+ if (it._ === 'rpc') {
1106
+ hasPendingRpc = true;
1107
+ break;
1108
+ }
1109
+ }
1110
+ if (hasPendingRpc) {
1111
+ this.log.debug('waiting for pending rpc queries to finish before closing connection');
1112
+ this._rescheduleInactivity();
1113
+ return;
1114
+ }
1018
1115
  if (!this._session.hasPendingMessages) {
1019
1116
  this.log.debug('no pending service messages, closing connection');
1020
1117
  super._onInactivityTimeout();
@@ -1047,14 +1144,10 @@ export class SessionConnection extends PersistentConnection {
1047
1144
  // between multiple connections using the same session
1048
1145
  this._flushTimer.emitWhenIdle();
1049
1146
  }
1050
- else if (this._inactivityPendingFlush) {
1051
- this.log.debug('pending messages sent, closing connection');
1052
- this._flushTimer.reset();
1053
- this._inactivityPendingFlush = false;
1054
- super._onInactivityTimeout();
1055
- }
1056
1147
  else {
1057
- this._flushTimer.emitBefore(this._session.lastPingTime + 60000);
1148
+ const nextPingTime = this._session.lastPingTime + PING_INTERVAL;
1149
+ const nextGetScheduleTime = this._session.getStateSchedule.raw[0]?.getState || Infinity;
1150
+ this._flushTimer.emitBefore(Math.min(nextPingTime, nextGetScheduleTime));
1058
1151
  }
1059
1152
  }
1060
1153
  _doFlush() {
@@ -1069,6 +1162,8 @@ export class SessionConnection extends PersistentConnection {
1069
1162
  let containerSize = 0;
1070
1163
  let ackRequest = null;
1071
1164
  let ackMsgIds = null;
1165
+ let getFutureSaltsRequest = null;
1166
+ let getFutureSaltsMsgId = null;
1072
1167
  let pingRequest = null;
1073
1168
  let pingId = null;
1074
1169
  let pingMsgId = null;
@@ -1100,16 +1195,16 @@ export class SessionConnection extends PersistentConnection {
1100
1195
  packetSize += ackRequest.length + 16;
1101
1196
  messageCount += 1;
1102
1197
  }
1103
- const getStateTime = now + 1500;
1104
- if (now - this._session.lastPingTime > 60000) {
1198
+ if (now - this._session.lastPingTime > PING_INTERVAL) {
1105
1199
  if (!this._session.lastPingMsgId.isZero()) {
1106
1200
  this.log.warn("didn't receive pong for previous ping (msg_id = %l)", this._session.lastPingMsgId);
1107
1201
  this._session.pendingMessages.delete(this._session.lastPingMsgId);
1108
1202
  }
1109
1203
  pingId = randomLong();
1110
1204
  const obj = {
1111
- _: 'mt_ping',
1205
+ _: 'mt_ping_delay_disconnect',
1112
1206
  pingId,
1207
+ disconnectDelay: 75,
1113
1208
  };
1114
1209
  this._session.lastPingTime = Date.now();
1115
1210
  pingRequest = TlBinaryWriter.serializeObject(this._writerMap, obj);
@@ -1174,6 +1269,16 @@ export class SessionConnection extends PersistentConnection {
1174
1269
  destroySessions = this._queuedDestroySession;
1175
1270
  this._queuedDestroySession = [];
1176
1271
  }
1272
+ if (this._salts.shouldFetchSalts()) {
1273
+ const obj = {
1274
+ _: 'mt_get_future_salts',
1275
+ num: 64,
1276
+ };
1277
+ getFutureSaltsRequest = TlBinaryWriter.serializeObject(this._writerMap, obj);
1278
+ containerSize += getFutureSaltsRequest.length + 16;
1279
+ containerMessageCount += 1;
1280
+ this._salts.isFetching = true;
1281
+ }
1177
1282
  let forceContainer = false;
1178
1283
  const rpcToSend = [];
1179
1284
  while (this._session.queuedRpc.length &&
@@ -1189,6 +1294,9 @@ export class SessionConnection extends PersistentConnection {
1189
1294
  containerSize += msg.data.length + 16;
1190
1295
  if (msg.gzipOverhead)
1191
1296
  containerSize += msg.gzipOverhead;
1297
+ if (msg.chainId) {
1298
+ containerSize += INVOKE_AFTER_MSG_SIZE;
1299
+ }
1192
1300
  // if message was already assigned a msg_id,
1193
1301
  // we must wrap it in a container with a newer msg_id
1194
1302
  if (msg.msgId)
@@ -1281,6 +1389,16 @@ export class SessionConnection extends PersistentConnection {
1281
1389
  otherPendings.push(pending);
1282
1390
  });
1283
1391
  }
1392
+ if (getFutureSaltsRequest) {
1393
+ getFutureSaltsMsgId = this._registerOutgoingMsgId(this._session.writeMessage(writer, getFutureSaltsRequest));
1394
+ const pending = {
1395
+ _: 'future_salts',
1396
+ containerId: getFutureSaltsMsgId,
1397
+ };
1398
+ this._session.pendingMessages.set(getFutureSaltsMsgId, pending);
1399
+ otherPendings.push(pending);
1400
+ }
1401
+ const getStateTime = now + GET_STATE_INTERVAL;
1284
1402
  for (let i = 0; i < rpcToSend.length; i++) {
1285
1403
  const msg = rpcToSend[i];
1286
1404
  // not using writeMessage here because we also need seqNo, and
@@ -1296,6 +1414,10 @@ export class SessionConnection extends PersistentConnection {
1296
1414
  _: 'rpc',
1297
1415
  rpc: msg,
1298
1416
  });
1417
+ if (msg.chainId) {
1418
+ msg.invokeAfter = this._session.addToChain(msg.chainId, msgId);
1419
+ this.log.debug('chain %s: invoke %l after %l', msg.chainId, msg.msgId, msg.invokeAfter);
1420
+ }
1299
1421
  }
1300
1422
  else {
1301
1423
  this.log.debug('%s: msg_id already assigned, reusing %l, seqno: %d', msg.method, msg.msgId, msg.seqNo);
@@ -1312,13 +1434,22 @@ export class SessionConnection extends PersistentConnection {
1312
1434
  rootMsgId = msg.msgId;
1313
1435
  writer.long(this._registerOutgoingMsgId(msg.msgId));
1314
1436
  writer.uint(msg.seqNo);
1437
+ const invokeAfterSize = msg.invokeAfter ? INVOKE_AFTER_MSG_SIZE : 0;
1438
+ const writeInvokeAfter = () => {
1439
+ if (!msg.invokeAfter)
1440
+ return;
1441
+ writer.uint(INVOKE_AFTER_MSG_ID);
1442
+ writer.long(msg.invokeAfter);
1443
+ };
1315
1444
  if (msg.gzipOverhead) {
1316
- writer.uint(msg.data.length + msg.gzipOverhead);
1317
- writer.uint(0x3072cfa1); // gzip_packed#3072cfa1
1445
+ writer.uint(msg.data.length + msg.gzipOverhead + invokeAfterSize);
1446
+ writeInvokeAfter();
1447
+ writer.uint(GZIP_PACKED_ID);
1318
1448
  writer.bytes(msg.data);
1319
1449
  }
1320
1450
  else {
1321
- writer.uint(msg.data.length);
1451
+ writer.uint(msg.data.length + invokeAfterSize);
1452
+ writeInvokeAfter();
1322
1453
  writer.raw(msg.data);
1323
1454
  }
1324
1455
  msg.sent = true;
@@ -1327,7 +1458,7 @@ export class SessionConnection extends PersistentConnection {
1327
1458
  // we now need to assign the container msg_id and seqno
1328
1459
  // we couldn't have assigned them earlier because mtproto
1329
1460
  // requires them to be >= than the contained messages
1330
- // writer.pos is expected to be packetSize
1461
+ packetSize = writer.pos;
1331
1462
  const containerId = this._session.getMessageId();
1332
1463
  writer.pos = 0;
1333
1464
  rootMsgId = containerId;
@@ -1353,9 +1484,9 @@ export class SessionConnection extends PersistentConnection {
1353
1484
  });
1354
1485
  }
1355
1486
  const result = writer.result();
1356
- 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);
1487
+ 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);
1357
1488
  const enc = this._session.encryptMessage(result);
1358
- this.send(enc).catch((err) => {
1489
+ const promise = this.send(enc).catch((err) => {
1359
1490
  this.log.error('error while sending pending messages (root msg_id = %l): %s', rootMsgId, err.stack);
1360
1491
  // put acks in the front so they are the first to be sent
1361
1492
  if (ackMsgIds) {
@@ -1365,6 +1496,14 @@ export class SessionConnection extends PersistentConnection {
1365
1496
  this._onMessageFailed(rootMsgId, 'unknown error');
1366
1497
  }
1367
1498
  });
1499
+ if (this._inactivityPendingFlush && !this._session.hasPendingMessages) {
1500
+ void promise.then(() => {
1501
+ this.log.debug('pending messages sent, closing connection');
1502
+ this._flushTimer.reset();
1503
+ this._inactivityPendingFlush = false;
1504
+ super._onInactivityTimeout();
1505
+ });
1506
+ }
1368
1507
  }
1369
1508
  }
1370
1509
  //# sourceMappingURL=session-connection.js.map