@mtcute/core 0.29.6 → 0.30.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 (118) hide show
  1. package/client.cjs +8 -0
  2. package/client.js +8 -0
  3. package/highlevel/client.d.cts +23 -7
  4. package/highlevel/client.d.ts +23 -7
  5. package/highlevel/methods/bots/answer-bot-guest-chat-query.cjs +26 -0
  6. package/highlevel/methods/bots/answer-bot-guest-chat-query.d.cts +12 -0
  7. package/highlevel/methods/bots/answer-bot-guest-chat-query.d.ts +12 -0
  8. package/highlevel/methods/bots/answer-bot-guest-chat-query.js +21 -0
  9. package/highlevel/methods/chats/join-chat.cjs +38 -16
  10. package/highlevel/methods/chats/join-chat.d.cts +20 -6
  11. package/highlevel/methods/chats/join-chat.d.ts +20 -6
  12. package/highlevel/methods/chats/join-chat.js +38 -16
  13. package/highlevel/methods/chats/save-draft.d.cts +4 -1
  14. package/highlevel/methods/chats/save-draft.d.ts +4 -1
  15. package/highlevel/methods/messages/send-copy-group.cjs +5 -9
  16. package/highlevel/methods/messages/send-copy-group.js +5 -9
  17. package/highlevel/methods.d.cts +3 -0
  18. package/highlevel/methods.d.ts +3 -0
  19. package/highlevel/storage/service/peers.cjs +2 -0
  20. package/highlevel/storage/service/peers.js +2 -0
  21. package/highlevel/types/files/utils.d.cts +1 -1
  22. package/highlevel/types/files/utils.d.ts +1 -1
  23. package/highlevel/types/media/poll.cjs +14 -0
  24. package/highlevel/types/media/poll.d.cts +6 -0
  25. package/highlevel/types/media/poll.d.ts +6 -0
  26. package/highlevel/types/media/poll.js +14 -0
  27. package/highlevel/types/messages/index.d.cts +1 -0
  28. package/highlevel/types/messages/index.d.ts +1 -0
  29. package/highlevel/types/messages/message.cjs +15 -1
  30. package/highlevel/types/messages/message.d.cts +8 -0
  31. package/highlevel/types/messages/message.d.ts +8 -0
  32. package/highlevel/types/messages/message.js +15 -1
  33. package/highlevel/types/messages/rich-message.cjs +50 -0
  34. package/highlevel/types/messages/rich-message.d.cts +18 -0
  35. package/highlevel/types/messages/rich-message.d.ts +18 -0
  36. package/highlevel/types/messages/rich-message.js +45 -0
  37. package/highlevel/types/peers/chat-permissions.cjs +6 -0
  38. package/highlevel/types/peers/chat-permissions.d.cts +4 -0
  39. package/highlevel/types/peers/chat-permissions.d.ts +4 -0
  40. package/highlevel/types/peers/chat-permissions.js +6 -0
  41. package/highlevel/types/peers/full-chat.cjs +6 -0
  42. package/highlevel/types/peers/full-chat.d.cts +2 -0
  43. package/highlevel/types/peers/full-chat.d.ts +2 -0
  44. package/highlevel/types/peers/full-chat.js +6 -0
  45. package/highlevel/types/peers/user.cjs +8 -0
  46. package/highlevel/types/peers/user.d.cts +4 -0
  47. package/highlevel/types/peers/user.d.ts +4 -0
  48. package/highlevel/types/peers/user.js +8 -0
  49. package/highlevel/types/updates/bot-guest-chat-query.cjs +39 -0
  50. package/highlevel/types/updates/bot-guest-chat-query.d.cts +21 -0
  51. package/highlevel/types/updates/bot-guest-chat-query.d.ts +21 -0
  52. package/highlevel/types/updates/bot-guest-chat-query.js +34 -0
  53. package/highlevel/types/updates/index.d.cts +5 -1
  54. package/highlevel/types/updates/index.d.ts +5 -1
  55. package/highlevel/types/updates/parse-update.cjs +3 -0
  56. package/highlevel/types/updates/parse-update.js +3 -0
  57. package/highlevel/types/updates/user-typing-update.cjs +2 -0
  58. package/highlevel/types/updates/user-typing-update.js +2 -0
  59. package/highlevel/updates/manager.cjs +214 -3
  60. package/highlevel/updates/manager.d.cts +1 -0
  61. package/highlevel/updates/manager.d.ts +1 -0
  62. package/highlevel/updates/manager.js +214 -3
  63. package/highlevel/utils/inspectable.cjs +1 -0
  64. package/highlevel/utils/inspectable.d.cts +1 -0
  65. package/highlevel/utils/inspectable.d.ts +1 -0
  66. package/highlevel/utils/inspectable.js +1 -0
  67. package/index.cjs +4 -0
  68. package/index.js +4 -0
  69. package/methods.cjs +2 -0
  70. package/methods.js +2 -0
  71. package/network/flood-control.cjs +149 -0
  72. package/network/flood-control.d.cts +79 -0
  73. package/network/flood-control.d.ts +79 -0
  74. package/network/flood-control.js +144 -0
  75. package/network/flood-control.test.d.cts +1 -0
  76. package/network/flood-control.test.d.ts +1 -0
  77. package/network/mtproto-session.cjs +1 -21
  78. package/network/mtproto-session.d.cts +1 -5
  79. package/network/mtproto-session.d.ts +1 -5
  80. package/network/mtproto-session.js +2 -22
  81. package/network/multi-session-connection.cjs +26 -16
  82. package/network/multi-session-connection.d.cts +1 -1
  83. package/network/multi-session-connection.d.ts +1 -1
  84. package/network/multi-session-connection.js +26 -16
  85. package/network/multi-session-connection.test.d.cts +1 -0
  86. package/network/multi-session-connection.test.d.ts +1 -0
  87. package/network/network-manager.cjs +3 -2
  88. package/network/network-manager.d.cts +12 -0
  89. package/network/network-manager.d.ts +12 -0
  90. package/network/network-manager.js +3 -2
  91. package/network/persistent-connection.cjs +25 -1
  92. package/network/persistent-connection.d.cts +5 -0
  93. package/network/persistent-connection.d.ts +5 -0
  94. package/network/persistent-connection.js +25 -1
  95. package/network/session-connection.cjs +5 -10
  96. package/network/session-connection.d.cts +0 -2
  97. package/network/session-connection.d.ts +0 -2
  98. package/network/session-connection.js +5 -10
  99. package/package.json +1 -1
  100. package/tl/api-schema.json +1 -1
  101. package/tl/binary/reader.cjs +257 -62
  102. package/tl/binary/reader.js +257 -62
  103. package/tl/binary/writer.cjs +837 -212
  104. package/tl/binary/writer.js +837 -212
  105. package/tl/compat/reader.cjs +31 -0
  106. package/tl/compat/reader.js +31 -0
  107. package/tl/index.d.cts +2478 -186
  108. package/tl/index.d.ts +2478 -186
  109. package/tl/inner-tl.cjs +34 -10
  110. package/tl/inner-tl.js +34 -10
  111. package/utils/binary/compat.cjs +84 -0
  112. package/utils/binary/compat.js +84 -0
  113. package/utils/index.d.cts +1 -0
  114. package/utils/index.d.ts +1 -0
  115. package/utils/long-utils.cjs +10 -0
  116. package/utils/long-utils.js +10 -0
  117. package/utils.cjs +7 -0
  118. package/utils.js +8 -1
