@mt-tl/server 0.1.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/LICENSE +21 -0
- package/README.md +50 -0
- package/dist/auth/handshake.d.ts +35 -0
- package/dist/auth/handshake.d.ts.map +1 -0
- package/dist/auth/handshake.js +208 -0
- package/dist/auth/handshake.js.map +1 -0
- package/dist/auth/nonce-store.d.ts +22 -0
- package/dist/auth/nonce-store.d.ts.map +1 -0
- package/dist/auth/nonce-store.js +23 -0
- package/dist/auth/nonce-store.js.map +1 -0
- package/dist/bootstrap.d.ts +36 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +82 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/config.d.ts +103 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -0
- package/dist/core/context.d.ts +57 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/context.js +27 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/errors.d.ts +33 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +47 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +5 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/rpc.d.ts +83 -0
- package/dist/core/rpc.d.ts.map +1 -0
- package/dist/core/rpc.js +102 -0
- package/dist/core/rpc.js.map +1 -0
- package/dist/core/updates.d.ts +56 -0
- package/dist/core/updates.d.ts.map +1 -0
- package/dist/core/updates.js +34 -0
- package/dist/core/updates.js.map +1 -0
- package/dist/create-server.d.ts +89 -0
- package/dist/create-server.d.ts.map +1 -0
- package/dist/create-server.js +109 -0
- package/dist/create-server.js.map +1 -0
- package/dist/crypto/aes-ige.d.ts +3 -0
- package/dist/crypto/aes-ige.d.ts.map +1 -0
- package/dist/crypto/aes-ige.js +55 -0
- package/dist/crypto/aes-ige.js.map +1 -0
- package/dist/crypto/dh.d.ts +21 -0
- package/dist/crypto/dh.d.ts.map +1 -0
- package/dist/crypto/dh.js +99 -0
- package/dist/crypto/dh.js.map +1 -0
- package/dist/crypto/hashes.d.ts +6 -0
- package/dist/crypto/hashes.d.ts.map +1 -0
- package/dist/crypto/hashes.js +14 -0
- package/dist/crypto/hashes.js.map +1 -0
- package/dist/crypto/msg-key.d.ts +15 -0
- package/dist/crypto/msg-key.d.ts.map +1 -0
- package/dist/crypto/msg-key.js +24 -0
- package/dist/crypto/msg-key.js.map +1 -0
- package/dist/crypto/rsa.d.ts +27 -0
- package/dist/crypto/rsa.d.ts.map +1 -0
- package/dist/crypto/rsa.js +50 -0
- package/dist/crypto/rsa.js.map +1 -0
- package/dist/dispatch/dispatcher.d.ts +72 -0
- package/dist/dispatch/dispatcher.d.ts.map +1 -0
- package/dist/dispatch/dispatcher.js +503 -0
- package/dist/dispatch/dispatcher.js.map +1 -0
- package/dist/dispatch/forwarders/in-process.d.ts +12 -0
- package/dist/dispatch/forwarders/in-process.d.ts.map +1 -0
- package/dist/dispatch/forwarders/in-process.js +15 -0
- package/dist/dispatch/forwarders/in-process.js.map +1 -0
- package/dist/dispatch/forwarders/print.d.ts +14 -0
- package/dist/dispatch/forwarders/print.d.ts.map +1 -0
- package/dist/dispatch/forwarders/print.js +23 -0
- package/dist/dispatch/forwarders/print.js.map +1 -0
- package/dist/dispatch/rpc-forwarder.d.ts +14 -0
- package/dist/dispatch/rpc-forwarder.d.ts.map +1 -0
- package/dist/dispatch/rpc-forwarder.js +2 -0
- package/dist/dispatch/rpc-forwarder.js.map +1 -0
- package/dist/dispatch/types.d.ts +32 -0
- package/dist/dispatch/types.d.ts.map +1 -0
- package/dist/dispatch/types.js +23 -0
- package/dist/dispatch/types.js.map +1 -0
- package/dist/gateway.d.ts +57 -0
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +150 -0
- package/dist/gateway.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/lib.d.ts +12 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +13 -0
- package/dist/lib.js.map +1 -0
- package/dist/server/message-pipeline.d.ts +57 -0
- package/dist/server/message-pipeline.d.ts.map +1 -0
- package/dist/server/message-pipeline.js +199 -0
- package/dist/server/message-pipeline.js.map +1 -0
- package/dist/session/inbound-tracker.d.ts +118 -0
- package/dist/session/inbound-tracker.d.ts.map +1 -0
- package/dist/session/inbound-tracker.js +170 -0
- package/dist/session/inbound-tracker.js.map +1 -0
- package/dist/session/message-id.d.ts +19 -0
- package/dist/session/message-id.d.ts.map +1 -0
- package/dist/session/message-id.js +30 -0
- package/dist/session/message-id.js.map +1 -0
- package/dist/session/salts.d.ts +51 -0
- package/dist/session/salts.d.ts.map +1 -0
- package/dist/session/salts.js +132 -0
- package/dist/session/salts.js.map +1 -0
- package/dist/session/session-manager.d.ts +18 -0
- package/dist/session/session-manager.d.ts.map +1 -0
- package/dist/session/session-manager.js +43 -0
- package/dist/session/session-manager.js.map +1 -0
- package/dist/storage/index.d.ts +14 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +16 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/memory.d.ts +3 -0
- package/dist/storage/memory.d.ts.map +1 -0
- package/dist/storage/memory.js +91 -0
- package/dist/storage/memory.js.map +1 -0
- package/dist/storage/mongo.d.ts +3 -0
- package/dist/storage/mongo.d.ts.map +1 -0
- package/dist/storage/mongo.js +175 -0
- package/dist/storage/mongo.js.map +1 -0
- package/dist/storage/types.d.ts +85 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +3 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/testkit.d.ts +11 -0
- package/dist/testkit.d.ts.map +1 -0
- package/dist/testkit.js +17 -0
- package/dist/testkit.js.map +1 -0
- package/dist/tl/codec.d.ts +37 -0
- package/dist/tl/codec.d.ts.map +1 -0
- package/dist/tl/codec.js +297 -0
- package/dist/tl/codec.js.map +1 -0
- package/dist/tl/layered-registry.d.ts +29 -0
- package/dist/tl/layered-registry.d.ts.map +1 -0
- package/dist/tl/layered-registry.js +118 -0
- package/dist/tl/layered-registry.js.map +1 -0
- package/dist/tl/protocol.d.ts +126 -0
- package/dist/tl/protocol.d.ts.map +1 -0
- package/dist/tl/protocol.js +22 -0
- package/dist/tl/protocol.js.map +1 -0
- package/dist/tl/reader.d.ts +30 -0
- package/dist/tl/reader.d.ts.map +1 -0
- package/dist/tl/reader.js +87 -0
- package/dist/tl/reader.js.map +1 -0
- package/dist/tl/registry.d.ts +39 -0
- package/dist/tl/registry.d.ts.map +1 -0
- package/dist/tl/registry.js +52 -0
- package/dist/tl/registry.js.map +1 -0
- package/dist/tl/writer.d.ts +24 -0
- package/dist/tl/writer.d.ts.map +1 -0
- package/dist/tl/writer.js +95 -0
- package/dist/tl/writer.js.map +1 -0
- package/dist/transport/connection-registry.d.ts +31 -0
- package/dist/transport/connection-registry.d.ts.map +1 -0
- package/dist/transport/connection-registry.js +84 -0
- package/dist/transport/connection-registry.js.map +1 -0
- package/dist/transport/connection.d.ts +62 -0
- package/dist/transport/connection.d.ts.map +1 -0
- package/dist/transport/connection.js +77 -0
- package/dist/transport/connection.js.map +1 -0
- package/dist/transport/framing.d.ts +39 -0
- package/dist/transport/framing.d.ts.map +1 -0
- package/dist/transport/framing.js +212 -0
- package/dist/transport/framing.js.map +1 -0
- package/dist/transport/proxy-protocol.d.ts +23 -0
- package/dist/transport/proxy-protocol.d.ts.map +1 -0
- package/dist/transport/proxy-protocol.js +108 -0
- package/dist/transport/proxy-protocol.js.map +1 -0
- package/dist/transport/server-common.d.ts +27 -0
- package/dist/transport/server-common.d.ts.map +1 -0
- package/dist/transport/server-common.js +27 -0
- package/dist/transport/server-common.js.map +1 -0
- package/dist/transport/tcp-server.d.ts +26 -0
- package/dist/transport/tcp-server.d.ts.map +1 -0
- package/dist/transport/tcp-server.js +91 -0
- package/dist/transport/tcp-server.js.map +1 -0
- package/dist/transport/ws-server.d.ts +19 -0
- package/dist/transport/ws-server.d.ts.map +1 -0
- package/dist/transport/ws-server.js +78 -0
- package/dist/transport/ws-server.js.map +1 -0
- package/dist/update-publisher.d.ts +34 -0
- package/dist/update-publisher.d.ts.map +1 -0
- package/dist/update-publisher.js +29 -0
- package/dist/update-publisher.js.map +1 -0
- package/dist/updates/mongo-update-log.d.ts +13 -0
- package/dist/updates/mongo-update-log.d.ts.map +1 -0
- package/dist/updates/mongo-update-log.js +39 -0
- package/dist/updates/mongo-update-log.js.map +1 -0
- package/dist/updates/presence-binder.d.ts +29 -0
- package/dist/updates/presence-binder.d.ts.map +1 -0
- package/dist/updates/presence-binder.js +36 -0
- package/dist/updates/presence-binder.js.map +1 -0
- package/dist/updates/presence.d.ts +31 -0
- package/dist/updates/presence.d.ts.map +1 -0
- package/dist/updates/presence.js +44 -0
- package/dist/updates/presence.js.map +1 -0
- package/dist/updates/push.d.ts +25 -0
- package/dist/updates/push.d.ts.map +1 -0
- package/dist/updates/push.js +72 -0
- package/dist/updates/push.js.map +1 -0
- package/dist/updates/redis-bus.d.ts +45 -0
- package/dist/updates/redis-bus.d.ts.map +1 -0
- package/dist/updates/redis-bus.js +59 -0
- package/dist/updates/redis-bus.js.map +1 -0
- package/dist/updates/redis-presence.d.ts +43 -0
- package/dist/updates/redis-presence.d.ts.map +1 -0
- package/dist/updates/redis-presence.js +65 -0
- package/dist/updates/redis-presence.js.map +1 -0
- package/dist/updates/render.d.ts +16 -0
- package/dist/updates/render.d.ts.map +1 -0
- package/dist/updates/render.js +46 -0
- package/dist/updates/render.js.map +1 -0
- package/dist/updates/router.d.ts +27 -0
- package/dist/updates/router.d.ts.map +1 -0
- package/dist/updates/router.js +36 -0
- package/dist/updates/router.js.map +1 -0
- package/dist/updates/types.d.ts +23 -0
- package/dist/updates/types.d.ts.map +1 -0
- package/dist/updates/types.js +2 -0
- package/dist/updates/types.js.map +1 -0
- package/dist/updates/update-bus.d.ts +29 -0
- package/dist/updates/update-bus.d.ts.map +1 -0
- package/dist/updates/update-bus.js +24 -0
- package/dist/updates/update-bus.js.map +1 -0
- package/dist/util/bytes.d.ts +12 -0
- package/dist/util/bytes.d.ts.map +1 -0
- package/dist/util/bytes.js +46 -0
- package/dist/util/bytes.js.map +1 -0
- package/package.json +84 -0
- package/src/auth/handshake.ts +262 -0
- package/src/auth/nonce-store.ts +39 -0
- package/src/bootstrap.ts +114 -0
- package/src/config.ts +103 -0
- package/src/core/context.ts +94 -0
- package/src/core/errors.ts +52 -0
- package/src/core/index.ts +4 -0
- package/src/core/rpc.ts +165 -0
- package/src/core/updates.ts +69 -0
- package/src/create-server.ts +181 -0
- package/src/crypto/aes-ige.ts +57 -0
- package/src/crypto/dh.ts +101 -0
- package/src/crypto/hashes.ts +17 -0
- package/src/crypto/msg-key.ts +29 -0
- package/src/crypto/rsa.ts +70 -0
- package/src/dispatch/dispatcher.ts +586 -0
- package/src/dispatch/forwarders/in-process.ts +14 -0
- package/src/dispatch/forwarders/print.ts +22 -0
- package/src/dispatch/rpc-forwarder.ts +15 -0
- package/src/dispatch/types.ts +60 -0
- package/src/gateway.ts +214 -0
- package/src/index.ts +53 -0
- package/src/lib.ts +24 -0
- package/src/server/message-pipeline.ts +256 -0
- package/src/session/inbound-tracker.ts +221 -0
- package/src/session/message-id.ts +43 -0
- package/src/session/salts.ts +162 -0
- package/src/session/session-manager.ts +66 -0
- package/src/storage/index.ts +26 -0
- package/src/storage/memory.ts +101 -0
- package/src/storage/mongo.ts +215 -0
- package/src/storage/types.ts +92 -0
- package/src/testkit.ts +19 -0
- package/src/tl/codec.ts +292 -0
- package/src/tl/layered-registry.ts +132 -0
- package/src/tl/protocol.ts +146 -0
- package/src/tl/reader.ts +99 -0
- package/src/tl/registry.ts +78 -0
- package/src/tl/writer.ts +104 -0
- package/src/transport/connection-registry.ts +91 -0
- package/src/transport/connection.ts +113 -0
- package/src/transport/framing.ts +223 -0
- package/src/transport/proxy-protocol.ts +109 -0
- package/src/transport/server-common.ts +49 -0
- package/src/transport/tcp-server.ts +102 -0
- package/src/transport/ws-server.ts +94 -0
- package/src/update-publisher.ts +47 -0
- package/src/updates/mongo-update-log.ts +49 -0
- package/src/updates/presence-binder.ts +51 -0
- package/src/updates/presence.ts +61 -0
- package/src/updates/push.ts +90 -0
- package/src/updates/redis-bus.ts +86 -0
- package/src/updates/redis-presence.ts +87 -0
- package/src/updates/render.ts +53 -0
- package/src/updates/router.ts +52 -0
- package/src/updates/types.ts +24 -0
- package/src/updates/update-bus.ts +49 -0
- package/src/util/bytes.ts +49 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Connection } from '../transport/connection.js';
|
|
2
|
+
import type { TlObject } from '@mt-tl/tl';
|
|
3
|
+
import type { AcceptResult } from '../session/inbound-tracker.js';
|
|
4
|
+
/** Per-message context extracted from the encrypted envelope. */
|
|
5
|
+
export interface MessageContext {
|
|
6
|
+
msgId: bigint;
|
|
7
|
+
seqNo: number;
|
|
8
|
+
sessionId: bigint;
|
|
9
|
+
authKeyId: bigint;
|
|
10
|
+
salt: bigint;
|
|
11
|
+
}
|
|
12
|
+
export interface SendOptions {
|
|
13
|
+
/** Notification (msg_id ends in 3), not a direct response. */
|
|
14
|
+
isNotification?: boolean;
|
|
15
|
+
/** Consumes a seqno slot and gets an odd seqno (default true). */
|
|
16
|
+
contentRelated?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/** Sends an encrypted message back to a connected client. */
|
|
19
|
+
export interface Responder {
|
|
20
|
+
sendEncrypted(conn: Connection, body: TlObject, opts?: SendOptions): void;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Reply to a rejected inbound message (the non-`ok` result of
|
|
24
|
+
* `InboundTracker.accept`): `bad_msg_notification` for a coded violation,
|
|
25
|
+
* `msg_detailed_info` for a duplicate whose answer is cached, or nothing for a
|
|
26
|
+
* benign silent drop. Shared by the pipeline (outer envelope) and the dispatcher
|
|
27
|
+
* (container-inner messages).
|
|
28
|
+
*/
|
|
29
|
+
export declare function replyToBadAccept(responder: Responder, conn: Connection, result: Exclude<AcceptResult, {
|
|
30
|
+
ok: true;
|
|
31
|
+
}>, msgId: bigint, seqNo: number): void;
|
|
32
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/dispatch/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAA;AAEjE,iEAAiE;AACjE,MAAM,WAAW,cAAc;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,WAAW;IACxB,8DAA8D;IAC9D,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,kEAAkE;IAClE,cAAc,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED,6DAA6D;AAC7D,MAAM,WAAW,SAAS;IACtB,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,IAAI,CAAA;CAC5E;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC5B,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,OAAO,CAAC,YAAY,EAAE;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,CAAC,EAC3C,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACd,IAAI,CAqBN"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reply to a rejected inbound message (the non-`ok` result of
|
|
3
|
+
* `InboundTracker.accept`): `bad_msg_notification` for a coded violation,
|
|
4
|
+
* `msg_detailed_info` for a duplicate whose answer is cached, or nothing for a
|
|
5
|
+
* benign silent drop. Shared by the pipeline (outer envelope) and the dispatcher
|
|
6
|
+
* (container-inner messages).
|
|
7
|
+
*/
|
|
8
|
+
export function replyToBadAccept(responder, conn, result, msgId, seqNo) {
|
|
9
|
+
if ('code' in result) {
|
|
10
|
+
responder.sendEncrypted(conn, { _: 'bad_msg_notification', bad_msg_id: msgId, bad_msg_seqno: seqNo, error_code: result.code }, { contentRelated: false });
|
|
11
|
+
}
|
|
12
|
+
else if ('detailed' in result) {
|
|
13
|
+
responder.sendEncrypted(conn, {
|
|
14
|
+
_: 'msg_detailed_info',
|
|
15
|
+
msg_id: msgId,
|
|
16
|
+
answer_msg_id: result.detailed.answerMsgId,
|
|
17
|
+
bytes: result.detailed.bytes,
|
|
18
|
+
status: 0,
|
|
19
|
+
}, { contentRelated: false });
|
|
20
|
+
}
|
|
21
|
+
// { drop: true } → no reply.
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/dispatch/types.ts"],"names":[],"mappings":"AAyBA;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAC5B,SAAoB,EACpB,IAAgB,EAChB,MAA2C,EAC3C,KAAa,EACb,KAAa;IAEb,IAAI,MAAM,IAAI,MAAM,EAAE,CAAC;QACnB,SAAS,CAAC,aAAa,CACnB,IAAI,EACJ,EAAE,CAAC,EAAE,sBAAsB,EAAE,UAAU,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,CAAC,IAAI,EAAE,EAC/F,EAAE,cAAc,EAAE,KAAK,EAAE,CAC5B,CAAA;IACL,CAAC;SAAM,IAAI,UAAU,IAAI,MAAM,EAAE,CAAC;QAC9B,SAAS,CAAC,aAAa,CACnB,IAAI,EACJ;YACI,CAAC,EAAE,mBAAmB;YACtB,MAAM,EAAE,KAAK;YACb,aAAa,EAAE,MAAM,CAAC,QAAQ,CAAC,WAAW;YAC1C,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK;YAC5B,MAAM,EAAE,CAAC;SACZ,EACD,EAAE,cAAc,EAAE,KAAK,EAAE,CAC5B,CAAA;IACL,CAAC;IACD,6BAA6B;AACjC,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type Storage } from './storage/index.js';
|
|
2
|
+
import type { RpcForwarder } from './dispatch/rpc-forwarder.js';
|
|
3
|
+
import { MessagePipeline } from './server/message-pipeline.js';
|
|
4
|
+
import { MtprotoWsServer } from './transport/ws-server.js';
|
|
5
|
+
import { MtprotoTcpServer } from './transport/tcp-server.js';
|
|
6
|
+
import { ConnectionRegistry } from './transport/connection-registry.js';
|
|
7
|
+
import type { Presence } from './updates/presence.js';
|
|
8
|
+
import type { UpdateBus } from './updates/update-bus.js';
|
|
9
|
+
import type { UpdateLog } from './core/updates.js';
|
|
10
|
+
import type { MTProtoConfig } from './config.js';
|
|
11
|
+
import { type Logger } from '@mt-tl/tl';
|
|
12
|
+
import type { MigrationRegistry } from '@mt-tl/tl';
|
|
13
|
+
import type { KeyObject } from 'node:crypto';
|
|
14
|
+
export interface Gateway {
|
|
15
|
+
/** Present when `config.wsPort` is set. */
|
|
16
|
+
wsServer?: MtprotoWsServer;
|
|
17
|
+
/** Present when `config.tcpPort` is set. */
|
|
18
|
+
tcpServer?: MtprotoTcpServer;
|
|
19
|
+
pipeline: MessagePipeline;
|
|
20
|
+
storage: Storage;
|
|
21
|
+
registry: ConnectionRegistry;
|
|
22
|
+
nodeId: string;
|
|
23
|
+
fingerprint: bigint;
|
|
24
|
+
/** Gateway's RSA public key (clients encrypt pq_inner_data with it). */
|
|
25
|
+
publicKey: KeyObject;
|
|
26
|
+
stats: {
|
|
27
|
+
constructors: number;
|
|
28
|
+
methods: number;
|
|
29
|
+
crcMismatches: number;
|
|
30
|
+
layers: number[];
|
|
31
|
+
};
|
|
32
|
+
/** Start every configured carrier. */
|
|
33
|
+
listen(): Promise<void>;
|
|
34
|
+
close(): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
export interface BuildOptions {
|
|
37
|
+
forwarder?: RpcForwarder;
|
|
38
|
+
/** Structured logger for observability; defaults to env-configured. */
|
|
39
|
+
logger?: Logger;
|
|
40
|
+
/** Enables server-push: presence map (Redis in prod, in-memory for tests). */
|
|
41
|
+
presence?: Presence;
|
|
42
|
+
/** Update bus this node consumes routed deliveries from (Redis pub/sub in prod). */
|
|
43
|
+
bus?: UpdateBus;
|
|
44
|
+
/** Per-predicate migration ladders (input up / output down). */
|
|
45
|
+
migrations?: MigrationRegistry;
|
|
46
|
+
/** Durable pts log read by the engine to answer getState/getDifference when managed. */
|
|
47
|
+
updateLog?: UpdateLog;
|
|
48
|
+
/** When true, the engine answers `updates.getState`/`getDifference` from `updateLog`. */
|
|
49
|
+
managedUpdates?: boolean;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Wires the gateway from config: load schema -> codec, RSA key, storage,
|
|
53
|
+
* handshake, dispatcher, pipeline, and the WS carrier. Returns the assembled
|
|
54
|
+
* gateway; call `wsServer.listen()` to start accepting clients.
|
|
55
|
+
*/
|
|
56
|
+
export declare function buildGateway(config: MTProtoConfig, opts?: BuildOptions): Promise<Gateway>;
|
|
57
|
+
//# sourceMappingURL=gateway.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AAKA,OAAO,EAAiB,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAMhE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAE5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAA;AAGvE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,WAAW,CAAA;AACrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE5C,MAAM,WAAW,OAAO;IACpB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,eAAe,CAAA;IAC1B,4CAA4C;IAC5C,SAAS,CAAC,EAAE,gBAAgB,CAAA;IAC5B,QAAQ,EAAE,eAAe,CAAA;IACzB,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,kBAAkB,CAAA;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,wEAAwE;IACxE,SAAS,EAAE,SAAS,CAAA;IACpB,KAAK,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAA;IACzF,sCAAsC;IACtC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACvB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACzB;AAED,MAAM,WAAW,YAAY;IACzB,SAAS,CAAC,EAAE,YAAY,CAAA;IACxB,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,oFAAoF;IACpF,GAAG,CAAC,EAAE,SAAS,CAAA;IACf,gEAAgE;IAChE,UAAU,CAAC,EAAE,iBAAiB,CAAA;IAC9B,wFAAwF;IACxF,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,yFAAyF;IACzF,cAAc,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,GAAE,YAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,CAmJnG"}
|
package/dist/gateway.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { protocolSchemaDir } from '@mt-tl/tl';
|
|
2
|
+
import { loadSchema } from './tl/registry.js';
|
|
3
|
+
import { TlCodec } from './tl/codec.js';
|
|
4
|
+
import { loadLayeredRegistry } from './tl/layered-registry.js';
|
|
5
|
+
import { loadRsaKeyPair } from './crypto/rsa.js';
|
|
6
|
+
import { createStorage } from './storage/index.js';
|
|
7
|
+
import { NonceStore } from './auth/nonce-store.js';
|
|
8
|
+
import { Handshake } from './auth/handshake.js';
|
|
9
|
+
import { SaltService } from './session/salts.js';
|
|
10
|
+
import { Dispatcher } from './dispatch/dispatcher.js';
|
|
11
|
+
import { PrintForwarder } from './dispatch/forwarders/print.js';
|
|
12
|
+
import { MessagePipeline } from './server/message-pipeline.js';
|
|
13
|
+
import { MtprotoWsServer } from './transport/ws-server.js';
|
|
14
|
+
import { MtprotoTcpServer } from './transport/tcp-server.js';
|
|
15
|
+
import { ConnectionRegistry } from './transport/connection-registry.js';
|
|
16
|
+
import { NodePresenceBinder, NoopPresenceBinder } from './updates/presence-binder.js';
|
|
17
|
+
import { PushService } from './updates/push.js';
|
|
18
|
+
import { createLogger } from '@mt-tl/tl';
|
|
19
|
+
/**
|
|
20
|
+
* Wires the gateway from config: load schema -> codec, RSA key, storage,
|
|
21
|
+
* handshake, dispatcher, pipeline, and the WS carrier. Returns the assembled
|
|
22
|
+
* gateway; call `wsServer.listen()` to start accepting clients.
|
|
23
|
+
*/
|
|
24
|
+
export async function buildGateway(config, opts = {}) {
|
|
25
|
+
const logger = opts.logger ?? createLogger({ name: config.nodeId });
|
|
26
|
+
// Merge the framework's protocol schema with the app's business schema — the
|
|
27
|
+
// consumer ships only business `.tl`; the protocol layer lives in @mt-tl/tl.
|
|
28
|
+
const { registry, constructors, methods, crcMismatches } = loadSchema([
|
|
29
|
+
protocolSchemaDir,
|
|
30
|
+
config.schemaDir,
|
|
31
|
+
]);
|
|
32
|
+
const layeredAll = loadLayeredRegistry(config.schemaLayersDir);
|
|
33
|
+
const layered = layeredAll.hasLayers() ? layeredAll : undefined;
|
|
34
|
+
// Decode-union: register every layer's constructor ids so older-layer clients
|
|
35
|
+
// decode by id (the name index keeps the newest, so encode is unaffected).
|
|
36
|
+
for (const def of layeredAll.allDefs())
|
|
37
|
+
registry.register(def);
|
|
38
|
+
const codec = new TlCodec(registry, layered);
|
|
39
|
+
const rsa = loadRsaKeyPair(config.rsaKeyPath);
|
|
40
|
+
const storage = await createStorage(config.storage);
|
|
41
|
+
const saltService = new SaltService(storage.salts);
|
|
42
|
+
const nonceStore = new NonceStore();
|
|
43
|
+
const handshake = new Handshake({
|
|
44
|
+
codec,
|
|
45
|
+
rsa,
|
|
46
|
+
storage,
|
|
47
|
+
saltService,
|
|
48
|
+
nonceStore,
|
|
49
|
+
defaultLayer: config.defaultLayer,
|
|
50
|
+
logger: logger.child({ scope: 'handshake' }),
|
|
51
|
+
});
|
|
52
|
+
const forwarder = opts.forwarder ?? new PrintForwarder(logger.child({ scope: 'rpc' }));
|
|
53
|
+
// Presence / server-push wiring (no-op unless a presence map is supplied).
|
|
54
|
+
const connRegistry = new ConnectionRegistry();
|
|
55
|
+
const binder = opts.presence
|
|
56
|
+
? new NodePresenceBinder(config.nodeId, connRegistry, opts.presence)
|
|
57
|
+
: new NoopPresenceBinder();
|
|
58
|
+
const migrations = opts.migrations;
|
|
59
|
+
const pipeline = new MessagePipeline({
|
|
60
|
+
codec,
|
|
61
|
+
storage,
|
|
62
|
+
handshake,
|
|
63
|
+
saltService,
|
|
64
|
+
defaultLayer: config.defaultLayer,
|
|
65
|
+
binder,
|
|
66
|
+
disableMsgKeyCheck: config.disableMsgKeyCheck,
|
|
67
|
+
disableSeqNoCheck: config.disableSeqNoCheck,
|
|
68
|
+
logger: logger.child({ scope: 'mtproto' }),
|
|
69
|
+
});
|
|
70
|
+
pipeline.dispatcher = new Dispatcher({
|
|
71
|
+
codec,
|
|
72
|
+
registry,
|
|
73
|
+
storage,
|
|
74
|
+
saltService,
|
|
75
|
+
responder: pipeline,
|
|
76
|
+
forwarder,
|
|
77
|
+
binder,
|
|
78
|
+
migrations,
|
|
79
|
+
logger: logger.child({ scope: 'rpc' }),
|
|
80
|
+
disableSeqNoCheck: config.disableSeqNoCheck,
|
|
81
|
+
updateLog: opts.updateLog,
|
|
82
|
+
managedUpdates: opts.managedUpdates,
|
|
83
|
+
allowedApiIds: config.allowedApiIds,
|
|
84
|
+
});
|
|
85
|
+
// Consume routed updates addressed to this node and push to local sockets.
|
|
86
|
+
if (opts.bus && opts.presence) {
|
|
87
|
+
const push = new PushService(connRegistry, pipeline, layered, migrations, logger.child({ scope: 'push' }));
|
|
88
|
+
opts.bus.subscribeNode(config.nodeId, msg => {
|
|
89
|
+
if (msg.authKeyId !== undefined)
|
|
90
|
+
push.deliverToAuthKey(msg.authKeyId, msg.update);
|
|
91
|
+
else if (msg.subject !== undefined)
|
|
92
|
+
push.deliver(msg.subject, msg.update);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
// Refresh presence TTL for locally-connected users + auth keys (heartbeat).
|
|
96
|
+
let heartbeat;
|
|
97
|
+
if (opts.presence) {
|
|
98
|
+
const presence = opts.presence;
|
|
99
|
+
heartbeat = setInterval(() => {
|
|
100
|
+
for (const subject of connRegistry.subjects())
|
|
101
|
+
void presence.add(subject, config.nodeId).catch(() => { });
|
|
102
|
+
for (const authKeyId of connRegistry.authKeys())
|
|
103
|
+
void presence.addAuthKey(authKeyId, config.nodeId).catch(() => { });
|
|
104
|
+
}, Math.max(5_000, Math.floor(config.updates.presenceTtlMs / 3)));
|
|
105
|
+
if (typeof heartbeat.unref === 'function')
|
|
106
|
+
heartbeat.unref();
|
|
107
|
+
}
|
|
108
|
+
const handlers = {
|
|
109
|
+
onPacket: (packet, conn) => pipeline.handlePacket(packet, conn),
|
|
110
|
+
onClose: (conn) => binder.unbind(conn),
|
|
111
|
+
};
|
|
112
|
+
const wsServer = config.wsPort !== undefined
|
|
113
|
+
? new MtprotoWsServer({
|
|
114
|
+
port: config.wsPort,
|
|
115
|
+
defaultLayer: config.defaultLayer,
|
|
116
|
+
trustProxy: config.trustProxy,
|
|
117
|
+
logger,
|
|
118
|
+
}, handlers)
|
|
119
|
+
: undefined;
|
|
120
|
+
const tcpServer = config.tcpPort !== undefined
|
|
121
|
+
? new MtprotoTcpServer({
|
|
122
|
+
port: config.tcpPort,
|
|
123
|
+
defaultLayer: config.defaultLayer,
|
|
124
|
+
trustProxy: config.trustProxy,
|
|
125
|
+
logger,
|
|
126
|
+
}, handlers)
|
|
127
|
+
: undefined;
|
|
128
|
+
return {
|
|
129
|
+
wsServer,
|
|
130
|
+
tcpServer,
|
|
131
|
+
pipeline,
|
|
132
|
+
storage,
|
|
133
|
+
registry: connRegistry,
|
|
134
|
+
nodeId: config.nodeId,
|
|
135
|
+
fingerprint: rsa.fingerprint,
|
|
136
|
+
publicKey: rsa.publicKey,
|
|
137
|
+
stats: { constructors, methods, crcMismatches, layers: layeredAll.layerNumbers() },
|
|
138
|
+
async listen() {
|
|
139
|
+
await Promise.all([wsServer?.listen(), tcpServer?.listen()]);
|
|
140
|
+
},
|
|
141
|
+
async close() {
|
|
142
|
+
if (heartbeat)
|
|
143
|
+
clearInterval(heartbeat);
|
|
144
|
+
wsServer?.close();
|
|
145
|
+
tcpServer?.close();
|
|
146
|
+
await storage.close();
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=gateway.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway.js","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './create-server.js';
|
|
2
|
+
export * from './update-publisher.js';
|
|
3
|
+
export { AppError, BadRequestError, AuthRequiredError, NotFoundError, FloodWaitError, InternalError, defineHook, type Hook, RpcRegistry, dispatchRpc, type DispatchDeps, type HandlerCtx, type RpcMethodSpec, type RpcMethodMap, type RpcModule, type UpdateEmitter, } from './core/index.js';
|
|
4
|
+
export type { MTProtoConfig } from './lib.js';
|
|
5
|
+
export { createLogger, noopLogger, type Logger, type LogLevel, type LoggerOptions, type Fields, } from './lib.js';
|
|
6
|
+
export { MigrationRegistry, type MigrationRung } from '@mt-tl/tl';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AAGrC,OAAO,EAEH,QAAQ,EACR,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,aAAa,EAEb,UAAU,EACV,KAAK,IAAI,EAET,WAAW,EACX,WAAW,EACX,KAAK,YAAY,EAEjB,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,aAAa,GACrB,MAAM,iBAAiB,CAAA;AAGxB,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAM7C,OAAO,EACH,YAAY,EACZ,UAAU,EACV,KAAK,MAAM,EACX,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,MAAM,GACd,MAAM,UAAU,CAAA;AAIjB,OAAO,EAAE,iBAAiB,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// @mt-tl/server — the consumer-facing framework facade. Install this (plus
|
|
2
|
+
// @mt-tl/tl for codegen) to build an MTProto server: define routes, listen.
|
|
3
|
+
// The protocol engine (transport/crypto/session/dispatch) and the handler layer
|
|
4
|
+
// (registry/dispatch/hooks/context/errors, under ./core) live in this same
|
|
5
|
+
// package; this file re-exports just the consumer surface — not the internal
|
|
6
|
+
// event/job/runner machinery.
|
|
7
|
+
export * from './create-server.js'; // createServer, definePlugin, MtprotoServer, Plugin, MethodOpts/Handler
|
|
8
|
+
export * from './update-publisher.js'; // createUpdatePublisher
|
|
9
|
+
// Handler surface (curated from ./core).
|
|
10
|
+
export {
|
|
11
|
+
// errors → rpc_error
|
|
12
|
+
AppError, BadRequestError, AuthRequiredError, NotFoundError, FloodWaitError, InternalError,
|
|
13
|
+
// hooks
|
|
14
|
+
defineHook,
|
|
15
|
+
// dispatch + registry (for tests / advanced wiring)
|
|
16
|
+
RpcRegistry, dispatchRpc, } from './core/index.js';
|
|
17
|
+
// Structured logger (re-exported from @mt-tl/tl) — build one with createLogger
|
|
18
|
+
// and pass it to createServer, or use the same factory in your app code for a
|
|
19
|
+
// unified log style. Handlers get a per-request child via `ctx.log`; the server
|
|
20
|
+
// exposes its root logger as `app.log`. See docs/guide/observability.md.
|
|
21
|
+
export { createLogger, noopLogger, } from './lib.js';
|
|
22
|
+
// Schema-version migration ladders (input `up` / output `down`) — pass via
|
|
23
|
+
// createServer(config, { migrations }). See docs/guide/releasing-a-version.md.
|
|
24
|
+
export { MigrationRegistry } from '@mt-tl/tl';
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,4EAA4E;AAC5E,gFAAgF;AAChF,2EAA2E;AAC3E,6EAA6E;AAC7E,8BAA8B;AAE9B,cAAc,oBAAoB,CAAA,CAAC,wEAAwE;AAC3G,cAAc,uBAAuB,CAAA,CAAC,wBAAwB;AAE9D,yCAAyC;AACzC,OAAO;AACH,qBAAqB;AACrB,QAAQ,EACR,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,aAAa;AACb,QAAQ;AACR,UAAU;AAEV,oDAAoD;AACpD,WAAW,EACX,WAAW,GAQd,MAAM,iBAAiB,CAAA;AAKxB,+EAA+E;AAC/E,8EAA8E;AAC9E,gFAAgF;AAChF,yEAAyE;AACzE,OAAO,EACH,YAAY,EACZ,UAAU,GAKb,MAAM,UAAU,CAAA;AAEjB,2EAA2E;AAC3E,+EAA+E;AAC/E,OAAO,EAAE,iBAAiB,EAAsB,MAAM,WAAW,CAAA"}
|
package/dist/lib.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { bootstrap, type BootstrapOptions, type ForwardHandler, type UpdatePublish } from './bootstrap.js';
|
|
2
|
+
export { buildGateway, type Gateway, type BuildOptions } from './gateway.js';
|
|
3
|
+
export { type MTProtoConfig } from './config.js';
|
|
4
|
+
export { InProcessForwarder } from './dispatch/forwarders/in-process.js';
|
|
5
|
+
export { createLogger, noopLogger, type Logger, type LogLevel, type LoggerOptions, type Fields, } from '@mt-tl/tl';
|
|
6
|
+
export type { RpcForwarder } from './dispatch/rpc-forwarder.js';
|
|
7
|
+
export type { RpcContext, RpcRequest, RpcResponse, SessionEffect } from '@mt-tl/tl';
|
|
8
|
+
export type { UpdateMessage, NodeDelivery } from './updates/types.js';
|
|
9
|
+
export { createRedisPresence, type RedisPresenceHandle } from './updates/redis-presence.js';
|
|
10
|
+
export { createRedisUpdateBus, type RedisBusHandle } from './updates/redis-bus.js';
|
|
11
|
+
export { UpdateRouter, type RouterOptions } from './updates/router.js';
|
|
12
|
+
//# sourceMappingURL=lib.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAC1G,OAAO,EAAE,YAAY,EAAE,KAAK,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAA;AAC5E,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAA;AACxE,OAAO,EACH,YAAY,EACZ,UAAU,EACV,KAAK,MAAM,EACX,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,MAAM,GACd,MAAM,WAAW,CAAA;AAClB,YAAY,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAC/D,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AACnF,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAIrE,OAAO,EAAE,mBAAmB,EAAE,KAAK,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AAC3F,OAAO,EAAE,oBAAoB,EAAE,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAClF,OAAO,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAA"}
|
package/dist/lib.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// The protocol ENGINE's internal API (transport/crypto/session/dispatch wiring).
|
|
2
|
+
// Consumers don't import this directly; they use `createServer()` (./create-server),
|
|
3
|
+
// which wraps it. Env-free and side-effect-free: the caller builds an MTProtoConfig.
|
|
4
|
+
export { bootstrap } from './bootstrap.js';
|
|
5
|
+
export { buildGateway } from './gateway.js';
|
|
6
|
+
export { InProcessForwarder } from './dispatch/forwarders/in-process.js';
|
|
7
|
+
export { createLogger, noopLogger, } from '@mt-tl/tl';
|
|
8
|
+
// Update-delivery adapters — the Redis pub/sub bus + presence behind the
|
|
9
|
+
// in-process push loop (multi-instance), and the router that fans updates to nodes.
|
|
10
|
+
export { createRedisPresence } from './updates/redis-presence.js';
|
|
11
|
+
export { createRedisUpdateBus } from './updates/redis-bus.js';
|
|
12
|
+
export { UpdateRouter } from './updates/router.js';
|
|
13
|
+
//# sourceMappingURL=lib.js.map
|
package/dist/lib.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib.js","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,qFAAqF;AACrF,qFAAqF;AACrF,OAAO,EAAE,SAAS,EAAkE,MAAM,gBAAgB,CAAA;AAC1G,OAAO,EAAE,YAAY,EAAmC,MAAM,cAAc,CAAA;AAE5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAA;AACxE,OAAO,EACH,YAAY,EACZ,UAAU,GAKb,MAAM,WAAW,CAAA;AAKlB,yEAAyE;AACzE,oFAAoF;AACpF,OAAO,EAAE,mBAAmB,EAA4B,MAAM,6BAA6B,CAAA;AAC3F,OAAO,EAAE,oBAAoB,EAAuB,MAAM,wBAAwB,CAAA;AAClF,OAAO,EAAE,YAAY,EAAsB,MAAM,qBAAqB,CAAA"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type Logger } from '@mt-tl/tl';
|
|
2
|
+
import type { TlCodec } from '../tl/codec.js';
|
|
3
|
+
import type { TlObject } from '@mt-tl/tl';
|
|
4
|
+
import type { Connection } from '../transport/connection.js';
|
|
5
|
+
import type { Storage } from '../storage/index.js';
|
|
6
|
+
import type { SaltService } from '../session/salts.js';
|
|
7
|
+
import { Handshake } from '../auth/handshake.js';
|
|
8
|
+
import { Dispatcher } from '../dispatch/dispatcher.js';
|
|
9
|
+
import { type Responder, type SendOptions } from '../dispatch/types.js';
|
|
10
|
+
import type { PresenceBinder } from '../updates/presence-binder.js';
|
|
11
|
+
export interface PipelineDeps {
|
|
12
|
+
codec: TlCodec;
|
|
13
|
+
storage: Storage;
|
|
14
|
+
handshake: Handshake;
|
|
15
|
+
saltService: SaltService;
|
|
16
|
+
defaultLayer: number;
|
|
17
|
+
/** Presence/registry binder; defaults to a no-op (push disabled). */
|
|
18
|
+
binder?: PresenceBinder;
|
|
19
|
+
/**
|
|
20
|
+
* Disable the inbound MTProto 2.0 `msg_key` integrity check.
|
|
21
|
+
*
|
|
22
|
+
* ⚠️ INSECURE — leave this `false` (the default). When `false`, every inbound
|
|
23
|
+
* encrypted message must carry a `msg_key` equal to the 2.0 recompute
|
|
24
|
+
* `SHA256(authKey[88:120] ‖ plaintext)[8:24]`, which authenticates the
|
|
25
|
+
* ciphertext (integrity + binding to the auth key). Setting it `true` makes the
|
|
26
|
+
* gateway accept any ciphertext that merely decrypts to a well-formed message,
|
|
27
|
+
* dropping that authentication — only enable it as a temporary interop shim for
|
|
28
|
+
* non-compliant clients (e.g. ones still on the MTProto 1.0 msg_key scheme; see
|
|
29
|
+
* docs/internals/msgkey-v1-quirk.md).
|
|
30
|
+
*/
|
|
31
|
+
disableMsgKeyCheck?: boolean;
|
|
32
|
+
/** Disable inbound seqno validation (bad_msg codes 32/34/35); default enforced.
|
|
33
|
+
* Interop shim for clients that don't set seqno to spec. */
|
|
34
|
+
disableSeqNoCheck?: boolean;
|
|
35
|
+
/** Observability sink; defaults to a no-op logger. */
|
|
36
|
+
logger?: Logger;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Central message pipeline. Routes plaintext (handshake) vs encrypted packets,
|
|
40
|
+
* performs MTProto 2.0 decrypt/encrypt, ensures the session, and hands decoded
|
|
41
|
+
* payloads to the dispatcher. Implements {@link Responder} so the dispatcher and
|
|
42
|
+
* session manager can send encrypted replies.
|
|
43
|
+
*/
|
|
44
|
+
export declare class MessagePipeline implements Responder {
|
|
45
|
+
private readonly deps;
|
|
46
|
+
/** Set once after construction (the dispatcher needs this pipeline as its Responder). */
|
|
47
|
+
dispatcher: Dispatcher;
|
|
48
|
+
private readonly binder;
|
|
49
|
+
private readonly log;
|
|
50
|
+
constructor(deps: PipelineDeps);
|
|
51
|
+
handlePacket(packet: Buffer, conn: Connection): Promise<void>;
|
|
52
|
+
private handlePlaintext;
|
|
53
|
+
private sendPlain;
|
|
54
|
+
private handleEncrypted;
|
|
55
|
+
sendEncrypted(conn: Connection, body: TlObject, opts?: SendOptions): void;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=message-pipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-pipeline.d.ts","sourceRoot":"","sources":["../../src/server/message-pipeline.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,MAAM,EAAE,MAAM,WAAW,CAAA;AAGnD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AAC5D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AACtD,OAAO,EAAyC,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAM9G,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAGnE,MAAM,WAAW,YAAY;IACzB,KAAK,EAAE,OAAO,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,SAAS,CAAA;IACpB,WAAW,EAAE,WAAW,CAAA;IACxB,YAAY,EAAE,MAAM,CAAA;IACpB,qEAAqE;IACrE,MAAM,CAAC,EAAE,cAAc,CAAA;IACvB;;;;;;;;;;;OAWG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B;iEAC6D;IAC7D,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;;;GAKG;AACH,qBAAa,eAAgB,YAAW,SAAS;IAMjC,OAAO,CAAC,QAAQ,CAAC,IAAI;IALjC,yFAAyF;IACzF,UAAU,EAAG,UAAU,CAAA;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IACvC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAQ;gBAEC,IAAI,EAAE,YAAY;IAYzC,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;YAcrD,eAAe;IAmB7B,OAAO,CAAC,SAAS;YAYH,eAAe;IA2G7B,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,GAAE,WAAgB,GAAG,IAAI;CA+BhF"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { noopLogger } from '@mt-tl/tl';
|
|
3
|
+
import { TlReader } from '../tl/reader.js';
|
|
4
|
+
import { TlWriter } from '../tl/writer.js';
|
|
5
|
+
import { Handshake } from '../auth/handshake.js';
|
|
6
|
+
import { replyToBadAccept } from '../dispatch/types.js';
|
|
7
|
+
import { ensureSession } from '../session/session-manager.js';
|
|
8
|
+
import { messageClass } from '../session/inbound-tracker.js';
|
|
9
|
+
import { igeDecrypt, igeEncrypt } from '../crypto/aes-ige.js';
|
|
10
|
+
import { generateMessageKey, computeMsgKey } from '../crypto/msg-key.js';
|
|
11
|
+
import { toBigIntLE, toBufferLE } from '../util/bytes.js';
|
|
12
|
+
import { NoopPresenceBinder } from '../updates/presence-binder.js';
|
|
13
|
+
/**
|
|
14
|
+
* Central message pipeline. Routes plaintext (handshake) vs encrypted packets,
|
|
15
|
+
* performs MTProto 2.0 decrypt/encrypt, ensures the session, and hands decoded
|
|
16
|
+
* payloads to the dispatcher. Implements {@link Responder} so the dispatcher and
|
|
17
|
+
* session manager can send encrypted replies.
|
|
18
|
+
*/
|
|
19
|
+
export class MessagePipeline {
|
|
20
|
+
deps;
|
|
21
|
+
/** Set once after construction (the dispatcher needs this pipeline as its Responder). */
|
|
22
|
+
dispatcher;
|
|
23
|
+
binder;
|
|
24
|
+
log;
|
|
25
|
+
constructor(deps) {
|
|
26
|
+
this.deps = deps;
|
|
27
|
+
this.binder = deps.binder ?? new NoopPresenceBinder();
|
|
28
|
+
this.log = deps.logger ?? noopLogger;
|
|
29
|
+
// Surface the insecure interop shim ONCE at startup, not per message.
|
|
30
|
+
if (deps.disableMsgKeyCheck) {
|
|
31
|
+
this.log.warn('enc.msgkey.disabled', {
|
|
32
|
+
insecure: true,
|
|
33
|
+
hint: 'inbound ciphertext integrity not verified; see docs/internals/msgkey-v1-quirk.md',
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async handlePacket(packet, conn) {
|
|
38
|
+
if (packet.length < 8) {
|
|
39
|
+
conn.close();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Any inbound traffic resets a pending ping_delay_disconnect idle timer.
|
|
43
|
+
conn.resetDisconnect();
|
|
44
|
+
const isPlaintext = packet.readUInt32LE(0) === 0 && packet.readUInt32LE(4) === 0;
|
|
45
|
+
if (isPlaintext)
|
|
46
|
+
return this.handlePlaintext(packet, conn);
|
|
47
|
+
return this.handleEncrypted(packet, conn);
|
|
48
|
+
}
|
|
49
|
+
// --- plaintext (handshake) ---------------------------------------------
|
|
50
|
+
async handlePlaintext(packet, conn) {
|
|
51
|
+
// [auth_key_id=0 (8)][msg_id (8)][len (4)][body]
|
|
52
|
+
if (packet.length < 20)
|
|
53
|
+
return conn.close();
|
|
54
|
+
const len = packet.readUInt32LE(16);
|
|
55
|
+
if (len < 4 || packet.length < 20 + len)
|
|
56
|
+
return conn.close();
|
|
57
|
+
const reader = new TlReader(packet.subarray(20, 20 + len));
|
|
58
|
+
const id = reader.readUInt32();
|
|
59
|
+
if (!Handshake.isHandshakeId(id))
|
|
60
|
+
return;
|
|
61
|
+
const res = await this.deps.handshake.handle(id, reader);
|
|
62
|
+
if (!res)
|
|
63
|
+
return;
|
|
64
|
+
if ('raw' in res) {
|
|
65
|
+
conn.send(res.raw);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
this.sendPlain(conn, res.reply);
|
|
69
|
+
}
|
|
70
|
+
sendPlain(conn, body) {
|
|
71
|
+
const bodyBuf = this.deps.codec.encode(body);
|
|
72
|
+
const w = new TlWriter(bodyBuf.length + 24);
|
|
73
|
+
w.writeLong(0n); // auth_key_id
|
|
74
|
+
w.writeLong(conn.nextMessageId());
|
|
75
|
+
w.writeUInt32(bodyBuf.length);
|
|
76
|
+
w.writeRaw(bodyBuf);
|
|
77
|
+
conn.send(w.toBuffer());
|
|
78
|
+
}
|
|
79
|
+
// --- encrypted ----------------------------------------------------------
|
|
80
|
+
async handleEncrypted(packet, conn) {
|
|
81
|
+
if (packet.length < 24)
|
|
82
|
+
return conn.close();
|
|
83
|
+
const authKeyId = toBigIntLE(packet.subarray(0, 8));
|
|
84
|
+
const msgKey = packet.subarray(8, 24);
|
|
85
|
+
const ciphertext = packet.subarray(24);
|
|
86
|
+
if (ciphertext.length % 16 !== 0 || ciphertext.length === 0) {
|
|
87
|
+
this.log.debug('enc.badlen', { authKeyId, len: ciphertext.length });
|
|
88
|
+
return conn.close();
|
|
89
|
+
}
|
|
90
|
+
const rec = await this.deps.storage.authKeys.getById(authKeyId);
|
|
91
|
+
this.log.debug('enc.key', { authKeyId, found: !!rec, blocked: rec?.isBlocked });
|
|
92
|
+
if (!rec || rec.isBlocked)
|
|
93
|
+
return conn.close();
|
|
94
|
+
const { aesKey, aesIv } = generateMessageKey(rec.key, msgKey, false);
|
|
95
|
+
const plain = igeDecrypt(ciphertext, aesKey, aesIv);
|
|
96
|
+
// Inbound msg_key integrity (MTProto 2.0): the packet's msg_key must equal
|
|
97
|
+
// SHA256(authKey[88:120] ‖ plaintext)[8:24]. This authenticates the ciphertext
|
|
98
|
+
// and binds it to the auth key. Disabling the check (deps.disableMsgKeyCheck)
|
|
99
|
+
// is insecure and only intended as a temporary interop shim for non-compliant
|
|
100
|
+
// clients — see docs/internals/msgkey-v1-quirk.md.
|
|
101
|
+
if (!this.deps.disableMsgKeyCheck) {
|
|
102
|
+
if (!computeMsgKey(rec.key, plain, false).equals(msgKey)) {
|
|
103
|
+
// Ciphertext failed integrity/binding — a forged or non-2.0 client.
|
|
104
|
+
this.log.warn('enc.msgkey.reject', { authKeyId });
|
|
105
|
+
return conn.close();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const r = new TlReader(plain);
|
|
109
|
+
const salt = r.readLong();
|
|
110
|
+
const sessionId = r.readLong();
|
|
111
|
+
const msgId = r.readLong();
|
|
112
|
+
const seqNo = r.readUInt32();
|
|
113
|
+
const len = r.readUInt32();
|
|
114
|
+
this.log.trace('enc.ok', { authKeyId, len });
|
|
115
|
+
if (len < 4 || len > plain.length)
|
|
116
|
+
return conn.close();
|
|
117
|
+
const payload = r.read(len);
|
|
118
|
+
// Bind auth/session state to the connection.
|
|
119
|
+
conn.ctx.authKeyId = authKeyId;
|
|
120
|
+
conn.ctx.authKey = rec.key;
|
|
121
|
+
conn.ctx.sessionId = sessionId;
|
|
122
|
+
if (rec.meta?.apiLayer && conn.ctx.apiLayer === this.deps.defaultLayer) {
|
|
123
|
+
conn.ctx.apiLayer = rec.meta.apiLayer;
|
|
124
|
+
}
|
|
125
|
+
// Server-salt schedule: advertise the current salt and validate the one the
|
|
126
|
+
// client encrypted with. A wrong/expired salt earns a `bad_server_salt`
|
|
127
|
+
// carrying the current salt — the client re-sends with it — and we drop this
|
|
128
|
+
// message. See docs/internals/protocol-compliance.md.
|
|
129
|
+
const { current, valid } = await this.deps.saltService.resolve(authKeyId, salt);
|
|
130
|
+
conn.ctx.serverSalt = current;
|
|
131
|
+
if (!valid) {
|
|
132
|
+
this.log.debug('salt.bad', { authKeyId });
|
|
133
|
+
this.sendEncrypted(conn, {
|
|
134
|
+
_: 'bad_server_salt',
|
|
135
|
+
bad_msg_id: msgId,
|
|
136
|
+
bad_msg_seqno: seqNo,
|
|
137
|
+
error_code: 48,
|
|
138
|
+
new_server_salt: current,
|
|
139
|
+
}, { contentRelated: false });
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
// Inbound msg_id / seqno validation (https://core.telegram.org/mtproto/description):
|
|
143
|
+
// a wrong/duplicate/out-of-window msg_id (or bad seqno) earns a
|
|
144
|
+
// `bad_msg_notification` and the message is dropped (not dispatched, no session
|
|
145
|
+
// touched). Runs after the salt gate so a salt re-send keeps its original
|
|
146
|
+
// msg_id. Content-relatedness is read from the payload's constructor id. See
|
|
147
|
+
// docs/internals/protocol-compliance.md.
|
|
148
|
+
const seqCheck = !this.deps.disableSeqNoCheck;
|
|
149
|
+
const check = conn.tracker.accept(msgId, seqNo, {
|
|
150
|
+
...messageClass(payload),
|
|
151
|
+
checkSeqNo: seqCheck,
|
|
152
|
+
checkOrder: seqCheck,
|
|
153
|
+
});
|
|
154
|
+
if (!check.ok) {
|
|
155
|
+
this.log.debug('msg.rejected', { authKeyId, result: check });
|
|
156
|
+
replyToBadAccept(this, conn, check, msgId, seqNo);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
await ensureSession(this.deps.storage, this, conn, { sessionId, authKeyId, firstMsgId: msgId, subject: rec.subject ?? undefined }, this.log);
|
|
160
|
+
// Register presence: by auth key (any connection — enables anonymous push)
|
|
161
|
+
// and, for already-authorized keys, by subject.
|
|
162
|
+
this.binder.bindAuthKey(conn, authKeyId.toString());
|
|
163
|
+
if (conn.ctx.subject !== undefined)
|
|
164
|
+
this.binder.bind(conn, conn.ctx.subject);
|
|
165
|
+
const ctx = { msgId, seqNo, sessionId, authKeyId, salt };
|
|
166
|
+
await this.dispatcher.dispatchPayload(payload, ctx, conn);
|
|
167
|
+
}
|
|
168
|
+
// --- Responder ----------------------------------------------------------
|
|
169
|
+
sendEncrypted(conn, body, opts = {}) {
|
|
170
|
+
const authKey = conn.ctx.authKey;
|
|
171
|
+
const authKeyId = conn.ctx.authKeyId;
|
|
172
|
+
if (!authKey || authKeyId === undefined)
|
|
173
|
+
return;
|
|
174
|
+
const bodyBuf = this.deps.codec.encode(body, conn.ctx.apiLayer);
|
|
175
|
+
const outMsgId = conn.nextMessageId(opts.isNotification);
|
|
176
|
+
// Cache the answer to a request so a later duplicate of it gets a
|
|
177
|
+
// `msg_detailed_info` (the request id is the `rpc_result.req_msg_id`).
|
|
178
|
+
if (body._ === 'rpc_result' && typeof body.req_msg_id === 'bigint') {
|
|
179
|
+
conn.tracker.recordAnswer(body.req_msg_id, outMsgId, bodyBuf.length);
|
|
180
|
+
}
|
|
181
|
+
const w = new TlWriter(bodyBuf.length + 32);
|
|
182
|
+
w.writeLong(conn.ctx.serverSalt ?? 0n);
|
|
183
|
+
w.writeLong(conn.ctx.sessionId ?? 0n);
|
|
184
|
+
w.writeLong(outMsgId);
|
|
185
|
+
w.writeUInt32(conn.nextSeqNo(opts.contentRelated ?? true));
|
|
186
|
+
w.writeUInt32(bodyBuf.length);
|
|
187
|
+
w.writeRaw(bodyBuf);
|
|
188
|
+
let plain = w.toBuffer();
|
|
189
|
+
// MTProto 2.0 padding: 12..1024 random bytes, total length divisible by 16.
|
|
190
|
+
const minPad = 12;
|
|
191
|
+
const pad = minPad + ((16 - ((plain.length + minPad) % 16)) % 16);
|
|
192
|
+
plain = Buffer.concat([plain, randomBytes(pad)]);
|
|
193
|
+
const msgKey = computeMsgKey(authKey, plain, true);
|
|
194
|
+
const { aesKey, aesIv } = generateMessageKey(authKey, msgKey, true);
|
|
195
|
+
const ciphertext = igeEncrypt(plain, aesKey, aesIv);
|
|
196
|
+
conn.send(Buffer.concat([toBufferLE(authKeyId, 8), msgKey, ciphertext]));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=message-pipeline.js.map
|