@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.
- package/client.cjs +8 -0
- package/client.js +8 -0
- package/highlevel/client.d.cts +23 -7
- package/highlevel/client.d.ts +23 -7
- package/highlevel/methods/bots/answer-bot-guest-chat-query.cjs +26 -0
- package/highlevel/methods/bots/answer-bot-guest-chat-query.d.cts +12 -0
- package/highlevel/methods/bots/answer-bot-guest-chat-query.d.ts +12 -0
- package/highlevel/methods/bots/answer-bot-guest-chat-query.js +21 -0
- package/highlevel/methods/chats/join-chat.cjs +38 -16
- package/highlevel/methods/chats/join-chat.d.cts +20 -6
- package/highlevel/methods/chats/join-chat.d.ts +20 -6
- package/highlevel/methods/chats/join-chat.js +38 -16
- package/highlevel/methods/chats/save-draft.d.cts +4 -1
- package/highlevel/methods/chats/save-draft.d.ts +4 -1
- package/highlevel/methods/messages/send-copy-group.cjs +5 -9
- package/highlevel/methods/messages/send-copy-group.js +5 -9
- package/highlevel/methods.d.cts +3 -0
- package/highlevel/methods.d.ts +3 -0
- package/highlevel/storage/service/peers.cjs +2 -0
- package/highlevel/storage/service/peers.js +2 -0
- package/highlevel/types/files/utils.d.cts +1 -1
- package/highlevel/types/files/utils.d.ts +1 -1
- package/highlevel/types/media/poll.cjs +14 -0
- package/highlevel/types/media/poll.d.cts +6 -0
- package/highlevel/types/media/poll.d.ts +6 -0
- package/highlevel/types/media/poll.js +14 -0
- package/highlevel/types/messages/index.d.cts +1 -0
- package/highlevel/types/messages/index.d.ts +1 -0
- package/highlevel/types/messages/message.cjs +15 -1
- package/highlevel/types/messages/message.d.cts +8 -0
- package/highlevel/types/messages/message.d.ts +8 -0
- package/highlevel/types/messages/message.js +15 -1
- package/highlevel/types/messages/rich-message.cjs +50 -0
- package/highlevel/types/messages/rich-message.d.cts +18 -0
- package/highlevel/types/messages/rich-message.d.ts +18 -0
- package/highlevel/types/messages/rich-message.js +45 -0
- package/highlevel/types/peers/chat-permissions.cjs +6 -0
- package/highlevel/types/peers/chat-permissions.d.cts +4 -0
- package/highlevel/types/peers/chat-permissions.d.ts +4 -0
- package/highlevel/types/peers/chat-permissions.js +6 -0
- package/highlevel/types/peers/full-chat.cjs +6 -0
- package/highlevel/types/peers/full-chat.d.cts +2 -0
- package/highlevel/types/peers/full-chat.d.ts +2 -0
- package/highlevel/types/peers/full-chat.js +6 -0
- package/highlevel/types/peers/user.cjs +8 -0
- package/highlevel/types/peers/user.d.cts +4 -0
- package/highlevel/types/peers/user.d.ts +4 -0
- package/highlevel/types/peers/user.js +8 -0
- package/highlevel/types/updates/bot-guest-chat-query.cjs +39 -0
- package/highlevel/types/updates/bot-guest-chat-query.d.cts +21 -0
- package/highlevel/types/updates/bot-guest-chat-query.d.ts +21 -0
- package/highlevel/types/updates/bot-guest-chat-query.js +34 -0
- package/highlevel/types/updates/index.d.cts +5 -1
- package/highlevel/types/updates/index.d.ts +5 -1
- package/highlevel/types/updates/parse-update.cjs +3 -0
- package/highlevel/types/updates/parse-update.js +3 -0
- package/highlevel/types/updates/user-typing-update.cjs +2 -0
- package/highlevel/types/updates/user-typing-update.js +2 -0
- package/highlevel/updates/manager.cjs +214 -3
- package/highlevel/updates/manager.d.cts +1 -0
- package/highlevel/updates/manager.d.ts +1 -0
- package/highlevel/updates/manager.js +214 -3
- package/highlevel/utils/inspectable.cjs +1 -0
- package/highlevel/utils/inspectable.d.cts +1 -0
- package/highlevel/utils/inspectable.d.ts +1 -0
- package/highlevel/utils/inspectable.js +1 -0
- package/index.cjs +4 -0
- package/index.js +4 -0
- package/methods.cjs +2 -0
- package/methods.js +2 -0
- package/network/flood-control.cjs +149 -0
- package/network/flood-control.d.cts +79 -0
- package/network/flood-control.d.ts +79 -0
- package/network/flood-control.js +144 -0
- package/network/flood-control.test.d.cts +1 -0
- package/network/flood-control.test.d.ts +1 -0
- package/network/mtproto-session.cjs +1 -21
- package/network/mtproto-session.d.cts +1 -5
- package/network/mtproto-session.d.ts +1 -5
- package/network/mtproto-session.js +2 -22
- package/network/multi-session-connection.cjs +26 -16
- package/network/multi-session-connection.d.cts +1 -1
- package/network/multi-session-connection.d.ts +1 -1
- package/network/multi-session-connection.js +26 -16
- package/network/multi-session-connection.test.d.cts +1 -0
- package/network/multi-session-connection.test.d.ts +1 -0
- package/network/network-manager.cjs +3 -2
- package/network/network-manager.d.cts +12 -0
- package/network/network-manager.d.ts +12 -0
- package/network/network-manager.js +3 -2
- package/network/persistent-connection.cjs +25 -1
- package/network/persistent-connection.d.cts +5 -0
- package/network/persistent-connection.d.ts +5 -0
- package/network/persistent-connection.js +25 -1
- package/network/session-connection.cjs +5 -10
- package/network/session-connection.d.cts +0 -2
- package/network/session-connection.d.ts +0 -2
- package/network/session-connection.js +5 -10
- package/package.json +1 -1
- package/tl/api-schema.json +1 -1
- package/tl/binary/reader.cjs +257 -62
- package/tl/binary/reader.js +257 -62
- package/tl/binary/writer.cjs +837 -212
- package/tl/binary/writer.js +837 -212
- package/tl/compat/reader.cjs +31 -0
- package/tl/compat/reader.js +31 -0
- package/tl/index.d.cts +2478 -186
- package/tl/index.d.ts +2478 -186
- package/tl/inner-tl.cjs +34 -10
- package/tl/inner-tl.js +34 -10
- package/utils/binary/compat.cjs +84 -0
- package/utils/binary/compat.js +84 -0
- package/utils/index.d.cts +1 -0
- package/utils/index.d.ts +1 -0
- package/utils/long-utils.cjs +10 -0
- package/utils/long-utils.js +10 -0
- package/utils.cjs +7 -0
- 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
|
|
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
|
|
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
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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.
|
|
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.
|
|
223
|
+
appVersion: "0.30.0",
|
|
223
224
|
systemLangCode: "en",
|
|
224
225
|
langPack: "",
|
|
225
226
|
// "langPacks are for official apps only"
|