@mtkruto/node 0.0.986 → 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 (39) 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 +314 -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/transport/2_transport_provider.d.ts +7 -14
  17. package/esm/transport/2_transport_provider.js +9 -12
  18. package/esm/utilities/0_queue.d.ts +6 -0
  19. package/esm/utilities/0_queue.js +38 -0
  20. package/package.json +1 -1
  21. package/script/client/0_utilities.d.ts +2 -0
  22. package/script/client/0_utilities.js +25 -1
  23. package/script/client/1_client_abstract.d.ts +15 -10
  24. package/script/client/1_client_abstract.js +26 -27
  25. package/script/client/2_client_plain.d.ts +9 -3
  26. package/script/client/2_client_plain.js +9 -5
  27. package/script/client/3_client.d.ts +30 -19
  28. package/script/client/3_client.js +313 -290
  29. package/script/connection/0_connection.d.ts +1 -0
  30. package/script/connection/0_connection.js +8 -0
  31. package/script/connection/1_connection_web_socket.d.ts +3 -1
  32. package/script/connection/1_connection_web_socket.js +28 -9
  33. package/script/constants.d.ts +1 -1
  34. package/script/constants.js +1 -1
  35. package/script/deps.js +1 -1
  36. package/script/transport/2_transport_provider.d.ts +7 -14
  37. package/script/transport/2_transport_provider.js +9 -12
  38. package/script/utilities/0_queue.d.ts +6 -0
  39. package/script/utilities/0_queue.js +42 -0
@@ -24,15 +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
29
  import { TLError } from "../tl/0_tl_raw_reader.js";
30
+ import { Queue } from "../utilities/0_queue.js";
30
31
  const d = debug("Client");
31
32
  const dGap = debug("Client/recoverUpdateGap");
32
33
  const dGapC = debug("Client/recoverChannelUpdateGap");
33
34
  const dAuth = debug("Client/authorize");
34
35
  const dRecv = debug("Client/receiveLoop");
35
- const UPDATE_GAP = Symbol();
36
36
  export const getEntity = Symbol();
37
37
  export const getStickerSetName = Symbol();
38
38
  export const handleMigrationError = Symbol();
