@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.
- package/highlevel/client.d.cts +4 -6
- package/highlevel/client.d.ts +4 -6
- 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.d.cts +2 -0
- package/highlevel/methods.d.ts +2 -0
- package/highlevel/storage/service/peers.cjs +1 -0
- package/highlevel/storage/service/peers.js +1 -0
- package/highlevel/types/files/utils.d.cts +1 -1
- package/highlevel/types/files/utils.d.ts +1 -1
- package/highlevel/types/messages/index.d.cts +1 -0
- package/highlevel/types/messages/index.d.ts +1 -0
- package/highlevel/types/messages/message.cjs +5 -0
- package/highlevel/types/messages/message.d.cts +3 -0
- package/highlevel/types/messages/message.d.ts +3 -0
- package/highlevel/types/messages/message.js +5 -0
- 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/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 +4 -0
- package/highlevel/types/peers/user.d.cts +2 -0
- package/highlevel/types/peers/user.d.ts +2 -0
- package/highlevel/types/peers/user.js +4 -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 +191 -0
- package/highlevel/updates/manager.js +191 -0
- 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 +2 -0
- package/index.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 +176 -26
- package/tl/binary/reader.js +176 -26
- package/tl/binary/writer.cjs +439 -20
- package/tl/binary/writer.js +439 -20
- package/tl/compat/reader.cjs +23 -0
- package/tl/compat/reader.js +23 -0
- package/tl/index.d.cts +414 -14
- package/tl/index.d.ts +414 -14
- package/tl/inner-tl.cjs +20 -8
- package/tl/inner-tl.js +20 -8
- package/utils/binary/compat.cjs +79 -0
- package/utils/binary/compat.js +79 -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
|
@@ -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"
|
|
@@ -8,6 +8,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
8
8
|
const io = require("@fuman/io");
|
|
9
9
|
const net = require("@fuman/net");
|
|
10
10
|
const utils = require("@fuman/utils");
|
|
11
|
+
const floodControl = require("./flood-control.cjs");
|
|
11
12
|
let nextConnectionUid = 0;
|
|
12
13
|
class PersistentConnection {
|
|
13
14
|
constructor(params, log) {
|
|
@@ -16,9 +17,20 @@ class PersistentConnection {
|
|
|
16
17
|
this.params.transport.setup?.(this.params.crypto, log);
|
|
17
18
|
this._codec = this.params.transport.packetCodec(params.dc);
|
|
18
19
|
this._codec.setup?.(this.params.crypto, this.log);
|
|
20
|
+
if (params.floodControl) {
|
|
21
|
+
this._floodControl = new floodControl.ConnectionFloodController(params.floodControl);
|
|
22
|
+
this._floodControl.setLogger(log.create("flood"));
|
|
23
|
+
}
|
|
19
24
|
this._onInactivityTimeout = this._onInactivityTimeout.bind(this);
|
|
20
25
|
this._fuman = new net.PersistentConnection({
|
|
21
|
-
connect: (dc) => {
|
|
26
|
+
connect: async (dc) => {
|
|
27
|
+
if (this._floodControl) {
|
|
28
|
+
if (!this._connectAbort || this._connectAbort.signal.aborted) {
|
|
29
|
+
this._connectAbort = new AbortController();
|
|
30
|
+
}
|
|
31
|
+
this._updateLogPrefix();
|
|
32
|
+
await this._floodControl.wait(this._connectAbort.signal);
|
|
33
|
+
}
|
|
22
34
|
this._updateLogPrefix();
|
|
23
35
|
this.log.debug("connecting to %j", dc);
|
|
24
36
|
return params.transport.connect(dc);
|
|
@@ -47,6 +59,14 @@ class PersistentConnection {
|
|
|
47
59
|
_inactive = true;
|
|
48
60
|
_destroyed = false;
|
|
49
61
|
_usable = false;
|
|
62
|
+
// each connection slot throttles its own connect attempts, mirroring
|
|
63
|
+
// TDLib's per-SessionProxy flood control. a fresh controller has empty
|
|
64
|
+
// limiters, so the first connect always passes immediately.
|
|
65
|
+
_floodControl;
|
|
66
|
+
// aborts a pending flood-control wait when the connection is being
|
|
67
|
+
// closed (manually or for good) — otherwise close() would block until
|
|
68
|
+
// the wait expires, and the connect would still proceed afterwards
|
|
69
|
+
_connectAbort;
|
|
50
70
|
onWait = new utils.Emitter();
|
|
51
71
|
onUsable = new utils.Emitter();
|
|
52
72
|
onError = new utils.Emitter();
|
|
@@ -111,6 +131,7 @@ class PersistentConnection {
|
|
|
111
131
|
return "reconnect";
|
|
112
132
|
}
|
|
113
133
|
async changeTransport(transport) {
|
|
134
|
+
this._connectAbort?.abort();
|
|
114
135
|
await this._fuman.close();
|
|
115
136
|
this._codec = transport.packetCodec(this.params.dc);
|
|
116
137
|
this._codec.setup?.(this.params.crypto, this.log);
|
|
@@ -134,6 +155,7 @@ class PersistentConnection {
|
|
|
134
155
|
utils.timers.clearTimeout(this._inactivityTimeout);
|
|
135
156
|
}
|
|
136
157
|
this._disconnectedManually = true;
|
|
158
|
+
this._connectAbort?.abort();
|
|
137
159
|
await this._fuman.close();
|
|
138
160
|
}
|
|
139
161
|
async destroy() {
|
|
@@ -141,6 +163,7 @@ class PersistentConnection {
|
|
|
141
163
|
utils.timers.clearTimeout(this._inactivityTimeout);
|
|
142
164
|
}
|
|
143
165
|
this._destroyed = true;
|
|
166
|
+
this._connectAbort?.abort();
|
|
144
167
|
await this._fuman.close();
|
|
145
168
|
}
|
|
146
169
|
_rescheduleInactivity() {
|
|
@@ -152,6 +175,7 @@ class PersistentConnection {
|
|
|
152
175
|
this.log.info("disconnected because of inactivity for %d", this.params.inactivityTimeout);
|
|
153
176
|
this._inactive = true;
|
|
154
177
|
this._inactivityTimeout = null;
|
|
178
|
+
this._connectAbort?.abort();
|
|
155
179
|
Promise.resolve(this._fuman.close()).catch((err) => {
|
|
156
180
|
this.log.warn("error closing transport: %e", err);
|
|
157
181
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ReconnectionStrategy } from '@fuman/net';
|
|
2
2
|
import { tl } from '../tl/index.js';
|
|
3
3
|
import { BasicDcOption, ICryptoProvider, Logger } from '../utils/index.js';
|
|
4
|
+
import { ConnectionFloodLimits, ConnectionFloodController } from './flood-control.js';
|
|
4
5
|
import { TelegramTransport } from './transports/abstract.js';
|
|
5
6
|
import { Emitter } from '@fuman/utils';
|
|
6
7
|
export interface PersistentConnectionParams {
|
|
@@ -10,6 +11,8 @@ export interface PersistentConnectionParams {
|
|
|
10
11
|
testMode: boolean;
|
|
11
12
|
reconnectionStrategy: ReconnectionStrategy;
|
|
12
13
|
inactivityTimeout?: number;
|
|
14
|
+
/** if present, each connection gates its connects through its own flood controller */
|
|
15
|
+
floodControl?: ConnectionFloodLimits;
|
|
13
16
|
}
|
|
14
17
|
/**
|
|
15
18
|
* Base class for persistent connections.
|
|
@@ -28,6 +31,8 @@ export declare abstract class PersistentConnection {
|
|
|
28
31
|
_inactive: boolean;
|
|
29
32
|
_destroyed: boolean;
|
|
30
33
|
_usable: boolean;
|
|
34
|
+
protected readonly _floodControl?: ConnectionFloodController;
|
|
35
|
+
private _connectAbort?;
|
|
31
36
|
readonly onWait: Emitter<number>;
|
|
32
37
|
readonly onUsable: Emitter<void>;
|
|
33
38
|
readonly onError: Emitter<Error>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ReconnectionStrategy } from '@fuman/net';
|
|
2
2
|
import { tl } from '../tl/index.js';
|
|
3
3
|
import { BasicDcOption, ICryptoProvider, Logger } from '../utils/index.js';
|
|
4
|
+
import { ConnectionFloodLimits, ConnectionFloodController } from './flood-control.js';
|
|
4
5
|
import { TelegramTransport } from './transports/abstract.js';
|
|
5
6
|
import { Emitter } from '@fuman/utils';
|
|
6
7
|
export interface PersistentConnectionParams {
|
|
@@ -10,6 +11,8 @@ export interface PersistentConnectionParams {
|
|
|
10
11
|
testMode: boolean;
|
|
11
12
|
reconnectionStrategy: ReconnectionStrategy;
|
|
12
13
|
inactivityTimeout?: number;
|
|
14
|
+
/** if present, each connection gates its connects through its own flood controller */
|
|
15
|
+
floodControl?: ConnectionFloodLimits;
|
|
13
16
|
}
|
|
14
17
|
/**
|
|
15
18
|
* Base class for persistent connections.
|
|
@@ -28,6 +31,8 @@ export declare abstract class PersistentConnection {
|
|
|
28
31
|
_inactive: boolean;
|
|
29
32
|
_destroyed: boolean;
|
|
30
33
|
_usable: boolean;
|
|
34
|
+
protected readonly _floodControl?: ConnectionFloodController;
|
|
35
|
+
private _connectAbort?;
|
|
31
36
|
readonly onWait: Emitter<number>;
|
|
32
37
|
readonly onUsable: Emitter<void>;
|
|
33
38
|
readonly onError: Emitter<Error>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FramedReader, FramedWriter } from "@fuman/io";
|
|
2
2
|
import { PersistentConnection as PersistentConnection$1, ip, ConnectionClosedError } from "@fuman/net";
|
|
3
3
|
import { Emitter, timers } from "@fuman/utils";
|
|
4
|
+
import { ConnectionFloodController } from "./flood-control.js";
|
|
4
5
|
let nextConnectionUid = 0;
|
|
5
6
|
class PersistentConnection {
|
|
6
7
|
constructor(params, log) {
|
|
@@ -9,9 +10,20 @@ class PersistentConnection {
|
|
|
9
10
|
this.params.transport.setup?.(this.params.crypto, log);
|
|
10
11
|
this._codec = this.params.transport.packetCodec(params.dc);
|
|
11
12
|
this._codec.setup?.(this.params.crypto, this.log);
|
|
13
|
+
if (params.floodControl) {
|
|
14
|
+
this._floodControl = new ConnectionFloodController(params.floodControl);
|
|
15
|
+
this._floodControl.setLogger(log.create("flood"));
|
|
16
|
+
}
|
|
12
17
|
this._onInactivityTimeout = this._onInactivityTimeout.bind(this);
|
|
13
18
|
this._fuman = new PersistentConnection$1({
|
|
14
|
-
connect: (dc) => {
|
|
19
|
+
connect: async (dc) => {
|
|
20
|
+
if (this._floodControl) {
|
|
21
|
+
if (!this._connectAbort || this._connectAbort.signal.aborted) {
|
|
22
|
+
this._connectAbort = new AbortController();
|
|
23
|
+
}
|
|
24
|
+
this._updateLogPrefix();
|
|
25
|
+
await this._floodControl.wait(this._connectAbort.signal);
|
|
26
|
+
}
|
|
15
27
|
this._updateLogPrefix();
|
|
16
28
|
this.log.debug("connecting to %j", dc);
|
|
17
29
|
return params.transport.connect(dc);
|
|
@@ -40,6 +52,14 @@ class PersistentConnection {
|
|
|
40
52
|
_inactive = true;
|
|
41
53
|
_destroyed = false;
|
|
42
54
|
_usable = false;
|
|
55
|
+
// each connection slot throttles its own connect attempts, mirroring
|
|
56
|
+
// TDLib's per-SessionProxy flood control. a fresh controller has empty
|
|
57
|
+
// limiters, so the first connect always passes immediately.
|
|
58
|
+
_floodControl;
|
|
59
|
+
// aborts a pending flood-control wait when the connection is being
|
|
60
|
+
// closed (manually or for good) — otherwise close() would block until
|
|
61
|
+
// the wait expires, and the connect would still proceed afterwards
|
|
62
|
+
_connectAbort;
|
|
43
63
|
onWait = new Emitter();
|
|
44
64
|
onUsable = new Emitter();
|
|
45
65
|
onError = new Emitter();
|
|
@@ -104,6 +124,7 @@ class PersistentConnection {
|
|
|
104
124
|
return "reconnect";
|
|
105
125
|
}
|
|
106
126
|
async changeTransport(transport) {
|
|
127
|
+
this._connectAbort?.abort();
|
|
107
128
|
await this._fuman.close();
|
|
108
129
|
this._codec = transport.packetCodec(this.params.dc);
|
|
109
130
|
this._codec.setup?.(this.params.crypto, this.log);
|
|
@@ -127,6 +148,7 @@ class PersistentConnection {
|
|
|
127
148
|
timers.clearTimeout(this._inactivityTimeout);
|
|
128
149
|
}
|
|
129
150
|
this._disconnectedManually = true;
|
|
151
|
+
this._connectAbort?.abort();
|
|
130
152
|
await this._fuman.close();
|
|
131
153
|
}
|
|
132
154
|
async destroy() {
|
|
@@ -134,6 +156,7 @@ class PersistentConnection {
|
|
|
134
156
|
timers.clearTimeout(this._inactivityTimeout);
|
|
135
157
|
}
|
|
136
158
|
this._destroyed = true;
|
|
159
|
+
this._connectAbort?.abort();
|
|
137
160
|
await this._fuman.close();
|
|
138
161
|
}
|
|
139
162
|
_rescheduleInactivity() {
|
|
@@ -145,6 +168,7 @@ class PersistentConnection {
|
|
|
145
168
|
this.log.info("disconnected because of inactivity for %d", this.params.inactivityTimeout);
|
|
146
169
|
this._inactive = true;
|
|
147
170
|
this._inactivityTimeout = null;
|
|
171
|
+
this._connectAbort?.abort();
|
|
148
172
|
Promise.resolve(this._fuman.close()).catch((err) => {
|
|
149
173
|
this.log.warn("error closing transport: %e", err);
|
|
150
174
|
});
|
|
@@ -48,7 +48,6 @@ class SessionConnection extends persistentConnection.PersistentConnection {
|
|
|
48
48
|
onDisconnect = new utils.Emitter();
|
|
49
49
|
onKeyChange = new utils.Emitter();
|
|
50
50
|
onTmpKeyChange = new utils.Emitter();
|
|
51
|
-
onFloodDone = new utils.Emitter();
|
|
52
51
|
onRequestAuth = new utils.Emitter();
|
|
53
52
|
onAuthBegin = new utils.Emitter();
|
|
54
53
|
onUpdate = new utils.Emitter();
|
|
@@ -120,7 +119,6 @@ class SessionConnection extends persistentConnection.PersistentConnection {
|
|
|
120
119
|
this.onDisconnect.clear();
|
|
121
120
|
this.onKeyChange.clear();
|
|
122
121
|
this.onTmpKeyChange.clear();
|
|
123
|
-
this.onFloodDone.clear();
|
|
124
122
|
this.onRequestAuth.clear();
|
|
125
123
|
this.onAuthBegin.clear();
|
|
126
124
|
this.onUpdate.clear();
|
|
@@ -184,7 +182,7 @@ class SessionConnection extends persistentConnection.PersistentConnection {
|
|
|
184
182
|
this.log.error("transport error %d", error.code);
|
|
185
183
|
this._onAllFailed(`transport error ${error.code}`);
|
|
186
184
|
if (error.code === 429) {
|
|
187
|
-
this.
|
|
185
|
+
this._floodControl?.addMtprotoError();
|
|
188
186
|
return;
|
|
189
187
|
}
|
|
190
188
|
}
|
|
@@ -1056,6 +1054,7 @@ class SessionConnection extends persistentConnection.PersistentConnection {
|
|
|
1056
1054
|
notifyNetworkChanged(online) {
|
|
1057
1055
|
this._online = online;
|
|
1058
1056
|
if (online) {
|
|
1057
|
+
this._floodControl?.notifyNetworkUp();
|
|
1059
1058
|
if (this._inactive) return;
|
|
1060
1059
|
this.reconnect();
|
|
1061
1060
|
} else {
|
|
@@ -1113,9 +1112,6 @@ class SessionConnection extends persistentConnection.PersistentConnection {
|
|
|
1113
1112
|
this._inactivityPendingFlush = true;
|
|
1114
1113
|
this._flush();
|
|
1115
1114
|
}
|
|
1116
|
-
flushWhenIdle() {
|
|
1117
|
-
this._flushTimer.emitWhenIdle();
|
|
1118
|
-
}
|
|
1119
1115
|
_handleGetStateTimeout = (msgId) => {
|
|
1120
1116
|
const pending = this._session.pendingGetStateTimeouts.get(msgId);
|
|
1121
1117
|
if (!pending) {
|
|
@@ -1125,13 +1121,12 @@ class SessionConnection extends persistentConnection.PersistentConnection {
|
|
|
1125
1121
|
this._onMessageFailed(msgId, "timeout");
|
|
1126
1122
|
};
|
|
1127
1123
|
_flush() {
|
|
1128
|
-
if (this._disconnectedManually || !this._session._authKey.ready || this._isPfsBindingPending
|
|
1124
|
+
if (this._disconnectedManually || !this._session._authKey.ready || this._isPfsBindingPending) {
|
|
1129
1125
|
this.log.debug(
|
|
1130
|
-
"skipping flush, connection is not usable (offline = %b, auth key ready = %b, pfs binding pending = %b
|
|
1126
|
+
"skipping flush, connection is not usable (offline = %b, auth key ready = %b, pfs binding pending = %b)",
|
|
1131
1127
|
this._disconnectedManually,
|
|
1132
1128
|
this._session._authKey.ready,
|
|
1133
|
-
this._isPfsBindingPending
|
|
1134
|
-
this._session.current429Timeout
|
|
1129
|
+
this._isPfsBindingPending
|
|
1135
1130
|
);
|
|
1136
1131
|
return;
|
|
1137
1132
|
}
|
|
@@ -43,7 +43,6 @@ export declare class SessionConnection extends PersistentConnection {
|
|
|
43
43
|
readonly onDisconnect: Emitter<void>;
|
|
44
44
|
readonly onKeyChange: Emitter<Uint8Array | null>;
|
|
45
45
|
readonly onTmpKeyChange: Emitter<[Uint8Array, number] | null>;
|
|
46
|
-
readonly onFloodDone: Emitter<void>;
|
|
47
46
|
readonly onRequestAuth: Emitter<void>;
|
|
48
47
|
readonly onAuthBegin: Emitter<void>;
|
|
49
48
|
readonly onUpdate: Emitter<tl.TypeUpdates>;
|
|
@@ -87,7 +86,6 @@ export declare class SessionConnection extends PersistentConnection {
|
|
|
87
86
|
notifyNetworkChanged(online: boolean): void;
|
|
88
87
|
private _cancelRpc;
|
|
89
88
|
protected _onInactivityTimeout(): void;
|
|
90
|
-
flushWhenIdle(): void;
|
|
91
89
|
private _handleGetStateTimeout;
|
|
92
90
|
private _flush;
|
|
93
91
|
private _doFlush;
|
|
@@ -43,7 +43,6 @@ export declare class SessionConnection extends PersistentConnection {
|
|
|
43
43
|
readonly onDisconnect: Emitter<void>;
|
|
44
44
|
readonly onKeyChange: Emitter<Uint8Array | null>;
|
|
45
45
|
readonly onTmpKeyChange: Emitter<[Uint8Array, number] | null>;
|
|
46
|
-
readonly onFloodDone: Emitter<void>;
|
|
47
46
|
readonly onRequestAuth: Emitter<void>;
|
|
48
47
|
readonly onAuthBegin: Emitter<void>;
|
|
49
48
|
readonly onUpdate: Emitter<tl.TypeUpdates>;
|
|
@@ -87,7 +86,6 @@ export declare class SessionConnection extends PersistentConnection {
|
|
|
87
86
|
notifyNetworkChanged(online: boolean): void;
|
|
88
87
|
private _cancelRpc;
|
|
89
88
|
protected _onInactivityTimeout(): void;
|
|
90
|
-
flushWhenIdle(): void;
|
|
91
89
|
private _handleGetStateTimeout;
|
|
92
90
|
private _flush;
|
|
93
91
|
private _doFlush;
|
|
@@ -41,7 +41,6 @@ class SessionConnection extends PersistentConnection {
|
|
|
41
41
|
onDisconnect = new Emitter();
|
|
42
42
|
onKeyChange = new Emitter();
|
|
43
43
|
onTmpKeyChange = new Emitter();
|
|
44
|
-
onFloodDone = new Emitter();
|
|
45
44
|
onRequestAuth = new Emitter();
|
|
46
45
|
onAuthBegin = new Emitter();
|
|
47
46
|
onUpdate = new Emitter();
|
|
@@ -113,7 +112,6 @@ class SessionConnection extends PersistentConnection {
|
|
|
113
112
|
this.onDisconnect.clear();
|
|
114
113
|
this.onKeyChange.clear();
|
|
115
114
|
this.onTmpKeyChange.clear();
|
|
116
|
-
this.onFloodDone.clear();
|
|
117
115
|
this.onRequestAuth.clear();
|
|
118
116
|
this.onAuthBegin.clear();
|
|
119
117
|
this.onUpdate.clear();
|
|
@@ -177,7 +175,7 @@ class SessionConnection extends PersistentConnection {
|
|
|
177
175
|
this.log.error("transport error %d", error.code);
|
|
178
176
|
this._onAllFailed(`transport error ${error.code}`);
|
|
179
177
|
if (error.code === 429) {
|
|
180
|
-
this.
|
|
178
|
+
this._floodControl?.addMtprotoError();
|
|
181
179
|
return;
|
|
182
180
|
}
|
|
183
181
|
}
|
|
@@ -1049,6 +1047,7 @@ class SessionConnection extends PersistentConnection {
|
|
|
1049
1047
|
notifyNetworkChanged(online) {
|
|
1050
1048
|
this._online = online;
|
|
1051
1049
|
if (online) {
|
|
1050
|
+
this._floodControl?.notifyNetworkUp();
|
|
1052
1051
|
if (this._inactive) return;
|
|
1053
1052
|
this.reconnect();
|
|
1054
1053
|
} else {
|
|
@@ -1106,9 +1105,6 @@ class SessionConnection extends PersistentConnection {
|
|
|
1106
1105
|
this._inactivityPendingFlush = true;
|
|
1107
1106
|
this._flush();
|
|
1108
1107
|
}
|
|
1109
|
-
flushWhenIdle() {
|
|
1110
|
-
this._flushTimer.emitWhenIdle();
|
|
1111
|
-
}
|
|
1112
1108
|
_handleGetStateTimeout = (msgId) => {
|
|
1113
1109
|
const pending = this._session.pendingGetStateTimeouts.get(msgId);
|
|
1114
1110
|
if (!pending) {
|
|
@@ -1118,13 +1114,12 @@ class SessionConnection extends PersistentConnection {
|
|
|
1118
1114
|
this._onMessageFailed(msgId, "timeout");
|
|
1119
1115
|
};
|
|
1120
1116
|
_flush() {
|
|
1121
|
-
if (this._disconnectedManually || !this._session._authKey.ready || this._isPfsBindingPending
|
|
1117
|
+
if (this._disconnectedManually || !this._session._authKey.ready || this._isPfsBindingPending) {
|
|
1122
1118
|
this.log.debug(
|
|
1123
|
-
"skipping flush, connection is not usable (offline = %b, auth key ready = %b, pfs binding pending = %b
|
|
1119
|
+
"skipping flush, connection is not usable (offline = %b, auth key ready = %b, pfs binding pending = %b)",
|
|
1124
1120
|
this._disconnectedManually,
|
|
1125
1121
|
this._session._authKey.ready,
|
|
1126
|
-
this._isPfsBindingPending
|
|
1127
|
-
this._session.current429Timeout
|
|
1122
|
+
this._isPfsBindingPending
|
|
1128
1123
|
);
|
|
1129
1124
|
return;
|
|
1130
1125
|
}
|