@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,132 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import type { TlDef } from '@mt-tl/tl'
|
|
4
|
+
import { parseType } from '@mt-tl/tl'
|
|
5
|
+
import type { TlValue } from '@mt-tl/tl'
|
|
6
|
+
|
|
7
|
+
interface RawDef {
|
|
8
|
+
id: string
|
|
9
|
+
predicate?: string
|
|
10
|
+
method?: string
|
|
11
|
+
params: Array<{ name: string; type: string }>
|
|
12
|
+
type: string
|
|
13
|
+
}
|
|
14
|
+
interface RawSchema {
|
|
15
|
+
constructors: RawDef[]
|
|
16
|
+
methods: RawDef[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface LayerSchema {
|
|
20
|
+
byName: Map<string, TlDef>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Per-layer schema snapshots, used to encode a result/update with the
|
|
25
|
+
* constructor id and fields valid for a client's negotiated layer (decoding
|
|
26
|
+
* stays global by id). Also answers whether a value is representable at a layer
|
|
27
|
+
* — a pushed update whose type doesn't exist there must be substituted/dropped.
|
|
28
|
+
*/
|
|
29
|
+
export class LayeredRegistry {
|
|
30
|
+
private layers = new Map<number, LayerSchema>()
|
|
31
|
+
private sortedLayers: number[] = []
|
|
32
|
+
|
|
33
|
+
addLayer(layer: number, defs: TlDef[]): void {
|
|
34
|
+
const byName = new Map<string, TlDef>()
|
|
35
|
+
for (const def of defs) if (!byName.has(def.name)) byName.set(def.name, def)
|
|
36
|
+
this.layers.set(layer, { byName })
|
|
37
|
+
this.sortedLayers = [...this.layers.keys()].sort((a, b) => a - b)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
hasLayers(): boolean {
|
|
41
|
+
return this.sortedLayers.length > 0
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Every distinct def across all layers (by id) — for the decode-union registry. */
|
|
45
|
+
allDefs(): TlDef[] {
|
|
46
|
+
const seen = new Set<string>()
|
|
47
|
+
const out: TlDef[] = []
|
|
48
|
+
for (const { byName } of this.layers.values()) {
|
|
49
|
+
for (const def of byName.values()) {
|
|
50
|
+
if (seen.has(def.id)) continue
|
|
51
|
+
seen.add(def.id)
|
|
52
|
+
out.push(def)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return out
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
layerNumbers(): number[] {
|
|
59
|
+
return [...this.sortedLayers]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Largest available layer <= requested (or the smallest if requested is below all). */
|
|
63
|
+
resolveLayer(requested: number): number | undefined {
|
|
64
|
+
if (!this.sortedLayers.length) return undefined
|
|
65
|
+
let best: number | undefined
|
|
66
|
+
for (const layer of this.sortedLayers) {
|
|
67
|
+
if (layer <= requested) best = layer
|
|
68
|
+
else break
|
|
69
|
+
}
|
|
70
|
+
return best ?? this.sortedLayers[0]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
resolve(name: string, layer: number): TlDef | undefined {
|
|
74
|
+
const l = this.resolveLayer(layer)
|
|
75
|
+
if (l === undefined) return undefined
|
|
76
|
+
return this.layers.get(l)?.byName.get(name)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** True iff every constructed type in the value tree exists at `layer`. */
|
|
80
|
+
representable(value: TlValue, layer: number): boolean {
|
|
81
|
+
if (value === null || value === undefined) return true
|
|
82
|
+
if (Array.isArray(value)) return value.every(v => this.representable(v, layer))
|
|
83
|
+
if (Buffer.isBuffer(value)) return true
|
|
84
|
+
if (typeof value === 'object') {
|
|
85
|
+
const obj = value as Record<string, unknown>
|
|
86
|
+
if (typeof obj._ === 'string') {
|
|
87
|
+
if (!this.resolve(obj._, layer)) return false
|
|
88
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
89
|
+
if (k !== '_' && !this.representable(v as TlValue, layer)) return false
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return true
|
|
93
|
+
}
|
|
94
|
+
return true // primitives, bigint, boolean, string, number
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function rawToDef(raw: RawDef, kind: 'constructor' | 'method'): TlDef {
|
|
99
|
+
const id = raw.id.toLowerCase().padStart(8, '0')
|
|
100
|
+
return {
|
|
101
|
+
id,
|
|
102
|
+
idNum: parseInt(id, 16) >>> 0,
|
|
103
|
+
name: (raw.predicate ?? raw.method)!,
|
|
104
|
+
kind,
|
|
105
|
+
params: raw.params.map(p => ({ name: p.name, raw: p.type, type: parseType(p.type) })),
|
|
106
|
+
type: raw.type,
|
|
107
|
+
isProtocol: false,
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Loads `scheme_<layer>.json` snapshots from a directory into a
|
|
113
|
+
* {@link LayeredRegistry}. A missing directory yields an empty registry
|
|
114
|
+
* (layered encoding then disabled — the gateway falls back to single-schema).
|
|
115
|
+
*/
|
|
116
|
+
export function loadLayeredRegistry(dir: string): LayeredRegistry {
|
|
117
|
+
const registry = new LayeredRegistry()
|
|
118
|
+
if (!existsSync(dir)) return registry
|
|
119
|
+
|
|
120
|
+
for (const file of readdirSync(dir)) {
|
|
121
|
+
const m = file.match(/_(\d+)\.json$/)
|
|
122
|
+
if (!m) continue
|
|
123
|
+
const layer = Number(m[1])
|
|
124
|
+
const raw = JSON.parse(readFileSync(join(dir, file), 'utf-8')) as RawSchema
|
|
125
|
+
const defs = [
|
|
126
|
+
...raw.constructors.map(c => rawToDef(c, 'constructor')),
|
|
127
|
+
...(raw.methods ?? []).map(c => rawToDef(c, 'method')),
|
|
128
|
+
]
|
|
129
|
+
registry.addLayer(layer, defs)
|
|
130
|
+
}
|
|
131
|
+
return registry
|
|
132
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { TlObject } from '@mt-tl/tl'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Typed views over the immutable MTProto protocol/service constructors.
|
|
5
|
+
*
|
|
6
|
+
* These are the ~30 types the gateway reads/writes directly during the
|
|
7
|
+
* handshake, session setup and service-message handling. They are still
|
|
8
|
+
* encoded/decoded by the generic {@link TlCodec} (the constructors live in the
|
|
9
|
+
* registry); these interfaces just give the gateway code type-safety when it
|
|
10
|
+
* builds or inspects them. `_` is the constructor predicate.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// --- auth key exchange ------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
export interface ResPQ extends TlObject {
|
|
16
|
+
_: 'resPQ'
|
|
17
|
+
nonce: Buffer // int128
|
|
18
|
+
server_nonce: Buffer // int128
|
|
19
|
+
pq: Buffer // bytes
|
|
20
|
+
server_public_key_fingerprints: bigint[] // Vector<long>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface PQInnerData extends TlObject {
|
|
24
|
+
_: 'p_q_inner_data' | 'p_q_inner_data_dc' | 'p_q_inner_data_temp' | 'p_q_inner_data_temp_dc'
|
|
25
|
+
pq: Buffer
|
|
26
|
+
p: Buffer
|
|
27
|
+
q: Buffer
|
|
28
|
+
nonce: Buffer
|
|
29
|
+
server_nonce: Buffer
|
|
30
|
+
new_nonce: Buffer // int256
|
|
31
|
+
dc?: number
|
|
32
|
+
expires_in?: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ServerDHParamsOk extends TlObject {
|
|
36
|
+
_: 'server_DH_params_ok'
|
|
37
|
+
nonce: Buffer
|
|
38
|
+
server_nonce: Buffer
|
|
39
|
+
encrypted_answer: Buffer
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ServerDHParamsFail extends TlObject {
|
|
43
|
+
_: 'server_DH_params_fail'
|
|
44
|
+
nonce: Buffer
|
|
45
|
+
server_nonce: Buffer
|
|
46
|
+
new_nonce_hash: Buffer
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface ServerDHInnerData extends TlObject {
|
|
50
|
+
_: 'server_DH_inner_data'
|
|
51
|
+
nonce: Buffer
|
|
52
|
+
server_nonce: Buffer
|
|
53
|
+
g: number
|
|
54
|
+
dh_prime: Buffer
|
|
55
|
+
g_a: Buffer
|
|
56
|
+
server_time: number
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface ClientDHInnerData extends TlObject {
|
|
60
|
+
_: 'client_DH_inner_data'
|
|
61
|
+
nonce: Buffer
|
|
62
|
+
server_nonce: Buffer
|
|
63
|
+
retry_id: bigint
|
|
64
|
+
g_b: Buffer
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface DhGenOk extends TlObject {
|
|
68
|
+
_: 'dh_gen_ok'
|
|
69
|
+
nonce: Buffer
|
|
70
|
+
server_nonce: Buffer
|
|
71
|
+
new_nonce_hash1: Buffer
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// --- session / service ------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
export interface NewSessionCreated extends TlObject {
|
|
77
|
+
_: 'new_session_created'
|
|
78
|
+
first_msg_id: bigint
|
|
79
|
+
unique_id: bigint
|
|
80
|
+
server_salt: bigint
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface RpcResult extends TlObject {
|
|
84
|
+
_: 'rpc_result'
|
|
85
|
+
req_msg_id: bigint
|
|
86
|
+
result: TlObject | boolean
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface RpcError extends TlObject {
|
|
90
|
+
_: 'rpc_error'
|
|
91
|
+
error_code: number
|
|
92
|
+
error_message: string
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface Pong extends TlObject {
|
|
96
|
+
_: 'pong'
|
|
97
|
+
msg_id: bigint
|
|
98
|
+
ping_id: bigint
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface MsgsAck extends TlObject {
|
|
102
|
+
_: 'msgs_ack'
|
|
103
|
+
msg_ids: bigint[]
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface BadServerSalt extends TlObject {
|
|
107
|
+
_: 'bad_server_salt'
|
|
108
|
+
bad_msg_id: bigint
|
|
109
|
+
bad_msg_seqno: number
|
|
110
|
+
error_code: number
|
|
111
|
+
new_server_salt: bigint
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface BadMsgNotification extends TlObject {
|
|
115
|
+
_: 'bad_msg_notification'
|
|
116
|
+
bad_msg_id: bigint
|
|
117
|
+
bad_msg_seqno: number
|
|
118
|
+
error_code: number
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface MsgContainer extends TlObject {
|
|
122
|
+
_: 'msg_container'
|
|
123
|
+
messages: TlObject[]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Well-known protocol constructor predicates, for branching in the pipeline. */
|
|
127
|
+
export const Predicate = {
|
|
128
|
+
req_pq: 'req_pq',
|
|
129
|
+
req_pq_multi: 'req_pq_multi',
|
|
130
|
+
req_DH_params: 'req_DH_params',
|
|
131
|
+
set_client_DH_params: 'set_client_DH_params',
|
|
132
|
+
ping: 'ping',
|
|
133
|
+
ping_delay_disconnect: 'ping_delay_disconnect',
|
|
134
|
+
msgs_ack: 'msgs_ack',
|
|
135
|
+
msgs_state_req: 'msgs_state_req',
|
|
136
|
+
rpc_drop_answer: 'rpc_drop_answer',
|
|
137
|
+
destroy_session: 'destroy_session',
|
|
138
|
+
get_future_salts: 'get_future_salts',
|
|
139
|
+
http_wait: 'http_wait',
|
|
140
|
+
msg_container: 'msg_container',
|
|
141
|
+
gzip_packed: 'gzip_packed',
|
|
142
|
+
invokeWithLayer: 'invokeWithLayer',
|
|
143
|
+
invokeWithoutUpdates: 'invokeWithoutUpdates',
|
|
144
|
+
invokeAfterMsg: 'invokeAfterMsg',
|
|
145
|
+
initConnection: 'initConnection',
|
|
146
|
+
} as const
|
package/src/tl/reader.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { toBigIntLE } from '../util/bytes.js'
|
|
2
|
+
|
|
3
|
+
export const BOOL_TRUE_ID = 0x997275b5
|
|
4
|
+
export const BOOL_FALSE_ID = 0xbc799737
|
|
5
|
+
export const VECTOR_ID = 0x1cb5c415
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Low-level reader for the TL wire format. Exposes only primitive reads; the
|
|
9
|
+
* codec drives constructor-id dispatch, vectors and flags. Ported from the
|
|
10
|
+
* existing `protocolBuffer.js` (same little-endian / padding rules).
|
|
11
|
+
*/
|
|
12
|
+
export class TlReader {
|
|
13
|
+
private pos = 0
|
|
14
|
+
|
|
15
|
+
constructor(private readonly buf: Buffer) {}
|
|
16
|
+
|
|
17
|
+
get position(): number {
|
|
18
|
+
return this.pos
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get remaining(): number {
|
|
22
|
+
return this.buf.length - this.pos
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private expect(len: number): void {
|
|
26
|
+
if (this.buf.length < this.pos + len) {
|
|
27
|
+
throw new Error(`TL read out of bounds: need ${len}, have ${this.remaining}`)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
read(len: number): Buffer {
|
|
32
|
+
this.expect(len)
|
|
33
|
+
const out = this.buf.subarray(this.pos, this.pos + len)
|
|
34
|
+
this.pos += len
|
|
35
|
+
return out
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
skip(len: number): void {
|
|
39
|
+
this.expect(len)
|
|
40
|
+
this.pos += len
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
readUInt8(): number {
|
|
44
|
+
this.expect(1)
|
|
45
|
+
return this.buf.readUInt8(this.pos++)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
readUInt32(): number {
|
|
49
|
+
this.expect(4)
|
|
50
|
+
const v = this.buf.readUInt32LE(this.pos)
|
|
51
|
+
this.pos += 4
|
|
52
|
+
return v
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
readInt32(): number {
|
|
56
|
+
this.expect(4)
|
|
57
|
+
const v = this.buf.readInt32LE(this.pos)
|
|
58
|
+
this.pos += 4
|
|
59
|
+
return v
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** TL `long` — 8 bytes LE, read as an unsigned bigint (matches existing server). */
|
|
63
|
+
readLong(): bigint {
|
|
64
|
+
return toBigIntLE(this.read(8))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
readDouble(): number {
|
|
68
|
+
this.expect(8)
|
|
69
|
+
const v = this.buf.readDoubleLE(this.pos)
|
|
70
|
+
this.pos += 8
|
|
71
|
+
return v
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
readInt128(): Buffer {
|
|
75
|
+
return Buffer.from(this.read(16))
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
readInt256(): Buffer {
|
|
79
|
+
return Buffer.from(this.read(32))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** TL `bytes`/`string` — length-prefixed with 4-byte alignment padding. */
|
|
83
|
+
readBytes(): Buffer {
|
|
84
|
+
let extra = 1
|
|
85
|
+
let len = this.readUInt8()
|
|
86
|
+
if (len >= 254) {
|
|
87
|
+
len = this.readUInt8() + this.readUInt8() * 256 + this.readUInt8() * 65536
|
|
88
|
+
extra = 4
|
|
89
|
+
}
|
|
90
|
+
const out = Buffer.from(this.read(len))
|
|
91
|
+
const rem = (len + extra) % 4
|
|
92
|
+
if (rem > 0) this.skip(4 - rem)
|
|
93
|
+
return out
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
readString(): string {
|
|
97
|
+
return this.readBytes().toString('utf-8')
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { TlDef } from '@mt-tl/tl'
|
|
2
|
+
import { parseSchemaDir } from '@mt-tl/tl'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolves TL definitions both ways: by wire constructor id (for decoding) and
|
|
6
|
+
* by name (for encoding a result the backend returned as `{ _: name, ... }`).
|
|
7
|
+
*
|
|
8
|
+
* Hand-written protocol/service classes register a {@link ProtocolCodec} for
|
|
9
|
+
* their id so the pipeline can read/write them with typed code; everything else
|
|
10
|
+
* falls through to the generic, IR-driven codec.
|
|
11
|
+
*/
|
|
12
|
+
export interface ProtocolCodec {
|
|
13
|
+
id: string
|
|
14
|
+
name: string
|
|
15
|
+
read(reader: import('./reader.js').TlReader): unknown
|
|
16
|
+
write(writer: import('./writer.js').TlWriter, value: unknown): void
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class TlRegistry {
|
|
20
|
+
private byId = new Map<number, TlDef>()
|
|
21
|
+
private byName = new Map<string, TlDef>()
|
|
22
|
+
private protocolCodecs = new Map<number, ProtocolCodec>()
|
|
23
|
+
|
|
24
|
+
register(def: TlDef): void {
|
|
25
|
+
if (!this.byId.has(def.idNum)) this.byId.set(def.idNum, def)
|
|
26
|
+
if (!this.byName.has(def.name)) this.byName.set(def.name, def)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
registerProtocolCodec(codec: ProtocolCodec): void {
|
|
30
|
+
this.protocolCodecs.set(parseInt(codec.id, 16) >>> 0, codec)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getById(idNum: number): TlDef | undefined {
|
|
34
|
+
return this.byId.get(idNum >>> 0)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getByName(name: string): TlDef | undefined {
|
|
38
|
+
return this.byName.get(name)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getProtocolCodec(idNum: number): ProtocolCodec | undefined {
|
|
42
|
+
return this.protocolCodecs.get(idNum >>> 0)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get size(): number {
|
|
46
|
+
return this.byId.size
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface LoadSchemaResult {
|
|
51
|
+
registry: TlRegistry
|
|
52
|
+
constructors: number
|
|
53
|
+
methods: number
|
|
54
|
+
crcMismatches: number
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Loads + merges one or more `.tl` schema directories into a single registry.
|
|
59
|
+
* The gateway merges the framework's protocol schema with the app's business
|
|
60
|
+
* schema — pass protocol first (earlier dirs win a name/id clash).
|
|
61
|
+
*/
|
|
62
|
+
export function loadSchema(dirs: string | string[]): LoadSchemaResult {
|
|
63
|
+
const list = Array.isArray(dirs) ? dirs : [dirs]
|
|
64
|
+
const registry = new TlRegistry()
|
|
65
|
+
let constructors = 0
|
|
66
|
+
let methods = 0
|
|
67
|
+
let crcMismatches = 0
|
|
68
|
+
for (const dir of list) {
|
|
69
|
+
const parsed = parseSchemaDir(dir)
|
|
70
|
+
crcMismatches += parsed.crcMismatches.length
|
|
71
|
+
for (const def of parsed.defs) {
|
|
72
|
+
registry.register(def)
|
|
73
|
+
if (def.kind === 'method') methods++
|
|
74
|
+
else constructors++
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { registry, constructors, methods, crcMismatches }
|
|
78
|
+
}
|
package/src/tl/writer.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { toBufferLE } from '../util/bytes.js'
|
|
2
|
+
import { BOOL_FALSE_ID, BOOL_TRUE_ID } from './reader.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Low-level writer for the TL wire format. Grows its backing buffer in 1 KiB
|
|
6
|
+
* steps. Ported from the existing `protocolBuffer.js`.
|
|
7
|
+
*/
|
|
8
|
+
export class TlWriter {
|
|
9
|
+
private buf: Buffer
|
|
10
|
+
private pos = 0
|
|
11
|
+
private readonly step = 1024
|
|
12
|
+
|
|
13
|
+
constructor(preallocate = 1024) {
|
|
14
|
+
this.buf = Buffer.allocUnsafe(preallocate)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
private alloc(len: number): void {
|
|
18
|
+
if (this.buf.length >= this.pos + len) return
|
|
19
|
+
let target = this.buf.length + this.step
|
|
20
|
+
while (target < this.pos + len) target += this.step
|
|
21
|
+
const next = Buffer.allocUnsafe(target)
|
|
22
|
+
this.buf.copy(next, 0, 0, this.pos)
|
|
23
|
+
this.buf = next
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
writeRaw(val: Buffer, len = val.length): this {
|
|
27
|
+
this.alloc(len)
|
|
28
|
+
val.copy(this.buf, this.pos, 0, len)
|
|
29
|
+
this.pos += len
|
|
30
|
+
return this
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
writeUInt8(val: number): this {
|
|
34
|
+
this.alloc(1)
|
|
35
|
+
this.buf.writeUInt8(val & 0xff, this.pos++)
|
|
36
|
+
return this
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
writeUInt32(val: number): this {
|
|
40
|
+
this.alloc(4)
|
|
41
|
+
this.buf.writeUInt32LE(val >>> 0, this.pos)
|
|
42
|
+
this.pos += 4
|
|
43
|
+
return this
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
writeInt32(val: number): this {
|
|
47
|
+
this.alloc(4)
|
|
48
|
+
this.buf.writeInt32LE(val | 0, this.pos)
|
|
49
|
+
this.pos += 4
|
|
50
|
+
return this
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
writeLong(val: bigint): this {
|
|
54
|
+
return this.writeRaw(toBufferLE(BigInt(val), 8), 8)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
writeDouble(val: number): this {
|
|
58
|
+
this.alloc(8)
|
|
59
|
+
this.buf.writeDoubleLE(val, this.pos)
|
|
60
|
+
this.pos += 8
|
|
61
|
+
return this
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** 16/32 raw bytes for int128/int256 (left-padded if a short buffer is given). */
|
|
65
|
+
writeFixed(val: Buffer, len: number): this {
|
|
66
|
+
if (val.length === len) return this.writeRaw(val, len)
|
|
67
|
+
const padded = Buffer.alloc(len)
|
|
68
|
+
val.copy(padded, len - val.length)
|
|
69
|
+
return this.writeRaw(padded, len)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
writeBytes(val: Buffer): this {
|
|
73
|
+
let len: number
|
|
74
|
+
if (val.length < 254) {
|
|
75
|
+
this.writeUInt8(val.length)
|
|
76
|
+
len = 1
|
|
77
|
+
} else {
|
|
78
|
+
// 0xfe marker + 3-byte LE length
|
|
79
|
+
this.writeUInt8(254)
|
|
80
|
+
this.writeUInt32(val.length)
|
|
81
|
+
this.pos--
|
|
82
|
+
len = 4
|
|
83
|
+
}
|
|
84
|
+
this.writeRaw(val)
|
|
85
|
+
len += val.length
|
|
86
|
+
while (len % 4 !== 0) {
|
|
87
|
+
this.writeUInt8(0)
|
|
88
|
+
len++
|
|
89
|
+
}
|
|
90
|
+
return this
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
writeString(val: string): this {
|
|
94
|
+
return this.writeBytes(val ? Buffer.from(val, 'utf-8') : Buffer.alloc(0))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
writeBool(val: boolean): this {
|
|
98
|
+
return this.writeUInt32(val ? BOOL_TRUE_ID : BOOL_FALSE_ID)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
toBuffer(): Buffer {
|
|
102
|
+
return Buffer.from(this.buf.subarray(0, this.pos))
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Connection } from './connection.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Per-node map of authorized subject -> live connections, used to deliver
|
|
5
|
+
* server-pushed updates to the right sockets. A subject (internal user id) may
|
|
6
|
+
* have several connections (multiple devices/sessions) on one node.
|
|
7
|
+
*/
|
|
8
|
+
export class ConnectionRegistry {
|
|
9
|
+
private bySubject = new Map<string, Set<Connection>>()
|
|
10
|
+
private connSubject = new Map<Connection, string>()
|
|
11
|
+
private byAuthKey = new Map<string, Set<Connection>>()
|
|
12
|
+
private connAuthKey = new Map<Connection, string>()
|
|
13
|
+
|
|
14
|
+
register(subject: string, conn: Connection): void {
|
|
15
|
+
const prev = this.connSubject.get(conn)
|
|
16
|
+
if (prev !== undefined && prev !== subject) removeFrom(this.bySubject, prev, conn)
|
|
17
|
+
this.connSubject.set(conn, subject)
|
|
18
|
+
addTo(this.bySubject, subject, conn)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Index a connection by its auth key, for delivery before/without a subject. */
|
|
22
|
+
registerAuthKey(authKeyId: string, conn: Connection): void {
|
|
23
|
+
if (this.connAuthKey.get(conn) === authKeyId) return
|
|
24
|
+
this.connAuthKey.set(conn, authKeyId)
|
|
25
|
+
addTo(this.byAuthKey, authKeyId, conn)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Remove a connection from both indexes; returns the keys it was held under. */
|
|
29
|
+
unregister(conn: Connection): { subject?: string; authKeyId?: string } {
|
|
30
|
+
const subject = this.connSubject.get(conn)
|
|
31
|
+
if (subject !== undefined) {
|
|
32
|
+
this.connSubject.delete(conn)
|
|
33
|
+
removeFrom(this.bySubject, subject, conn)
|
|
34
|
+
}
|
|
35
|
+
const authKeyId = this.connAuthKey.get(conn)
|
|
36
|
+
if (authKeyId !== undefined) {
|
|
37
|
+
this.connAuthKey.delete(conn)
|
|
38
|
+
removeFrom(this.byAuthKey, authKeyId, conn)
|
|
39
|
+
}
|
|
40
|
+
return { subject, authKeyId }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
subjectOf(conn: Connection): string | undefined {
|
|
44
|
+
return this.connSubject.get(conn)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
hasSubject(subject: string): boolean {
|
|
48
|
+
return this.bySubject.has(subject)
|
|
49
|
+
}
|
|
50
|
+
hasAuthKey(authKeyId: string): boolean {
|
|
51
|
+
return this.byAuthKey.has(authKeyId)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getBySubject(subject: string): Connection[] {
|
|
55
|
+
const set = this.bySubject.get(subject)
|
|
56
|
+
return set ? [...set] : []
|
|
57
|
+
}
|
|
58
|
+
getByAuthKey(authKeyId: string): Connection[] {
|
|
59
|
+
const set = this.byAuthKey.get(authKeyId)
|
|
60
|
+
return set ? [...set] : []
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** All subjects with at least one local connection (for presence heartbeat). */
|
|
64
|
+
subjects(): string[] {
|
|
65
|
+
return [...this.bySubject.keys()]
|
|
66
|
+
}
|
|
67
|
+
/** All auth keys with at least one local connection (for presence heartbeat). */
|
|
68
|
+
authKeys(): string[] {
|
|
69
|
+
return [...this.byAuthKey.keys()]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get size(): number {
|
|
73
|
+
return this.connSubject.size
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function addTo<K>(map: Map<K, Set<Connection>>, key: K, conn: Connection): void {
|
|
78
|
+
let set = map.get(key)
|
|
79
|
+
if (!set) {
|
|
80
|
+
set = new Set()
|
|
81
|
+
map.set(key, set)
|
|
82
|
+
}
|
|
83
|
+
set.add(conn)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function removeFrom<K>(map: Map<K, Set<Connection>>, key: K, conn: Connection): void {
|
|
87
|
+
const set = map.get(key)
|
|
88
|
+
if (!set) return
|
|
89
|
+
set.delete(conn)
|
|
90
|
+
if (set.size === 0) map.delete(key)
|
|
91
|
+
}
|