@steve02081504/fount-p2p 0.0.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 +62 -0
- package/core/bytes_codec.mjs +51 -0
- package/core/canonical_json.mjs +21 -0
- package/core/composite_key.mjs +91 -0
- package/core/constants.mjs +53 -0
- package/core/entity_id.mjs +53 -0
- package/core/entity_id_parse.mjs +40 -0
- package/core/evfs_logical_path.mjs +15 -0
- package/core/hexIds.mjs +73 -0
- package/core/hlc.mjs +114 -0
- package/core/random_id.mjs +9 -0
- package/crypto/channel.mjs +101 -0
- package/crypto/checkpoint_sign.mjs +67 -0
- package/crypto/crypto.mjs +158 -0
- package/crypto/key.mjs +431 -0
- package/dag/canonicalize_row.mjs +65 -0
- package/dag/event_query.mjs +15 -0
- package/dag/index.mjs +335 -0
- package/dag/storage.mjs +333 -0
- package/dag/strip_extensions.mjs +18 -0
- package/dag/tunables.json +6 -0
- package/discovery/bt.mjs +260 -0
- package/discovery/index.mjs +134 -0
- package/discovery/mdns.mjs +138 -0
- package/discovery/nostr.mjs +273 -0
- package/entity/files/acl.mjs +38 -0
- package/entity/files/evfs.mjs +189 -0
- package/entity/files/evfs_ref.mjs +37 -0
- package/entity/files/manifest_acl_registry.mjs +93 -0
- package/entity/files/replica_host_cache.mjs +46 -0
- package/entity/files/url.mjs +19 -0
- package/entity/hosting_registry.mjs +47 -0
- package/entity/localized_core.mjs +141 -0
- package/entity/logical_entity.mjs +25 -0
- package/entity/logical_entity_id_registry.mjs +40 -0
- package/entity/node_hash.mjs +15 -0
- package/entity/presentation_registry.mjs +44 -0
- package/entity/profile.mjs +256 -0
- package/entity/replica.mjs +20 -0
- package/entity/session_snapshot_registry.mjs +38 -0
- package/federation/chunk_fetch_pending.mjs +128 -0
- package/federation/chunk_fetch_scheduler.mjs +97 -0
- package/federation/dag_order_cache.mjs +124 -0
- package/federation/dedupe_slot.mjs +33 -0
- package/federation/message_rate_limit.mjs +23 -0
- package/federation/operator_key_chain.mjs +200 -0
- package/federation/topo_order_memo.mjs +44 -0
- package/federation/volatile_streams.mjs +52 -0
- package/federation/want_ids.mjs +158 -0
- package/files/assemble.mjs +220 -0
- package/files/assemble_stream.mjs +129 -0
- package/files/chunk_fetch.mjs +113 -0
- package/files/chunk_fetch_verify.mjs +23 -0
- package/files/chunk_provider_registry.mjs +70 -0
- package/files/chunk_responder.mjs +79 -0
- package/files/chunk_store.mjs +87 -0
- package/files/manifest.mjs +99 -0
- package/files/transfer_key.mjs +87 -0
- package/files/transfer_key_registry.mjs +108 -0
- package/governance/branch.mjs +230 -0
- package/governance/join_pow.mjs +165 -0
- package/governance/owner_succession_ballot.mjs +57 -0
- package/governance/tunables.json +7 -0
- package/index.mjs +39 -0
- package/link/channel_mux.mjs +222 -0
- package/link/frame.mjs +246 -0
- package/link/handshake.mjs +175 -0
- package/link/link.mjs +617 -0
- package/link/rtc.mjs +109 -0
- package/link/sdp_fingerprint.mjs +25 -0
- package/mailbox/consumer_registry.mjs +71 -0
- package/mailbox/deliver_or_store.mjs +190 -0
- package/mailbox/importance.mjs +72 -0
- package/mailbox/parse.mjs +73 -0
- package/mailbox/prune.mjs +106 -0
- package/mailbox/rate.mjs +86 -0
- package/mailbox/settings.mjs +53 -0
- package/mailbox/store.mjs +256 -0
- package/mailbox/tunables.json +12 -0
- package/mailbox/wire.mjs +40 -0
- package/node/denylist.mjs +309 -0
- package/node/entity_store.mjs +214 -0
- package/node/identity.mjs +99 -0
- package/node/instance.mjs +115 -0
- package/node/network.mjs +306 -0
- package/node/personal_block.mjs +286 -0
- package/node/reputation_store.mjs +326 -0
- package/node/retention_policy.mjs +82 -0
- package/node/signaling_config.mjs +32 -0
- package/node/storage.mjs +30 -0
- package/node/storage_plugins.mjs +71 -0
- package/node/user_paths.mjs +10 -0
- package/overlay/index.mjs +191 -0
- package/package.json +73 -0
- package/permissions/bitmask.mjs +35 -0
- package/permissions/evaluator.mjs +90 -0
- package/permissions/index.mjs +12 -0
- package/permissions/layered.mjs +39 -0
- package/registries/action.mjs +94 -0
- package/registries/event_type.mjs +75 -0
- package/registries/inbound.mjs +90 -0
- package/registries/p2p_viewer.mjs +35 -0
- package/registries/part_path.mjs +35 -0
- package/registries/room_provider.mjs +47 -0
- package/reputation/engine.mjs +458 -0
- package/reputation/math.mjs +73 -0
- package/reputation/pick_score.mjs +11 -0
- package/reputation/relay_dedupe.mjs +22 -0
- package/reputation/tunables.json +36 -0
- package/rooms/scoped_link.mjs +185 -0
- package/schemas/discovery.mjs +110 -0
- package/schemas/federation_pull.mjs +102 -0
- package/schemas/mailbox.mjs +37 -0
- package/schemas/remote_event.mjs +17 -0
- package/timeline/append_core.mjs +54 -0
- package/timeline/materialize_runner.mjs +26 -0
- package/timeline/prune.mjs +39 -0
- package/timeline/retention.mjs +54 -0
- package/timeline/retention_runner.mjs +35 -0
- package/timeline/verify_remote.mjs +25 -0
- package/transport/group_link_set.mjs +324 -0
- package/transport/ice_servers.mjs +72 -0
- package/transport/link_registry.mjs +749 -0
- package/transport/peer_identity_maps.mjs +94 -0
- package/transport/peer_pool.mjs +355 -0
- package/transport/remote_user_room.mjs +109 -0
- package/transport/room_scopes.mjs +2 -0
- package/transport/room_wire_action.mjs +19 -0
- package/transport/rtc_connection_budget.mjs +180 -0
- package/transport/rtc_mdns_filter.mjs +118 -0
- package/transport/stale_peer_log.mjs +50 -0
- package/transport/user_room.mjs +263 -0
- package/trust_graph/build.mjs +99 -0
- package/trust_graph/cache.mjs +44 -0
- package/trust_graph/engine.mjs +184 -0
- package/trust_graph/registry.mjs +38 -0
- package/trust_graph/resolve.mjs +105 -0
- package/trust_graph/send.mjs +88 -0
- package/trust_graph/tunables.json +13 -0
- package/utils/async_mutex.mjs +69 -0
- package/utils/debug_log.mjs +16 -0
- package/utils/duration.mjs +28 -0
- package/utils/json_io.mjs +57 -0
- package/utils/lru.mjs +22 -0
- package/utils/map_pool.mjs +27 -0
- package/wire/group_part.mjs +43 -0
- package/wire/ingress.mjs +58 -0
- package/wire/part_common.mjs +55 -0
- package/wire/part_fanout.mjs +71 -0
- package/wire/part_ingress.mjs +172 -0
- package/wire/part_invoke.mjs +81 -0
- package/wire/rate_bucket.mjs +49 -0
- package/wire/volatile_signature.mjs +76 -0
- package/wire/wait.mjs +127 -0
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer'
|
|
2
|
+
import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'node:crypto'
|
|
3
|
+
|
|
4
|
+
import { compareHex64Asc, normalizeHex64 } from '../core/hexIds.mjs'
|
|
5
|
+
import { sha256Hex, keyPairFromSeed } from '../crypto/crypto.mjs'
|
|
6
|
+
import { advertiseTopic, listenSignals, listDiscoveryProviders, registerDiscoveryProvider, sendSignal, subscribeTopic } from '../discovery/index.mjs'
|
|
7
|
+
import { createMdnsDiscoveryProvider } from '../discovery/mdns.mjs'
|
|
8
|
+
import { mergeSignalingRelayUrls, createNostrDiscoveryProvider } from '../discovery/nostr.mjs'
|
|
9
|
+
import { buildSignedAdvert, verifySignedAdvert } from '../link/handshake.mjs'
|
|
10
|
+
import { createLink } from '../link/link.mjs'
|
|
11
|
+
import { ensureNodeSeed, getNodeHash, getNodeTransportSettings } from '../node/identity.mjs'
|
|
12
|
+
import { getSignalingRuntimeConfig } from '../node/instance.mjs'
|
|
13
|
+
import { createOverlayRouter } from '../overlay/index.mjs'
|
|
14
|
+
import { createLruMap } from '../utils/lru.mjs'
|
|
15
|
+
|
|
16
|
+
import { DEFAULT_ICE_SERVERS } from './ice_servers.mjs'
|
|
17
|
+
|
|
18
|
+
const SIGNAL_DOMAIN = 'fount-signal-v1'
|
|
19
|
+
const NODE_TOPIC_DOMAIN = 'fount-rdv-node:'
|
|
20
|
+
const GROUP_TOPIC_DOMAIN = 'fount-rdv-group:'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 由 nodeHash 派生节点 rendezvous topic。
|
|
24
|
+
* @param {string} nodeHash 节点 64 hex
|
|
25
|
+
* @returns {string} rendezvous topic 哈希
|
|
26
|
+
*/
|
|
27
|
+
export function nodeRendezvousTopic(nodeHash) {
|
|
28
|
+
return sha256Hex(`${NODE_TOPIC_DOMAIN}${normalizeHex64(nodeHash)}`)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 由房间密钥派生群组 rendezvous topic。
|
|
33
|
+
* @param {string} roomSecret 房间密钥
|
|
34
|
+
* @returns {string} rendezvous topic 哈希
|
|
35
|
+
*/
|
|
36
|
+
export function groupRendezvousTopic(roomSecret) {
|
|
37
|
+
return sha256Hex(`${GROUP_TOPIC_DOMAIN}${String(roomSecret || '')}`)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 由 topic 派生信令 AES 密钥。
|
|
42
|
+
* @param {string} topic rendezvous 主题
|
|
43
|
+
* @returns {Buffer} AES-256 密钥
|
|
44
|
+
*/
|
|
45
|
+
function signalKeyForTopic(topic) {
|
|
46
|
+
return createHash('sha256').update(`${SIGNAL_DOMAIN}:${String(topic)}`).digest()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 加密信令包为 AES-GCM 字节序列。
|
|
51
|
+
* @param {string} topic rendezvous 主题
|
|
52
|
+
* @param {unknown} packet 待加密 JSON 对象
|
|
53
|
+
* @returns {Uint8Array} 加密后的字节
|
|
54
|
+
*/
|
|
55
|
+
export function encryptSignalPacket(topic, packet) {
|
|
56
|
+
const iv = randomBytes(12)
|
|
57
|
+
const cipher = createCipheriv('aes-256-gcm', signalKeyForTopic(topic), iv)
|
|
58
|
+
const ciphertext = Buffer.concat([
|
|
59
|
+
cipher.update(Buffer.from(JSON.stringify(packet), 'utf8')),
|
|
60
|
+
cipher.final(),
|
|
61
|
+
])
|
|
62
|
+
return Buffer.from(JSON.stringify({
|
|
63
|
+
iv: iv.toString('base64'),
|
|
64
|
+
authTag: cipher.getAuthTag().toString('base64'),
|
|
65
|
+
ciphertext: ciphertext.toString('base64'),
|
|
66
|
+
}))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 解密信令包;失败时返回 null。
|
|
71
|
+
* @param {string} topic rendezvous 主题
|
|
72
|
+
* @param {Uint8Array} bytes 加密字节
|
|
73
|
+
* @returns {object | null} 解密后的 JSON 对象
|
|
74
|
+
*/
|
|
75
|
+
export function decryptSignalPacket(topic, bytes) {
|
|
76
|
+
try {
|
|
77
|
+
const payload = JSON.parse(Buffer.from(bytes).toString('utf8'))
|
|
78
|
+
const decipher = createDecipheriv(
|
|
79
|
+
'aes-256-gcm',
|
|
80
|
+
signalKeyForTopic(topic),
|
|
81
|
+
Buffer.from(payload.iv, 'base64'),
|
|
82
|
+
)
|
|
83
|
+
decipher.setAuthTag(Buffer.from(payload.authTag, 'base64'))
|
|
84
|
+
const plain = Buffer.concat([
|
|
85
|
+
decipher.update(Buffer.from(payload.ciphertext, 'base64')),
|
|
86
|
+
decipher.final(),
|
|
87
|
+
])
|
|
88
|
+
return JSON.parse(plain.toString('utf8'))
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return null
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 解析或从节点种子推导本地身份。
|
|
97
|
+
* @param {{ nodeHash?: string, nodePubKey?: string, secretKey?: Uint8Array } | undefined} localIdentity 可选的预置身份
|
|
98
|
+
* @returns {{ nodeHash: string, nodePubKey: string, secretKey: Uint8Array }} 规范化后的本地身份
|
|
99
|
+
*/
|
|
100
|
+
function resolveLocalIdentity(localIdentity) {
|
|
101
|
+
if (localIdentity?.nodeHash && localIdentity?.nodePubKey && localIdentity?.secretKey)
|
|
102
|
+
return {
|
|
103
|
+
nodeHash: normalizeHex64(localIdentity.nodeHash),
|
|
104
|
+
nodePubKey: normalizeHex64(localIdentity.nodePubKey),
|
|
105
|
+
secretKey: localIdentity.secretKey,
|
|
106
|
+
}
|
|
107
|
+
const secretKey = Buffer.from(ensureNodeSeed(), 'hex')
|
|
108
|
+
const { publicKey } = keyPairFromSeed(secretKey)
|
|
109
|
+
return {
|
|
110
|
+
nodeHash: getNodeHash(),
|
|
111
|
+
nodePubKey: Buffer.from(publicKey).toString('hex'),
|
|
112
|
+
secretKey,
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 创建带 backlog 的缓冲信令会话。
|
|
118
|
+
* @param {(message: unknown) => Promise<void>} sendRemote 远端发送回调
|
|
119
|
+
* @returns {{ send: (message: unknown) => Promise<void>, onRemote: (handler: (message: unknown) => void) => () => void, deliver: (message: unknown) => void, clear: () => void }} 信令会话
|
|
120
|
+
*/
|
|
121
|
+
function createBufferedSignalSession(sendRemote) {
|
|
122
|
+
/** @type {Set<(message: unknown) => void>} */
|
|
123
|
+
const handlers = new Set()
|
|
124
|
+
/** @type {unknown[]} */
|
|
125
|
+
const backlog = []
|
|
126
|
+
return {
|
|
127
|
+
/**
|
|
128
|
+
* 发送信令消息到远端。
|
|
129
|
+
* @param {unknown} message 信令消息
|
|
130
|
+
* @returns {Promise<void>}
|
|
131
|
+
*/
|
|
132
|
+
async send(message) {
|
|
133
|
+
await sendRemote(message)
|
|
134
|
+
},
|
|
135
|
+
/**
|
|
136
|
+
* 注册远端信令 handler(含 backlog 回放)。
|
|
137
|
+
* @param {(message: unknown) => void} handler 入站回调
|
|
138
|
+
* @returns {() => void} 取消订阅函数
|
|
139
|
+
*/
|
|
140
|
+
onRemote(handler) {
|
|
141
|
+
handlers.add(handler)
|
|
142
|
+
for (const pending of backlog.splice(0))
|
|
143
|
+
handler(pending)
|
|
144
|
+
return () => handlers.delete(handler)
|
|
145
|
+
},
|
|
146
|
+
/**
|
|
147
|
+
* 投递信令消息;无 handler 时入 backlog。
|
|
148
|
+
* @param {unknown} message 信令消息
|
|
149
|
+
* @returns {void}
|
|
150
|
+
*/
|
|
151
|
+
deliver(message) {
|
|
152
|
+
if (!handlers.size) {
|
|
153
|
+
backlog.push(message)
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
for (const handler of handlers)
|
|
157
|
+
handler(message)
|
|
158
|
+
},
|
|
159
|
+
/**
|
|
160
|
+
* 清空 backlog 与 handler。
|
|
161
|
+
* @returns {void}
|
|
162
|
+
*/
|
|
163
|
+
clear() {
|
|
164
|
+
backlog.length = 0
|
|
165
|
+
handlers.clear()
|
|
166
|
+
},
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* 向 Map<key, Set<listener>> 订阅并返回取消函数。
|
|
172
|
+
* @param {Map<string, Set<Function>>} buckets 监听器桶
|
|
173
|
+
* @param {string} key 桶键
|
|
174
|
+
* @param {Function} listener 监听器
|
|
175
|
+
* @returns {() => void} 取消订阅函数
|
|
176
|
+
*/
|
|
177
|
+
function subscribeBucket(buckets, key, listener) {
|
|
178
|
+
if (!buckets.has(key)) buckets.set(key, new Set())
|
|
179
|
+
buckets.get(key).add(listener)
|
|
180
|
+
return () => {
|
|
181
|
+
const set = buckets.get(key)
|
|
182
|
+
if (!set) return
|
|
183
|
+
set.delete(listener)
|
|
184
|
+
if (!set.size) buckets.delete(key)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* 创建 P2P 链路注册表(discovery、信令、直连与 overlay relay)。
|
|
190
|
+
* @param {object} [opts] 选项
|
|
191
|
+
* @param {{ nodeHash?: string, nodePubKey?: string, secretKey?: Uint8Array }} [opts.localIdentity] 本地身份
|
|
192
|
+
* @param {typeof createLink} [opts.createLink] 链路工厂(可注入 mock)
|
|
193
|
+
* @param {RTCConfiguration['iceServers']} [opts.iceServers] ICE 服务器列表
|
|
194
|
+
* @param {number} [opts.maxActive] 最大并发活跃链路数
|
|
195
|
+
* @param {boolean} [opts.autoRegisterDiscoveryProviders] 是否自动注册 discovery provider
|
|
196
|
+
* @returns {object} link registry 接口
|
|
197
|
+
*/
|
|
198
|
+
export function createLinkRegistry(opts = {}) {
|
|
199
|
+
const localIdentity = resolveLocalIdentity(opts.localIdentity)
|
|
200
|
+
const createLinkImpl = opts.createLink ?? createLink
|
|
201
|
+
const iceServers = opts.iceServers?.length ? opts.iceServers : DEFAULT_ICE_SERVERS
|
|
202
|
+
const maxActive = Math.max(4, Number(opts.maxActive) || 32)
|
|
203
|
+
const autoRegisterDiscoveryProviders = opts.autoRegisterDiscoveryProviders !== false
|
|
204
|
+
const selfTopic = nodeRendezvousTopic(localIdentity.nodeHash)
|
|
205
|
+
/** @type {Map<string, Awaited<ReturnType<typeof createLinkImpl>>>} */
|
|
206
|
+
const links = new Map()
|
|
207
|
+
/** @type {Map<string, Promise<Awaited<ReturnType<typeof createLinkImpl>> | null>>} 按 nodeHash 去重的主动外拨 */
|
|
208
|
+
const inflights = new Map()
|
|
209
|
+
/** @type {Map<string, ReturnType<typeof createBufferedSignalSession>>} 按 connId 索引的信令会话(每条 PC 一个方向) */
|
|
210
|
+
const signalSessions = new Map()
|
|
211
|
+
/** @type {Map<string, Set<string>>} */
|
|
212
|
+
const scopeInterests = new Map()
|
|
213
|
+
/** @type {Map<string, Set<Function>>} */
|
|
214
|
+
const scopeListeners = new Map()
|
|
215
|
+
/** @type {Map<string, Function>} */
|
|
216
|
+
const scopeAuthorizers = new Map()
|
|
217
|
+
/** @type {Set<(nodeHash: string, link: unknown) => void>} */
|
|
218
|
+
const linkUpListeners = new Set()
|
|
219
|
+
/** @type {Set<(nodeHash: string, reason: string) => void>} */
|
|
220
|
+
const linkDownListeners = new Set()
|
|
221
|
+
const recentAdverts = createLruMap(1024)
|
|
222
|
+
let runtimeStarted = false
|
|
223
|
+
let stopAdvert = null
|
|
224
|
+
let stopSignalListener = null
|
|
225
|
+
let overlayRouter = null
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* 启动 discovery runtime(advert + signal listener)。
|
|
229
|
+
* @returns {Promise<void>}
|
|
230
|
+
*/
|
|
231
|
+
async function ensureDiscoveryRuntime() {
|
|
232
|
+
if (runtimeStarted) return
|
|
233
|
+
runtimeStarted = true
|
|
234
|
+
if (autoRegisterDiscoveryProviders) {
|
|
235
|
+
const providerIds = new Set(listDiscoveryProviders().map(provider => provider.id))
|
|
236
|
+
if (!providerIds.has('mdns'))
|
|
237
|
+
registerDiscoveryProvider(createMdnsDiscoveryProvider())
|
|
238
|
+
if (!providerIds.has('bt'))
|
|
239
|
+
await import('@stoprocent/noble')
|
|
240
|
+
.then(() => import('../discovery/bt.mjs'))
|
|
241
|
+
.then(bt => registerDiscoveryProvider(bt.createBluetoothDiscoveryProvider()))
|
|
242
|
+
.catch(() => { })
|
|
243
|
+
if (!providerIds.has('nostr'))
|
|
244
|
+
// 测试通过 initNode({ signaling: { relayOverride } }) 注入共享 loopback relay;生产则回落到用户 relay + 默认公网 relay。
|
|
245
|
+
// 新 discovery 栈也必须尊重这层 runtime override,否则 live 双节点测试会各打各的公网 relay。
|
|
246
|
+
registerDiscoveryProvider(createNostrDiscoveryProvider({
|
|
247
|
+
relayUrls: getSignalingRuntimeConfig().relayOverride
|
|
248
|
+
?? mergeSignalingRelayUrls(getNodeTransportSettings().relayUrls),
|
|
249
|
+
}))
|
|
250
|
+
}
|
|
251
|
+
if (!listDiscoveryProviders().length) return
|
|
252
|
+
stopSignalListener = await listenSignals(selfTopic, bytes => {
|
|
253
|
+
void handleIncomingSignal(bytes).catch(() => { })
|
|
254
|
+
})
|
|
255
|
+
stopAdvert = await advertiseTopic(selfTopic, encryptSignalPacket(selfTopic, {
|
|
256
|
+
type: 'advert',
|
|
257
|
+
body: await buildSignedAdvert(selfTopic, Date.now(), localIdentity),
|
|
258
|
+
}))
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* 为单条 PC 创建按 connId 标记的信令会话。出站信令都带上 connId,
|
|
263
|
+
* 使同一对节点的两个方向(各自一条 PC)在信令层互不串扰。
|
|
264
|
+
* @param {string} remoteNodeHash 远端节点 64 hex
|
|
265
|
+
* @param {string} connId 连接标识
|
|
266
|
+
* @returns {ReturnType<typeof createBufferedSignalSession>} 信令会话
|
|
267
|
+
*/
|
|
268
|
+
function createConnSession(remoteNodeHash, connId) {
|
|
269
|
+
const normalized = normalizeHex64(remoteNodeHash)
|
|
270
|
+
const topic = nodeRendezvousTopic(normalized)
|
|
271
|
+
return createBufferedSignalSession(async message => {
|
|
272
|
+
await sendSignal(topic, normalized, encryptSignalPacket(topic, {
|
|
273
|
+
type: 'signal',
|
|
274
|
+
from: localIdentity.nodeHash,
|
|
275
|
+
connId,
|
|
276
|
+
body: message,
|
|
277
|
+
}))
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* 判断信令 body 是否为发起方 offer(据此决定入站是否新建应答 PC)。
|
|
283
|
+
* @param {unknown} body 信令 body
|
|
284
|
+
* @returns {boolean} 是 offer 则 true
|
|
285
|
+
*/
|
|
286
|
+
function isOfferSignalBody(body) {
|
|
287
|
+
return !!body && typeof body === 'object'
|
|
288
|
+
&& body.type === 'description'
|
|
289
|
+
&& body.description?.type === 'offer'
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* 保留由较小 nodeHash 发起的那条链(两端对任意一条链算出一致结论),用于双 PC glare 择一。
|
|
294
|
+
* @param {Awaited<ReturnType<typeof createLinkImpl>>} link 链路实例
|
|
295
|
+
* @param {string} remoteNodeHash 远端节点 64 hex
|
|
296
|
+
* @returns {boolean} 本条链应保留则 true
|
|
297
|
+
*/
|
|
298
|
+
function linkIsPreferred(link, remoteNodeHash) {
|
|
299
|
+
const cmp = compareHex64Asc(localIdentity.nodeHash, remoteNodeHash)
|
|
300
|
+
return link.initiator ? cmp < 0 : cmp > 0
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* 为链路绑定 envelope 派发与 down 回调。
|
|
305
|
+
* @param {string} remoteNodeHash 远端节点 64 hex
|
|
306
|
+
* @param {Awaited<ReturnType<typeof createLinkImpl>>} link 链路实例
|
|
307
|
+
* @returns {void}
|
|
308
|
+
*/
|
|
309
|
+
function wireLink(remoteNodeHash, link) {
|
|
310
|
+
link.onEnvelope((envelope, senderNodeHash) => {
|
|
311
|
+
void dispatchEnvelope(senderNodeHash, envelope, link).catch(() => { })
|
|
312
|
+
})
|
|
313
|
+
link.onDown(reason => {
|
|
314
|
+
// 只有关闭的是当前规范链路才对外报 linkDown;双 PC 并存期关掉的败者不应误发 peer-leave。
|
|
315
|
+
const wasCanonical = links.get(remoteNodeHash) === link
|
|
316
|
+
if (!wasCanonical) return
|
|
317
|
+
links.delete(remoteNodeHash)
|
|
318
|
+
for (const listener of linkDownListeners)
|
|
319
|
+
try { listener(remoteNodeHash, reason) } catch { /* ignore */ }
|
|
320
|
+
})
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* 注册已建立的链路,并做双 PC glare 择一:同一对节点若因双向同时发起而建成两条 PC,
|
|
325
|
+
* 保留由较小 nodeHash 发起的那条(两端一致),关闭另一条。单条链路(常态)直接安装。
|
|
326
|
+
* @param {string} remoteNodeHash 远端节点 64 hex
|
|
327
|
+
* @param {Awaited<ReturnType<typeof createLinkImpl>>} candidate 候选链路
|
|
328
|
+
* @returns {Promise<void>}
|
|
329
|
+
*/
|
|
330
|
+
async function registerResolvedLink(remoteNodeHash, candidate) {
|
|
331
|
+
const normalized = normalizeHex64(remoteNodeHash)
|
|
332
|
+
const existing = links.get(normalized)
|
|
333
|
+
// 已有规范链且候选不优先:候选出局,保留现有。
|
|
334
|
+
if (existing && existing !== candidate && !linkIsPreferred(candidate, normalized)) {
|
|
335
|
+
await candidate.close('glare-loser')
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
// 候选胜出(或无现有):先把 candidate 设为规范链,再关旧链——
|
|
339
|
+
// 这样旧链 onDown 时 links.get 已指向 candidate,不会被误判为规范链而对外报 linkDown。
|
|
340
|
+
links.set(normalized, candidate)
|
|
341
|
+
wireLink(normalized, candidate)
|
|
342
|
+
if (existing && existing !== candidate)
|
|
343
|
+
await existing.close('glare-replaced')
|
|
344
|
+
for (const listener of linkUpListeners)
|
|
345
|
+
try { listener(normalized, candidate) } catch { /* ignore */ }
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* 为一条 connId 会话建 PC 并走 glare 择一:建 link → 绑 onDown 清 session → ready → registerResolvedLink。
|
|
350
|
+
* initiator 侧建链前先 trimToBudget 腾预算。出错则清理该 connId 会话。
|
|
351
|
+
* @param {{ remoteNodeHash: string, connId: string, session: ReturnType<typeof createBufferedSignalSession>, initiator: boolean }} opts 建链参数
|
|
352
|
+
* @returns {Promise<Awaited<ReturnType<typeof createLinkImpl>> | null>} 当前规范链(本条可能因 glare 被关,取 links 现值);失败 null
|
|
353
|
+
*/
|
|
354
|
+
async function buildConnLink({ remoteNodeHash, connId, session, initiator }) {
|
|
355
|
+
try {
|
|
356
|
+
if (initiator) await trimToBudget()
|
|
357
|
+
const link = await createLinkImpl({ nodeHash: remoteNodeHash, initiator, signal: session, iceServers, localIdentity })
|
|
358
|
+
link.onDown(() => signalSessions.delete(connId))
|
|
359
|
+
await link.ready
|
|
360
|
+
await registerResolvedLink(remoteNodeHash, link)
|
|
361
|
+
return links.get(remoteNodeHash) ?? null
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
signalSessions.delete(connId)
|
|
365
|
+
return null
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* 处理入站加密信令并可能发起被动建链。
|
|
371
|
+
* @param {Uint8Array} bytes 加密信令字节
|
|
372
|
+
* @returns {Promise<void>}
|
|
373
|
+
*/
|
|
374
|
+
async function handleIncomingSignal(bytes) {
|
|
375
|
+
const packet = decryptSignalPacket(selfTopic, bytes)
|
|
376
|
+
if (!packet || packet.type !== 'signal') return
|
|
377
|
+
const remoteNodeHash = normalizeHex64(packet.from)
|
|
378
|
+
const connId = String(packet.connId || '')
|
|
379
|
+
if (!remoteNodeHash || remoteNodeHash === localIdentity.nodeHash || !connId) return
|
|
380
|
+
let session = signalSessions.get(connId)
|
|
381
|
+
if (!session) {
|
|
382
|
+
// 只有全新的 offer 才开一条独立应答 PC;answer/ice 若无对应 connId 会话则是迟到/无效帧,丢弃。
|
|
383
|
+
// 应答 PC 按 connId 独立创建,不受按 nodeHash 去重的 inflight 阻挡——这是支持双向同时建链的关键。
|
|
384
|
+
if (!isOfferSignalBody(packet.body)) return
|
|
385
|
+
session = createConnSession(remoteNodeHash, connId)
|
|
386
|
+
signalSessions.set(connId, session)
|
|
387
|
+
void buildConnLink({ remoteNodeHash, connId, session, initiator: false })
|
|
388
|
+
}
|
|
389
|
+
session.deliver(packet.body)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* 计算节点在 scope 兴趣中的权重(用于 eviction)。
|
|
394
|
+
* @param {string} remoteNodeHash 远端节点 64 hex
|
|
395
|
+
* @returns {number} 权重值
|
|
396
|
+
*/
|
|
397
|
+
function scopeWeight(remoteNodeHash) {
|
|
398
|
+
let weight = 0
|
|
399
|
+
for (const hashes of scopeInterests.values())
|
|
400
|
+
if (hashes.has(remoteNodeHash)) weight++
|
|
401
|
+
return weight
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* 超出 maxActive 时驱逐权重最低的链路。
|
|
406
|
+
* @returns {Promise<void>}
|
|
407
|
+
*/
|
|
408
|
+
async function trimToBudget() {
|
|
409
|
+
if (links.size < maxActive) return
|
|
410
|
+
const candidates = [...links.entries()]
|
|
411
|
+
.sort((left, right) => scopeWeight(left[0]) - scopeWeight(right[0]) || compareHex64Asc(left[0], right[0]))
|
|
412
|
+
const victim = candidates[0]
|
|
413
|
+
if (victim) await victim[1].close('budget-evict')
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* 主动建链到远端节点(initiator)。
|
|
418
|
+
* @param {string} remoteNodeHash 远端节点 64 hex
|
|
419
|
+
* @returns {Promise<Awaited<ReturnType<typeof createLinkImpl>> | null>} 链路实例;失败时 null
|
|
420
|
+
*/
|
|
421
|
+
async function ensureDirectLinkToNode(remoteNodeHash) {
|
|
422
|
+
await ensureDiscoveryRuntime()
|
|
423
|
+
const normalized = normalizeHex64(remoteNodeHash)
|
|
424
|
+
if (!normalized || normalized === localIdentity.nodeHash) return null
|
|
425
|
+
if (links.has(normalized)) return links.get(normalized)
|
|
426
|
+
if (inflights.has(normalized)) return await inflights.get(normalized)
|
|
427
|
+
// 想连就直拨(单向零额外成本);若对端也在同时拨,双方各建一条 PC,由 registerResolvedLink 择一。
|
|
428
|
+
// buildConnLink 成功时返回的是规范链而非本条 candidate——glare 双 PC 择一时本条可能是被关闭的败者。
|
|
429
|
+
const connId = randomBytes(16).toString('hex')
|
|
430
|
+
const session = createConnSession(normalized, connId)
|
|
431
|
+
signalSessions.set(connId, session)
|
|
432
|
+
const task = buildConnLink({ remoteNodeHash: normalized, connId, session, initiator: true })
|
|
433
|
+
.finally(() => inflights.delete(normalized))
|
|
434
|
+
inflights.set(normalized, task)
|
|
435
|
+
return await task
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* 经已有直连发送 envelope。
|
|
440
|
+
* @param {string} remoteNodeHash 远端节点 64 hex
|
|
441
|
+
* @param {{ scope: string, action: string, payload: unknown }} envelope 信封
|
|
442
|
+
* @returns {Promise<boolean>} 是否发送成功
|
|
443
|
+
*/
|
|
444
|
+
async function sendDirectToNodeLink(remoteNodeHash, envelope) {
|
|
445
|
+
const normalized = normalizeHex64(remoteNodeHash)
|
|
446
|
+
if (!normalized) return false
|
|
447
|
+
const link = links.get(normalized)
|
|
448
|
+
if (!link) return false
|
|
449
|
+
try {
|
|
450
|
+
return await link.send(envelope)
|
|
451
|
+
}
|
|
452
|
+
catch {
|
|
453
|
+
return false
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* 懒创建 overlay 路由器。
|
|
459
|
+
* @returns {ReturnType<typeof createOverlayRouter>} overlay 路由器
|
|
460
|
+
*/
|
|
461
|
+
function getOverlayRouter() {
|
|
462
|
+
if (overlayRouter) return overlayRouter
|
|
463
|
+
overlayRouter = createOverlayRouter({
|
|
464
|
+
localIdentity,
|
|
465
|
+
sendToNodeLink: sendDirectToNodeLink,
|
|
466
|
+
/**
|
|
467
|
+
* 列出当前所有活跃链路。
|
|
468
|
+
* @returns {Array<{ nodeHash: string, link: Awaited<ReturnType<typeof createLinkImpl>> }>} 链路列表
|
|
469
|
+
*/
|
|
470
|
+
listLinks() {
|
|
471
|
+
return [...links.entries()].map(([nodeHash, link]) => ({ nodeHash, link }))
|
|
472
|
+
},
|
|
473
|
+
subscribeScope,
|
|
474
|
+
})
|
|
475
|
+
overlayRouter.onRelay((body, meta) => {
|
|
476
|
+
void dispatchEnvelope(meta.path[0], body, null).catch(() => { })
|
|
477
|
+
})
|
|
478
|
+
return overlayRouter
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* 经 overlay 多跳 relay envelope 到无直连的节点。
|
|
483
|
+
* @param {string} remoteNodeHash 远端节点 64 hex
|
|
484
|
+
* @param {{ scope: string, action: string, payload: unknown }} envelope 信封
|
|
485
|
+
* @returns {Promise<boolean>} 是否 relay 成功
|
|
486
|
+
*/
|
|
487
|
+
async function relayEnvelopeToNode(remoteNodeHash, envelope) {
|
|
488
|
+
if (!links.size || envelope?.scope === 'overlay') return false
|
|
489
|
+
try {
|
|
490
|
+
const path = await getOverlayRouter().discoverRoute(remoteNodeHash)
|
|
491
|
+
await getOverlayRouter().relay(path, envelope)
|
|
492
|
+
return true
|
|
493
|
+
}
|
|
494
|
+
catch {
|
|
495
|
+
return false
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* 确保到远端节点的链路(直连或被动应答)。
|
|
501
|
+
* @param {string} remoteNodeHash 远端节点 64 hex
|
|
502
|
+
* @returns {Promise<Awaited<ReturnType<typeof createLinkImpl>> | null>} 链路实例
|
|
503
|
+
*/
|
|
504
|
+
async function ensureLinkToNode(remoteNodeHash) {
|
|
505
|
+
return await ensureDirectLinkToNode(remoteNodeHash)
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* 发送 envelope:优先直连,失败则 overlay relay。
|
|
510
|
+
* @param {string} remoteNodeHash 远端节点 64 hex
|
|
511
|
+
* @param {{ scope: string, action: string, payload: unknown }} envelope 信封
|
|
512
|
+
* @returns {Promise<boolean>} 是否发送成功
|
|
513
|
+
*/
|
|
514
|
+
async function sendToNodeLink(remoteNodeHash, envelope) {
|
|
515
|
+
return await sendDirectToNodeLink(remoteNodeHash, envelope)
|
|
516
|
+
|| await relayEnvelopeToNode(remoteNodeHash, envelope)
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* 将入站 envelope 派发到 scope 监听器(经 authorizer 校验)。
|
|
521
|
+
* @param {string} senderNodeHash 发送方节点 64 hex
|
|
522
|
+
* @param {{ scope: string, action: string, payload: unknown }} envelope 信封
|
|
523
|
+
* @param {Awaited<ReturnType<typeof createLinkImpl>>} link 来源链路
|
|
524
|
+
* @returns {Promise<void>}
|
|
525
|
+
*/
|
|
526
|
+
async function dispatchEnvelope(senderNodeHash, envelope, link) {
|
|
527
|
+
const scope = String(envelope?.scope || '')
|
|
528
|
+
for (const [prefix, authorizer] of scopeAuthorizers.entries())
|
|
529
|
+
if (scope.startsWith(prefix)) {
|
|
530
|
+
const allowed = await Promise.resolve(authorizer(scope, senderNodeHash, envelope, link))
|
|
531
|
+
if (!allowed) return
|
|
532
|
+
}
|
|
533
|
+
for (const [prefix, listeners] of scopeListeners.entries())
|
|
534
|
+
if (scope.startsWith(prefix))
|
|
535
|
+
for (const listener of listeners)
|
|
536
|
+
await Promise.resolve(listener(senderNodeHash, envelope, link))
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* 订阅指定 scope 前缀的 envelope。
|
|
541
|
+
* @param {string} prefix scope 前缀
|
|
542
|
+
* @param {(senderNodeHash: string, envelope: { scope: string, action: string, payload: unknown }, link: Awaited<ReturnType<typeof createLinkImpl>>) => void | Promise<void>} listener 监听器
|
|
543
|
+
* @returns {() => void} 取消订阅函数
|
|
544
|
+
*/
|
|
545
|
+
function subscribeScope(prefix, listener) {
|
|
546
|
+
return subscribeBucket(scopeListeners, String(prefix), listener)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* 注册 scope 前缀的 authorizer(入站校验)。
|
|
551
|
+
* @param {string} prefix scope 前缀
|
|
552
|
+
* @param {(scope: string, senderNodeHash: string, envelope: object, link: Awaited<ReturnType<typeof createLinkImpl>>) => boolean | Promise<boolean>} authorizer 校验函数
|
|
553
|
+
* @returns {() => void} 取消注册函数
|
|
554
|
+
*/
|
|
555
|
+
function registerScopeAuthorizer(prefix, authorizer) {
|
|
556
|
+
scopeAuthorizers.set(String(prefix), authorizer)
|
|
557
|
+
return () => scopeAuthorizers.delete(String(prefix))
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return {
|
|
561
|
+
localIdentity,
|
|
562
|
+
ensureRuntime: ensureDiscoveryRuntime,
|
|
563
|
+
ensureLinkToNode,
|
|
564
|
+
/**
|
|
565
|
+
* 获取到指定节点的活跃链路。
|
|
566
|
+
* @param {string} nodeHash 节点 64 hex
|
|
567
|
+
* @returns {Awaited<ReturnType<typeof createLinkImpl>> | null} 链路实例;不存在时 null
|
|
568
|
+
*/
|
|
569
|
+
getLink(nodeHash) {
|
|
570
|
+
return links.get(normalizeHex64(nodeHash)) || null
|
|
571
|
+
},
|
|
572
|
+
/**
|
|
573
|
+
* 列出所有活跃链路。
|
|
574
|
+
* @returns {Array<{ nodeHash: string, link: Awaited<ReturnType<typeof createLinkImpl>> }>} 链路列表
|
|
575
|
+
*/
|
|
576
|
+
listLinks() {
|
|
577
|
+
return [...links.entries()].map(([nodeHash, link]) => ({ nodeHash, link }))
|
|
578
|
+
},
|
|
579
|
+
/**
|
|
580
|
+
* 关闭到指定节点的链路。
|
|
581
|
+
* @param {string} nodeHash 节点 64 hex
|
|
582
|
+
* @param {string} [reason='manual-close'] 关闭原因
|
|
583
|
+
* @returns {Promise<void>}
|
|
584
|
+
*/
|
|
585
|
+
async closeLink(nodeHash, reason = 'manual-close') {
|
|
586
|
+
const normalized = normalizeHex64(nodeHash)
|
|
587
|
+
const link = links.get(normalized)
|
|
588
|
+
if (!link) return
|
|
589
|
+
await link.close(reason)
|
|
590
|
+
},
|
|
591
|
+
sendToNodeLink,
|
|
592
|
+
/**
|
|
593
|
+
* 订阅链路建立事件。
|
|
594
|
+
* @param {(nodeHash: string, link: unknown) => void} listener 回调
|
|
595
|
+
* @returns {() => void} 取消订阅函数
|
|
596
|
+
*/
|
|
597
|
+
onLinkUp(listener) {
|
|
598
|
+
linkUpListeners.add(listener)
|
|
599
|
+
return () => linkUpListeners.delete(listener)
|
|
600
|
+
},
|
|
601
|
+
/**
|
|
602
|
+
* 订阅链路断开事件。
|
|
603
|
+
* @param {(nodeHash: string, reason: string) => void} listener 回调
|
|
604
|
+
* @returns {() => void} 取消订阅函数
|
|
605
|
+
*/
|
|
606
|
+
onLinkDown(listener) {
|
|
607
|
+
linkDownListeners.add(listener)
|
|
608
|
+
return () => linkDownListeners.delete(listener)
|
|
609
|
+
},
|
|
610
|
+
/**
|
|
611
|
+
* 注册 scope 兴趣成员(影响 eviction 权重)。
|
|
612
|
+
* @param {string} scope scope 名称
|
|
613
|
+
* @param {string[]} nodeHashes 成员 nodeHash 列表
|
|
614
|
+
* @returns {void}
|
|
615
|
+
*/
|
|
616
|
+
registerScopeInterest(scope, nodeHashes) {
|
|
617
|
+
scopeInterests.set(String(scope), new Set((Array.isArray(nodeHashes) ? nodeHashes : []).map(normalizeHex64).filter(Boolean)))
|
|
618
|
+
},
|
|
619
|
+
/**
|
|
620
|
+
* 释放 scope 兴趣。
|
|
621
|
+
* @param {string} scope scope 名称
|
|
622
|
+
* @returns {void}
|
|
623
|
+
*/
|
|
624
|
+
releaseScopeInterest(scope) {
|
|
625
|
+
scopeInterests.delete(String(scope))
|
|
626
|
+
},
|
|
627
|
+
registerScopeAuthorizer,
|
|
628
|
+
subscribeScope,
|
|
629
|
+
/**
|
|
630
|
+
* 订阅指定节点的 advert 广播。
|
|
631
|
+
* @param {string} nodeHash 目标节点 64 hex
|
|
632
|
+
* @param {(verifiedNodeHash: string, body: object) => void | Promise<void>} onAdvert advert 回调
|
|
633
|
+
* @returns {Promise<() => void>} 取消订阅函数
|
|
634
|
+
*/
|
|
635
|
+
async subscribeNodeAdvert(nodeHash, onAdvert) {
|
|
636
|
+
const topic = nodeRendezvousTopic(nodeHash)
|
|
637
|
+
return await subscribeTopic(topic, async bytes => {
|
|
638
|
+
const packet = decryptSignalPacket(topic, bytes)
|
|
639
|
+
if (packet?.type !== 'advert') return
|
|
640
|
+
const verifiedNodeHash = await verifySignedAdvert(topic, packet.body)
|
|
641
|
+
if (!verifiedNodeHash) return
|
|
642
|
+
recentAdverts.touch(verifiedNodeHash, Date.now())
|
|
643
|
+
await Promise.resolve(onAdvert(verifiedNodeHash, packet.body))
|
|
644
|
+
})
|
|
645
|
+
},
|
|
646
|
+
recentAdverts,
|
|
647
|
+
relayEnvelopeToNode,
|
|
648
|
+
/**
|
|
649
|
+
* 关闭 registry:停止 discovery、overlay 并断开所有链路。
|
|
650
|
+
* @returns {Promise<void>}
|
|
651
|
+
*/
|
|
652
|
+
async shutdown() {
|
|
653
|
+
stopAdvert?.()
|
|
654
|
+
stopSignalListener?.()
|
|
655
|
+
overlayRouter?.close()
|
|
656
|
+
overlayRouter = null
|
|
657
|
+
for (const link of links.values())
|
|
658
|
+
await link.close('registry-shutdown')
|
|
659
|
+
links.clear()
|
|
660
|
+
inflights.clear()
|
|
661
|
+
for (const session of signalSessions.values()) session.clear()
|
|
662
|
+
signalSessions.clear()
|
|
663
|
+
},
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
let defaultRegistry = null
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* 获取进程级默认 link registry 单例。
|
|
671
|
+
* @returns {ReturnType<typeof createLinkRegistry>} 默认 registry
|
|
672
|
+
*/
|
|
673
|
+
export function getLinkRegistry() {
|
|
674
|
+
if (!defaultRegistry) defaultRegistry = createLinkRegistry()
|
|
675
|
+
return defaultRegistry
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* 默认 registry 的 ensureLinkToNode 代理。
|
|
680
|
+
* @param {...any} args 转发参数
|
|
681
|
+
* @returns {ReturnType<ReturnType<typeof createLinkRegistry>['ensureLinkToNode']>} 链路实例
|
|
682
|
+
*/
|
|
683
|
+
export const ensureLinkToNode = (...args) => getLinkRegistry().ensureLinkToNode(...args)
|
|
684
|
+
/**
|
|
685
|
+
* 默认 registry 的 getLink 代理。
|
|
686
|
+
* @param {...any} args 转发参数
|
|
687
|
+
* @returns {ReturnType<ReturnType<typeof createLinkRegistry>['getLink']>} 链路实例
|
|
688
|
+
*/
|
|
689
|
+
export const getLink = (...args) => getLinkRegistry().getLink(...args)
|
|
690
|
+
/**
|
|
691
|
+
* 默认 registry 的 listLinks 代理。
|
|
692
|
+
* @param {...any} args 转发参数
|
|
693
|
+
* @returns {ReturnType<ReturnType<typeof createLinkRegistry>['listLinks']>} 链路列表
|
|
694
|
+
*/
|
|
695
|
+
export const listLinks = (...args) => getLinkRegistry().listLinks(...args)
|
|
696
|
+
/**
|
|
697
|
+
* 默认 registry 的 closeLink 代理。
|
|
698
|
+
* @param {...any} args 转发参数
|
|
699
|
+
* @returns {ReturnType<ReturnType<typeof createLinkRegistry>['closeLink']>} 关闭完成
|
|
700
|
+
*/
|
|
701
|
+
export const closeLink = (...args) => getLinkRegistry().closeLink(...args)
|
|
702
|
+
/**
|
|
703
|
+
* 默认 registry 的 sendToNodeLink 代理。
|
|
704
|
+
* @param {...any} args 转发参数
|
|
705
|
+
* @returns {ReturnType<ReturnType<typeof createLinkRegistry>['sendToNodeLink']>} 是否成功
|
|
706
|
+
*/
|
|
707
|
+
export const sendToNodeLink = (...args) => getLinkRegistry().sendToNodeLink(...args)
|
|
708
|
+
/**
|
|
709
|
+
* 默认 registry 的 relayEnvelopeToNode 代理。
|
|
710
|
+
* @param {...any} args 转发参数
|
|
711
|
+
* @returns {ReturnType<ReturnType<typeof createLinkRegistry>['relayEnvelopeToNode']>} 是否成功
|
|
712
|
+
*/
|
|
713
|
+
export const relayEnvelopeToNode = (...args) => getLinkRegistry().relayEnvelopeToNode(...args)
|
|
714
|
+
/**
|
|
715
|
+
* 默认 registry 的 onLinkUp 代理。
|
|
716
|
+
* @param {...any} args 转发参数
|
|
717
|
+
* @returns {ReturnType<ReturnType<typeof createLinkRegistry>['onLinkUp']>} 取消订阅函数
|
|
718
|
+
*/
|
|
719
|
+
export const onLinkUp = (...args) => getLinkRegistry().onLinkUp(...args)
|
|
720
|
+
/**
|
|
721
|
+
* 默认 registry 的 onLinkDown 代理。
|
|
722
|
+
* @param {...any} args 转发参数
|
|
723
|
+
* @returns {ReturnType<ReturnType<typeof createLinkRegistry>['onLinkDown']>} 取消订阅函数
|
|
724
|
+
*/
|
|
725
|
+
export const onLinkDown = (...args) => getLinkRegistry().onLinkDown(...args)
|
|
726
|
+
/**
|
|
727
|
+
* 默认 registry 的 registerScopeInterest 代理。
|
|
728
|
+
* @param {...any} args 转发参数
|
|
729
|
+
* @returns {ReturnType<ReturnType<typeof createLinkRegistry>['registerScopeInterest']>} 无返回值
|
|
730
|
+
*/
|
|
731
|
+
export const registerScopeInterest = (...args) => getLinkRegistry().registerScopeInterest(...args)
|
|
732
|
+
/**
|
|
733
|
+
* 默认 registry 的 releaseScopeInterest 代理。
|
|
734
|
+
* @param {...any} args 转发参数
|
|
735
|
+
* @returns {ReturnType<ReturnType<typeof createLinkRegistry>['releaseScopeInterest']>} 无返回值
|
|
736
|
+
*/
|
|
737
|
+
export const releaseScopeInterest = (...args) => getLinkRegistry().releaseScopeInterest(...args)
|
|
738
|
+
/**
|
|
739
|
+
* 默认 registry 的 registerScopeAuthorizer 代理。
|
|
740
|
+
* @param {...any} args 转发参数
|
|
741
|
+
* @returns {ReturnType<ReturnType<typeof createLinkRegistry>['registerScopeAuthorizer']>} 取消注册函数
|
|
742
|
+
*/
|
|
743
|
+
export const registerScopeAuthorizer = (...args) => getLinkRegistry().registerScopeAuthorizer(...args)
|
|
744
|
+
/**
|
|
745
|
+
* 默认 registry 的 subscribeScope 代理。
|
|
746
|
+
* @param {...any} args 转发参数
|
|
747
|
+
* @returns {ReturnType<ReturnType<typeof createLinkRegistry>['subscribeScope']>} 取消订阅函数
|
|
748
|
+
*/
|
|
749
|
+
export const subscribeScope = (...args) => getLinkRegistry().subscribeScope(...args)
|