@mtkruto/browser 0.119.0 → 0.120.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/README.md +1 -1
- package/esm/0_errors.d.ts.map +1 -1
- package/esm/0_errors.js +9 -31
- package/esm/3_errors.js +2 -12
- package/esm/4_errors.js +2 -12
- package/esm/_dnt.polyfills.d.ts +0 -99
- package/esm/_dnt.polyfills.d.ts.map +1 -1
- package/esm/_dnt.polyfills.js +1 -127
- package/esm/client/0_abortable_loop.js +26 -39
- package/esm/client/0_storage_operations.js +179 -218
- package/esm/client/1_client_plain.js +4 -22
- package/esm/client/2_account_manager.js +140 -149
- package/esm/client/2_bot_info_manager.js +26 -38
- package/esm/client/2_business_connection_manager.js +10 -23
- package/esm/client/2_client_encrypted.js +198 -215
- package/esm/client/2_file_manager.js +255 -262
- package/esm/client/2_network_statistics_manager.js +31 -44
- package/esm/client/2_payment_manager.js +7 -20
- package/esm/client/2_reaction_manager.js +7 -20
- package/esm/client/2_translations_manager.js +101 -111
- package/esm/client/2_update_manager.js +750 -745
- package/esm/client/3_client_encrypted_pool.js +10 -26
- package/esm/client/3_message_manager.js +503 -508
- package/esm/client/3_video_chat_manager.js +57 -68
- package/esm/client/4_callback_query_manager.js +18 -30
- package/esm/client/4_chat_list_manager.js +140 -146
- package/esm/client/4_chat_manager.js +161 -169
- package/esm/client/4_checklist_manager.js +26 -39
- package/esm/client/4_context.js +244 -259
- package/esm/client/4_forum_manager.js +67 -73
- package/esm/client/4_gift_manager.js +22 -35
- package/esm/client/4_inline_query_manager.js +16 -28
- package/esm/client/4_link_preview_manager.js +6 -19
- package/esm/client/4_poll_manager.js +44 -57
- package/esm/client/4_story_manager.js +41 -53
- package/esm/client/5_composer.js +13 -26
- package/esm/client/6_client.js +866 -896
- package/esm/client/6_client_dispatcher.js +308 -325
- package/esm/client/7_client_worker.js +16 -29
- package/esm/connection/1_connection_tcp.js +55 -82
- package/esm/connection/1_connection_web_socket.js +75 -91
- package/esm/deps/jsr.io/@roj/tgcrypto/1.0.1/dist/tgcrypto.js +3 -11
- package/esm/deps/jsr.io/@std/async/1.2.0/mux_async_iterator.js +31 -47
- package/esm/deps/jsr.io/@std/async/1.2.0/tee.js +11 -34
- package/esm/deps/jsr.io/@std/cache/0.2.2/lru_cache.js +30 -47
- package/esm/deps/jsr.io/@std/datetime/0.225.7/_date_time_formatter.js +4 -17
- package/esm/session/0_session_state.js +12 -38
- package/esm/session/1_session.js +49 -72
- package/esm/session/2_session_encrypted.js +422 -420
- package/esm/storage/2_storage_indexed_db.js +26 -44
- package/esm/storage/2_storage_local_storage.js +3 -16
- package/esm/storage/2_storage_memory.js +24 -41
- package/esm/storage/2_storage_session_storage.js +3 -16
- package/esm/tl/1_tl_reader.d.ts +1 -1
- package/esm/tl/1_tl_reader.d.ts.map +1 -1
- package/esm/tl/1_tl_reader.js +95 -103
- package/esm/tl/1_tl_writer.js +169 -178
- package/esm/transport/0_transport.js +1 -8
- package/esm/transport/1_transport_abridged.js +11 -24
- package/esm/transport/1_transport_intermediate.js +10 -23
- package/esm/utilities/0_mutex.js +4 -19
- package/esm/utilities/0_part_stream.js +11 -25
- package/esm/utilities/1_crypto.js +42 -53
- package/esm/utilities/2_queue.js +29 -47
- package/package.json +1 -1
- package/script/0_errors.d.ts.map +1 -1
- package/script/0_errors.js +9 -31
- package/script/3_errors.js +2 -12
- package/script/4_errors.js +2 -12
- package/script/_dnt.polyfills.d.ts +0 -99
- package/script/_dnt.polyfills.d.ts.map +1 -1
- package/script/_dnt.polyfills.js +0 -128
- package/script/client/0_abortable_loop.js +27 -40
- package/script/client/0_storage_operations.js +179 -218
- package/script/client/1_client_plain.js +4 -22
- package/script/client/2_account_manager.js +140 -149
- package/script/client/2_bot_info_manager.js +26 -38
- package/script/client/2_business_connection_manager.js +10 -23
- package/script/client/2_client_encrypted.js +199 -216
- package/script/client/2_file_manager.js +255 -262
- package/script/client/2_network_statistics_manager.js +32 -45
- package/script/client/2_payment_manager.js +7 -20
- package/script/client/2_reaction_manager.js +7 -20
- package/script/client/2_translations_manager.js +102 -112
- package/script/client/2_update_manager.js +750 -745
- package/script/client/3_client_encrypted_pool.js +10 -26
- package/script/client/3_message_manager.js +503 -508
- package/script/client/3_video_chat_manager.js +57 -68
- package/script/client/4_callback_query_manager.js +18 -30
- package/script/client/4_chat_list_manager.js +140 -146
- package/script/client/4_chat_manager.js +161 -169
- package/script/client/4_checklist_manager.js +26 -39
- package/script/client/4_context.js +244 -259
- package/script/client/4_forum_manager.js +67 -73
- package/script/client/4_gift_manager.js +22 -35
- package/script/client/4_inline_query_manager.js +16 -28
- package/script/client/4_link_preview_manager.js +6 -19
- package/script/client/4_poll_manager.js +44 -57
- package/script/client/4_story_manager.js +41 -53
- package/script/client/5_composer.js +13 -26
- package/script/client/6_client.js +866 -896
- package/script/client/6_client_dispatcher.js +308 -325
- package/script/client/7_client_worker.js +16 -29
- package/script/connection/1_connection_tcp.js +55 -82
- package/script/connection/1_connection_web_socket.js +75 -91
- package/script/deps/jsr.io/@roj/tgcrypto/1.0.1/dist/tgcrypto.js +3 -11
- package/script/deps/jsr.io/@std/async/1.2.0/mux_async_iterator.js +31 -47
- package/script/deps/jsr.io/@std/async/1.2.0/tee.js +11 -34
- package/script/deps/jsr.io/@std/cache/0.2.2/lru_cache.js +30 -47
- package/script/deps/jsr.io/@std/datetime/0.225.7/_date_time_formatter.js +4 -17
- package/script/session/0_session_state.js +12 -38
- package/script/session/1_session.js +49 -72
- package/script/session/2_session_encrypted.js +423 -421
- package/script/storage/2_storage_indexed_db.js +26 -44
- package/script/storage/2_storage_local_storage.js +3 -16
- package/script/storage/2_storage_memory.js +24 -41
- package/script/storage/2_storage_session_storage.js +3 -16
- package/script/tl/1_tl_reader.d.ts +1 -1
- package/script/tl/1_tl_reader.d.ts.map +1 -1
- package/script/tl/1_tl_reader.js +96 -104
- package/script/tl/1_tl_writer.js +170 -179
- package/script/transport/0_transport.js +1 -8
- package/script/transport/1_transport_abridged.js +11 -24
- package/script/transport/1_transport_intermediate.js +10 -23
- package/script/utilities/0_mutex.js +4 -19
- package/script/utilities/0_part_stream.js +11 -25
- package/script/utilities/1_crypto.js +43 -54
- package/script/utilities/2_queue.js +30 -48
|
@@ -17,18 +17,6 @@
|
|
|
17
17
|
* You should have received a copy of the GNU Lesser General Public License
|
|
18
18
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
19
19
|
*/
|
|
20
|
-
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
21
|
-
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
22
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
23
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
24
|
-
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
25
|
-
};
|
|
26
|
-
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
27
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
28
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
29
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
30
|
-
};
|
|
31
|
-
var _SessionEncrypted_instances, _a, _SessionEncrypted_TGCRYPTO_INITED, _SessionEncrypted_id, _SessionEncrypted_L, _SessionEncrypted_LsendLoop, _SessionEncrypted_LreceiveLoop, _SessionEncrypted_LpingLoop, _SessionEncrypted_authKey, _SessionEncrypted_authKeyId, _SessionEncrypted_sentMessages, _SessionEncrypted_pendingMessages, _SessionEncrypted_containers, _SessionEncrypted_pendingPings, _SessionEncrypted_toAcknowledge, _SessionEncrypted_pendingAcks, _SessionEncrypted_assertNotDisconnected, _SessionEncrypted_invalidateSession, _SessionEncrypted_rejectAllPending, _SessionEncrypted_onMessageFailed, _SessionEncrypted_setServerSalt, _SessionEncrypted_receive, _SessionEncrypted_encryptMessage, _SessionEncrypted_decryptMessage, _SessionEncrypted_awakeSendLoop, _SessionEncrypted_sendLoop, _SessionEncrypted_sendLoopBody, _SessionEncrypted_receiveLoop, _SessionEncrypted_receiveLoopBody, _SessionEncrypted_onMessage, _SessionEncrypted_onRpcResult, _SessionEncrypted_onMsgDetailedInfo, _SessionEncrypted_onMsgNewDetailedInfo, _SessionEncrypted_onBadMsgNotification, _SessionEncrypted_onBadServerSalt, _SessionEncrypted_onPong, _SessionEncrypted_onNewSessionCreated, _SessionEncrypted_onMessageContainer, _SessionEncrypted_pingInterval, _SessionEncrypted_pingLoop, _SessionEncrypted_timeElapsed, _SessionEncrypted_pingLoopBody, _SessionEncrypted_sendPingDelayDisconnect, _SessionEncrypted_resendPendingPing;
|
|
32
20
|
import { assertEquals, concat, delay, ige256Decrypt, ige256Encrypt, initTgCrypto, LruCache, SECOND } from "../0_deps.js";
|
|
33
21
|
import { ConnectionError, TransportError } from "../0_errors.js";
|
|
34
22
|
import { drop, getLogger, getRandomId, gunzip, intFromBytes, intToBytes, mod, sha1, sha256, toUnixTimestamp } from "../1_utilities.js";
|
|
@@ -43,465 +31,479 @@ const GZIP_PACKED = 0x3072CFA1;
|
|
|
43
31
|
const RPC_RESULT = 0xF35C6D01;
|
|
44
32
|
const RPC_ERROR = Mtproto.schema.definitions["rpc_error"][0];
|
|
45
33
|
export class SessionEncrypted extends Session {
|
|
34
|
+
static #TGCRYPTO_INITED = false;
|
|
35
|
+
#id = getRandomId();
|
|
36
|
+
handlers = {};
|
|
37
|
+
#L;
|
|
38
|
+
#LsendLoop;
|
|
39
|
+
#LreceiveLoop;
|
|
40
|
+
#LpingLoop;
|
|
41
|
+
#authKey = new Uint8Array();
|
|
42
|
+
#authKeyId = 0n;
|
|
43
|
+
#sentMessages = new Set();
|
|
44
|
+
#pendingMessages = new Array();
|
|
45
|
+
#containers = new LruCache(20_000);
|
|
46
|
+
#pendingPings = new Map();
|
|
47
|
+
#toAcknowledge = new Array();
|
|
48
|
+
#pendingAcks = new LruCache(100);
|
|
46
49
|
constructor(dc, params) {
|
|
47
50
|
super(dc, params);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
configurable: true,
|
|
53
|
-
writable: true,
|
|
54
|
-
value: {}
|
|
55
|
-
});
|
|
56
|
-
_SessionEncrypted_L.set(this, void 0);
|
|
57
|
-
_SessionEncrypted_LsendLoop.set(this, void 0);
|
|
58
|
-
_SessionEncrypted_LreceiveLoop.set(this, void 0);
|
|
59
|
-
_SessionEncrypted_LpingLoop.set(this, void 0);
|
|
60
|
-
_SessionEncrypted_authKey.set(this, new Uint8Array());
|
|
61
|
-
_SessionEncrypted_authKeyId.set(this, 0n);
|
|
62
|
-
_SessionEncrypted_sentMessages.set(this, new Set());
|
|
63
|
-
_SessionEncrypted_pendingMessages.set(this, new Array());
|
|
64
|
-
_SessionEncrypted_containers.set(this, new LruCache(20_000));
|
|
65
|
-
_SessionEncrypted_pendingPings.set(this, new Map());
|
|
66
|
-
_SessionEncrypted_toAcknowledge.set(this, new Array());
|
|
67
|
-
_SessionEncrypted_pendingAcks.set(this, new LruCache(100));
|
|
68
|
-
//// SEND LOOP ////
|
|
69
|
-
_SessionEncrypted_awakeSendLoop.set(this, void 0);
|
|
70
|
-
_SessionEncrypted_sendLoop.set(this, new AbortableLoop(__classPrivateFieldGet(this, _SessionEncrypted_instances, "m", _SessionEncrypted_sendLoopBody).bind(this), (err) => {
|
|
71
|
-
__classPrivateFieldGet(this, _SessionEncrypted_LsendLoop, "f").error("unhandled receive loop error:", err);
|
|
72
|
-
}));
|
|
73
|
-
//// RECEIVE LOOP ////
|
|
74
|
-
_SessionEncrypted_receiveLoop.set(this, new AbortableLoop(__classPrivateFieldGet(this, _SessionEncrypted_instances, "m", _SessionEncrypted_receiveLoopBody).bind(this), (err) => {
|
|
75
|
-
__classPrivateFieldGet(this, _SessionEncrypted_LreceiveLoop, "f").error("unhandled receive loop error:", err);
|
|
76
|
-
}));
|
|
77
|
-
//// PING LOOP ////
|
|
78
|
-
_SessionEncrypted_pingInterval.set(this, 56 * SECOND);
|
|
79
|
-
_SessionEncrypted_pingLoop.set(this, new AbortableLoop(__classPrivateFieldGet(this, _SessionEncrypted_instances, "m", _SessionEncrypted_pingLoopBody).bind(this), (_, err) => {
|
|
80
|
-
__classPrivateFieldGet(this, _SessionEncrypted_LpingLoop, "f").error(err);
|
|
81
|
-
}));
|
|
82
|
-
_SessionEncrypted_timeElapsed.set(this, 0);
|
|
83
|
-
const L = __classPrivateFieldSet(this, _SessionEncrypted_L, getLogger("SessionEncrypted").client(id++), "f");
|
|
84
|
-
__classPrivateFieldSet(this, _SessionEncrypted_LsendLoop, L.branch("sendLoop"), "f");
|
|
85
|
-
__classPrivateFieldSet(this, _SessionEncrypted_LreceiveLoop, L.branch("receiveLoop"), "f");
|
|
86
|
-
__classPrivateFieldSet(this, _SessionEncrypted_LpingLoop, L.branch("pingLoop"), "f");
|
|
51
|
+
const L = this.#L = getLogger("SessionEncrypted").client(id++);
|
|
52
|
+
this.#LsendLoop = L.branch("sendLoop");
|
|
53
|
+
this.#LreceiveLoop = L.branch("receiveLoop");
|
|
54
|
+
this.#LpingLoop = L.branch("pingLoop");
|
|
87
55
|
}
|
|
88
56
|
async setAuthKey(key) {
|
|
89
57
|
const hash = await sha1(key);
|
|
90
|
-
|
|
91
|
-
|
|
58
|
+
this.#authKeyId = intFromBytes(hash.slice(-8));
|
|
59
|
+
this.#authKey = key;
|
|
92
60
|
}
|
|
93
61
|
get authKey() {
|
|
94
|
-
return
|
|
62
|
+
return this.#authKey;
|
|
95
63
|
}
|
|
96
64
|
async connect() {
|
|
97
65
|
if (!this.isConnected) {
|
|
98
|
-
|
|
66
|
+
this.#rejectAllPending(new ConnectionError("The connection was closed."));
|
|
99
67
|
}
|
|
100
68
|
await super.connect();
|
|
101
|
-
if (!
|
|
69
|
+
if (!SessionEncrypted.#TGCRYPTO_INITED) {
|
|
102
70
|
await initTgCrypto();
|
|
103
|
-
|
|
71
|
+
SessionEncrypted.#TGCRYPTO_INITED = true;
|
|
104
72
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
73
|
+
this.#receiveLoop.start();
|
|
74
|
+
this.#sendLoop.start();
|
|
75
|
+
this.#pingLoop.start();
|
|
76
|
+
this.#awakeSendLoop?.();
|
|
109
77
|
}
|
|
110
78
|
disconnect() {
|
|
111
79
|
super.disconnect();
|
|
112
80
|
this.state.reset();
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
81
|
+
this.#id = getRandomId();
|
|
82
|
+
this.#pingLoop.abort();
|
|
83
|
+
this.#awakeSendLoop?.();
|
|
84
|
+
this.#rejectAllPending(new ConnectionError("The connection was disconnected."));
|
|
85
|
+
}
|
|
86
|
+
#assertNotDisconnected() {
|
|
87
|
+
if (this.isDisconnected) {
|
|
88
|
+
throw new ConnectionError("The connection was disconnected.");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async #invalidateSession(reason) {
|
|
92
|
+
this.#L.debug("invalidating session because of", reason);
|
|
93
|
+
this.#id = getRandomId();
|
|
94
|
+
this.state.reset();
|
|
95
|
+
this.disconnect();
|
|
96
|
+
await this.connect();
|
|
97
|
+
this.#rejectAllPending(new SessionError("The session was invalidated."));
|
|
98
|
+
}
|
|
99
|
+
#rejectAllPending(reason) {
|
|
100
|
+
for (const id of this.#sentMessages) {
|
|
101
|
+
this.#onMessageFailed(id, reason);
|
|
102
|
+
}
|
|
103
|
+
for (const pendingPing of this.#pendingPings.values()) {
|
|
104
|
+
pendingPing.promiseWithResolvers.reject(reason);
|
|
105
|
+
}
|
|
106
|
+
this.#sentMessages.clear();
|
|
107
|
+
this.#pendingPings.clear();
|
|
108
|
+
this.#containers.clear();
|
|
109
|
+
}
|
|
110
|
+
#onMessageFailed(id, reason) {
|
|
111
|
+
this.#sentMessages.delete(id);
|
|
112
|
+
const pendingContainer = this.#containers.get(id);
|
|
113
|
+
if (pendingContainer) {
|
|
114
|
+
for (const id of pendingContainer) {
|
|
115
|
+
this.#onMessageFailed(id, reason);
|
|
116
|
+
}
|
|
117
|
+
this.#containers.delete(id);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const pendingAck = this.#pendingAcks.get(id);
|
|
121
|
+
if (pendingAck) {
|
|
122
|
+
for (const id of pendingAck) {
|
|
123
|
+
this.#toAcknowledge.push(id);
|
|
124
|
+
}
|
|
125
|
+
this.#pendingAcks.delete(id);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const pendingPing = this.#pendingPings.get(id);
|
|
129
|
+
if (pendingPing) {
|
|
130
|
+
this.#pendingPings.delete(id);
|
|
131
|
+
if (reason instanceof SessionError) {
|
|
132
|
+
drop(this.#resendPendingPing(pendingPing));
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
pendingPing.promiseWithResolvers.reject(reason);
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// message was not sent by us
|
|
140
|
+
this.handlers.onMessageFailed?.(id, reason);
|
|
141
|
+
}
|
|
142
|
+
#setServerSalt(newServerSalt) {
|
|
143
|
+
this.state.serverSalt = newServerSalt;
|
|
144
|
+
this.handlers.onNewServerSalt?.(newServerSalt);
|
|
117
145
|
}
|
|
118
146
|
async send(body) {
|
|
119
147
|
if (!this.isDisconnected && !this.isConnected) {
|
|
120
148
|
await super.waitUntilConnected();
|
|
121
149
|
}
|
|
122
|
-
|
|
150
|
+
this.#assertNotDisconnected();
|
|
123
151
|
const pendingMessage = { body, promiseWithResolvers: Promise.withResolvers() };
|
|
124
|
-
|
|
125
|
-
|
|
152
|
+
this.#pendingMessages.push(pendingMessage);
|
|
153
|
+
this.#awakeSendLoop?.();
|
|
126
154
|
return await pendingMessage.promiseWithResolvers.promise;
|
|
127
155
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
__classPrivateFieldGet(this, _SessionEncrypted_L, "f").debug("invalidating session because of", reason);
|
|
135
|
-
__classPrivateFieldSet(this, _SessionEncrypted_id, getRandomId(), "f");
|
|
136
|
-
this.state.reset();
|
|
137
|
-
this.disconnect();
|
|
138
|
-
await this.connect();
|
|
139
|
-
__classPrivateFieldGet(this, _SessionEncrypted_instances, "m", _SessionEncrypted_rejectAllPending).call(this, new SessionError("The session was invalidated."));
|
|
140
|
-
}, _SessionEncrypted_rejectAllPending = function _SessionEncrypted_rejectAllPending(reason) {
|
|
141
|
-
for (const id of __classPrivateFieldGet(this, _SessionEncrypted_sentMessages, "f")) {
|
|
142
|
-
__classPrivateFieldGet(this, _SessionEncrypted_instances, "m", _SessionEncrypted_onMessageFailed).call(this, id, reason);
|
|
143
|
-
}
|
|
144
|
-
for (const pendingPing of __classPrivateFieldGet(this, _SessionEncrypted_pendingPings, "f").values()) {
|
|
145
|
-
pendingPing.promiseWithResolvers.reject(reason);
|
|
146
|
-
}
|
|
147
|
-
__classPrivateFieldGet(this, _SessionEncrypted_sentMessages, "f").clear();
|
|
148
|
-
__classPrivateFieldGet(this, _SessionEncrypted_pendingPings, "f").clear();
|
|
149
|
-
__classPrivateFieldGet(this, _SessionEncrypted_containers, "f").clear();
|
|
150
|
-
}, _SessionEncrypted_onMessageFailed = function _SessionEncrypted_onMessageFailed(id, reason) {
|
|
151
|
-
__classPrivateFieldGet(this, _SessionEncrypted_sentMessages, "f").delete(id);
|
|
152
|
-
const pendingContainer = __classPrivateFieldGet(this, _SessionEncrypted_containers, "f").get(id);
|
|
153
|
-
if (pendingContainer) {
|
|
154
|
-
for (const id of pendingContainer) {
|
|
155
|
-
__classPrivateFieldGet(this, _SessionEncrypted_instances, "m", _SessionEncrypted_onMessageFailed).call(this, id, reason);
|
|
156
|
-
}
|
|
157
|
-
__classPrivateFieldGet(this, _SessionEncrypted_containers, "f").delete(id);
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
const pendingAck = __classPrivateFieldGet(this, _SessionEncrypted_pendingAcks, "f").get(id);
|
|
161
|
-
if (pendingAck) {
|
|
162
|
-
for (const id of pendingAck) {
|
|
163
|
-
__classPrivateFieldGet(this, _SessionEncrypted_toAcknowledge, "f").push(id);
|
|
156
|
+
async #receive() {
|
|
157
|
+
this.#assertNotDisconnected();
|
|
158
|
+
const buffer = await this.transport.transport.receive();
|
|
159
|
+
if (buffer.length === 4) {
|
|
160
|
+
const int = intFromBytes(buffer);
|
|
161
|
+
throw new TransportError(Number(int));
|
|
164
162
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (pendingPing) {
|
|
170
|
-
__classPrivateFieldGet(this, _SessionEncrypted_pendingPings, "f").delete(id);
|
|
171
|
-
if (reason instanceof SessionError) {
|
|
172
|
-
drop(__classPrivateFieldGet(this, _SessionEncrypted_instances, "m", _SessionEncrypted_resendPendingPing).call(this, pendingPing));
|
|
163
|
+
try {
|
|
164
|
+
const decrypted = await this.#decryptMessage(buffer);
|
|
165
|
+
this.#L.in(decrypted);
|
|
166
|
+
return decrypted;
|
|
173
167
|
}
|
|
174
|
-
|
|
175
|
-
|
|
168
|
+
catch (err) {
|
|
169
|
+
this.#L.error("decryption error:", err);
|
|
170
|
+
await this.#invalidateSession("decryption error");
|
|
171
|
+
throw err;
|
|
176
172
|
}
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
// message was not sent by us
|
|
180
|
-
this.handlers.onMessageFailed?.(id, reason);
|
|
181
|
-
}, _SessionEncrypted_setServerSalt = function _SessionEncrypted_setServerSalt(newServerSalt) {
|
|
182
|
-
this.state.serverSalt = newServerSalt;
|
|
183
|
-
this.handlers.onNewServerSalt?.(newServerSalt);
|
|
184
|
-
}, _SessionEncrypted_receive = async function _SessionEncrypted_receive() {
|
|
185
|
-
__classPrivateFieldGet(this, _SessionEncrypted_instances, "m", _SessionEncrypted_assertNotDisconnected).call(this);
|
|
186
|
-
const buffer = await this.transport.transport.receive();
|
|
187
|
-
if (buffer.length === 4) {
|
|
188
|
-
const int = intFromBytes(buffer);
|
|
189
|
-
throw new TransportError(Number(int));
|
|
190
|
-
}
|
|
191
|
-
try {
|
|
192
|
-
const decrypted = await __classPrivateFieldGet(this, _SessionEncrypted_instances, "m", _SessionEncrypted_decryptMessage).call(this, buffer);
|
|
193
|
-
__classPrivateFieldGet(this, _SessionEncrypted_L, "f").in(decrypted);
|
|
194
|
-
return decrypted;
|
|
195
|
-
}
|
|
196
|
-
catch (err) {
|
|
197
|
-
__classPrivateFieldGet(this, _SessionEncrypted_L, "f").error("decryption error:", err);
|
|
198
|
-
await __classPrivateFieldGet(this, _SessionEncrypted_instances, "m", _SessionEncrypted_invalidateSession).call(this, "decryption error");
|
|
199
|
-
throw err;
|
|
200
173
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
__classPrivateFieldGet(this, _SessionEncrypted_LsendLoop, "f").debug("no pending messages");
|
|
242
|
-
return await new Promise((resolve) => {
|
|
243
|
-
const onAbort = () => {
|
|
244
|
-
__classPrivateFieldGet(this, _SessionEncrypted_LsendLoop, "f").debug("got aborted while sleeping");
|
|
245
|
-
resolve();
|
|
246
|
-
};
|
|
247
|
-
signal.addEventListener("abort", onAbort);
|
|
248
|
-
__classPrivateFieldSet(this, _SessionEncrypted_awakeSendLoop, () => {
|
|
249
|
-
__classPrivateFieldGet(this, _SessionEncrypted_LsendLoop, "f").debug("got awaken");
|
|
250
|
-
resolve();
|
|
251
|
-
signal.removeEventListener("abort", onAbort);
|
|
252
|
-
}, "f");
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
const msg_id = this.state.nextMessageId();
|
|
256
|
-
const seqno = this.state.nextSeqNo(true);
|
|
257
|
-
let message = {
|
|
258
|
-
_: "message",
|
|
259
|
-
msg_id,
|
|
260
|
-
seqno,
|
|
261
|
-
body: pendingMessage.body,
|
|
262
|
-
};
|
|
263
|
-
__classPrivateFieldGet(this, _SessionEncrypted_LsendLoop, "f").debug("msg_id =", msg_id, "seqno =", seqno);
|
|
264
|
-
if (__classPrivateFieldGet(this, _SessionEncrypted_toAcknowledge, "f").length) {
|
|
265
|
-
const msg_ids = __classPrivateFieldGet(this, _SessionEncrypted_toAcknowledge, "f").splice(0, 8192);
|
|
266
|
-
__classPrivateFieldGet(this, _SessionEncrypted_LsendLoop, "f").debug("acknowledging", msg_ids.length, "message(s) while sending this one");
|
|
267
|
-
const ack = {
|
|
268
|
-
_: "message",
|
|
269
|
-
msg_id: this.state.nextMessageId(),
|
|
270
|
-
seqno: this.state.nextSeqNo(false),
|
|
271
|
-
body: Mtproto.serializeObject({ _: "msgs_ack", msg_ids }),
|
|
272
|
-
};
|
|
273
|
-
__classPrivateFieldGet(this, _SessionEncrypted_LsendLoop, "f").debug("msgs_ack msg_id =", ack.msg_id, "seqno =", seqno);
|
|
274
|
-
__classPrivateFieldGet(this, _SessionEncrypted_pendingAcks, "f").set(ack.msg_id, msg_ids);
|
|
275
|
-
message = {
|
|
276
|
-
_: "message",
|
|
277
|
-
msg_id: this.state.nextMessageId(),
|
|
278
|
-
seqno: this.state.nextSeqNo(false),
|
|
279
|
-
body: {
|
|
280
|
-
_: "msg_container",
|
|
281
|
-
messages: [message, ack],
|
|
282
|
-
},
|
|
283
|
-
};
|
|
284
|
-
__classPrivateFieldGet(this, _SessionEncrypted_LsendLoop, "f").debug("container msg_id =", message.msg_id, "seqno =", message.seqno);
|
|
285
|
-
}
|
|
286
|
-
try {
|
|
287
|
-
const payload = await __classPrivateFieldGet(this, _SessionEncrypted_instances, "m", _SessionEncrypted_encryptMessage).call(this, message);
|
|
288
|
-
await this.transport.transport.send(payload);
|
|
289
|
-
pendingMessage.promiseWithResolvers.resolve(msg_id);
|
|
290
|
-
}
|
|
291
|
-
catch (err) {
|
|
292
|
-
pendingMessage.promiseWithResolvers.reject(err);
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
__classPrivateFieldGet(this, _SessionEncrypted_LsendLoop, "f").out(message);
|
|
296
|
-
__classPrivateFieldGet(this, _SessionEncrypted_sentMessages, "f").add(msg_id);
|
|
297
|
-
if (!(message.body instanceof Uint8Array)) {
|
|
298
|
-
const msg_ids = message.body.messages.map((v) => v.msg_id);
|
|
299
|
-
__classPrivateFieldGet(this, _SessionEncrypted_LsendLoop, "f").debug("sent container", message.msg_id, "with messages", ...msg_ids);
|
|
300
|
-
__classPrivateFieldGet(this, _SessionEncrypted_containers, "f").set(message.msg_id, msg_ids);
|
|
301
|
-
}
|
|
302
|
-
else {
|
|
303
|
-
__classPrivateFieldGet(this, _SessionEncrypted_LsendLoop, "f").debug("sent message", message.msg_id);
|
|
304
|
-
}
|
|
305
|
-
}, _SessionEncrypted_receiveLoopBody = async function _SessionEncrypted_receiveLoopBody(loop) {
|
|
306
|
-
let message;
|
|
307
|
-
try {
|
|
308
|
-
__classPrivateFieldGet(this, _SessionEncrypted_LreceiveLoop, "f").debug("receiving");
|
|
309
|
-
message = await __classPrivateFieldGet(this, _SessionEncrypted_instances, "m", _SessionEncrypted_receive).call(this);
|
|
310
|
-
}
|
|
311
|
-
catch (err) {
|
|
312
|
-
__classPrivateFieldGet(this, _SessionEncrypted_LreceiveLoop, "f").error("failed to receive message:", err);
|
|
174
|
+
async #encryptMessage(message) {
|
|
175
|
+
const payloadWriter = new TLWriter();
|
|
176
|
+
payloadWriter.writeInt64(this.state.serverSalt);
|
|
177
|
+
payloadWriter.writeInt64(this.#id);
|
|
178
|
+
payloadWriter.write(await serializeMessage(message));
|
|
179
|
+
payloadWriter.write(new Uint8Array(mod(-(payloadWriter.buffer.length + 12), 16) + 12));
|
|
180
|
+
const payload = payloadWriter.buffer;
|
|
181
|
+
const messageKey = (await sha256(concat([this.#authKey.subarray(88, 120), payload]))).subarray(8, 24);
|
|
182
|
+
const a = await sha256(concat([messageKey, this.#authKey.subarray(0, 36)]));
|
|
183
|
+
const b = await sha256(concat([this.#authKey.subarray(40, 76), messageKey]));
|
|
184
|
+
const aesKey = concat([a.subarray(0, 8), b.subarray(8, 24), a.subarray(24, 32)]);
|
|
185
|
+
const aesIV = concat([b.subarray(0, 8), a.subarray(8, 24), b.subarray(24, 32)]);
|
|
186
|
+
const messageWriter = new TLWriter();
|
|
187
|
+
messageWriter.writeInt64(this.#authKeyId);
|
|
188
|
+
messageWriter.write(messageKey);
|
|
189
|
+
messageWriter.write(ige256Encrypt(payload, aesKey, aesIV));
|
|
190
|
+
return messageWriter.buffer;
|
|
191
|
+
}
|
|
192
|
+
async #decryptMessage(buffer) {
|
|
193
|
+
const reader = new TLReader(buffer);
|
|
194
|
+
assertEquals(reader.readInt64(), this.#authKeyId);
|
|
195
|
+
const messageKey_ = reader.readInt128();
|
|
196
|
+
const messageKey = intToBytes(messageKey_, 16);
|
|
197
|
+
const a = await sha256(concat([messageKey, this.#authKey.subarray(8, 44)]));
|
|
198
|
+
const b = await sha256(concat([this.#authKey.subarray(48, 84), messageKey]));
|
|
199
|
+
const aesKey = concat([a.subarray(0, 8), b.subarray(8, 24), a.subarray(24, 32)]);
|
|
200
|
+
const aesIv = concat([b.subarray(0, 8), a.subarray(8, 24), b.subarray(24, 32)]);
|
|
201
|
+
const plaintext = ige256Decrypt(reader.buffer, aesKey, aesIv);
|
|
202
|
+
assertEquals(plaintext.buffer.byteLength % 4, 0);
|
|
203
|
+
const plainReader = new TLReader(plaintext);
|
|
204
|
+
const _salt = plainReader.readInt64();
|
|
205
|
+
const _sessionId_ = plainReader.readInt64(false);
|
|
206
|
+
return deserializeMessage(plainReader);
|
|
207
|
+
}
|
|
208
|
+
//// SEND LOOP ////
|
|
209
|
+
#awakeSendLoop;
|
|
210
|
+
#sendLoop = new AbortableLoop(this.#sendLoopBody.bind(this), (err) => {
|
|
211
|
+
this.#LsendLoop.error("unhandled receive loop error:", err);
|
|
212
|
+
});
|
|
213
|
+
async #sendLoopBody(loop, signal) {
|
|
313
214
|
if (!this.isConnected) {
|
|
314
|
-
|
|
215
|
+
this.#LsendLoop.debug("aborting as not connected");
|
|
315
216
|
loop.abort();
|
|
316
217
|
return;
|
|
317
218
|
}
|
|
318
|
-
|
|
219
|
+
const pendingMessage = this.#pendingMessages.shift();
|
|
220
|
+
if (pendingMessage === undefined) {
|
|
221
|
+
this.#LsendLoop.debug("no pending messages");
|
|
222
|
+
return await new Promise((resolve) => {
|
|
223
|
+
const onAbort = () => {
|
|
224
|
+
this.#LsendLoop.debug("got aborted while sleeping");
|
|
225
|
+
resolve();
|
|
226
|
+
};
|
|
227
|
+
signal.addEventListener("abort", onAbort);
|
|
228
|
+
this.#awakeSendLoop = () => {
|
|
229
|
+
this.#LsendLoop.debug("got awaken");
|
|
230
|
+
resolve();
|
|
231
|
+
signal.removeEventListener("abort", onAbort);
|
|
232
|
+
};
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
const msg_id = this.state.nextMessageId();
|
|
236
|
+
const seqno = this.state.nextSeqNo(true);
|
|
237
|
+
let message = {
|
|
238
|
+
_: "message",
|
|
239
|
+
msg_id,
|
|
240
|
+
seqno,
|
|
241
|
+
body: pendingMessage.body,
|
|
242
|
+
};
|
|
243
|
+
this.#LsendLoop.debug("msg_id =", msg_id, "seqno =", seqno);
|
|
244
|
+
if (this.#toAcknowledge.length) {
|
|
245
|
+
const msg_ids = this.#toAcknowledge.splice(0, 8192);
|
|
246
|
+
this.#LsendLoop.debug("acknowledging", msg_ids.length, "message(s) while sending this one");
|
|
247
|
+
const ack = {
|
|
248
|
+
_: "message",
|
|
249
|
+
msg_id: this.state.nextMessageId(),
|
|
250
|
+
seqno: this.state.nextSeqNo(false),
|
|
251
|
+
body: Mtproto.serializeObject({ _: "msgs_ack", msg_ids }),
|
|
252
|
+
};
|
|
253
|
+
this.#LsendLoop.debug("msgs_ack msg_id =", ack.msg_id, "seqno =", seqno);
|
|
254
|
+
this.#pendingAcks.set(ack.msg_id, msg_ids);
|
|
255
|
+
message = {
|
|
256
|
+
_: "message",
|
|
257
|
+
msg_id: this.state.nextMessageId(),
|
|
258
|
+
seqno: this.state.nextSeqNo(false),
|
|
259
|
+
body: {
|
|
260
|
+
_: "msg_container",
|
|
261
|
+
messages: [message, ack],
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
this.#LsendLoop.debug("container msg_id =", message.msg_id, "seqno =", message.seqno);
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
const payload = await this.#encryptMessage(message);
|
|
268
|
+
await this.transport.transport.send(payload);
|
|
269
|
+
pendingMessage.promiseWithResolvers.resolve(msg_id);
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
pendingMessage.promiseWithResolvers.reject(err);
|
|
319
273
|
return;
|
|
320
274
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
if (message.body instanceof Uint8Array) {
|
|
324
|
-
|
|
275
|
+
this.#LsendLoop.out(message);
|
|
276
|
+
this.#sentMessages.add(msg_id);
|
|
277
|
+
if (!(message.body instanceof Uint8Array)) {
|
|
278
|
+
const msg_ids = message.body.messages.map((v) => v.msg_id);
|
|
279
|
+
this.#LsendLoop.debug("sent container", message.msg_id, "with messages", ...msg_ids);
|
|
280
|
+
this.#containers.set(message.msg_id, msg_ids);
|
|
325
281
|
}
|
|
326
282
|
else {
|
|
327
|
-
|
|
283
|
+
this.#LsendLoop.debug("sent message", message.msg_id);
|
|
328
284
|
}
|
|
329
285
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if (id === RPC_RESULT) {
|
|
346
|
-
__classPrivateFieldGet(this, _SessionEncrypted_instances, "m", _SessionEncrypted_onRpcResult).call(this, msgId, reader.buffer, logger);
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
if (!Mtproto.schema.identifierToName[id]) {
|
|
350
|
-
logger.debug("identified body as a non-MTProto constructor");
|
|
351
|
-
reader.unreadInt32();
|
|
352
|
-
this.handlers.onUpdate?.(reader.buffer);
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
let type;
|
|
356
|
-
try {
|
|
357
|
-
reader.unreadInt32();
|
|
358
|
-
type = await Mtproto.deserializeType(X, reader);
|
|
359
|
-
}
|
|
360
|
-
catch (err) {
|
|
361
|
-
logger.error("failed to deserialize MTProto type:", err);
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
logger.debug("received", repr(type));
|
|
365
|
-
if (Mtproto.is("new_session_created", type)) {
|
|
366
|
-
__classPrivateFieldGet(this, _SessionEncrypted_instances, "m", _SessionEncrypted_onNewSessionCreated).call(this, msgId, type);
|
|
367
|
-
}
|
|
368
|
-
else if (Mtproto.is("pong", type)) {
|
|
369
|
-
__classPrivateFieldGet(this, _SessionEncrypted_instances, "m", _SessionEncrypted_onPong).call(this, msgId, type);
|
|
370
|
-
}
|
|
371
|
-
else if (Mtproto.is("bad_server_salt", type)) {
|
|
372
|
-
__classPrivateFieldGet(this, _SessionEncrypted_instances, "m", _SessionEncrypted_onBadServerSalt).call(this, type);
|
|
373
|
-
}
|
|
374
|
-
else if (Mtproto.is("bad_msg_notification", type)) {
|
|
375
|
-
await __classPrivateFieldGet(this, _SessionEncrypted_instances, "m", _SessionEncrypted_onBadMsgNotification).call(this, msgId, type, logger);
|
|
376
|
-
}
|
|
377
|
-
else if (Mtproto.is("msg_detailed_info", type)) {
|
|
378
|
-
__classPrivateFieldGet(this, _SessionEncrypted_instances, "m", _SessionEncrypted_onMsgDetailedInfo).call(this, type, logger);
|
|
379
|
-
}
|
|
380
|
-
else if (Mtproto.is("msg_new_detailed_info", type)) {
|
|
381
|
-
__classPrivateFieldGet(this, _SessionEncrypted_instances, "m", _SessionEncrypted_onMsgNewDetailedInfo).call(this, type, logger);
|
|
382
|
-
}
|
|
383
|
-
else {
|
|
384
|
-
logger.warning(`unhandled MTProto type: ${repr(type)}`);
|
|
385
|
-
}
|
|
386
|
-
}, _SessionEncrypted_onRpcResult = async function _SessionEncrypted_onRpcResult(msgId, body, logger) {
|
|
387
|
-
logger.debug("received rpc_result");
|
|
388
|
-
__classPrivateFieldGet(this, _SessionEncrypted_toAcknowledge, "f").push(msgId);
|
|
389
|
-
let reader = new TLReader(body);
|
|
390
|
-
const reqMsgId = reader.readInt64();
|
|
391
|
-
let id = reader.readInt32(false);
|
|
392
|
-
if (id === GZIP_PACKED) {
|
|
393
|
-
logger.debug("unpacking compressed rpc_result");
|
|
394
|
-
reader = new TLReader(await gunzip(reader.readBytes()));
|
|
395
|
-
id = reader.readInt32(false);
|
|
396
|
-
reader.unreadInt32();
|
|
397
|
-
}
|
|
398
|
-
else {
|
|
399
|
-
reader.unreadInt32();
|
|
400
|
-
}
|
|
401
|
-
if (id === RPC_ERROR) {
|
|
402
|
-
logger.debug("received rpc_error from message", msgId);
|
|
403
|
-
const error = await Mtproto.deserializeType("rpc_error", reader);
|
|
404
|
-
this.handlers.onRpcError?.(reqMsgId, error);
|
|
405
|
-
}
|
|
406
|
-
else {
|
|
407
|
-
this.handlers.onRpcResult?.(reqMsgId, reader.buffer);
|
|
408
|
-
}
|
|
409
|
-
}, _SessionEncrypted_onMsgDetailedInfo = function _SessionEncrypted_onMsgDetailedInfo(msgDetailedInfo, logger) {
|
|
410
|
-
logger.debug("scheduling the acknowledgement of", msgDetailedInfo.answer_msg_id, "because of", msgDetailedInfo._);
|
|
411
|
-
__classPrivateFieldGet(this, _SessionEncrypted_toAcknowledge, "f").push(msgDetailedInfo.answer_msg_id);
|
|
412
|
-
}, _SessionEncrypted_onMsgNewDetailedInfo = function _SessionEncrypted_onMsgNewDetailedInfo(msgNewDetailedInfo, logger) {
|
|
413
|
-
logger.debug("scheduling the acknowledgement of", msgNewDetailedInfo.answer_msg_id, "because of", msgNewDetailedInfo._);
|
|
414
|
-
__classPrivateFieldGet(this, _SessionEncrypted_toAcknowledge, "f").push(msgNewDetailedInfo.answer_msg_id);
|
|
415
|
-
}, _SessionEncrypted_onBadMsgNotification = async function _SessionEncrypted_onBadMsgNotification(msgId, badMsgNotification, logger) {
|
|
416
|
-
let low = false;
|
|
417
|
-
switch (badMsgNotification.error_code) {
|
|
418
|
-
case 16: // message ID too low
|
|
419
|
-
low = true;
|
|
420
|
-
/* falls through */
|
|
421
|
-
case 17: // message ID too high
|
|
422
|
-
this.state.timeDifference = Math.abs(toUnixTimestamp(new Date()) - Number(msgId >> 32n));
|
|
423
|
-
if (!low) {
|
|
424
|
-
this.state.timeDifference = -this.state.timeDifference;
|
|
425
|
-
logger.debug("resetting time difference to", -this.state.timeDifference, "because the ID of the message", badMsgNotification.bad_msg_id, "was too high");
|
|
426
|
-
await __classPrivateFieldGet(this, _SessionEncrypted_instances, "m", _SessionEncrypted_invalidateSession).call(this, "message ID too high");
|
|
286
|
+
//// RECEIVE LOOP ////
|
|
287
|
+
#receiveLoop = new AbortableLoop(this.#receiveLoopBody.bind(this), (err) => {
|
|
288
|
+
this.#LreceiveLoop.error("unhandled receive loop error:", err);
|
|
289
|
+
});
|
|
290
|
+
async #receiveLoopBody(loop) {
|
|
291
|
+
let message;
|
|
292
|
+
try {
|
|
293
|
+
this.#LreceiveLoop.debug("receiving");
|
|
294
|
+
message = await this.#receive();
|
|
295
|
+
}
|
|
296
|
+
catch (err) {
|
|
297
|
+
this.#LreceiveLoop.error("failed to receive message:", err);
|
|
298
|
+
if (!this.isConnected) {
|
|
299
|
+
this.#LreceiveLoop.debug("aborting as not connected");
|
|
300
|
+
loop.abort();
|
|
427
301
|
return;
|
|
428
302
|
}
|
|
429
303
|
else {
|
|
430
|
-
|
|
304
|
+
return;
|
|
431
305
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
__classPrivateFieldGet(this, _SessionEncrypted_instances, "m", _SessionEncrypted_onMessageFailed).call(this, badServerSalt.bad_msg_id, new SessionError(badServerSalt._));
|
|
445
|
-
}, _SessionEncrypted_onPong = function _SessionEncrypted_onPong(msgId, pong) {
|
|
446
|
-
__classPrivateFieldGet(this, _SessionEncrypted_toAcknowledge, "f").push(msgId);
|
|
447
|
-
const pendingPing = __classPrivateFieldGet(this, _SessionEncrypted_pendingPings, "f").get(pong.msg_id);
|
|
448
|
-
if (pendingPing) {
|
|
449
|
-
pendingPing.promiseWithResolvers.resolve(pong);
|
|
450
|
-
__classPrivateFieldGet(this, _SessionEncrypted_pendingPings, "f").delete(pong.msg_id);
|
|
451
|
-
}
|
|
452
|
-
else {
|
|
453
|
-
// pong is not ours
|
|
454
|
-
this.handlers.onPong?.(pong);
|
|
306
|
+
}
|
|
307
|
+
try {
|
|
308
|
+
if (message.body instanceof Uint8Array) {
|
|
309
|
+
await this.#onMessage(message.msg_id, message.body, null);
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
await this.#onMessageContainer(message.msg_id, message.body);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
catch (err) {
|
|
316
|
+
this.#LreceiveLoop.error("failed to handle message:", err);
|
|
317
|
+
}
|
|
455
318
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
if (
|
|
463
|
-
|
|
319
|
+
//// RECEIVE LOOP HANDLERS ////
|
|
320
|
+
async #onMessage(msgId, body, containerId) {
|
|
321
|
+
this.#LreceiveLoop.debug("received message with ID", msgId, "and size", body.length, "inside", ...(containerId === null ? ["no container"] : ["container", containerId]));
|
|
322
|
+
const logger = this.#LreceiveLoop.branch(msgId + "");
|
|
323
|
+
let reader = new TLReader(body);
|
|
324
|
+
let id = reader.readInt32(false);
|
|
325
|
+
if (id === GZIP_PACKED) {
|
|
326
|
+
logger.debug("unpacking compressed body");
|
|
327
|
+
reader = new TLReader(await gunzip(reader.readBytes()));
|
|
328
|
+
id = reader.readInt32(false);
|
|
329
|
+
}
|
|
330
|
+
if (id === RPC_RESULT) {
|
|
331
|
+
this.#onRpcResult(msgId, reader.buffer, logger);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (!Mtproto.schema.identifierToName[id]) {
|
|
335
|
+
logger.debug("identified body as a non-MTProto constructor");
|
|
336
|
+
reader.unreadInt32();
|
|
337
|
+
this.handlers.onUpdate?.(reader.buffer);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
let type;
|
|
341
|
+
try {
|
|
342
|
+
reader.unreadInt32();
|
|
343
|
+
type = await Mtproto.deserializeType(X, reader);
|
|
344
|
+
}
|
|
345
|
+
catch (err) {
|
|
346
|
+
logger.error("failed to deserialize MTProto type:", err);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
logger.debug("received", repr(type));
|
|
350
|
+
if (Mtproto.is("new_session_created", type)) {
|
|
351
|
+
this.#onNewSessionCreated(msgId, type);
|
|
352
|
+
}
|
|
353
|
+
else if (Mtproto.is("pong", type)) {
|
|
354
|
+
this.#onPong(msgId, type);
|
|
355
|
+
}
|
|
356
|
+
else if (Mtproto.is("bad_server_salt", type)) {
|
|
357
|
+
this.#onBadServerSalt(type);
|
|
358
|
+
}
|
|
359
|
+
else if (Mtproto.is("bad_msg_notification", type)) {
|
|
360
|
+
await this.#onBadMsgNotification(msgId, type, logger);
|
|
361
|
+
}
|
|
362
|
+
else if (Mtproto.is("msg_detailed_info", type)) {
|
|
363
|
+
this.#onMsgDetailedInfo(type, logger);
|
|
364
|
+
}
|
|
365
|
+
else if (Mtproto.is("msg_new_detailed_info", type)) {
|
|
366
|
+
this.#onMsgNewDetailedInfo(type, logger);
|
|
464
367
|
}
|
|
465
368
|
else {
|
|
466
|
-
|
|
369
|
+
logger.warning(`unhandled MTProto type: ${repr(type)}`);
|
|
467
370
|
}
|
|
468
371
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
372
|
+
async #onRpcResult(msgId, body, logger) {
|
|
373
|
+
logger.debug("received rpc_result");
|
|
374
|
+
this.#toAcknowledge.push(msgId);
|
|
375
|
+
let reader = new TLReader(body);
|
|
376
|
+
const reqMsgId = reader.readInt64();
|
|
377
|
+
let id = reader.readInt32(false);
|
|
378
|
+
if (id === GZIP_PACKED) {
|
|
379
|
+
logger.debug("unpacking compressed rpc_result");
|
|
380
|
+
reader = new TLReader(await gunzip(reader.readBytes()));
|
|
381
|
+
id = reader.readInt32(false);
|
|
382
|
+
reader.unreadInt32();
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
reader.unreadInt32();
|
|
386
|
+
}
|
|
387
|
+
if (id === RPC_ERROR) {
|
|
388
|
+
logger.debug("received rpc_error from message", msgId);
|
|
389
|
+
const error = await Mtproto.deserializeType("rpc_error", reader);
|
|
390
|
+
this.handlers.onRpcError?.(reqMsgId, error);
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
this.handlers.onRpcResult?.(reqMsgId, reader.buffer);
|
|
394
|
+
}
|
|
477
395
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
396
|
+
#onMsgDetailedInfo(msgDetailedInfo, logger) {
|
|
397
|
+
logger.debug("scheduling the acknowledgement of", msgDetailedInfo.answer_msg_id, "because of", msgDetailedInfo._);
|
|
398
|
+
this.#toAcknowledge.push(msgDetailedInfo.answer_msg_id);
|
|
399
|
+
}
|
|
400
|
+
#onMsgNewDetailedInfo(msgNewDetailedInfo, logger) {
|
|
401
|
+
logger.debug("scheduling the acknowledgement of", msgNewDetailedInfo.answer_msg_id, "because of", msgNewDetailedInfo._);
|
|
402
|
+
this.#toAcknowledge.push(msgNewDetailedInfo.answer_msg_id);
|
|
403
|
+
}
|
|
404
|
+
async #onBadMsgNotification(msgId, badMsgNotification, logger) {
|
|
405
|
+
let low = false;
|
|
406
|
+
switch (badMsgNotification.error_code) {
|
|
407
|
+
case 16: // message ID too low
|
|
408
|
+
low = true;
|
|
409
|
+
/* falls through */
|
|
410
|
+
case 17: // message ID too high
|
|
411
|
+
this.state.timeDifference = Math.abs(toUnixTimestamp(new Date()) - Number(msgId >> 32n));
|
|
412
|
+
if (!low) {
|
|
413
|
+
this.state.timeDifference = -this.state.timeDifference;
|
|
414
|
+
logger.debug("resetting time difference to", -this.state.timeDifference, "because the ID of the message", badMsgNotification.bad_msg_id, "was too high");
|
|
415
|
+
await this.#invalidateSession("message ID too high");
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
logger.debug("resending message", badMsgNotification.bad_msg_id, "because its ID was too low");
|
|
420
|
+
}
|
|
421
|
+
break;
|
|
422
|
+
case 48: // bad server salt
|
|
423
|
+
// resend
|
|
424
|
+
this.#L.debug("resending message that caused bad_server_salt");
|
|
425
|
+
break;
|
|
426
|
+
default:
|
|
427
|
+
await this.#invalidateSession("unexpected bad_msg_notification");
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
this.#onMessageFailed(badMsgNotification.bad_msg_id, new SessionError(badMsgNotification._));
|
|
431
|
+
}
|
|
432
|
+
#onBadServerSalt(badServerSalt) {
|
|
433
|
+
this.#setServerSalt(badServerSalt.new_server_salt);
|
|
434
|
+
this.#onMessageFailed(badServerSalt.bad_msg_id, new SessionError(badServerSalt._));
|
|
435
|
+
}
|
|
436
|
+
#onPong(msgId, pong) {
|
|
437
|
+
this.#toAcknowledge.push(msgId);
|
|
438
|
+
const pendingPing = this.#pendingPings.get(pong.msg_id);
|
|
439
|
+
if (pendingPing) {
|
|
440
|
+
pendingPing.promiseWithResolvers.resolve(pong);
|
|
441
|
+
this.#pendingPings.delete(pong.msg_id);
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
// pong is not ours
|
|
445
|
+
this.handlers.onPong?.(pong);
|
|
446
|
+
}
|
|
483
447
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
448
|
+
#onNewSessionCreated(msgId, newSessionCreated) {
|
|
449
|
+
this.#setServerSalt(newSessionCreated.server_salt);
|
|
450
|
+
this.#toAcknowledge.push(msgId);
|
|
487
451
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
const messageId = await this.send(Mtproto.serializeObject(pendingPing.call));
|
|
499
|
-
__classPrivateFieldGet(this, _SessionEncrypted_pendingPings, "f").set(messageId, pendingPing);
|
|
500
|
-
__classPrivateFieldGet(this, _SessionEncrypted_LreceiveLoop, "f").debug("ping resent");
|
|
452
|
+
async #onMessageContainer(msgId, msgContainer) {
|
|
453
|
+
this.#LreceiveLoop.debug("received container with ID", msgId, "and", msgContainer.messages.length, "message(s)");
|
|
454
|
+
for (const message of msgContainer.messages) {
|
|
455
|
+
if (message.body instanceof Uint8Array) {
|
|
456
|
+
await this.#onMessage(message.msg_id, message.body, msgId);
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
await this.#onMessageContainer(msgId, message.body);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
501
462
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
463
|
+
//// PING LOOP ////
|
|
464
|
+
#pingInterval = 56 * SECOND;
|
|
465
|
+
#pingLoop = new AbortableLoop(this.#pingLoopBody.bind(this), (_, err) => {
|
|
466
|
+
this.#LpingLoop.error(err);
|
|
467
|
+
});
|
|
468
|
+
#timeElapsed = 0;
|
|
469
|
+
async #pingLoopBody(_loop, signal) {
|
|
470
|
+
const ms = Math.max(0, this.#pingInterval - this.#timeElapsed);
|
|
471
|
+
if (ms) {
|
|
472
|
+
this.#LpingLoop.debug(`sending ping in ${ms}ms`);
|
|
473
|
+
await delay(ms, { signal });
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
this.#LpingLoop.debug("sending ping now");
|
|
477
|
+
}
|
|
478
|
+
signal.throwIfAborted();
|
|
479
|
+
const then = Date.now();
|
|
480
|
+
try {
|
|
481
|
+
await this.#sendPingDelayDisconnect(this.#pingInterval / SECOND + 15);
|
|
482
|
+
this.#LpingLoop.debug("received pong");
|
|
483
|
+
}
|
|
484
|
+
finally {
|
|
485
|
+
this.#timeElapsed = Date.now() - then;
|
|
486
|
+
this.#LpingLoop.debug(`took ${this.#timeElapsed}`);
|
|
487
|
+
}
|
|
488
|
+
signal.throwIfAborted();
|
|
489
|
+
}
|
|
490
|
+
async #sendPingDelayDisconnect(disconnect_delay) {
|
|
491
|
+
const ping_id = getRandomId();
|
|
492
|
+
const call = { _: "ping_delay_disconnect", ping_id, disconnect_delay };
|
|
493
|
+
const messageId = await this.send(Mtproto.serializeObject(call));
|
|
494
|
+
const promiseWithResolvers = Promise.withResolvers();
|
|
495
|
+
this.#pendingPings.set(messageId, { call, promiseWithResolvers });
|
|
496
|
+
await promiseWithResolvers.promise;
|
|
497
|
+
}
|
|
498
|
+
async #resendPendingPing(pendingPing) {
|
|
499
|
+
try {
|
|
500
|
+
const messageId = await this.send(Mtproto.serializeObject(pendingPing.call));
|
|
501
|
+
this.#pendingPings.set(messageId, pendingPing);
|
|
502
|
+
this.#LreceiveLoop.debug("ping resent");
|
|
503
|
+
}
|
|
504
|
+
catch (err) {
|
|
505
|
+
this.#LreceiveLoop.debug("rejecting ping because of failed resend:", err);
|
|
506
|
+
pendingPing.promiseWithResolvers.reject(err);
|
|
507
|
+
}
|
|
505
508
|
}
|
|
506
|
-
}
|
|
507
|
-
_SessionEncrypted_TGCRYPTO_INITED = { value: false };
|
|
509
|
+
}
|