@@ -0,0 +1,79 @@
1
+ import { Logger } from '../utils/logger.js';
2
+ export interface FloodLimit {
3
+ /** maximum events allowed within `windowMs` */
4
+ count: number;
5
+ /** window size in ms */
6
+ windowMs: number;
7
+ }
8
+ /**
9
+ * Sliding-window flood control. Port of TDLib's `FloodControlStrict`
10
+ * (tdutils/td/utils/FloodControlStrict.h).
11
+ */
12
+ export declare class FloodControl {
13
+ private readonly _rules;
14
+ private readonly _maxWindow;
15
+ private _events;
16
+ constructor(rules: readonly FloodLimit[]);
17
+ /** Earliest absolute perf-now ms at which a new event is permitted. */
18
+ getWakeupAt(now: number): number;
19
+ addEvent(now: number): void;
20
+ private _prune;
21
+ reset(): void;
22
+ }
23
+ export declare const DEFAULT_SANITY_LIMITS: readonly FloodLimit[];
24
+ export declare const DEFAULT_FLOOD_LIMITS: readonly FloodLimit[];
25
+ export declare const DEFAULT_MTPROTO_ERROR_LIMITS: readonly FloodLimit[];
26
+ export interface ConnectionFloodLimits {
27
+ sanity?: readonly FloodLimit[];
28
+ flood?: readonly FloodLimit[];
29
+ mtprotoError?: readonly FloodLimit[];
30
+ }
31
+ /**
32
+ * Per-connection-slot flood control bundle modeled after TDLib's
33
+ * `ConnectionCreator` client state (`ConnectionCreator.cpp`) — TDLib keys
34
+ * its flood control by pool slot (the `SessionProxy#i` hash), so each
35
+ * socket's connect history is throttled independently.
36
+ *
37
+ * Three limiters compose:
38
+ * - `sanity` — absolute ceiling, always applied
39
+ * - `flood` — burst limit on connection attempts, always applied
40
+ * - `mtprotoError` — ticks only on MTProto-level errors, always applied
41
+ *
42
+ * TDLib additionally has a stricter "online" regime keyed to app foreground
43
+ * state — we have no such signal, so only the permissive regime is modeled.
44
+ *
45
+ * `wait()` blocks until all limiters allow a fresh connection and records
46
+ * the event atomically (sanity + flood).
47
+ */
48
+ export declare class ConnectionFloodController {
49
+ readonly sanity: FloodControl;
50
+ readonly flood: FloodControl;
51
+ readonly mtprotoError: FloodControl;
52
+ private _log?;
53
+ constructor(limits?: ConnectionFloodLimits);
54
+ setLogger(log: Logger): void;
55
+ /**
56
+ * Notify a network-up transition.
57
+ *
58
+ * TDLib clears sanity + online-regime flood + backoff on
59
+ * `ConnectionCreator::on_network(true)`. We don't model the online regime,
60
+ * and our transport layer owns its own reconnect backoff, so only the
61
+ * sanity limiter is reset here.
62
+ */
63
+ notifyNetworkUp(): void;
64
+ /** Record an MTProto-level error (transport 429; per TDLib, other codes don't tick this). */
65
+ addMtprotoError(now?: number): void;
66
+ /** Earliest perf-now ms at which a new connection attempt is permitted. */
67
+ getWakeupAt(now: number): number;
68
+ /**
69
+ * Block until a connection attempt is permitted under all applicable
70
+ * limiters, then record the attempt. Concurrent waiters serialize fairly
71
+ * because the first to wake records its event before the next checks state.
72
+ *
73
+ * If `signal` aborts, the pending timer is cleared and the promise rejects
74
+ * with the signal's reason.
75
+ */
76
+ wait(signal?: AbortSignal): Promise<void>;
77
+ /** Reset all internal state. Mainly for tests. */
78
+ reset(): void;
79
+ }
@@ -0,0 +1,144 @@
1
+ import { timers } from "@fuman/utils";
2
+ class FloodControl {
3
+ _rules;
4
+ _maxWindow;
5
+ _events = [];
6
+ constructor(rules) {
7
+ if (rules.length === 0) throw new Error("FloodControl needs at least one rule");
8
+ this._rules = rules;
9
+ this._maxWindow = rules.reduce((m, r) => Math.max(m, r.windowMs), 0);
10
+ }
11
+ /** Earliest absolute perf-now ms at which a new event is permitted. */
12
+ getWakeupAt(now) {
13
+ let wakeup = 0;
14
+ for (const { count, windowMs } of this._rules) {
15
+ const cutoff = now - windowMs;
16
+ let inWindow = 0;
17
+ let oldestInWindowIdx = -1;
18
+ for (let i = this._events.length - 1; i >= 0; i--) {
19
+ if (this._events[i] <= cutoff) break;
20
+ inWindow++;
21
+ oldestInWindowIdx = i;
22
+ }
23
+ if (inWindow >= count) {
24
+ const t = this._events[oldestInWindowIdx] + windowMs;
25
+ if (t > wakeup) wakeup = t;
26
+ }
27
+ }
28
+ return wakeup;
29
+ }
30
+ addEvent(now) {
31
+ this._events.push(now);
32
+ this._prune(now);
33
+ }
34
+ _prune(now) {
35
+ const cutoff = now - this._maxWindow;
36
+ let i = 0;
37
+ while (i < this._events.length && this._events[i] < cutoff) i++;
38
+ if (i > 0) this._events.splice(0, i);
39
+ }
40
+ reset() {
41
+ this._events = [];
42
+ }
43
+ }
44
+ const DEFAULT_SANITY_LIMITS = [
45
+ { count: 5, windowMs: 1e4 }
46
+ ];
47
+ const DEFAULT_FLOOD_LIMITS = [
48
+ { count: 1, windowMs: 1e3 },
49
+ { count: 4, windowMs: 2e3 },
50
+ { count: 8, windowMs: 3e3 }
51
+ ];
52
+ const DEFAULT_MTPROTO_ERROR_LIMITS = [
53
+ { count: 1, windowMs: 1e3 },
54
+ { count: 4, windowMs: 2e3 },
55
+ { count: 8, windowMs: 3e3 }
56
+ ];
57
+ class ConnectionFloodController {
58
+ sanity;
59
+ flood;
60
+ mtprotoError;
61
+ _log;
62
+ constructor(limits = {}) {
63
+ this.sanity = new FloodControl(limits.sanity ?? DEFAULT_SANITY_LIMITS);
64
+ this.flood = new FloodControl(limits.flood ?? DEFAULT_FLOOD_LIMITS);
65
+ this.mtprotoError = new FloodControl(limits.mtprotoError ?? DEFAULT_MTPROTO_ERROR_LIMITS);
66
+ }
67
+ setLogger(log) {
68
+ this._log = log;
69
+ }
70
+ /**
71
+ * Notify a network-up transition.
72
+ *
73
+ * TDLib clears sanity + online-regime flood + backoff on
74
+ * `ConnectionCreator::on_network(true)`. We don't model the online regime,
75
+ * and our transport layer owns its own reconnect backoff, so only the
76
+ * sanity limiter is reset here.
77
+ */
78
+ notifyNetworkUp() {
79
+ this._log?.debug("network up, resetting sanity limiter");
80
+ this.sanity.reset();
81
+ }
82
+ /** Record an MTProto-level error (transport 429; per TDLib, other codes don't tick this). */
83
+ addMtprotoError(now = performance.now()) {
84
+ this._log?.debug("recording mtproto error");
85
+ this.mtprotoError.addEvent(now);
86
+ }
87
+ /** Earliest perf-now ms at which a new connection attempt is permitted. */
88
+ getWakeupAt(now) {
89
+ return Math.max(
90
+ this.sanity.getWakeupAt(now),
91
+ this.flood.getWakeupAt(now),
92
+ this.mtprotoError.getWakeupAt(now)
93
+ );
94
+ }
95
+ /**
96
+ * Block until a connection attempt is permitted under all applicable
97
+ * limiters, then record the attempt. Concurrent waiters serialize fairly
98
+ * because the first to wake records its event before the next checks state.
99
+ *
100
+ * If `signal` aborts, the pending timer is cleared and the promise rejects
101
+ * with the signal's reason.
102
+ */
103
+ async wait(signal) {
104
+ let waited = false;
105
+ while (true) {
106
+ signal?.throwIfAborted();
107
+ const now = performance.now();
108
+ const wakeup = this.getWakeupAt(now);
109
+ if (wakeup <= now) {
110
+ this.sanity.addEvent(now);
111
+ this.flood.addEvent(now);
112
+ if (waited) this._log?.debug("admitted connect after waiting");
113
+ return;
114
+ }
115
+ const delay = wakeup - now;
116
+ this._log?.debug("connect throttled (waiting %dms)", Math.round(delay));
117
+ waited = true;
118
+ await new Promise((resolve, reject) => {
119
+ const handle = timers.setTimeout(() => {
120
+ signal?.removeEventListener("abort", onAbort);
121
+ resolve();
122
+ }, delay);
123
+ const onAbort = () => {
124
+ timers.clearTimeout(handle);
125
+ reject(signal.reason);
126
+ };
127
+ signal?.addEventListener("abort", onAbort, { once: true });
128
+ });
129
+ }
130
+ }
131
+ /** Reset all internal state. Mainly for tests. */
132
+ reset() {
133
+ this.sanity.reset();
134
+ this.flood.reset();
135
+ this.mtprotoError.reset();
136
+ }
137
+ }
138
+ export {
139
+ ConnectionFloodController,
140
+ DEFAULT_FLOOD_LIMITS,
141
+ DEFAULT_MTPROTO_ERROR_LIMITS,
142
+ DEFAULT_SANITY_LIMITS,
143
+ FloodControl
144
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -36,6 +36,7 @@ class MtprotoSession {
36
36
  // recent msg ids
37
37
  recentOutgoingMsgIds = new utils.LruSet(1e3, longUtils.LongSet);
38
38
  recentIncomingMsgIds = new utils.LruSet(1e3, longUtils.LongSet);
39
+ // <deno-tsignore>
39
40
  recentStateRequests = new utils.LruMap(1e3, longUtils.LongMap);
40
41
  // queues
41
42
  queuedRpc = new utils.Deque();
@@ -60,9 +61,6 @@ class MtprotoSession {
60
61
  lastSessionCreatedUid = Long.ZERO;
61
62
  initConnectionCalled = false;
62
63
  authorizationPending = false;
63
- next429Timeout = 1e3;
64
- current429Timeout;
65
- next429ResetTimeout;
66
64
  get hasPendingMessages() {
67
65
  return Boolean(
68
66
  this.queuedRpc.length || this.queuedAcks.length || this.queuedStateReq.length || this.queuedResendReq.length || this.queuedCancelReq.length
@@ -75,7 +73,6 @@ class MtprotoSession {
75
73
  if (withAuthKey) {
76
74
  this.resetAuthKey();
77
75
  }
78
- utils.timers.clearTimeout(this.current429Timeout);
79
76
  this.resetState(withAuthKey);
80
77
  }
81
78
  resetAuthKey() {
@@ -197,23 +194,6 @@ class MtprotoSession {
197
194
  else writer.object(content);
198
195
  return messageId;
199
196
  }
200
- onTransportFlood(callback) {
201
- if (this.current429Timeout) return;
202
- this.resetLastPing(true);
203
- const timeout = this.next429Timeout;
204
- this.next429Timeout = Math.min(this.next429Timeout * 2, 32e3);
205
- utils.timers.clearTimeout(this.current429Timeout);
206
- utils.timers.clearTimeout(this.next429ResetTimeout);
207
- this.current429Timeout = utils.timers.setTimeout(() => {
208
- this.current429Timeout = void 0;
209
- callback();
210
- }, timeout);
211
- this.next429ResetTimeout = utils.timers.setTimeout(() => {
212
- this.next429ResetTimeout = void 0;
213
- this.next429Timeout = 1e3;
214
- }, 6e4);
215
- this.log.debug("transport flood, waiting for %d ms before proceeding", timeout);
216
- }
217
197
  resetLastPing(withTime = false) {
218
198
  if (withTime) this.lastPingTime = 0;
219
199
  if (!this.lastPingMsgId.isZero()) {
@@ -1,4 +1,4 @@
1
- import { Deferred, Deque, LruMap, LruSet, timers } from '@fuman/utils';
1
+ import { Deferred, timers, Deque, LruMap, LruSet } from '@fuman/utils';
2
2
  import { TlBinaryWriter, TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime';
3
3
  import { mtp, tl } from '../tl/index.js';
4
4
  import { ICryptoProvider, Logger, LongMap, SortedArray } from '../utils/index.js';
@@ -94,9 +94,6 @@ export declare class MtprotoSession {
94
94
  lastSessionCreatedUid: Long;
95
95
  initConnectionCalled: boolean;
96
96
  authorizationPending: boolean;
97
- next429Timeout: number;
98
- current429Timeout?: timers.Timer;
99
- next429ResetTimeout?: timers.Timer;
100
97
  constructor(_crypto: ICryptoProvider, log: Logger, _readerMap: TlReaderMap, _writerMap: TlWriterMap, _salts: ServerSaltManager);
101
98
  get hasPendingMessages(): boolean;
102
99
  /**
@@ -120,7 +117,6 @@ export declare class MtprotoSession {
120
117
  /** Decrypt a single MTProto message using session's keys */
121
118
  decryptMessage(data: Uint8Array, callback: Parameters<AuthKey['decryptMessage']>[2]): void;
122
119
  writeMessage(writer: TlBinaryWriter, content: tl.TlObject | mtp.TlObject | Uint8Array, isContentRelated?: boolean): Long;
123
- onTransportFlood(callback: () => void): void;
124
120
  resetLastPing(withTime?: boolean): void;
125
121
  addToChain(chainId: string | number, msgId: Long): Long | undefined;
126
122
  removeFromChain(chainId: string | number, msgId: Long): void;
@@ -1,4 +1,4 @@
1
- import { Deferred, Deque, LruMap, LruSet, timers } from '@fuman/utils';
1
+ import { Deferred, timers, Deque, LruMap, LruSet } from '@fuman/utils';
2
2
  import { TlBinaryWriter, TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime';
3
3
  import { mtp, tl } from '../tl/index.js';
4
4
  import { ICryptoProvider, Logger, LongMap, SortedArray } from '../utils/index.js';
@@ -94,9 +94,6 @@ export declare class MtprotoSession {
94
94
  lastSessionCreatedUid: Long;
95
95
  initConnectionCalled: boolean;
96
96
  authorizationPending: boolean;
97
- next429Timeout: number;
98
- current429Timeout?: timers.Timer;
99
- next429ResetTimeout?: timers.Timer;
100
97
  constructor(_crypto: ICryptoProvider, log: Logger, _readerMap: TlReaderMap, _writerMap: TlWriterMap, _salts: ServerSaltManager);
101
98
  get hasPendingMessages(): boolean;
102
99
  /**
@@ -120,7 +117,6 @@ export declare class MtprotoSession {
120
117
  /** Decrypt a single MTProto message using session's keys */
121
118
  decryptMessage(data: Uint8Array, callback: Parameters<AuthKey['decryptMessage']>[2]): void;
122
119
  writeMessage(writer: TlBinaryWriter, content: tl.TlObject | mtp.TlObject | Uint8Array, isContentRelated?: boolean): Long;
123
- onTransportFlood(callback: () => void): void;
124
120
  resetLastPing(withTime?: boolean): void;
125
121
  addToChain(chainId: string | number, msgId: Long): Long | undefined;
126
122
  removeFromChain(chainId: string | number, msgId: Long): void;
@@ -1,4 +1,4 @@
1
- import { LruSet, LruMap, Deque, timers } from "@fuman/utils";
1
+ import { LruSet, LruMap, Deque } from "@fuman/utils";
2
2
  import { TlSerializationCounter } from "@mtcute/tl-runtime";
3
3
  import Long from "long";
4
4
  import { randomLong, LongSet, LongMap, compareLongs } from "../utils/long-utils.js";
@@ -29,6 +29,7 @@ class MtprotoSession {
29
29
  // recent msg ids
30
30
  recentOutgoingMsgIds = new LruSet(1e3, LongSet);
31
31
  recentIncomingMsgIds = new LruSet(1e3, LongSet);
32
+ // <deno-tsignore>
32
33
  recentStateRequests = new LruMap(1e3, LongMap);
33
34
  // queues
34
35
  queuedRpc = new Deque();
@@ -53,9 +54,6 @@ class MtprotoSession {
53
54
  lastSessionCreatedUid = Long.ZERO;
54
55
  initConnectionCalled = false;
55
56
  authorizationPending = false;
56
- next429Timeout = 1e3;
57
- current429Timeout;
58
- next429ResetTimeout;
59
57
  get hasPendingMessages() {
60
58
  return Boolean(
61
59
  this.queuedRpc.length || this.queuedAcks.length || this.queuedStateReq.length || this.queuedResendReq.length || this.queuedCancelReq.length
@@ -68,7 +66,6 @@ class MtprotoSession {
68
66
  if (withAuthKey) {
69
67
  this.resetAuthKey();
70
68
  }
71
- timers.clearTimeout(this.current429Timeout);
72
69
  this.resetState(withAuthKey);
73
70
  }
74
71
  resetAuthKey() {
@@ -190,23 +187,6 @@ class MtprotoSession {
190
187
  else writer.object(content);
191
188
  return messageId;
192
189
  }
193
- onTransportFlood(callback) {
194
- if (this.current429Timeout) return;
195
- this.resetLastPing(true);
196
- const timeout = this.next429Timeout;
197
- this.next429Timeout = Math.min(this.next429Timeout * 2, 32e3);
198
- timers.clearTimeout(this.current429Timeout);
199
- timers.clearTimeout(this.next429ResetTimeout);
200
- this.current429Timeout = timers.setTimeout(() => {
201
- this.current429Timeout = void 0;
202
- callback();
203
- }, timeout);
204
- this.next429ResetTimeout = timers.setTimeout(() => {
205
- this.next429ResetTimeout = void 0;
206
- this.next429Timeout = 1e3;
207
- }, 6e4);
208
- this.log.debug("transport flood, waiting for %d ms before proceeding", timeout);
209
- }
210
190
  resetLastPing(withTime = false) {
211
191
  if (withTime) this.lastPingTime = 0;
212
192
  if (!this.lastPingMsgId.isZero()) {
@@ -16,6 +16,24 @@ class MultiSessionConnection {
16
16
  this._enforcePfs = _count > 1 && params.isMainConnection;
17
17
  this._updateConnections();
18
18
  }
19
+ static _pickConnection(loads, ready) {
20
+ let min = Infinity;
21
+ let minIdx = 0;
22
+ let foundReady = false;
23
+ for (let i = 0; i < loads.length; i++) {
24
+ const isReady = ready[i];
25
+ if (foundReady && !isReady) continue;
26
+ if (isReady && !foundReady) {
27
+ foundReady = true;
28
+ min = Infinity;
29
+ }
30
+ if (loads[i] < min) {
31
+ min = loads[i];
32
+ minIdx = i;
33
+ }
34
+ }
35
+ return minIdx;
36
+ }
19
37
  _log;
20
38
  _enforcePfs = false;
21
39
  // NB: dont forget to update .reset()
@@ -103,10 +121,6 @@ class MultiSessionConnection {
103
121
  conn.onUsable.add(() => this.onUsable.emit(i));
104
122
  conn.onWait.add(() => this.onWait.emit(i));
105
123
  conn.onRequestAuth.add(() => this.onRequestAuth.emit(i));
106
- conn.onFloodDone.add(() => {
107
- this._log.debug("received flood-done from connection %d", i);
108
- this._connections.forEach((it) => it.flushWhenIdle());
109
- });
110
124
  this._connections.push(conn);
111
125
  if (this._authKey !== null) conn._session._authKey.setup(this._authKey);
112
126
  if (connect && !enforcePfsChanged) conn.connect();
@@ -127,19 +141,15 @@ class MultiSessionConnection {
127
141
  this.onRequestAuth.clear();
128
142
  this._destroyed = true;
129
143
  }
130
- _nextConnection = 0;
131
144
  sendRpc(request, timeout, abortSignal, chainId) {
132
- let min = Infinity;
133
- let minIdx = 0;
134
- for (let i = 0; i < this._connections.length; i++) {
135
- const conn = this._connections[i];
136
- const total = conn._session.queuedRpc.length + conn._session.pendingMessages.size;
137
- if (total < min) {
138
- min = total;
139
- minIdx = i;
140
- }
141
- }
142
- return this._connections[minIdx].sendRpc(request, timeout, abortSignal, chainId);
145
+ const loads = this._connections.map((c) => c._session.queuedRpc.length + c._session.pendingMessages.size);
146
+ const ready = this._connections.map((c) => c.isConnected);
147
+ return this._connections[MultiSessionConnection._pickConnection(loads, ready)].sendRpc(
148
+ request,
149
+ timeout,
150
+ abortSignal,
151
+ chainId
152
+ );
143
153
  }
144
154
  connect() {
145
155
  for (const conn of this._connections) {
@@ -6,6 +6,7 @@ import { Deferred, Emitter } from '@fuman/utils';
6
6
  export declare class MultiSessionConnection {
7
7
  readonly params: SessionConnectionParams;
8
8
  private _count;
9
+ static _pickConnection(loads: readonly number[], ready: readonly boolean[]): number;
9
10
  private _log;
10
11
  private _enforcePfs;
11
12
  readonly onRequestKeys: Emitter<Deferred<void>>;
@@ -25,7 +26,6 @@ export declare class MultiSessionConnection {
25
26
  private _updateConnections;
26
27
  _destroyed: boolean;
27
28
  destroy(): Promise<void>;
28
- private _nextConnection;
29
29
  sendRpc<T extends tl.RpcMethod>(request: T, timeout?: number, abortSignal?: AbortSignal, chainId?: string | number): Promise<tl.RpcCallReturn[T['_']] | mtp.RawMt_rpc_error>;
30
30
  connect(): void;
31
31
  ensureConnected(): void;
@@ -6,6 +6,7 @@ import { Deferred, Emitter } from '@fuman/utils';
6
6
  export declare class MultiSessionConnection {
7
7
  readonly params: SessionConnectionParams;
8
8
  private _count;
9
+ static _pickConnection(loads: readonly number[], ready: readonly boolean[]): number;
9
10
  private _log;
10
11
  private _enforcePfs;
11
12
  readonly onRequestKeys: Emitter<Deferred<void>>;
@@ -25,7 +26,6 @@ export declare class MultiSessionConnection {
25
26
  private _updateConnections;
26
27
  _destroyed: boolean;
27
28
  destroy(): Promise<void>;
28
- private _nextConnection;
29
29
  sendRpc<T extends tl.RpcMethod>(request: T, timeout?: number, abortSignal?: AbortSignal, chainId?: string | number): Promise<tl.RpcCallReturn[T['_']] | mtp.RawMt_rpc_error>;
30
30
  connect(): void;
31
31
  ensureConnected(): void;
@@ -9,6 +9,24 @@ class MultiSessionConnection {
9
9
  this._enforcePfs = _count > 1 && params.isMainConnection;
10
10
  this._updateConnections();
11
11
  }
12
+ static _pickConnection(loads, ready) {
13
+ let min = Infinity;
14
+ let minIdx = 0;
15
+ let foundReady = false;
16
+ for (let i = 0; i < loads.length; i++) {
17
+ const isReady = ready[i];
18
+ if (foundReady && !isReady) continue;
19
+ if (isReady && !foundReady) {
20
+ foundReady = true;
21
+ min = Infinity;
22
+ }
23
+ if (loads[i] < min) {
24
+ min = loads[i];
25
+ minIdx = i;
26
+ }
27
+ }
28
+ return minIdx;
29
+ }
12
30
  _log;
13
31
  _enforcePfs = false;
14
32
  // NB: dont forget to update .reset()
@@ -96,10 +114,6 @@ class MultiSessionConnection {
96
114
  conn.onUsable.add(() => this.onUsable.emit(i));
97
115
  conn.onWait.add(() => this.onWait.emit(i));
98
116
  conn.onRequestAuth.add(() => this.onRequestAuth.emit(i));
99
- conn.onFloodDone.add(() => {
100
- this._log.debug("received flood-done from connection %d", i);
101
- this._connections.forEach((it) => it.flushWhenIdle());
102
- });
103
117
  this._connections.push(conn);
104
118
  if (this._authKey !== null) conn._session._authKey.setup(this._authKey);
105
119
  if (connect && !enforcePfsChanged) conn.connect();
@@ -120,19 +134,15 @@ class MultiSessionConnection {
120
134
  this.onRequestAuth.clear();
121
135
  this._destroyed = true;
122
136
  }
123
- _nextConnection = 0;
124
137
  sendRpc(request, timeout, abortSignal, chainId) {
125
- let min = Infinity;
126
- let minIdx = 0;
127
- for (let i = 0; i < this._connections.length; i++) {
128
- const conn = this._connections[i];
129
- const total = conn._session.queuedRpc.length + conn._session.pendingMessages.size;
130
- if (total < min) {
131
- min = total;
132
- minIdx = i;
133
- }
134
- }
135
- return this._connections[minIdx].sendRpc(request, timeout, abortSignal, chainId);
138
+ const loads = this._connections.map((c) => c._session.queuedRpc.length + c._session.pendingMessages.size);
139
+ const ready = this._connections.map((c) => c.isConnected);
140
+ return this._connections[MultiSessionConnection._pickConnection(loads, ready)].sendRpc(
141
+ request,
142
+ timeout,
143
+ abortSignal,
144
+ chainId
145
+ );
136
146
  }
137
147
  connect() {
138
148
  for (const conn of this._connections) {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -53,7 +53,8 @@ class DcConnectionManager {
53
53
  // NB: we need a separate salts manager for each pfs connection, because server salts are auth_key-bound
54
54
  salts: managerParams.usePfs ? new serverSalt.ServerSaltManager() : this._salts,
55
55
  platform: managerParams.platform,
56
- pingInterval: managerParams.pingInterval ?? 6e4
56
+ pingInterval: managerParams.pingInterval ?? 6e4,
57
+ floodControl: managerParams.floodControl === false ? void 0 : managerParams.floodControl ?? {}
57
58
  });
58
59
  const mainParams = baseConnectionParams();
59
60
  mainParams.isMainConnection = true;
@@ -226,7 +227,7 @@ class NetworkManager {
226
227
  _: "initConnection",
227
228
  deviceModel,
228
229
  systemVersion: "1.0",
229
- appVersion: "0.29.6",
230
+ appVersion: "0.30.0",
230
231
  systemLangCode: "en",
231
232
  langPack: "",
232
233
  // "langPacks are for official apps only"
@@ -7,6 +7,7 @@ import { mtp, tl } from '../tl/index.js';
7
7
  import { ICorePlatform } from '../types/platform.js';
8
8
  import { DcOptions, ICryptoProvider, Logger } from '../utils/index.js';
9
9
  import { ConfigManager } from './config-manager.js';
10
+ import { ConnectionFloodLimits } from './flood-control.js';
10
11
  import { TelegramTransport } from './transports/abstract.js';
11
12
  import { MultiSessionConnection } from './multi-session-connection.js';
12
13
  export type ConnectionKind = 'main' | 'upload' | 'download' | 'downloadSmall';
@@ -83,6 +84,17 @@ export interface NetworkManagerExtraParams {
83
84
  * @default 60000 (1 minute)
84
85
  */
85
86
  pingInterval?: number;
87
+ /**
88
+ * Per-connection-slot flood-control limits (each socket throttles its own
89
+ * connect attempts independently, like TDLib). Override only if you
90
+ * understand the consequences — defaults are derived from TDLib and
91
+ * should fit most clients.
92
+ *
93
+ * See {@link ConnectionFloodLimits} for the limiter taxonomy.
94
+ *
95
+ * `false` to disable flood control.
96
+ */
97
+ floodControl?: ConnectionFloodLimits | false;
86
98
  }
87
99
  /** Options that can be customized when making an RPC call */
88
100
  export interface RpcCallOptions {
@@ -7,6 +7,7 @@ import { mtp, tl } from '../tl/index.js';
7
7
  import { ICorePlatform } from '../types/platform.js';
8
8
  import { DcOptions, ICryptoProvider, Logger } from '../utils/index.js';
9
9
  import { ConfigManager } from './config-manager.js';
10
+ import { ConnectionFloodLimits } from './flood-control.js';
10
11
  import { TelegramTransport } from './transports/abstract.js';
11
12
  import { MultiSessionConnection } from './multi-session-connection.js';
12
13
  export type ConnectionKind = 'main' | 'upload' | 'download' | 'downloadSmall';
@@ -83,6 +84,17 @@ export interface NetworkManagerExtraParams {
83
84
  * @default 60000 (1 minute)
84
85
  */
85
86
  pingInterval?: number;
87
+ /**
88
+ * Per-connection-slot flood-control limits (each socket throttles its own
89
+ * connect attempts independently, like TDLib). Override only if you
90
+ * understand the consequences — defaults are derived from TDLib and
91
+ * should fit most clients.
92
+ *
93
+ * See {@link ConnectionFloodLimits} for the limiter taxonomy.
94
+ *
95
+ * `false` to disable flood control.
96
+ */
97
+ floodControl?: ConnectionFloodLimits | false;
86
98
  }
87
99
  /** Options that can be customized when making an RPC call */
88
100
  export interface RpcCallOptions {
@@ -46,7 +46,8 @@ class DcConnectionManager {
46
46
  // NB: we need a separate salts manager for each pfs connection, because server salts are auth_key-bound
47
47
  salts: managerParams.usePfs ? new ServerSaltManager() : this._salts,
48
48
  platform: managerParams.platform,
49
- pingInterval: managerParams.pingInterval ?? 6e4
49
+ pingInterval: managerParams.pingInterval ?? 6e4,
50
+ floodControl: managerParams.floodControl === false ? void 0 : managerParams.floodControl ?? {}
50
51
  });
51
52
  const mainParams = baseConnectionParams();
52
53
  mainParams.isMainConnection = true;
@@ -219,7 +220,7 @@ class NetworkManager {
219
220
  _: "initConnection",
220
221
  deviceModel,
221
222
  systemVersion: "1.0",
222
- appVersion: "0.29.6",
223
+ appVersion: "0.30.0",
223
224
  systemLangCode: "en",
224
225
  langPack: "",
225
226
  // "langPacks are for official apps only"