@@ -51,8 +51,8 @@ export class Client extends ClientAbstract {
51
51
  * @param apiHash App's API hash from [my.telegram.org/apps](https://my.telegram.org/apps). Defaults to empty string (unset).
52
52
  * @param params Other parameters.
53
53
  */
54
- constructor(storage = new StorageMemory(), apiId = 0, apiHash = "", params, cdn = false) {
55
- super(params?.transportProvider, cdn);
54
+ constructor(storage = new StorageMemory(), apiId = 0, apiHash = "", params) {
55
+ super(params);
56
56
  Object.defineProperty(this, "storage", {
57
57
  enumerable: true,
58
58
  configurable: true,
@@ -107,12 +107,6 @@ export class Client extends ClientAbstract {
107
107
  writable: true,
108
108
  value: void 0
109
109
  });
110
- Object.defineProperty(this, "updateHandler", {
111
- enumerable: true,
112
- configurable: true,
113
- writable: true,
114
- value: null
115
- });
116
110
  Object.defineProperty(this, "parseMode", {
117
111
  enumerable: true,
118
112
  configurable: true,
@@ -167,12 +161,26 @@ export class Client extends ClientAbstract {
167
161
  writable: true,
168
162
  value: void 0
169
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
+ });
170
172
  Object.defineProperty(this, "storageInited", {
171
173
  enumerable: true,
172
174
  configurable: true,
173
175
  writable: true,
174
176
  value: false
175
177
  });
178
+ Object.defineProperty(this, "connectMutex", {
179
+ enumerable: true,
180
+ configurable: true,
181
+ writable: true,
182
+ value: new Mutex()
183
+ });
176
184
  Object.defineProperty(this, "connectionInited", {
177
185
  enumerable: true,
178
186
  configurable: true,
@@ -191,23 +199,25 @@ export class Client extends ClientAbstract {
191
199
  writable: true,
192
200
  value: 0n
193
201
  });
194
- Object.defineProperty(this, "updateApplicationMutex", {
202
+ Object.defineProperty(this, "handleUpdateQueue", {
195
203
  enumerable: true,
196
204
  configurable: true,
197
205
  writable: true,
198
- value: new Mutex()
206
+ value: new Queue()
199
207
  });
200
- Object.defineProperty(this, "updateProcessLock", {
208
+ Object.defineProperty(this, "processUpdatesQueue", {
201
209
  enumerable: true,
202
210
  configurable: true,
203
211
  writable: true,
204
- value: new Mutex()
212
+ value: new Queue()
205
213
  });
206
- Object.defineProperty(this, "updateGapRecoveryMutex", {
214
+ Object.defineProperty(this, "handler", {
207
215
  enumerable: true,
208
216
  configurable: true,
209
217
  writable: true,
210
- value: new Mutex()
218
+ value: (_upd, next) => {
219
+ next();
220
+ }
211
221
  });
212
222
  this.parseMode = params?.parseMode ?? ParseMode.None;
213
223
  this.appVersion = params?.appVersion ?? APP_VERSION;
@@ -219,6 +229,9 @@ export class Client extends ClientAbstract {
219
229
  this.publicKeys = params?.publicKeys;
220
230
  this.autoStart = params?.autoStart ?? true;
221
231
  }
232
+ propagateConnectionState(connectionState) {
233
+ return this.handler({ connectionState }, resolve);
234
+ }
222
235
  /**
223
236
  * Sets the DC and resets the auth key stored in the session provider
224
237
  * if the stored DC was not the same as the `dc` parameter.
@@ -248,38 +261,47 @@ export class Client extends ClientAbstract {
248
261
  * Before establishing the connection, the session is saved.
249
262
  */
250
263
  async connect() {
251
- if (!this.storageInited) {
252
- await this.storage.init();
253
- this.storageInited = true;
254
- }
255
- const authKey = await this.storage.getAuthKey();
256
- if (authKey == null) {
257
- 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
+ }
258
290
  const dc = await this.storage.getDc();
259
291
  if (dc != null) {
260
- plain.setDc(dc);
292
+ await this.setDc(dc);
261
293
  }
262
- await plain.connect();
263
- const { authKey, salt } = await plain.createAuthKey();
264
- await plain.disconnect();
265
- await this.storage.setAuthKey(authKey);
266
- await this.setAuth(authKey);
267
- this.state.salt = salt;
268
- }
269
- else {
270
- await this.setAuth(authKey);
271
- }
272
- const dc = await this.storage.getDc();
273
- if (dc != null) {
274
- 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());
275
301
  }
276
- await super.connect();
277
- if (dc == null) {
278
- await this.storage.setDc(this.transportProvider.initialDc);
302
+ finally {
303
+ release();
279
304
  }
280
- d("encrypted client connected");
281
- drop(this.receiveLoop());
282
- drop(this.pingLoop());
283
305
  }
284
306
  async fetchState(source) {
285
307
  const state = await this.invoke(new functions.UpdatesGetState());
@@ -353,7 +375,7 @@ export class Client extends ClientAbstract {
353
375
  params = mustPrompt("Bot token:");
354
376
  }
355
377
  else {
356
- 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:") };
357
379
  }
358
380
  }
359
381
  dAuth("authorizing with %s", typeof params === "string" ? "bot token" : params instanceof types.AuthExportedAuthorization ? "exported authorization" : "AuthorizeUserParams");
@@ -513,7 +535,7 @@ export class Client extends ClientAbstract {
513
535
  await this.authorize(params);
514
536
  }
515
537
  async receiveLoop() {
516
- if (!this.auth) {
538
+ if (!this.auth || !this.transport) {
517
539
  throw new Error("Not connected");
518
540
  }
519
541
  while (this.connected) {
@@ -522,7 +544,7 @@ export class Client extends ClientAbstract {
522
544
  await this.send(new types.MsgsAck({ msgIds: [...this.toAcknowledge] }));
523
545
  this.toAcknowledge.clear();
524
546
  }
525
- const buffer = await this.transport.receive();
547
+ const buffer = await this.transport.transport.receive();
526
548
  let decrypted;
527
549
  try {
528
550
  decrypted = await (decryptMessage(buffer, this.auth.key, this.auth.id, this.sessionId));
@@ -539,8 +561,8 @@ export class Client extends ClientAbstract {
539
561
  body = new TLReader(gunzip(body.packedData)).readObject();
540
562
  }
541
563
  dRecv("received %s", body.constructor.name);
542
- if (body instanceof types.Updates || body instanceof types.TypeUpdate) {
543
- drop(this.processUpdates(body));
564
+ if (body instanceof types.TypeUpdates || body instanceof types.TypeUpdate) {
565
+ this.processUpdatesQueue.add(() => this.processUpdates(body));
544
566
  }
545
567
  else if (message.body instanceof RPCResult) {
546
568
  let result = message.body.result;
@@ -567,7 +589,10 @@ export class Client extends ClientAbstract {
567
589
  }
568
590
  };
569
591
  if (result instanceof types.TypeUpdates || result instanceof types.TypeUpdate) {
570
- this.processUpdates(result).then(resolvePromise).catch();
592
+ this.processUpdatesQueue.add(async () => {
593
+ await this.processUpdates(result);
594
+ resolvePromise();
595
+ });
571
596
  }
572
597
  else {
573
598
  await this.processResult(result);
@@ -619,7 +644,7 @@ export class Client extends ClientAbstract {
619
644
  }
620
645
  }
621
646
  async invoke(function_, noWait) {
622
- if (!this.auth) {
647
+ if (!this.auth || !this.transport) {
623
648
  if (this.autoStart && !this.autoStarted) {
624
649
  await this.start();
625
650
  }
@@ -627,7 +652,7 @@ export class Client extends ClientAbstract {
627
652
  throw new Error("Not connected");
628
653
  }
629
654
  }
630
- if (!this.auth) {
655
+ if (!this.auth || !this.transport) {
631
656
  UNREACHABLE();
632
657
  }
633
658
  let seqNo = this.state.seqNo * 2;
@@ -637,7 +662,7 @@ export class Client extends ClientAbstract {
637
662
  }
638
663
  const messageId = this.lastMsgId = getMessageId(this.lastMsgId);
639
664
  const message = new Message_(messageId, seqNo, function_);
640
- 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));
641
666
  d("invoked %s", function_.constructor.name);
642
667
  if (noWait) {
643
668
  return;
@@ -689,222 +714,160 @@ export class Client extends ClientAbstract {
689
714
  }
690
715
  }
691
716
  }
692
- async applyUpdateNoGap(update, usePts = true) {
693
- const release = await this.updateApplicationMutex.acquire();
694
- try {
695
- if ((update instanceof types.UpdateNewMessage) ||
696
- (update instanceof types.UpdateDeleteMessages) ||
697
- (update instanceof types.UpdateReadHistoryInbox) ||
698
- (update instanceof types.UpdateReadHistoryOutbox) ||
699
- (update instanceof types.UpdateWebPage) ||
700
- (update instanceof types.UpdateReadMessagesContents) ||
701
- (update instanceof types.UpdateEditMessage) ||
702
- (update instanceof types.UpdateFolderPeers) ||
703
- (update instanceof types.UpdatePinnedMessages) ||
704
- (update instanceof types.UpdatePinnedChannelMessages) ||
705
- (update instanceof types.UpdateShortMessage) ||
706
- (update instanceof types.UpdateShortChatMessage) ||
707
- (update instanceof types.UpdateShortSentMessage)) {
708
- if (update.pts != 0 && update.ptsCount != 0) {
709
- const localState = await this.getLocalState();
710
- if (localState.pts + update.ptsCount > update.pts) {
711
- // the update is already applied
712
- return;
713
- }
714
- else if (localState.pts + update.ptsCount < update.pts) {
715
- // there is an update gap that needs to be filled
716
- throw UPDATE_GAP;
717
- }
718
- localState.pts = update.pts;
719
- d("applied update with pts %d", update.pts);
720
- await this.storage.setState(localState);
721
- }
722
- }
723
- else if (usePts &&
724
- ((update instanceof types.UpdateNewChannelMessage) ||
725
- (update instanceof types.UpdateDeleteChannelMessages) ||
726
- (update instanceof types.UpdateEditChannelMessage) ||
727
- (update instanceof types.UpdateChannelWebPage))) {
728
- const channelId = update instanceof types.UpdateNewChannelMessage || update instanceof types.UpdateEditChannelMessage ? update.message.peerId[as](types.PeerChannel).channelId : update.channelId;
729
- let localPts = await this.storage.getChannelPts(channelId);
730
- if (!localPts) {
731
- localPts = update.pts - update.ptsCount;
732
- }
733
- if (localPts + update.ptsCount > update.pts) {
734
- // already applied
735
- return;
736
- }
737
- else if (localPts + update.ptsCount < update.pts) {
738
- // should call channelGetDifference
739
- throw UPDATE_GAP;
740
- }
741
- d("applied update with pts %d", update.pts);
742
- 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();
743
722
  }
744
- if (update instanceof types.UpdateNewMessage || update instanceof types.UpdateNewMessage || update instanceof types.UpdateNewChannelMessage || update instanceof types.UpdateNewChannelMessage) {
745
- if (update.message instanceof types.Message || update.message instanceof types.MessageService) {
746
- await this.storage.setMessage(peerToChatId(update.message.peerId), update.message.id, update.message);
747
- }
723
+ else {
724
+ await this.recoverUpdateGap("processUpdates");
748
725
  }
749
- else if (update instanceof types.UpdateDeleteChannelMessages) {
750
- for (const message of update.messages) {
751
- await this.storage.setMessage(getChannelChatId(update.channelId), message, null);
752
- }
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();
753
736
  }
754
- else if (update instanceof types.UpdateDeleteMessages) {
755
- for (const message of update.messages) {
756
- const chatId = await this.storage.getMessageChat(message);
757
- if (chatId) {
758
- await this.storage.setMessage(chatId, message, null);
759
- }
760
- }
737
+ else {
738
+ await this.recoverChannelUpdateGap(channelId, "processUpdates");
761
739
  }
762
- // apply update (call listeners)
763
- this.updateHandler?.(this, update);
764
- }
765
- finally {
766
- release();
767
740
  }
768
741
  }
769
- async applyUpdate(update) {
770
- if (update instanceof types.TypeUpdates &&
771
- !(update instanceof types.UpdateShortMessage) &&
772
- !(update instanceof types.UpdateShortChatMessage) &&
773
- !(update instanceof types.UpdateShortSentMessage)) {
774
- // other constructors inheriting Updates are not applicable
775
- 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;
776
765
  }
777
- if (update instanceof types.TypeUpdate && update instanceof types.UpdateChannelTooLong) {
778
- // updateChannelTooLong is not applicable
779
- UNREACHABLE();
766
+ else if (updates_ instanceof types.TypeUpdate) {
767
+ updates = [updates_];
780
768
  }
781
- // can't apply updates when filling gap
782
- const release = await this.updateGapRecoveryMutex.acquire();
783
- try {
784
- await this.applyUpdateNoGap(update);
785
- release();
769
+ else {
770
+ UNREACHABLE();
786
771
  }
787
- catch (err) {
788
- release();
789
- if (err == UPDATE_GAP) {
790
- if ((update instanceof types.UpdateNewChannelMessage) ||
791
- (update instanceof types.UpdateDeleteChannelMessages) ||
792
- (update instanceof types.UpdateEditChannelMessage) ||
793
- (update instanceof types.UpdateChannelWebPage)) {
794
- const channelId = update instanceof types.UpdateNewChannelMessage || update instanceof types.UpdateEditChannelMessage ? update.message.peerId[as](types.PeerChannel).channelId : update.channelId;
795
- 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;
796
783
  }
797
- else if ((update instanceof types.UpdateNewMessage) ||
798
- (update instanceof types.UpdateDeleteMessages) ||
799
- (update instanceof types.UpdateReadHistoryInbox) ||
800
- (update instanceof types.UpdateReadHistoryOutbox) ||
801
- (update instanceof types.UpdateWebPage) ||
802
- (update instanceof types.UpdateReadMessagesContents) ||
803
- (update instanceof types.UpdateEditMessage) ||
804
- (update instanceof types.UpdateFolderPeers) ||
805
- (update instanceof types.UpdatePinnedMessages) ||
806
- (update instanceof types.UpdatePinnedChannelMessages) ||
807
- (update instanceof types.UpdateShortMessage) ||
808
- (update instanceof types.UpdateShortChatMessage) ||
809
- (update instanceof types.UpdateShortSentMessage)) {
810
- 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);
811
789
  }
812
790
  else {
813
- // can't detect update gap from other types of updates
814
- UNREACHABLE();
791
+ localState.pts = update.pts;
815
792
  }
816
- // just for integrity
817
- const release = await this.updateGapRecoveryMutex.acquire();
818
- try {
819
- await this.applyUpdateNoGap(update);
793
+ }
794
+ else if (hasChannelPts(update)) {
795
+ if (update.pts == 0) {
796
+ continue;
820
797
  }
821
- catch (err) {
822
- if (err == UPDATE_GAP) {
823
- // the gap must have been filled until now
824
- UNREACHABLE();
825
- }
826
- else {
827
- throw err;
828
- }
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);
829
804
  }
830
- finally {
831
- release();
805
+ currentPts ??= update.pts;
806
+ if (currentPts + ptsCount > update.pts) {
807
+ updates = updates.filter((v) => v != update);
808
+ }
809
+ else {
810
+ channelPtsMap.set(channelId, update.pts);
832
811
  }
833
812
  }
834
- else {
835
- 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);
836
820
  }
837
821
  }
838
- }
839
- async processUpdates(updates, release) {
840
- release ??= await this.updateProcessLock.acquire();
841
- try {
842
- if (updates instanceof types.TypeUpdates) {
843
- if (updates instanceof types.Updates) {
844
- await this.processChats(updates.chats);
845
- await this.processUsers(updates.users);
846
- await this.setUpdateStateDate(updates.date);
847
- for (const update of updates.updates) {
848
- await this.processUpdates(update, release);
849
- }
850
- }
851
- else if (updates instanceof types.UpdateShortMessage ||
852
- updates instanceof types.UpdateShortChatMessage ||
853
- updates instanceof types.UpdateShortSentMessage) {
854
- await this.setUpdateStateDate(updates.date);
855
- await this.applyUpdate(updates);
856
- }
857
- else if (updates instanceof types.UpdatesTooLong) {
858
- await this.recoverUpdateGap("updatesTooLong");
859
- }
860
- else if (updates instanceof types.UpdatesCombined) {
861
- await this.setUpdateStateDate(updates.date);
862
- await this.processChats(updates.chats);
863
- await this.processUsers(updates.users);
864
- for (const update of updates.updates) {
865
- await this.processUpdates(update, release);
866
- }
867
- }
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);
868
837
  }
869
- else if (updates instanceof types.TypeUpdate && updates instanceof types.UpdateChannelTooLong) {
870
- if (updates.pts != undefined) {
871
- 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);
872
841
  }
873
- await this.recoverChannelUpdateGap(updates.channelId, "updateChannelTooLong");
842
+ await this.recoverChannelUpdateGap(update.channelId, "updateChannelTooLong");
874
843
  }
875
- else {
876
- if (updates instanceof types.UpdateUserName) {
877
- 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);
878
851
  }
879
- else if (updates instanceof types.UpdatePtsChanged) {
880
- await this.fetchState("updatePtsChanged");
881
- if (this.updateState) {
882
- await this.storage.setState(this.updateState);
883
- }
884
- else {
885
- UNREACHABLE();
886
- }
852
+ else {
853
+ UNREACHABLE();
887
854
  }
888
- 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);
889
859
  }
890
860
  }
891
- catch (err) {
892
- d("error processing updates: %O", err);
893
- }
894
- finally {
895
- release();
896
- }
861
+ this.handleUpdateQueue.add(async () => {
862
+ for (const update of updatesToHandle) {
863
+ await this.handleUpdate(update);
864
+ }
865
+ });
897
866
  }
898
867
  async setUpdateStateDate(date) {
899
- const release = await this.updateApplicationMutex.acquire();
900
- try {
901
- const localState = await this.getLocalState();
902
- localState.date = date;
903
- await this.storage.setState(localState);
904
- }
905
- finally {
906
- release();
907
- }
868
+ const localState = await this.getLocalState();
869
+ localState.date = date;
870
+ await this.storage.setState(localState);
908
871
  }
909
872
  async getLocalState() {
910
873
  let localState = await this.storage.getState();
@@ -928,7 +891,7 @@ export class Client extends ClientAbstract {
928
891
  }
929
892
  async recoverUpdateGap(source) {
930
893
  dGap("recovering from update gap [%s]", source);
931
- const release = await this.updateGapRecoveryMutex.acquire();
894
+ await this.propagateConnectionState("updating");
932
895
  try {
933
896
  let state = await this.getLocalState();
934
897
  while (true) {
@@ -937,10 +900,10 @@ export class Client extends ClientAbstract {
937
900
  await this.processChats(difference.chats);
938
901
  await this.processUsers(difference.users);
939
902
  for (const message of difference.newMessages) {
940
- await this.applyUpdateNoGap(new types.UpdateNewMessage({ message, pts: 0, ptsCount: 0 }));
903
+ await this.processUpdates(new types.UpdateNewMessage({ message, pts: 0, ptsCount: 0 }), true);
941
904
  }
942
905
  for (const update of difference.otherUpdates) {
943
- await this.applyUpdateNoGap(update);
906
+ await this.processUpdates(update, true);
944
907
  }
945
908
  if (difference instanceof types.UpdatesDifference) {
946
909
  await this.storage.setState(difference.state[as](types.UpdatesState));
@@ -955,6 +918,7 @@ export class Client extends ClientAbstract {
955
918
  }
956
919
  }
957
920
  else if (difference instanceof types.UpdatesDifferenceTooLong) {
921
+ // TODO: we actually do now
958
922
  // stored messages should be invalidated in case we store messages in the future
959
923
  state.pts = difference.pts;
960
924
  dGap("received differenceTooLong");
@@ -970,61 +934,55 @@ export class Client extends ClientAbstract {
970
934
  }
971
935
  }
972
936
  finally {
973
- release();
937
+ this.stateChangeHandler(this.connected);
974
938
  }
975
939
  }
976
940
  async recoverChannelUpdateGap(channelId, source) {
977
941
  dGapC("recovering channel update gap [%o, %s]", channelId, source);
978
- const release = await this.updateGapRecoveryMutex.acquire();
979
- try {
980
- const pts_ = await this.storage.getChannelPts(channelId);
981
- let pts = pts_ == null ? 1 : pts_;
982
- while (true) {
983
- const { accessHash } = await this.getInputPeer(ZERO_CHANNEL_ID + -Number(channelId)).then((v) => v[as](types.InputPeerChannel));
984
- const difference = await this.invoke(new functions.UpdatesGetChannelDifference({
985
- pts,
986
- channel: new types.InputChannel({ channelId, accessHash: accessHash }),
987
- filter: new types.ChannelMessagesFilterEmpty(),
988
- limit: await this.storage.getAccountType() == "user" ? CHANNEL_DIFFERENCE_LIMIT_USER : CHANNEL_DIFFERENCE_LIMIT_BOT,
989
- }));
990
- if (difference instanceof types.UpdatesChannelDifference) {
991
- await this.processChats(difference.chats);
992
- await this.processUsers(difference.users);
993
- for (const message of difference.newMessages) {
994
- await this.applyUpdateNoGap(new types.UpdateNewChannelMessage({ message, pts: 0, ptsCount: 0 }), false);
995
- }
996
- for (const update of difference.otherUpdates) {
997
- await this.applyUpdateNoGap(update, false);
998
- }
999
- await this.storage.setChannelPts(channelId, difference.pts);
1000
- dGapC("recovered from update gap [%o, %s]", channelId, source);
1001
- 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);
1002
957
  }
1003
- else if (difference instanceof types.UpdatesChannelDifferenceTooLong) {
1004
- // invalidate messages
1005
- dGapC("received channelDifferenceTooLong");
1006
- await this.processChats(difference.chats);
1007
- await this.processUsers(difference.users);
1008
- for (const message of difference.messages) {
1009
- await this.applyUpdateNoGap(new types.UpdateNewChannelMessage({ message, pts: 0, ptsCount: 0 }), false);
1010
- }
1011
- const pts_ = difference.dialog[as](types.Dialog).pts;
1012
- if (pts_ != undefined) {
1013
- pts = pts_;
1014
- }
1015
- else {
1016
- UNREACHABLE();
1017
- }
1018
- dGapC("processed channelDifferenceTooLong");
958
+ for (const update of difference.otherUpdates) {
959
+ await this.processUpdates(update, true);
1019
960
  }
1020
- else if (difference instanceof types.UpdatesChannelDifferenceEmpty) {
1021
- dGapC("there was no update gap");
1022
- 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_;
976
+ }
977
+ else {
978
+ UNREACHABLE();
1023
979
  }
980
+ dGapC("processed channelDifferenceTooLong");
981
+ }
982
+ else if (difference instanceof types.UpdatesChannelDifferenceEmpty) {
983
+ dGapC("there was no update gap");
984
+ break;
1024
985
  }
1025
- }
1026
- finally {
1027
- release();
1028
986
  }
1029
987
  }
1030
988
  async getInputPeer(id) {
@@ -1341,7 +1299,8 @@ export class Client extends ClientAbstract {
1341
1299
  langPack: this.langPack,
1342
1300
  systemLangCode: this.systemLangCode,
1343
1301
  systemVersion: this.systemVersion,
1344
- }, true);
1302
+ cdn: true,
1303
+ });
1345
1304
  let dc = String(dcId);
1346
1305
  if (this.dcId < 0) {
1347
1306
  dc += "-test";
@@ -1452,4 +1411,68 @@ export class Client extends ClientAbstract {
1452
1411
  }
1453
1412
  return constructUser(users[0][as](types.User));
1454
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
+ }
1455
1477
  }
1478
+ const resolve = () => Promise.resolve();