@mtkruto/node 0.0.985 → 0.0.987

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 (45) hide show
  1. package/esm/client/0_utilities.d.ts +2 -0
  2. package/esm/client/0_utilities.js +22 -0
  3. package/esm/client/1_client_abstract.d.ts +15 -10
  4. package/esm/client/1_client_abstract.js +26 -27
  5. package/esm/client/2_client_plain.d.ts +9 -3
  6. package/esm/client/2_client_plain.js +9 -5
  7. package/esm/client/3_client.d.ts +30 -19
  8. package/esm/client/3_client.js +319 -291
  9. package/esm/connection/0_connection.d.ts +1 -0
  10. package/esm/connection/0_connection.js +8 -0
  11. package/esm/connection/1_connection_web_socket.d.ts +3 -1
  12. package/esm/connection/1_connection_web_socket.js +28 -9
  13. package/esm/constants.d.ts +1 -1
  14. package/esm/constants.js +1 -1
  15. package/esm/deps.js +1 -1
  16. package/esm/tl/0_tl_raw_reader.d.ts +2 -0
  17. package/esm/tl/0_tl_raw_reader.js +3 -1
  18. package/esm/tl/3_tl_reader.js +2 -2
  19. package/esm/transport/2_transport_provider.d.ts +7 -14
  20. package/esm/transport/2_transport_provider.js +9 -12
  21. package/esm/utilities/0_queue.d.ts +6 -0
  22. package/esm/utilities/0_queue.js +38 -0
  23. package/package.json +1 -1
  24. package/script/client/0_utilities.d.ts +2 -0
  25. package/script/client/0_utilities.js +25 -1
  26. package/script/client/1_client_abstract.d.ts +15 -10
  27. package/script/client/1_client_abstract.js +26 -27
  28. package/script/client/2_client_plain.d.ts +9 -3
  29. package/script/client/2_client_plain.js +9 -5
  30. package/script/client/3_client.d.ts +30 -19
  31. package/script/client/3_client.js +318 -290
  32. package/script/connection/0_connection.d.ts +1 -0
  33. package/script/connection/0_connection.js +8 -0
  34. package/script/connection/1_connection_web_socket.d.ts +3 -1
  35. package/script/connection/1_connection_web_socket.js +28 -9
  36. package/script/constants.d.ts +1 -1
  37. package/script/constants.js +1 -1
  38. package/script/deps.js +1 -1
  39. package/script/tl/0_tl_raw_reader.d.ts +2 -0
  40. package/script/tl/0_tl_raw_reader.js +5 -2
  41. package/script/tl/3_tl_reader.js +1 -1
  42. package/script/transport/2_transport_provider.d.ts +7 -14
  43. package/script/transport/2_transport_provider.js +9 -12
  44. package/script/utilities/0_queue.d.ts +6 -0
  45. package/script/utilities/0_queue.js +42 -0
@@ -24,14 +24,15 @@ import { checkPassword } from "./0_password.js";
24
24
  import { ClientAbstract } from "./1_client_abstract.js";
25
25
  import { ClientPlain } from "./2_client_plain.js";
26
26
  import { drop, mustPrompt, mustPromptOneOf } from "../utilities/1_misc.js";
27
- import { getChannelChatId, peerToChatId } from "./0_utilities.js";
27
+ import { getChannelChatId, hasChannelPts, hasPts, peerToChatId } from "./0_utilities.js";
28
28
  import { constructUser } from "../types/1_user.js";
29
+ import { TLError } from "../tl/0_tl_raw_reader.js";
30
+ import { Queue } from "../utilities/0_queue.js";
29
31
  const d = debug("Client");
30
32
  const dGap = debug("Client/recoverUpdateGap");
31
33
  const dGapC = debug("Client/recoverChannelUpdateGap");
32
34
  const dAuth = debug("Client/authorize");
33
35
  const dRecv = debug("Client/receiveLoop");
34
- const UPDATE_GAP = Symbol();
35
36
  export const getEntity = Symbol();
36
37
  export const getStickerSetName = Symbol();
37
38
  export const handleMigrationError = Symbol();
