@mtcute/core 0.29.7 → 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 (90) hide show
  1. package/highlevel/client.d.cts +4 -6
  2. package/highlevel/client.d.ts +4 -6
  3. package/highlevel/methods/chats/join-chat.cjs +38 -16
  4. package/highlevel/methods/chats/join-chat.d.cts +20 -6
  5. package/highlevel/methods/chats/join-chat.d.ts +20 -6
  6. package/highlevel/methods/chats/join-chat.js +38 -16
  7. package/highlevel/methods/chats/save-draft.d.cts +4 -1
  8. package/highlevel/methods/chats/save-draft.d.ts +4 -1
  9. package/highlevel/methods.d.cts +2 -0
  10. package/highlevel/methods.d.ts +2 -0
  11. package/highlevel/storage/service/peers.cjs +1 -0
  12. package/highlevel/storage/service/peers.js +1 -0
  13. package/highlevel/types/files/utils.d.cts +1 -1
  14. package/highlevel/types/files/utils.d.ts +1 -1
  15. package/highlevel/types/messages/index.d.cts +1 -0
  16. package/highlevel/types/messages/index.d.ts +1 -0
  17. package/highlevel/types/messages/message.cjs +5 -0
  18. package/highlevel/types/messages/message.d.cts +3 -0
  19. package/highlevel/types/messages/message.d.ts +3 -0
  20. package/highlevel/types/messages/message.js +5 -0
  21. package/highlevel/types/messages/rich-message.cjs +50 -0
  22. package/highlevel/types/messages/rich-message.d.cts +18 -0
  23. package/highlevel/types/messages/rich-message.d.ts +18 -0
  24. package/highlevel/types/messages/rich-message.js +45 -0
  25. package/highlevel/types/peers/full-chat.cjs +6 -0
  26. package/highlevel/types/peers/full-chat.d.cts +2 -0
  27. package/highlevel/types/peers/full-chat.d.ts +2 -0
  28. package/highlevel/types/peers/full-chat.js +6 -0
  29. package/highlevel/types/peers/user.cjs +4 -0
  30. package/highlevel/types/peers/user.d.cts +2 -0
  31. package/highlevel/types/peers/user.d.ts +2 -0
  32. package/highlevel/types/peers/user.js +4 -0
  33. package/highlevel/types/updates/user-typing-update.cjs +2 -0
  34. package/highlevel/types/updates/user-typing-update.js +2 -0
  35. package/highlevel/updates/manager.cjs +191 -0
  36. package/highlevel/updates/manager.js +191 -0
  37. package/highlevel/utils/inspectable.cjs +1 -0
  38. package/highlevel/utils/inspectable.d.cts +1 -0
  39. package/highlevel/utils/inspectable.d.ts +1 -0
  40. package/highlevel/utils/inspectable.js +1 -0
  41. package/index.cjs +2 -0
  42. package/index.js +2 -0
  43. package/network/flood-control.cjs +149 -0
  44. package/network/flood-control.d.cts +79 -0
  45. package/network/flood-control.d.ts +79 -0
  46. package/network/flood-control.js +144 -0
  47. package/network/flood-control.test.d.cts +1 -0
  48. package/network/flood-control.test.d.ts +1 -0
  49. package/network/mtproto-session.cjs +1 -21
  50. package/network/mtproto-session.d.cts +1 -5
  51. package/network/mtproto-session.d.ts +1 -5
  52. package/network/mtproto-session.js +2 -22
  53. package/network/multi-session-connection.cjs +26 -16
  54. package/network/multi-session-connection.d.cts +1 -1
  55. package/network/multi-session-connection.d.ts +1 -1
  56. package/network/multi-session-connection.js +26 -16
  57. package/network/multi-session-connection.test.d.cts +1 -0
  58. package/network/multi-session-connection.test.d.ts +1 -0
  59. package/network/network-manager.cjs +3 -2
  60. package/network/network-manager.d.cts +12 -0
  61. package/network/network-manager.d.ts +12 -0
  62. package/network/network-manager.js +3 -2
  63. package/network/persistent-connection.cjs +25 -1
  64. package/network/persistent-connection.d.cts +5 -0
  65. package/network/persistent-connection.d.ts +5 -0
  66. package/network/persistent-connection.js +25 -1
  67. package/network/session-connection.cjs +5 -10
  68. package/network/session-connection.d.cts +0 -2
  69. package/network/session-connection.d.ts +0 -2
  70. package/network/session-connection.js +5 -10
  71. package/package.json +1 -1
  72. package/tl/api-schema.json +1 -1
  73. package/tl/binary/reader.cjs +176 -26
  74. package/tl/binary/reader.js +176 -26
  75. package/tl/binary/writer.cjs +439 -20
  76. package/tl/binary/writer.js +439 -20
  77. package/tl/compat/reader.cjs +23 -0
  78. package/tl/compat/reader.js +23 -0
  79. package/tl/index.d.cts +414 -14
  80. package/tl/index.d.ts +414 -14
  81. package/tl/inner-tl.cjs +20 -8
  82. package/tl/inner-tl.js +20 -8
  83. package/utils/binary/compat.cjs +79 -0
  84. package/utils/binary/compat.js +79 -0
  85. package/utils/index.d.cts +1 -0
  86. package/utils/index.d.ts +1 -0
  87. package/utils/long-utils.cjs +10 -0
  88. package/utils/long-utils.js +10 -0
  89. package/utils.cjs +7 -0
  90. package/utils.js +8 -1
@@ -0,0 +1,149 @@
1
+ if (typeof globalThis !== "undefined" && !globalThis._MTCUTE_CJS_DEPRECATION_WARNED) {
2
+ globalThis._MTCUTE_CJS_DEPRECATION_WARNED = true;
3
+ console.warn("[@mtcute/core] CommonJS bundles are deprecated. They will be removed completely in one of the upcoming releases. No support is provided for CommonJS users. Please consider switching to ESM, it's " + (/* @__PURE__ */ new Date()).getFullYear() + " already.");
4
+ console.warn("[@mtcute/core] Learn more about switching to ESM: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c");
5
+ }
6
+ "use strict";
7
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
8
+ const utils = require("@fuman/utils");
9
+ class FloodControl {
10
+ _rules;
11
+ _maxWindow;
12
+ _events = [];
13
+ constructor(rules) {
14
+ if (rules.length === 0) throw new Error("FloodControl needs at least one rule");
15
+ this._rules = rules;
16
+ this._maxWindow = rules.reduce((m, r) => Math.max(m, r.windowMs), 0);
17
+ }
18
+ /** Earliest absolute perf-now ms at which a new event is permitted. */
19
+ getWakeupAt(now) {
20
+ let wakeup = 0;
21
+ for (const { count, windowMs } of this._rules) {
22
+ const cutoff = now - windowMs;
23
+ let inWindow = 0;
24
+ let oldestInWindowIdx = -1;
25
+ for (let i = this._events.length - 1; i >= 0; i--) {
26
+ if (this._events[i] <= cutoff) break;
27
+ inWindow++;
28
+ oldestInWindowIdx = i;
29
+ }
30
+ if (inWindow >= count) {
31
+ const t = this._events[oldestInWindowIdx] + windowMs;
32
+ if (t > wakeup) wakeup = t;
33
+ }
34
+ }
35
+ return wakeup;
36
+ }
37
+ addEvent(now) {
38
+ this._events.push(now);
39
+ this._prune(now);
40
+ }
41
+ _prune(now) {
42
+ const cutoff = now - this._maxWindow;
43
+ let i = 0;
44
+ while (i < this._events.length && this._events[i] < cutoff) i++;
45
+ if (i > 0) this._events.splice(0, i);
46
+ }
47
+ reset() {
48
+ this._events = [];
49
+ }
50
+ }
51
+ const DEFAULT_SANITY_LIMITS = [
52
+ { count: 5, windowMs: 1e4 }
53
+ ];
54
+ const DEFAULT_FLOOD_LIMITS = [
55
+ { count: 1, windowMs: 1e3 },
56
+ { count: 4, windowMs: 2e3 },
57
+ { count: 8, windowMs: 3e3 }
58
+ ];
59
+ const DEFAULT_MTPROTO_ERROR_LIMITS = [
60
+ { count: 1, windowMs: 1e3 },
61
+ { count: 4, windowMs: 2e3 },
62
+ { count: 8, windowMs: 3e3 }
63
+ ];
64
+ class ConnectionFloodController {
65
+ sanity;
66
+ flood;
67
+ mtprotoError;
68
+ _log;
69
+ constructor(limits = {}) {
70
+ this.sanity = new FloodControl(limits.sanity ?? DEFAULT_SANITY_LIMITS);
71
+ this.flood = new FloodControl(limits.flood ?? DEFAULT_FLOOD_LIMITS);
72
+ this.mtprotoError = new FloodControl(limits.mtprotoError ?? DEFAULT_MTPROTO_ERROR_LIMITS);
73
+ }
74
+ setLogger(log) {
75
+ this._log = log;
76
+ }
77
+ /**
78
+ * Notify a network-up transition.
79
+ *
80
+ * TDLib clears sanity + online-regime flood + backoff on
81
+ * `ConnectionCreator::on_network(true)`. We don't model the online regime,
82
+ * and our transport layer owns its own reconnect backoff, so only the
83
+ * sanity limiter is reset here.
84
+ */
85
+ notifyNetworkUp() {
86
+ this._log?.debug("network up, resetting sanity limiter");
87
+ this.sanity.reset();
88
+ }
89
+ /** Record an MTProto-level error (transport 429; per TDLib, other codes don't tick this). */
90
+ addMtprotoError(now = performance.now()) {
91
+ this._log?.debug("recording mtproto error");
92
+ this.mtprotoError.addEvent(now);
93
+ }
94
+ /** Earliest perf-now ms at which a new connection attempt is permitted. */
95
+ getWakeupAt(now) {
96
+ return Math.max(
97
+ this.sanity.getWakeupAt(now),
98
+ this.flood.getWakeupAt(now),
99
+ this.mtprotoError.getWakeupAt(now)
100
+ );
101
+ }
102
+ /**
103
+ * Block until a connection attempt is permitted under all applicable
104
+ * limiters, then record the attempt. Concurrent waiters serialize fairly
105
+ * because the first to wake records its event before the next checks state.
106
+ *
107
+ * If `signal` aborts, the pending timer is cleared and the promise rejects
108
+ * with the signal's reason.
109
+ */
110
+ async wait(signal) {
111
+ let waited = false;
112
+ while (true) {
113
+ signal?.throwIfAborted();
114
+ const now = performance.now();
115
+ const wakeup = this.getWakeupAt(now);
116
+ if (wakeup <= now) {
117
+ this.sanity.addEvent(now);
118
+ this.flood.addEvent(now);
119
+ if (waited) this._log?.debug("admitted connect after waiting");
120
+ return;
121
+ }
122
+ const delay = wakeup - now;
123
+ this._log?.debug("connect throttled (waiting %dms)", Math.round(delay));
124
+ waited = true;
125
+ await new Promise((resolve, reject) => {
126
+ const handle = utils.timers.setTimeout(() => {
127
+ signal?.removeEventListener("abort", onAbort);
128
+ resolve();
129
+ }, delay);
130
+ const onAbort = () => {
131
+ utils.timers.clearTimeout(handle);
132
+ reject(signal.reason);
133
+ };
134
+ signal?.addEventListener("abort", onAbort, { once: true });
135
+ });
136
+ }
137
+ }
138
+ /** Reset all internal state. Mainly for tests. */
139
+ reset() {
140
+ this.sanity.reset();
141
+ this.flood.reset();
142
+ this.mtprotoError.reset();
143
+ }
144
+ }
145
+ exports.ConnectionFloodController = ConnectionFloodController;
146
+ exports.DEFAULT_FLOOD_LIMITS = DEFAULT_FLOOD_LIMITS;
147
+ exports.DEFAULT_MTPROTO_ERROR_LIMITS = DEFAULT_MTPROTO_ERROR_LIMITS;
148
+ exports.DEFAULT_SANITY_LIMITS = DEFAULT_SANITY_LIMITS;
149
+ exports.FloodControl = FloodControl;
@@ -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,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) {