@@ -50,8 +51,8 @@ export class Client extends ClientAbstract {
50
51
  * @param apiHash App's API hash from [my.telegram.org/apps](https://my.telegram.org/apps). Defaults to empty string (unset).
51
52
  * @param params Other parameters.
52
53
  */
53
- constructor(storage = new StorageMemory(), apiId = 0, apiHash = "", params, cdn = false) {
54
- super(params?.transportProvider, cdn);
54
+ constructor(storage = new StorageMemory(), apiId = 0, apiHash = "", params) {
55
+ super(params);
55
56
  Object.defineProperty(this, "storage", {
56
57
  enumerable: true,
57
58
  configurable: true,
@@ -106,12 +107,6 @@ export class Client extends ClientAbstract {
106
107
  writable: true,
107
108
  value: void 0
108
109
  });
109
- Object.defineProperty(this, "updateHandler", {
110
- enumerable: true,
111
- configurable: true,
112
- writable: true,
113
- value: null
114
- });
115
110
  Object.defineProperty(this, "parseMode", {
116
111
  enumerable: true,
117
112
  configurable: true,
@@ -166,12 +161,26 @@ export class Client extends ClientAbstract {
166
161
  writable: true,
167
162
  value: void 0
168
163
  });
164
+ Object.defineProperty(this, "stateChangeHandler", {
165
+ enumerable: true,
166
+ configurable: true,
167
+ writable: true,
168
+ value: ((connected) => {
169
+ this.propagateConnectionState(connected ? "ready" : "not-connected");
170
+ }).bind(this)
171
+ });
169
172
  Object.defineProperty(this, "storageInited", {
170
173
  enumerable: true,
171
174
  configurable: true,
172
175
  writable: true,
173
176
  value: false
174
177
  });
178
+ Object.defineProperty(this, "connectMutex", {
179
+ enumerable: true,
180
+ configurable: true,
181
+ writable: true,
182
+ value: new Mutex()
183
+ });
175
184
  Object.defineProperty(this, "connectionInited", {
176
185
  enumerable: true,
177
186
  configurable: true,
@@ -190,23 +199,25 @@ export class Client extends ClientAbstract {
190
199
  writable: true,
191
200
  value: 0n
192
201
  });
193
- Object.defineProperty(this, "updateApplicationMutex", {
202
+ Object.defineProperty(this, "handleUpdateQueue", {
194
203
  enumerable: true,
195
204
  configurable: true,
196
205
  writable: true,
197
- value: new Mutex()
206
+ value: new Queue()
198
207
  });
199
- Object.defineProperty(this, "updateProcessLock", {
208
+ Object.defineProperty(this, "processUpdatesQueue", {
200
209
  enumerable: true,
201
210
  configurable: true,
202
211
  writable: true,
203
- value: new Mutex()
212
+ value: new Queue()
204
213
  });
205
- Object.defineProperty(this, "updateGapRecoveryMutex", {
214
+ Object.defineProperty(this, "handler", {
206
215
  enumerable: true,
207
216
  configurable: true,
208
217
  writable: true,
209
- value: new Mutex()
218
+ value: (_upd, next) => {
219
+ next();
220
+ }
210
221
  });
211
222
  this.parseMode = params?.parseMode ?? ParseMode.None;
212
223
  this.appVersion = params?.appVersion ?? APP_VERSION;
@@ -218,6 +229,9 @@ export class Client extends ClientAbstract {
218
229
  this.publicKeys = params?.publicKeys;
219
230
  this.autoStart = params?.autoStart ?? true;
220
231
  }
232
+ propagateConnectionState(connectionState) {
233
+ return this.handler({ connectionState }, resolve);
234
+ }
221
235
  /**
222
236
  * Sets the DC and resets the auth key stored in the session provider
223
237
  * if the stored DC was not the same as the `dc` parameter.
@@ -247,38 +261,47 @@ export class Client extends ClientAbstract {
247
261
  * Before establishing the connection, the session is saved.
248
262
  */
249
263
  async connect() {
250
- if (!this.storageInited) {
251
- await this.storage.init();
252
- this.storageInited = true;
253
- }
254
- const authKey = await this.storage.getAuthKey();
255
- if (authKey == null) {
256
- const plain = new ClientPlain(this.transportProvider, this.publicKeys);
264
+ const release = await this.connectMutex.acquire();
265
+ try {
266
+ if (this.connected) {
267
+ return;
268
+ }
269
+ if (!this.storageInited) {
270
+ await this.storage.init();
271
+ this.storageInited = true;
272
+ }
273
+ const authKey = await this.storage.getAuthKey();
274
+ if (authKey == null) {
275
+ const plain = new ClientPlain({ initialDc: this.initialDc, transportProvider: this.transportProvider, cdn: this.cdn, publicKeys: this.publicKeys });
276
+ const dc = await this.storage.getDc();
277
+ if (dc != null) {
278
+ plain.setDc(dc);
279
+ }
280
+ await plain.connect();
281
+ const { authKey, salt } = await plain.createAuthKey();
282
+ await plain.disconnect();
283
+ await this.storage.setAuthKey(authKey);
284
+ await this.setAuth(authKey);
285
+ this.state.salt = salt;
286
+ }
287
+ else {
288
+ await this.setAuth(authKey);
289
+ }
257
290
  const dc = await this.storage.getDc();
258
291
  if (dc != null) {
259
- plain.setDc(dc);
292
+ await this.setDc(dc);
260
293
  }
261
- await plain.connect();
262
- const { authKey, salt } = await plain.createAuthKey();
263
- await plain.disconnect();
264
- await this.storage.setAuthKey(authKey);
265
- await this.setAuth(authKey);
266
- this.state.salt = salt;
267
- }
268
- else {
269
- await this.setAuth(authKey);
270
- }
271
- const dc = await this.storage.getDc();
272
- if (dc != null) {
273
- await this.setDc(dc);
294
+ await super.connect();
295
+ if (dc == null) {
296
+ await this.storage.setDc(this.initialDc);
297
+ }
298
+ d("encrypted client connected");
299
+ drop(this.receiveLoop());
300
+ drop(this.pingLoop());
274
301
  }
275
- await super.connect();
276
- if (dc == null) {
277
- await this.storage.setDc(this.transportProvider.initialDc);
302
+ finally {
303
+ release();
278
304
  }
279
- d("encrypted client connected");
280
- drop(this.receiveLoop());
281
- drop(this.pingLoop());
282
305
  }
283
306
  async fetchState(source) {
284
307
  const state = await this.invoke(new functions.UpdatesGetState());
@@ -352,7 +375,7 @@ export class Client extends ClientAbstract {
352
375
  params = mustPrompt("Bot token:");
353
376
  }
354
377
  else {
355
- params = { phone: () => mustPrompt("Phone number:"), code: () => mustPrompt("Verification code:"), password: () => mustPrompt(`Password:`) };
378
+ params = { phone: () => mustPrompt("Phone number:"), code: () => mustPrompt("Verification code:"), password: () => mustPrompt("Password:") };
356
379
  }
357
380
  }
358
381
  dAuth("authorizing with %s", typeof params === "string" ? "bot token" : params instanceof types.AuthExportedAuthorization ? "exported authorization" : "AuthorizeUserParams");
@@ -512,7 +535,7 @@ export class Client extends ClientAbstract {
512
535
  await this.authorize(params);
513
536
  }
514
537
  async receiveLoop() {
515
- if (!this.auth) {
538
+ if (!this.auth || !this.transport) {
516
539
  throw new Error("Not connected");
517
540
  }
518
541
  while (this.connected) {
@@ -521,7 +544,7 @@ export class Client extends ClientAbstract {
521
544
  await this.send(new types.MsgsAck({ msgIds: [...this.toAcknowledge] }));
522
545
  this.toAcknowledge.clear();
523
546
  }
524
- const buffer = await this.transport.receive();
547
+ const buffer = await this.transport.transport.receive();
525
548
  let decrypted;
526
549
  try {
527
550
  decrypted = await (decryptMessage(buffer, this.auth.key, this.auth.id, this.sessionId));
@@ -538,8 +561,8 @@ export class Client extends ClientAbstract {
538
561
  body = new TLReader(gunzip(body.packedData)).readObject();
539
562
  }
540
563
  dRecv("received %s", body.constructor.name);
541
- if (body instanceof types.Updates || body instanceof types.TypeUpdate) {
542
- drop(this.processUpdates(body));
564
+ if (body instanceof types.TypeUpdates || body instanceof types.TypeUpdate) {
565
+ this.processUpdatesQueue.add(() => this.processUpdates(body));
543
566
  }
544
567
  else if (message.body instanceof RPCResult) {
545
568
  let result = message.body.result;
@@ -566,7 +589,10 @@ export class Client extends ClientAbstract {
566
589
  }
567
590
  };
568
591
  if (result instanceof types.TypeUpdates || result instanceof types.TypeUpdate) {
569
- this.processUpdates(result).then(resolvePromise).catch();
592
+ this.processUpdatesQueue.add(async () => {
593
+ await this.processUpdates(result);
594
+ resolvePromise();
595
+ });
570
596
  }
571
597
  else {
572
598
  await this.processResult(result);
@@ -596,6 +622,10 @@ export class Client extends ClientAbstract {
596
622
  if (!this.connected) {
597
623
  break;
598
624
  }
625
+ else if (err instanceof TLError) {
626
+ dRecv("failed to deserialize: %o", err);
627
+ drop(this.recoverUpdateGap("deserialize"));
628
+ }
599
629
  else {
600
630
  throw err;
601
631
  }
@@ -614,7 +644,7 @@ export class Client extends ClientAbstract {
614
644
  }
615
645
  }
616
646
  async invoke(function_, noWait) {
617
- if (!this.auth) {
647
+ if (!this.auth || !this.transport) {
618
648
  if (this.autoStart && !this.autoStarted) {
619
649
  await this.start();
620
650
  }
@@ -622,7 +652,7 @@ export class Client extends ClientAbstract {
622
652
  throw new Error("Not connected");
623
653
  }
624
654
  }
625
- if (!this.auth) {
655
+ if (!this.auth || !this.transport) {
626
656
  UNREACHABLE();
627
657
  }
628
658
  let seqNo = this.state.seqNo * 2;
@@ -632,7 +662,7 @@ export class Client extends ClientAbstract {
632
662
  }
633
663
  const messageId = this.lastMsgId = getMessageId(this.lastMsgId);
634
664
  const message = new Message_(messageId, seqNo, function_);
635
- await this.transport.send(await encryptMessage(message, this.auth.key, this.auth.id, this.state.salt, this.sessionId));
665
+ await this.transport.transport.send(await encryptMessage(message, this.auth.key, this.auth.id, this.state.salt, this.sessionId));
636
666
  d("invoked %s", function_.constructor.name);
637
667
  if (noWait) {
638
668
  return;
@@ -684,222 +714,160 @@ export class Client extends ClientAbstract {
684
714
  }
685
715
  }
686
716
  }
687
- async applyUpdateNoGap(update, usePts = true) {
688
- const release = await this.updateApplicationMutex.acquire();
689
- try {
690
- if ((update instanceof types.UpdateNewMessage) ||
691
- (update instanceof types.UpdateDeleteMessages) ||
692
- (update instanceof types.UpdateReadHistoryInbox) ||
693
- (update instanceof types.UpdateReadHistoryOutbox) ||
694
- (update instanceof types.UpdateWebPage) ||
695
- (update instanceof types.UpdateReadMessagesContents) ||
696
- (update instanceof types.UpdateEditMessage) ||
697
- (update instanceof types.UpdateFolderPeers) ||
698
- (update instanceof types.UpdatePinnedMessages) ||
699
- (update instanceof types.UpdatePinnedChannelMessages) ||
700
- (update instanceof types.UpdateShortMessage) ||
701
- (update instanceof types.UpdateShortChatMessage) ||
702
- (update instanceof types.UpdateShortSentMessage)) {
703
- if (update.pts != 0 && update.ptsCount != 0) {
704
- const localState = await this.getLocalState();
705
- if (localState.pts + update.ptsCount > update.pts) {
706
- // the update is already applied
707
- return;
708
- }
709
- else if (localState.pts + update.ptsCount < update.pts) {
710
- // there is an update gap that needs to be filled
711
- throw UPDATE_GAP;
712
- }
713
- localState.pts = update.pts;
714
- d("applied update with pts %d", update.pts);
715
- await this.storage.setState(localState);
716
- }
717
- }
718
- else if (usePts &&
719
- ((update instanceof types.UpdateNewChannelMessage) ||
720
- (update instanceof types.UpdateDeleteChannelMessages) ||
721
- (update instanceof types.UpdateEditChannelMessage) ||
722
- (update instanceof types.UpdateChannelWebPage))) {
723
- const channelId = update instanceof types.UpdateNewChannelMessage || update instanceof types.UpdateEditChannelMessage ? update.message.peerId[as](types.PeerChannel).channelId : update.channelId;
724
- let localPts = await this.storage.getChannelPts(channelId);
725
- if (!localPts) {
726
- localPts = update.pts - update.ptsCount;
727
- }
728
- if (localPts + update.ptsCount > update.pts) {
729
- // already applied
730
- return;
731
- }
732
- else if (localPts + update.ptsCount < update.pts) {
733
- // should call channelGetDifference
734
- throw UPDATE_GAP;
735
- }
736
- d("applied update with pts %d", update.pts);
737
- await this.storage.setChannelPts(channelId, update.pts);
717
+ async checkGap(pts, ptsCount, assertNoGap) {
718
+ const localState = await this.getLocalState();
719
+ if (localState.pts + ptsCount < pts) {
720
+ if (assertNoGap) {
721
+ UNREACHABLE();
738
722
  }
739
- if (update instanceof types.UpdateNewMessage || update instanceof types.UpdateNewMessage || update instanceof types.UpdateNewChannelMessage || update instanceof types.UpdateNewChannelMessage) {
740
- if (update.message instanceof types.Message || update.message instanceof types.MessageService) {
741
- await this.storage.setMessage(peerToChatId(update.message.peerId), update.message.id, update.message);
742
- }
723
+ else {
724
+ await this.recoverUpdateGap("processUpdates");
743
725
  }
744
- else if (update instanceof types.UpdateDeleteChannelMessages) {
745
- for (const message of update.messages) {
746
- await this.storage.setMessage(getChannelChatId(update.channelId), message, null);
747
- }
726
+ }
727
+ }
728
+ async checkChannelGap(channelId, pts, ptsCount, assertNoGap) {
729
+ let localPts = await this.storage.getChannelPts(channelId);
730
+ if (!localPts) {
731
+ localPts = pts - ptsCount;
732
+ }
733
+ if (localPts + ptsCount < pts) {
734
+ if (assertNoGap) {
735
+ UNREACHABLE();
748
736
  }
749
- else if (update instanceof types.UpdateDeleteMessages) {
750
- for (const message of update.messages) {
751
- const chatId = await this.storage.getMessageChat(message);
752
- if (chatId) {
753
- await this.storage.setMessage(chatId, message, null);
754
- }
755
- }
737
+ else {
738
+ await this.recoverChannelUpdateGap(channelId, "processUpdates");
756
739
  }
757
- // apply update (call listeners)
758
- this.updateHandler?.(this, update);
759
- }
760
- finally {
761
- release();
762
740
  }
763
741
  }
764
- async applyUpdate(update) {
765
- if (update instanceof types.TypeUpdates &&
766
- !(update instanceof types.UpdateShortMessage) &&
767
- !(update instanceof types.UpdateShortChatMessage) &&
768
- !(update instanceof types.UpdateShortSentMessage)) {
769
- // other constructors inheriting Updates are not applicable
770
- UNREACHABLE();
742
+ async processUpdates(updates_, assertNoGap = false) {
743
+ /// First, individual updates (Update[1]) and updateShort* are extracted from Updates.[2]
744
+ ///
745
+ /// If an updatesTooLong[3] was received, an update gap recovery is initiated and no further action will be taken.
746
+ ///
747
+ /// [1]: https://core.telegram.org/type/Update
748
+ /// [2]: https://core.telegram.org/type/Updates
749
+ /// [3]: https://core.telegram.org/constructor/updatesTooLong
750
+ let updates;
751
+ if (updates_ instanceof types.UpdatesCombined || updates_ instanceof types.Updates) {
752
+ updates = updates_.updates;
753
+ }
754
+ else if (updates_ instanceof types.UpdateShort) {
755
+ updates = [updates_.update];
756
+ }
757
+ else if (updates_ instanceof types.UpdateShortMessage ||
758
+ updates_ instanceof types.UpdateShortChatMessage ||
759
+ updates_ instanceof types.UpdateShortSentMessage) {
760
+ updates = [updates_];
761
+ }
762
+ else if (updates_ instanceof types.UpdatesTooLong) {
763
+ await this.recoverUpdateGap("updatesTooLong");
764
+ return;
771
765
  }
772
- if (update instanceof types.TypeUpdate && update instanceof types.UpdateChannelTooLong) {
773
- // updateChannelTooLong is not applicable
774
- UNREACHABLE();
766
+ else if (updates_ instanceof types.TypeUpdate) {
767
+ updates = [updates_];
775
768
  }
776
- // can't apply updates when filling gap
777
- const release = await this.updateGapRecoveryMutex.acquire();
778
- try {
779
- await this.applyUpdateNoGap(update);
780
- release();
769
+ else {
770
+ UNREACHABLE();
781
771
  }
782
- catch (err) {
783
- release();
784
- if (err == UPDATE_GAP) {
785
- if ((update instanceof types.UpdateNewChannelMessage) ||
786
- (update instanceof types.UpdateDeleteChannelMessages) ||
787
- (update instanceof types.UpdateEditChannelMessage) ||
788
- (update instanceof types.UpdateChannelWebPage)) {
789
- const channelId = update instanceof types.UpdateNewChannelMessage || update instanceof types.UpdateEditChannelMessage ? update.message.peerId[as](types.PeerChannel).channelId : update.channelId;
790
- await this.recoverChannelUpdateGap(channelId, "applyUpdate");
772
+ /// Then, we go through each Update and updateShort*, and see if they are order-sensitive.
773
+ /// If they were, we check the local state to see if it is OK to process them right away.
774
+ ///
775
+ /// If we there was a gap, a recovery process will be initiated and the processing will be postponed.
776
+ let localState = null;
777
+ let originalPts = null;
778
+ const channelPtsMap = new Map();
779
+ for (const update of updates) {
780
+ if (hasPts(update)) {
781
+ if (update.pts == 0) {
782
+ continue;
791
783
  }
792
- else if ((update instanceof types.UpdateNewMessage) ||
793
- (update instanceof types.UpdateDeleteMessages) ||
794
- (update instanceof types.UpdateReadHistoryInbox) ||
795
- (update instanceof types.UpdateReadHistoryOutbox) ||
796
- (update instanceof types.UpdateWebPage) ||
797
- (update instanceof types.UpdateReadMessagesContents) ||
798
- (update instanceof types.UpdateEditMessage) ||
799
- (update instanceof types.UpdateFolderPeers) ||
800
- (update instanceof types.UpdatePinnedMessages) ||
801
- (update instanceof types.UpdatePinnedChannelMessages) ||
802
- (update instanceof types.UpdateShortMessage) ||
803
- (update instanceof types.UpdateShortChatMessage) ||
804
- (update instanceof types.UpdateShortSentMessage)) {
805
- await this.recoverUpdateGap("applyUpdate");
784
+ await this.checkGap(update.pts, update.ptsCount, assertNoGap);
785
+ localState ??= await this.getLocalState();
786
+ originalPts ??= localState.pts;
787
+ if (localState.pts + update.ptsCount > update.pts) {
788
+ updates = updates.filter((v) => v != update);
806
789
  }
807
790
  else {
808
- // can't detect update gap from other types of updates
809
- UNREACHABLE();
791
+ localState.pts = update.pts;
810
792
  }
811
- // just for integrity
812
- const release = await this.updateGapRecoveryMutex.acquire();
813
- try {
814
- await this.applyUpdateNoGap(update);
793
+ }
794
+ else if (hasChannelPts(update)) {
795
+ if (update.pts == 0) {
796
+ continue;
815
797
  }
816
- catch (err) {
817
- if (err == UPDATE_GAP) {
818
- // the gap must have been filled until now
819
- UNREACHABLE();
820
- }
821
- else {
822
- throw err;
823
- }
798
+ const ptsCount = "ptsCount" in update ? update.ptsCount : 1;
799
+ const channelId = update instanceof types.UpdateNewChannelMessage || update instanceof types.UpdateEditChannelMessage ? update.message.peerId[as](types.PeerChannel).channelId : update.channelId;
800
+ await this.checkChannelGap(channelId, update.pts, ptsCount, assertNoGap);
801
+ let currentPts = channelPtsMap.get(channelId);
802
+ if (currentPts === undefined) {
803
+ currentPts = await this.storage.getChannelPts(channelId);
804
+ }
805
+ currentPts ??= update.pts;
806
+ if (currentPts + ptsCount > update.pts) {
807
+ updates = updates.filter((v) => v != update);
824
808
  }
825
- finally {
826
- release();
809
+ else {
810
+ channelPtsMap.set(channelId, update.pts);
827
811
  }
828
812
  }
829
- else {
830
- throw err;
813
+ }
814
+ if (!assertNoGap) {
815
+ if (localState != null && originalPts != null && localState.pts != originalPts) {
816
+ await this.storage.setState(localState);
817
+ }
818
+ for (const [channelId, pts] of channelPtsMap.entries()) {
819
+ await this.storage.setChannelPts(channelId, pts);
831
820
  }
832
821
  }
833
- }
834
- async processUpdates(updates, release) {
835
- release ??= await this.updateProcessLock.acquire();
836
- try {
837
- if (updates instanceof types.TypeUpdates) {
838
- if (updates instanceof types.Updates) {
839
- await this.processChats(updates.chats);
840
- await this.processUsers(updates.users);
841
- await this.setUpdateStateDate(updates.date);
842
- for (const update of updates.updates) {
843
- await this.processUpdates(update, release);
844
- }
845
- }
846
- else if (updates instanceof types.UpdateShortMessage ||
847
- updates instanceof types.UpdateShortChatMessage ||
848
- updates instanceof types.UpdateShortSentMessage) {
849
- await this.setUpdateStateDate(updates.date);
850
- await this.applyUpdate(updates);
851
- }
852
- else if (updates instanceof types.UpdatesTooLong) {
853
- await this.recoverUpdateGap("updatesTooLong");
854
- }
855
- else if (updates instanceof types.UpdatesCombined) {
856
- await this.setUpdateStateDate(updates.date);
857
- await this.processChats(updates.chats);
858
- await this.processUsers(updates.users);
859
- for (const update of updates.updates) {
860
- await this.processUpdates(update, release);
861
- }
862
- }
822
+ /// We process the updates when we are sure there is no gap.
823
+ if (updates_ instanceof types.Updates || updates_ instanceof types.UpdatesCombined) {
824
+ await this.processChats(updates_.chats);
825
+ await this.processUsers(updates_.users);
826
+ await this.setUpdateStateDate(updates_.date);
827
+ }
828
+ else if (updates_ instanceof types.UpdateShort) {
829
+ await this.setUpdateStateDate(updates_.date);
830
+ }
831
+ const updatesToHandle = new Array();
832
+ for (const update of updates) {
833
+ if (update instanceof types.UpdateShortMessage ||
834
+ update instanceof types.UpdateShortChatMessage ||
835
+ update instanceof types.UpdateShortSentMessage) {
836
+ await this.setUpdateStateDate(update.date);
863
837
  }
864
- else if (updates instanceof types.TypeUpdate && updates instanceof types.UpdateChannelTooLong) {
865
- if (updates.pts != undefined) {
866
- await this.storage.setChannelPts(updates.channelId, updates.pts);
838
+ else if (update instanceof types.UpdateChannelTooLong) {
839
+ if (update.pts != undefined) {
840
+ await this.storage.setChannelPts(update.channelId, update.pts);
867
841
  }
868
- await this.recoverChannelUpdateGap(updates.channelId, "updateChannelTooLong");
842
+ await this.recoverChannelUpdateGap(update.channelId, "updateChannelTooLong");
869
843
  }
870
- else {
871
- if (updates instanceof types.UpdateUserName) {
872
- await this.storage.updateUsernames("user", updates.userId, updates.usernames.map((v) => v[as](types.Username)).map((v) => v.username));
844
+ else if (update instanceof types.UpdateUserName) {
845
+ await this.storage.updateUsernames("user", update.userId, update.usernames.map((v) => v[as](types.Username)).map((v) => v.username));
846
+ }
847
+ else if (update instanceof types.UpdatePtsChanged) {
848
+ await this.fetchState("updatePtsChanged");
849
+ if (this.updateState) {
850
+ await this.storage.setState(this.updateState);
873
851
  }
874
- else if (updates instanceof types.UpdatePtsChanged) {
875
- await this.fetchState("updatePtsChanged");
876
- if (this.updateState) {
877
- await this.storage.setState(this.updateState);
878
- }
879
- else {
880
- UNREACHABLE();
881
- }
852
+ else {
853
+ UNREACHABLE();
882
854
  }
883
- await this.applyUpdate(updates);
855
+ }
856
+ /// If there were any Update, they will be passed to the update handling queue.
857
+ if (update instanceof types.TypeUpdate) {
858
+ updatesToHandle.push(update);
884
859
  }
885
860
  }
886
- catch (err) {
887
- d("error processing updates: %O", err);
888
- }
889
- finally {
890
- release();
891
- }
861
+ this.handleUpdateQueue.add(async () => {
862
+ for (const update of updatesToHandle) {
863
+ await this.handleUpdate(update);
864
+ }
865
+ });
892
866
  }
893
867
  async setUpdateStateDate(date) {
894
- const release = await this.updateApplicationMutex.acquire();
895
- try {
896
- const localState = await this.getLocalState();
897
- localState.date = date;
898
- await this.storage.setState(localState);
899
- }
900
- finally {
901
- release();
902
- }
868
+ const localState = await this.getLocalState();
869
+ localState.date = date;
870
+ await this.storage.setState(localState);
903
871
  }
904
872
  async getLocalState() {
905
873
  let localState = await this.storage.getState();
@@ -923,7 +891,7 @@ export class Client extends ClientAbstract {
923
891
  }
924
892
  async recoverUpdateGap(source) {
925
893
  dGap("recovering from update gap [%s]", source);
926
- const release = await this.updateGapRecoveryMutex.acquire();
894
+ await this.propagateConnectionState("updating");
927
895
  try {
928
896
  let state = await this.getLocalState();
929
897
  while (true) {
@@ -932,10 +900,10 @@ export class Client extends ClientAbstract {
932
900
  await this.processChats(difference.chats);
933
901
  await this.processUsers(difference.users);
934
902
  for (const message of difference.newMessages) {
935
- await this.applyUpdateNoGap(new types.UpdateNewMessage({ message, pts: 0, ptsCount: 0 }));
903
+ await this.processUpdates(new types.UpdateNewMessage({ message, pts: 0, ptsCount: 0 }), true);
936
904
  }
937
905
  for (const update of difference.otherUpdates) {
938
- await this.applyUpdateNoGap(update);
906
+ await this.processUpdates(update, true);
939
907
  }
940
908
  if (difference instanceof types.UpdatesDifference) {
941
909
  await this.storage.setState(difference.state[as](types.UpdatesState));
@@ -950,6 +918,7 @@ export class Client extends ClientAbstract {
950
918
  }
951
919
  }
952
920
  else if (difference instanceof types.UpdatesDifferenceTooLong) {
921
+ // TODO: we actually do now
953
922
  // stored messages should be invalidated in case we store messages in the future
954
923
  state.pts = difference.pts;
955
924
  dGap("received differenceTooLong");
@@ -965,61 +934,55 @@ export class Client extends ClientAbstract {
965
934
  }
966
935
  }
967
936
  finally {
968
- release();
937
+ this.stateChangeHandler(this.connected);
969
938
  }
970
939
  }
971
940
  async recoverChannelUpdateGap(channelId, source) {
972
941
  dGapC("recovering channel update gap [%o, %s]", channelId, source);
973
- const release = await this.updateGapRecoveryMutex.acquire();
974
- try {
975
- const pts_ = await this.storage.getChannelPts(channelId);
976
- let pts = pts_ == null ? 1 : pts_;
977
- while (true) {
978
- const { accessHash } = await this.getInputPeer(ZERO_CHANNEL_ID + -Number(channelId)).then((v) => v[as](types.InputPeerChannel));
979
- const difference = await this.invoke(new functions.UpdatesGetChannelDifference({
980
- pts,
981
- channel: new types.InputChannel({ channelId, accessHash: accessHash }),
982
- filter: new types.ChannelMessagesFilterEmpty(),
983
- limit: await this.storage.getAccountType() == "user" ? CHANNEL_DIFFERENCE_LIMIT_USER : CHANNEL_DIFFERENCE_LIMIT_BOT,
984
- }));
985
- if (difference instanceof types.UpdatesChannelDifference) {
986
- await this.processChats(difference.chats);
987
- await this.processUsers(difference.users);
988
- for (const message of difference.newMessages) {
989
- await this.applyUpdateNoGap(new types.UpdateNewChannelMessage({ message, pts: 0, ptsCount: 0 }), false);
990
- }
991
- for (const update of difference.otherUpdates) {
992
- await this.applyUpdateNoGap(update, false);
993
- }
994
- await this.storage.setChannelPts(channelId, difference.pts);
995
- dGapC("recovered from update gap [%o, %s]", channelId, source);
996
- break;
942
+ const pts_ = await this.storage.getChannelPts(channelId);
943
+ let pts = pts_ == null ? 1 : pts_;
944
+ while (true) {
945
+ const { accessHash } = await this.getInputPeer(ZERO_CHANNEL_ID + -Number(channelId)).then((v) => v[as](types.InputPeerChannel));
946
+ const difference = await this.invoke(new functions.UpdatesGetChannelDifference({
947
+ pts,
948
+ channel: new types.InputChannel({ channelId, accessHash: accessHash }),
949
+ filter: new types.ChannelMessagesFilterEmpty(),
950
+ limit: await this.storage.getAccountType() == "user" ? CHANNEL_DIFFERENCE_LIMIT_USER : CHANNEL_DIFFERENCE_LIMIT_BOT,
951
+ }));
952
+ if (difference instanceof types.UpdatesChannelDifference) {
953
+ await this.processChats(difference.chats);
954
+ await this.processUsers(difference.users);
955
+ for (const message of difference.newMessages) {
956
+ await this.processUpdates(new types.UpdateNewChannelMessage({ message, pts: 0, ptsCount: 0 }), true);
997
957
  }
998
- else if (difference instanceof types.UpdatesChannelDifferenceTooLong) {
999
- // invalidate messages
1000
- dGapC("received channelDifferenceTooLong");
1001
- await this.processChats(difference.chats);
1002
- await this.processUsers(difference.users);
1003
- for (const message of difference.messages) {
1004
- await this.applyUpdateNoGap(new types.UpdateNewChannelMessage({ message, pts: 0, ptsCount: 0 }), false);
1005
- }
1006
- const pts_ = difference.dialog[as](types.Dialog).pts;
1007
- if (pts_ != undefined) {
1008
- pts = pts_;
1009
- }
1010
- else {
1011
- UNREACHABLE();
1012
- }
1013
- dGapC("processed channelDifferenceTooLong");
958
+ for (const update of difference.otherUpdates) {
959
+ await this.processUpdates(update, true);
1014
960
  }
1015
- else if (difference instanceof types.UpdatesChannelDifferenceEmpty) {
1016
- dGapC("there was no update gap");
1017
- break;
961
+ await this.storage.setChannelPts(channelId, difference.pts);
962
+ dGapC("recovered from update gap [%o, %s]", channelId, source);
963
+ break;
964
+ }
965
+ else if (difference instanceof types.UpdatesChannelDifferenceTooLong) {
966
+ // invalidate messages
967
+ dGapC("received channelDifferenceTooLong");
968
+ await this.processChats(difference.chats);
969
+ await this.processUsers(difference.users);
970
+ for (const message of difference.messages) {
971
+ await this.processUpdates(new types.UpdateNewChannelMessage({ message, pts: 0, ptsCount: 0 }), true);
972
+ }
973
+ const pts_ = difference.dialog[as](types.Dialog).pts;
974
+ if (pts_ != undefined) {
975
+ pts = pts_;
1018
976
  }
977
+ else {
978
+ UNREACHABLE();
979
+ }
980
+ dGapC("processed channelDifferenceTooLong");
981
+ }
982
+ else if (difference instanceof types.UpdatesChannelDifferenceEmpty) {
983
+ dGapC("there was no update gap");
984
+ break;
1019
985
  }
1020
- }
1021
- finally {
1022
- release();
1023
986
  }
1024
987
  }
1025
988
  async getInputPeer(id) {
@@ -1336,7 +1299,8 @@ export class Client extends ClientAbstract {
1336
1299
  langPack: this.langPack,
1337
1300
  systemLangCode: this.systemLangCode,
1338
1301
  systemVersion: this.systemVersion,
1339
- }, true);
1302
+ cdn: true,
1303
+ });
1340
1304
  let dc = String(dcId);
1341
1305
  if (this.dcId < 0) {
1342
1306
  dc += "-test";
@@ -1447,4 +1411,68 @@ export class Client extends ClientAbstract {
1447
1411
  }
1448
1412
  return constructUser(users[0][as](types.User));
1449
1413
  }
1414
+ async handleUpdate(update) {
1415
+ if (update instanceof types.UpdateNewMessage || update instanceof types.UpdateNewMessage || update instanceof types.UpdateNewChannelMessage || update instanceof types.UpdateNewChannelMessage) {
1416
+ if (update.message instanceof types.Message || update.message instanceof types.MessageService) {
1417
+ await this.storage.setMessage(peerToChatId(update.message.peerId), update.message.id, update.message);
1418
+ }
1419
+ }
1420
+ else if (update instanceof types.UpdateDeleteChannelMessages) {
1421
+ for (const message of update.messages) {
1422
+ await this.storage.setMessage(getChannelChatId(update.channelId), message, null);
1423
+ }
1424
+ }
1425
+ else if (update instanceof types.UpdateDeleteMessages) {
1426
+ for (const message of update.messages) {
1427
+ const chatId = await this.storage.getMessageChat(message);
1428
+ if (chatId) {
1429
+ await this.storage.setMessage(chatId, message, null);
1430
+ }
1431
+ }
1432
+ }
1433
+ if (update instanceof types.UpdateNewMessage ||
1434
+ update instanceof types.UpdateNewChannelMessage ||
1435
+ update instanceof types.UpdateEditMessage ||
1436
+ update instanceof types.UpdateEditChannelMessage) {
1437
+ const key = update instanceof types.UpdateNewMessage || update instanceof types.UpdateNewChannelMessage ? "message" : "editedMessage";
1438
+ const message = await constructMessage(update.message, this[getEntity].bind(this), this.getMessage.bind(this), this[getStickerSetName].bind(this));
1439
+ await this.handler({ [key]: message }, resolve);
1440
+ }
1441
+ }
1442
+ use(middleware) {
1443
+ const handler = this.handler;
1444
+ this.handler = async (upd, next) => {
1445
+ let called = false;
1446
+ await middleware(upd, async () => {
1447
+ if (called)
1448
+ return;
1449
+ called = true;
1450
+ await handler(upd, next);
1451
+ });
1452
+ };
1453
+ }
1454
+ on(filter, handler) {
1455
+ const type = typeof filter === "string" ? filter : filter[0];
1456
+ const keys = Array.isArray(filter) ? filter.slice(1) : [];
1457
+ this.use((update, next) => {
1458
+ if (type in update) {
1459
+ if (keys.length > 0) {
1460
+ for (const key of keys) {
1461
+ // deno-lint-ignore ban-ts-comment
1462
+ // @ts-ignore
1463
+ if (!(key in update[type])) {
1464
+ return next();
1465
+ }
1466
+ }
1467
+ }
1468
+ // deno-lint-ignore ban-ts-comment
1469
+ // @ts-ignore
1470
+ return handler(update, next);
1471
+ }
1472
+ else {
1473
+ return next();
1474
+ }
1475
+ });
1476
+ }
1450
1477
  }
1478
+ const resolve = () => Promise.resolve();