@steve02081504/fount-p2p 0.0.10 → 0.0.12
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 +25 -7
- package/core/bytes_codec.mjs +65 -10
- package/core/tcp_port.mjs +1 -1
- package/crypto/checkpoint_sign.mjs +2 -2
- package/dag/canonicalize_row.mjs +3 -3
- package/dag/index.mjs +2 -3
- package/discovery/bt/index.mjs +5 -5
- package/discovery/bt/probe_child.mjs +20 -0
- package/discovery/bt/runtime.mjs +59 -15
- package/discovery/index.mjs +45 -48
- package/discovery/mdns.mjs +72 -17
- package/discovery/nostr.mjs +165 -129
- package/federation/chunk_fetch_pending.mjs +4 -5
- package/federation/dag_order_cache.mjs +1 -1
- package/federation/entity_key_chain.mjs +3 -4
- package/federation/topo_order_memo.mjs +11 -4
- package/files/chunk_fetch.mjs +2 -2
- package/files/evfs.mjs +2 -2
- package/link/channel_mux.mjs +10 -10
- package/link/frame.mjs +76 -124
- package/link/handshake.mjs +5 -5
- package/link/pipe.mjs +49 -75
- package/link/providers/ble_gatt.mjs +15 -27
- package/link/providers/index.mjs +7 -9
- package/link/providers/lan_tcp.mjs +18 -33
- package/link/providers/link_id_pipe.mjs +46 -0
- package/link/providers/webrtc.mjs +43 -52
- package/link/rtc.mjs +21 -9
- package/mailbox/deliver_or_store.mjs +1 -1
- package/node/identity.mjs +4 -4
- package/node/network.mjs +9 -9
- package/overlay/index.mjs +13 -13
- package/package.json +3 -2
- package/rooms/scoped_link.mjs +22 -38
- package/schemas/discovery.mjs +1 -2
- package/schemas/federation_pull.mjs +2 -2
- package/schemas/part_query.mjs +1 -1
- package/schemas/remote_event.mjs +1 -1
- package/transport/advert_ingest.mjs +20 -0
- package/transport/group_link_set.mjs +26 -45
- package/transport/ice_servers.mjs +12 -3
- package/transport/link_registry.mjs +181 -523
- package/transport/offer_answer.mjs +194 -0
- package/transport/peer_pool.mjs +53 -64
- package/transport/remote_user_room.mjs +13 -15
- package/transport/rtc_connection_budget.mjs +18 -4
- package/transport/runtime_bootstrap.mjs +369 -0
- package/transport/signal_crypto.mjs +104 -0
- package/transport/user_room.mjs +22 -15
- package/trust_graph/send.mjs +1 -1
- package/utils/emit_safe.mjs +11 -0
- package/utils/shuffle.mjs +13 -0
- package/utils/ttl_map.mjs +29 -4
- package/wire/ingress.mjs +1 -1
- package/wire/part_ingress.mjs +6 -8
- package/wire/part_invoke.mjs +4 -4
- package/wire/part_query.mjs +2 -3
- package/wire/part_query.tunables.json +6 -1
- package/wire/part_query_cache.mjs +1 -1
- package/wire/volatile_signature.mjs +1 -1
|
@@ -1,106 +1,19 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer'
|
|
2
|
-
import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'node:crypto'
|
|
3
2
|
|
|
4
3
|
import { compareHex64Asc, normalizeHex64 } from '../core/hexIds.mjs'
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { createMdnsDiscoveryProvider } from '../discovery/mdns.mjs'
|
|
10
|
-
import { mergeSignalingRelayUrls, createNostrDiscoveryProvider } from '../discovery/nostr.mjs'
|
|
11
|
-
import { buildSignedAdvert, verifySignedAdvert } from '../link/handshake.mjs'
|
|
12
|
-
import { createBleGattLinkProvider } from '../link/providers/ble_gatt.mjs'
|
|
13
|
-
import {
|
|
14
|
-
listAvailableLinkProviders,
|
|
15
|
-
listLinkProviders,
|
|
16
|
-
registerLinkProvider,
|
|
17
|
-
unregisterLinkProvider,
|
|
18
|
-
} from '../link/providers/index.mjs'
|
|
19
|
-
import { createLanTcpLinkProvider } from '../link/providers/lan_tcp.mjs'
|
|
20
|
-
import { createWebRtcLinkProvider } from '../link/providers/webrtc.mjs'
|
|
21
|
-
import { ensureNodeSeed, getNodeHash, getNodeTransportSettings } from '../node/identity.mjs'
|
|
22
|
-
import { getSignalingRuntimeConfig } from '../node/instance.mjs'
|
|
4
|
+
import { keyPairFromSeed } from '../crypto/crypto.mjs'
|
|
5
|
+
import { subscribeTopic } from '../discovery/index.mjs'
|
|
6
|
+
import { listLinkProviders } from '../link/providers/index.mjs'
|
|
7
|
+
import { ensureNodeSeed, getNodeHash } from '../node/identity.mjs'
|
|
23
8
|
import { createOverlayRouter } from '../overlay/index.mjs'
|
|
9
|
+
import { emitSafe } from '../utils/emit_safe.mjs'
|
|
24
10
|
import { createLruMap } from '../utils/lru.mjs'
|
|
25
11
|
|
|
12
|
+
import { ingestSignedAdvert } from './advert_ingest.mjs'
|
|
26
13
|
import { DEFAULT_ICE_SERVERS } from './ice_servers.mjs'
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const GROUP_TOPIC_DOMAIN = 'fount-rdv-group:'
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* 由 nodeHash 派生节点 rendezvous topic。
|
|
34
|
-
* @param {string} nodeHash 节点 64 hex
|
|
35
|
-
* @returns {string} rendezvous topic 哈希
|
|
36
|
-
*/
|
|
37
|
-
export function nodeRendezvousTopic(nodeHash) {
|
|
38
|
-
return sha256Hex(`${NODE_TOPIC_DOMAIN}${normalizeHex64(nodeHash)}`)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* 由房间密钥派生群组 rendezvous topic。
|
|
43
|
-
* @param {string} roomSecret 房间密钥
|
|
44
|
-
* @returns {string} rendezvous topic 哈希
|
|
45
|
-
*/
|
|
46
|
-
export function groupRendezvousTopic(roomSecret) {
|
|
47
|
-
return sha256Hex(`${GROUP_TOPIC_DOMAIN}${String(roomSecret || '')}`)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* 由 topic 派生信令 AES 密钥。
|
|
52
|
-
* @param {string} topic rendezvous 主题
|
|
53
|
-
* @returns {Buffer} AES-256 密钥
|
|
54
|
-
*/
|
|
55
|
-
function signalKeyForTopic(topic) {
|
|
56
|
-
return createHash('sha256').update(`${SIGNAL_DOMAIN}:${String(topic)}`).digest()
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* 加密信令包为 AES-GCM 字节序列。
|
|
61
|
-
* @param {string} topic rendezvous 主题
|
|
62
|
-
* @param {unknown} packet 待加密 JSON 对象
|
|
63
|
-
* @returns {Uint8Array} 加密后的字节
|
|
64
|
-
*/
|
|
65
|
-
export function encryptSignalPacket(topic, packet) {
|
|
66
|
-
const iv = randomBytes(12)
|
|
67
|
-
const cipher = createCipheriv('aes-256-gcm', signalKeyForTopic(topic), iv)
|
|
68
|
-
const ciphertext = Buffer.concat([
|
|
69
|
-
cipher.update(Buffer.from(JSON.stringify(packet), 'utf8')),
|
|
70
|
-
cipher.final(),
|
|
71
|
-
])
|
|
72
|
-
return Buffer.from(JSON.stringify({
|
|
73
|
-
iv: iv.toString('base64'),
|
|
74
|
-
authTag: cipher.getAuthTag().toString('base64'),
|
|
75
|
-
ciphertext: ciphertext.toString('base64'),
|
|
76
|
-
}))
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* 解密信令包;失败时返回 null。
|
|
81
|
-
* @param {string} topic rendezvous 主题
|
|
82
|
-
* @param {Uint8Array} bytes 加密字节
|
|
83
|
-
* @returns {object | null} 解密后的 JSON 对象
|
|
84
|
-
*/
|
|
85
|
-
export function decryptSignalPacket(topic, bytes) {
|
|
86
|
-
try {
|
|
87
|
-
const payload = JSON.parse(Buffer.from(bytes).toString('utf8'))
|
|
88
|
-
const decipher = createDecipheriv(
|
|
89
|
-
'aes-256-gcm',
|
|
90
|
-
signalKeyForTopic(topic),
|
|
91
|
-
Buffer.from(payload.iv, 'base64'),
|
|
92
|
-
)
|
|
93
|
-
decipher.setAuthTag(Buffer.from(payload.authTag, 'base64'))
|
|
94
|
-
const plain = Buffer.concat([
|
|
95
|
-
decipher.update(Buffer.from(payload.ciphertext, 'base64')),
|
|
96
|
-
decipher.final(),
|
|
97
|
-
])
|
|
98
|
-
return JSON.parse(plain.toString('utf8'))
|
|
99
|
-
}
|
|
100
|
-
catch {
|
|
101
|
-
return null
|
|
102
|
-
}
|
|
103
|
-
}
|
|
14
|
+
import { createOfferAnswerDial } from './offer_answer.mjs'
|
|
15
|
+
import { createRuntimeBootstrap } from './runtime_bootstrap.mjs'
|
|
16
|
+
import { nodeRendezvousTopic } from './signal_crypto.mjs'
|
|
104
17
|
|
|
105
18
|
/**
|
|
106
19
|
* 解析或从节点种子推导本地身份。
|
|
@@ -123,60 +36,6 @@ function resolveLocalIdentity(localIdentity) {
|
|
|
123
36
|
}
|
|
124
37
|
}
|
|
125
38
|
|
|
126
|
-
/**
|
|
127
|
-
* 创建带 backlog 的缓冲信令会话。
|
|
128
|
-
* @param {(message: unknown) => Promise<void>} sendRemote 远端发送回调
|
|
129
|
-
* @returns {{ send: (message: unknown) => Promise<void>, onRemote: (handler: (message: unknown) => void) => () => void, deliver: (message: unknown) => void, clear: () => void }} 信令会话
|
|
130
|
-
*/
|
|
131
|
-
function createBufferedSignalSession(sendRemote) {
|
|
132
|
-
/** @type {Set<(message: unknown) => void>} */
|
|
133
|
-
const handlers = new Set()
|
|
134
|
-
/** @type {unknown[]} */
|
|
135
|
-
const backlog = []
|
|
136
|
-
return {
|
|
137
|
-
/**
|
|
138
|
-
* 发送信令消息到远端。
|
|
139
|
-
* @param {unknown} message 信令消息
|
|
140
|
-
* @returns {Promise<void>}
|
|
141
|
-
*/
|
|
142
|
-
async send(message) {
|
|
143
|
-
await sendRemote(message)
|
|
144
|
-
},
|
|
145
|
-
/**
|
|
146
|
-
* 注册远端信令 handler(含 backlog 回放)。
|
|
147
|
-
* @param {(message: unknown) => void} handler 入站回调
|
|
148
|
-
* @returns {() => void} 取消订阅函数
|
|
149
|
-
*/
|
|
150
|
-
onRemote(handler) {
|
|
151
|
-
handlers.add(handler)
|
|
152
|
-
for (const pending of backlog.splice(0))
|
|
153
|
-
handler(pending)
|
|
154
|
-
return () => handlers.delete(handler)
|
|
155
|
-
},
|
|
156
|
-
/**
|
|
157
|
-
* 投递信令消息;无 handler 时入 backlog。
|
|
158
|
-
* @param {unknown} message 信令消息
|
|
159
|
-
* @returns {void}
|
|
160
|
-
*/
|
|
161
|
-
deliver(message) {
|
|
162
|
-
if (!handlers.size) {
|
|
163
|
-
backlog.push(message)
|
|
164
|
-
return
|
|
165
|
-
}
|
|
166
|
-
for (const handler of handlers)
|
|
167
|
-
handler(message)
|
|
168
|
-
},
|
|
169
|
-
/**
|
|
170
|
-
* 清空 backlog 与 handler。
|
|
171
|
-
* @returns {void}
|
|
172
|
-
*/
|
|
173
|
-
clear() {
|
|
174
|
-
backlog.length = 0
|
|
175
|
-
handlers.clear()
|
|
176
|
-
},
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
39
|
/**
|
|
181
40
|
* 向 Map<key, Set<listener>> 订阅并返回取消函数。
|
|
182
41
|
* @param {Map<string, Set<Function>>} buckets 监听器桶
|
|
@@ -209,14 +68,12 @@ export function createLinkRegistry(options = {}) {
|
|
|
209
68
|
const localIdentity = resolveLocalIdentity(options.localIdentity)
|
|
210
69
|
const iceServers = options.iceServers?.length ? options.iceServers : DEFAULT_ICE_SERVERS
|
|
211
70
|
const maxActive = Math.max(4, Number(options.maxActive) || 32)
|
|
212
|
-
const autoRegisterDiscoveryProviders = options.autoRegisterDiscoveryProviders !== false
|
|
213
|
-
const autoRegisterLinkProviders = options.autoRegisterLinkProviders !== false
|
|
214
71
|
const selfTopic = nodeRendezvousTopic(localIdentity.nodeHash)
|
|
215
72
|
/** @type {Map<string, object>} */
|
|
216
73
|
const links = new Map()
|
|
217
|
-
/** @type {Map<string, Promise<object | null>>}
|
|
74
|
+
/** @type {Map<string, Promise<object | null>>} */
|
|
218
75
|
const inflights = new Map()
|
|
219
|
-
/** @type {Map<string, ReturnType<typeof createBufferedSignalSession>>}
|
|
76
|
+
/** @type {Map<string, ReturnType<typeof import('./offer_answer.mjs').createBufferedSignalSession>>} */
|
|
220
77
|
const signalSessions = new Map()
|
|
221
78
|
/** @type {Map<string, Set<string>>} */
|
|
222
79
|
const scopeInterests = new Map()
|
|
@@ -229,180 +86,23 @@ export function createLinkRegistry(options = {}) {
|
|
|
229
86
|
/** @type {Set<(nodeHash: string, reason: string) => void>} */
|
|
230
87
|
const linkDownListeners = new Set()
|
|
231
88
|
const recentAdverts = createLruMap(1024)
|
|
232
|
-
let runtimeStarted = false
|
|
233
|
-
let stopAdvert = null
|
|
234
|
-
let stopSignalListener = null
|
|
235
|
-
/** @type {Array<() => void>} */
|
|
236
|
-
const stopLinkListeners = []
|
|
237
89
|
let overlayRouter = null
|
|
238
|
-
/** @type {ReturnType<typeof createLanTcpLinkProvider> | null} 本 registry 持有的 lan_tcp 实例 */
|
|
239
|
-
let ownedLanTcp = null
|
|
240
|
-
/** @type {ReturnType<typeof createBleGattLinkProvider> | null} 本 registry 持有的 ble_gatt 实例 */
|
|
241
|
-
let ownedBleGatt = null
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* 自动注册默认 LinkProvider(lan_tcp / webrtc / ble_gatt)。
|
|
245
|
-
* lan_tcp / ble_gatt 每 registry 一个实例(避免 ensureListening 覆盖他人的 localIdentity);
|
|
246
|
-
* webrtc 按 id 单例注册。拨号时再经 isAvailable / canReach 跳过。
|
|
247
|
-
* @returns {Promise<void>}
|
|
248
|
-
*/
|
|
249
|
-
async function ensureLinkProviders() {
|
|
250
|
-
if (!autoRegisterLinkProviders) return
|
|
251
|
-
if (!ownedLanTcp) {
|
|
252
|
-
ownedLanTcp = createLanTcpLinkProvider()
|
|
253
|
-
registerLinkProvider(ownedLanTcp)
|
|
254
|
-
}
|
|
255
|
-
if (!ownedBleGatt) {
|
|
256
|
-
ownedBleGatt = createBleGattLinkProvider()
|
|
257
|
-
registerLinkProvider(ownedBleGatt)
|
|
258
|
-
}
|
|
259
|
-
const ids = new Set(listLinkProviders().map(provider => provider.id))
|
|
260
|
-
if (!ids.has('webrtc'))
|
|
261
|
-
registerLinkProvider(createWebRtcLinkProvider())
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* 本 registry 的 LAN TCP 监听端口。
|
|
266
|
-
* @returns {number | null} listen 端口;未 listen 为 null
|
|
267
|
-
*/
|
|
268
|
-
function resolveLanTcpPort() {
|
|
269
|
-
const endpoint = typeof ownedLanTcp?.localEndpoint === 'function' ? ownedLanTcp.localEndpoint() : null
|
|
270
|
-
return normalizeTcpPort(endpoint?.port)
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* 构造带本机身份与(若已 listen)tcpPort 的签名 advert。
|
|
275
|
-
* node / group / scoped 广播共用,保证 LAN hint 可从任意 topic 学到。
|
|
276
|
-
* @param {string} topic 广播主题
|
|
277
|
-
* @returns {Promise<{ nodeHash: string, nodePubKey: string, ts: number, sig: string, tcpPort?: number }>} 签名 advert
|
|
278
|
-
*/
|
|
279
|
-
async function buildLocalAdvert(topic) {
|
|
280
|
-
const tcpPort = resolveLanTcpPort()
|
|
281
|
-
return await buildSignedAdvert(topic, Date.now(), {
|
|
282
|
-
...localIdentity,
|
|
283
|
-
...tcpPort != null ? { tcpPort } : {},
|
|
284
|
-
})
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* 启动 discovery + link provider listening。
|
|
289
|
-
* 先 ensureListening(拿到 lan_tcp 端口),再广播带 tcpPort 的 advert。
|
|
290
|
-
* @returns {Promise<void>}
|
|
291
|
-
*/
|
|
292
|
-
async function ensureDiscoveryRuntime() {
|
|
293
|
-
if (runtimeStarted) return
|
|
294
|
-
runtimeStarted = true
|
|
295
|
-
await ensureLinkProviders()
|
|
296
|
-
if (autoRegisterDiscoveryProviders) {
|
|
297
|
-
const providerIds = new Set(listDiscoveryProviders().map(provider => provider.id))
|
|
298
|
-
if (!providerIds.has('mdns'))
|
|
299
|
-
registerDiscoveryProvider(createMdnsDiscoveryProvider())
|
|
300
|
-
if (!providerIds.has('bt')) {
|
|
301
|
-
const bt = await import('../discovery/bt/index.mjs').catch(() => null)
|
|
302
|
-
if (await bt?.canUseBluetoothDiscovery?.())
|
|
303
|
-
registerDiscoveryProvider(bt.createBluetoothDiscoveryProvider())
|
|
304
|
-
}
|
|
305
|
-
if (!providerIds.has('nostr'))
|
|
306
|
-
// 测试通过 initNode({ signaling: { relayOverride } }) 注入共享 loopback relay;生产则回落到用户 relay + 默认公网 relay。
|
|
307
|
-
// 新 discovery 栈也必须尊重这层 runtime override,否则 live 双节点测试会各打各的公网 relay。
|
|
308
|
-
registerDiscoveryProvider(createNostrDiscoveryProvider({
|
|
309
|
-
relayUrls: getSignalingRuntimeConfig().relayOverride
|
|
310
|
-
?? mergeSignalingRelayUrls(getNodeTransportSettings().relayUrls),
|
|
311
|
-
}))
|
|
312
|
-
}
|
|
313
|
-
// 只对本 registry 持有的 lan_tcp / ble_gatt 调用 ensureListening。
|
|
314
|
-
// 切勿遍历其它 registry 的 lan_tcp:* / ble_gatt:* ——会覆盖其 localIdentity/onInbound。
|
|
315
|
-
/** @type {import('../link/providers/index.mjs').LinkProvider[]} */
|
|
316
|
-
const listenProviders = []
|
|
317
|
-
if (ownedLanTcp) listenProviders.push(ownedLanTcp)
|
|
318
|
-
// 无适配器时 isAvailable 应在 import native 前返回 false;勿无条件 ensureListening(会 loadBleno)。
|
|
319
|
-
if (ownedBleGatt && await Promise.resolve(ownedBleGatt.isAvailable()))
|
|
320
|
-
listenProviders.push(ownedBleGatt)
|
|
321
|
-
for (const provider of await listAvailableLinkProviders()) {
|
|
322
|
-
const id = String(provider.id)
|
|
323
|
-
if (id.startsWith('lan_tcp') || id.startsWith('ble_gatt')) continue
|
|
324
|
-
if (typeof provider.ensureListening === 'function')
|
|
325
|
-
listenProviders.push(provider)
|
|
326
|
-
}
|
|
327
|
-
for (const provider of listenProviders)
|
|
328
|
-
try {
|
|
329
|
-
const stop = await provider.ensureListening({
|
|
330
|
-
localIdentity,
|
|
331
|
-
/**
|
|
332
|
-
* @param {object} link 入站链路
|
|
333
|
-
* @returns {void}
|
|
334
|
-
*/
|
|
335
|
-
onInbound(link) {
|
|
336
|
-
void (async () => {
|
|
337
|
-
try {
|
|
338
|
-
await link.ready
|
|
339
|
-
const remote = normalizeHex64(link.nodeHash)
|
|
340
|
-
if (!remote) {
|
|
341
|
-
await link.close('inbound-no-nodehash')
|
|
342
|
-
return
|
|
343
|
-
}
|
|
344
|
-
await registerResolvedLink(remote, link)
|
|
345
|
-
}
|
|
346
|
-
catch { /* ignore failed inbound */ }
|
|
347
|
-
})()
|
|
348
|
-
},
|
|
349
|
-
})
|
|
350
|
-
if (typeof stop === 'function') stopLinkListeners.push(stop)
|
|
351
|
-
}
|
|
352
|
-
catch { /* provider listen unavailable — normal degrade */ }
|
|
353
|
-
|
|
354
|
-
if (listDiscoveryProviders().length) {
|
|
355
|
-
stopSignalListener = await listenSignals(selfTopic, bytes => {
|
|
356
|
-
void handleIncomingSignal(bytes).catch(() => { })
|
|
357
|
-
})
|
|
358
|
-
stopAdvert = await advertiseTopic(selfTopic, encryptSignalPacket(selfTopic, {
|
|
359
|
-
type: 'advert',
|
|
360
|
-
body: await buildLocalAdvert(selfTopic),
|
|
361
|
-
}))
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* 为单条 PC 创建按 connId 标记的信令会话。出站信令都带上 connId,
|
|
367
|
-
* 使同一对节点的两个方向(各自一条 PC)在信令层互不串扰。
|
|
368
|
-
* @param {string} remoteNodeHash 远端节点 64 hex
|
|
369
|
-
* @param {string} connId 连接标识
|
|
370
|
-
* @returns {ReturnType<typeof createBufferedSignalSession>} 信令会话
|
|
371
|
-
*/
|
|
372
|
-
function createConnSession(remoteNodeHash, connId) {
|
|
373
|
-
const normalized = normalizeHex64(remoteNodeHash)
|
|
374
|
-
const topic = nodeRendezvousTopic(normalized)
|
|
375
|
-
return createBufferedSignalSession(async message => {
|
|
376
|
-
await sendSignal(topic, normalized, encryptSignalPacket(topic, {
|
|
377
|
-
type: 'signal',
|
|
378
|
-
from: localIdentity.nodeHash,
|
|
379
|
-
connId,
|
|
380
|
-
body: message,
|
|
381
|
-
}))
|
|
382
|
-
})
|
|
383
|
-
}
|
|
384
90
|
|
|
385
|
-
/**
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
*/
|
|
390
|
-
function isOfferSignalBody(body) {
|
|
391
|
-
return !!body && typeof body === 'object'
|
|
392
|
-
&& body.type === 'description'
|
|
393
|
-
&& body.description?.type === 'offer'
|
|
394
|
-
}
|
|
91
|
+
/** @type {(bytes: Uint8Array) => Promise<void>} */
|
|
92
|
+
let handleIncomingSignal = async () => { }
|
|
93
|
+
/** @type {(provider: import('../link/providers/index.mjs').LinkProvider, remoteNodeHash: string) => Promise<object | null>} */
|
|
94
|
+
let dialOfferAnswer = async () => null
|
|
395
95
|
|
|
396
96
|
/**
|
|
397
97
|
* 择链:更高 level 优先;同 level 时保留由较小 nodeHash 发起的那条(glare 两端一致)。
|
|
398
|
-
* @param {object} link
|
|
98
|
+
* @param {object} link 候选链路
|
|
399
99
|
* @param {string} remoteNodeHash 远端节点 64 hex
|
|
400
|
-
* @param {object | null} [against]
|
|
401
|
-
* @returns {boolean}
|
|
100
|
+
* @param {object | null} [against] 当前规范链
|
|
101
|
+
* @returns {boolean} 候选链应保留为规范链时 true
|
|
402
102
|
*/
|
|
403
103
|
function linkIsPreferred(link, remoteNodeHash, against = null) {
|
|
404
|
-
const level =
|
|
405
|
-
const againstLevel =
|
|
104
|
+
const level = link.level || 0
|
|
105
|
+
const againstLevel = against?.level || 0
|
|
406
106
|
if (against && level !== againstLevel) return level > againstLevel
|
|
407
107
|
const cmp = compareHex64Asc(localIdentity.nodeHash, remoteNodeHash)
|
|
408
108
|
return link.initiator ? cmp < 0 : cmp > 0
|
|
@@ -419,12 +119,10 @@ export function createLinkRegistry(options = {}) {
|
|
|
419
119
|
void dispatchEnvelope(senderNodeHash, envelope, link).catch(() => { })
|
|
420
120
|
})
|
|
421
121
|
link.onDown(reason => {
|
|
422
|
-
// 只有关闭的是当前规范链路才对外报 linkDown;双 PC 并存期关掉的败者不应误发 peer-leave。
|
|
423
122
|
const wasCanonical = links.get(remoteNodeHash) === link
|
|
424
123
|
if (!wasCanonical) return
|
|
425
124
|
links.delete(remoteNodeHash)
|
|
426
|
-
|
|
427
|
-
try { listener(remoteNodeHash, reason) } catch { /* ignore */ }
|
|
125
|
+
emitSafe(linkDownListeners, remoteNodeHash, reason)
|
|
428
126
|
})
|
|
429
127
|
}
|
|
430
128
|
|
|
@@ -438,79 +136,48 @@ export function createLinkRegistry(options = {}) {
|
|
|
438
136
|
const normalized = normalizeHex64(remoteNodeHash)
|
|
439
137
|
const existing = links.get(normalized)
|
|
440
138
|
if (existing && existing !== candidate && !linkIsPreferred(candidate, normalized, existing)) {
|
|
441
|
-
await candidate.close(
|
|
139
|
+
await candidate.close(candidate.level !== existing.level ? 'provider-loser' : 'glare-loser')
|
|
442
140
|
return
|
|
443
141
|
}
|
|
444
142
|
links.set(normalized, candidate)
|
|
445
143
|
wireLink(normalized, candidate)
|
|
446
144
|
if (existing && existing !== candidate)
|
|
447
|
-
await existing.close(
|
|
448
|
-
|
|
449
|
-
try { listener(normalized, candidate) } catch { /* ignore */ }
|
|
145
|
+
await existing.close(candidate.level !== existing.level ? 'provider-replaced' : 'glare-replaced')
|
|
146
|
+
emitSafe(linkUpListeners, normalized, candidate)
|
|
450
147
|
}
|
|
451
148
|
|
|
452
149
|
/**
|
|
453
|
-
*
|
|
454
|
-
* @
|
|
455
|
-
|
|
456
|
-
function findOfferAnswerProvider() {
|
|
457
|
-
return listLinkProviders().find(provider => provider.caps?.needsOfferAnswer) ?? null
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Offer/answer provider:为一条 connId 会话建链并走 glare 择一。
|
|
462
|
-
* @param {{ provider: import('../link/providers/index.mjs').LinkProvider, remoteNodeHash: string, connId: string, session: ReturnType<typeof createBufferedSignalSession>, initiator: boolean }} options 建链参数
|
|
463
|
-
* @returns {Promise<object | null>} 当前规范链;失败 null
|
|
150
|
+
* 入站链路共用 onInbound(lan_tcp / 后台 ble 等)。
|
|
151
|
+
* @param {object} link 入站链路
|
|
152
|
+
* @returns {void}
|
|
464
153
|
*/
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
nodeHash
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
signalSessions.delete(connId)
|
|
476
|
-
return null
|
|
154
|
+
function onInboundLink(link) {
|
|
155
|
+
void (async () => {
|
|
156
|
+
try {
|
|
157
|
+
await link.ready
|
|
158
|
+
const remote = normalizeHex64(link.nodeHash)
|
|
159
|
+
if (!remote) {
|
|
160
|
+
await link.close('inbound-no-nodehash')
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
await registerResolvedLink(remote, link)
|
|
477
164
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
await registerResolvedLink(remoteNodeHash, link)
|
|
481
|
-
return links.get(remoteNodeHash) ?? null
|
|
482
|
-
}
|
|
483
|
-
catch {
|
|
484
|
-
signalSessions.delete(connId)
|
|
485
|
-
return null
|
|
486
|
-
}
|
|
165
|
+
catch { /* ignore failed inbound */ }
|
|
166
|
+
})()
|
|
487
167
|
}
|
|
488
168
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
let session = signalSessions.get(connId)
|
|
502
|
-
if (!session) {
|
|
503
|
-
// 只有全新的 offer 才开一条独立应答 PC;answer/ice 若无对应 connId 会话则是迟到/无效帧,丢弃。
|
|
504
|
-
// 应答 PC 按 connId 独立创建,不受按 nodeHash 去重的 inflight 阻挡——这是支持双向同时建链的关键。
|
|
505
|
-
if (!isOfferSignalBody(packet.body)) return
|
|
506
|
-
const provider = findOfferAnswerProvider()
|
|
507
|
-
if (!provider) return
|
|
508
|
-
session = createConnSession(remoteNodeHash, connId)
|
|
509
|
-
signalSessions.set(connId, session)
|
|
510
|
-
void buildConnLink({ provider, remoteNodeHash, connId, session, initiator: false })
|
|
511
|
-
}
|
|
512
|
-
session.deliver(packet.body)
|
|
513
|
-
}
|
|
169
|
+
const bootstrap = createRuntimeBootstrap({
|
|
170
|
+
localIdentity,
|
|
171
|
+
selfTopic,
|
|
172
|
+
autoRegisterDiscoveryProviders: options.autoRegisterDiscoveryProviders !== false,
|
|
173
|
+
autoRegisterLinkProviders: options.autoRegisterLinkProviders !== false,
|
|
174
|
+
onInboundLink,
|
|
175
|
+
/**
|
|
176
|
+
* @param {Uint8Array} bytes 入站信令字节
|
|
177
|
+
* @returns {void}
|
|
178
|
+
*/
|
|
179
|
+
handleIncomingSignal: bytes => handleIncomingSignal(bytes),
|
|
180
|
+
})
|
|
514
181
|
|
|
515
182
|
/**
|
|
516
183
|
* 计算节点在 scope 兴趣中的权重(用于 eviction)。
|
|
@@ -530,24 +197,38 @@ export function createLinkRegistry(options = {}) {
|
|
|
530
197
|
*/
|
|
531
198
|
async function trimToBudget() {
|
|
532
199
|
if (links.size < maxActive) return
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
200
|
+
let victimHash = null
|
|
201
|
+
let victimLink = null
|
|
202
|
+
let victimWeight = Infinity
|
|
203
|
+
for (const [nodeHash, link] of links) {
|
|
204
|
+
const weight = scopeWeight(nodeHash)
|
|
205
|
+
if (
|
|
206
|
+
victimHash == null
|
|
207
|
+
|| weight < victimWeight
|
|
208
|
+
|| (weight === victimWeight && compareHex64Asc(nodeHash, victimHash) < 0)
|
|
209
|
+
) {
|
|
210
|
+
victimHash = nodeHash
|
|
211
|
+
victimLink = link
|
|
212
|
+
victimWeight = weight
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (victimLink) await victimLink.close('budget-evict')
|
|
537
216
|
}
|
|
538
217
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
218
|
+
; ({ handleIncomingSignal, dialOfferAnswer } = createOfferAnswerDial({
|
|
219
|
+
localIdentity,
|
|
220
|
+
iceServers,
|
|
221
|
+
selfTopic,
|
|
222
|
+
signalSessions,
|
|
223
|
+
registerResolvedLink,
|
|
224
|
+
trimToBudget,
|
|
225
|
+
/**
|
|
226
|
+
* 读取当前规范链。
|
|
227
|
+
* @param {string} remoteNodeHash 远端节点 64 hex
|
|
228
|
+
* @returns {object | null | undefined} 规范链实例
|
|
229
|
+
*/
|
|
230
|
+
getCanonicalLink: remoteNodeHash => links.get(normalizeHex64(remoteNodeHash)),
|
|
231
|
+
}))
|
|
551
232
|
|
|
552
233
|
/**
|
|
553
234
|
* 经非 offer/answer provider 拨号。
|
|
@@ -574,21 +255,25 @@ export function createLinkRegistry(options = {}) {
|
|
|
574
255
|
* @returns {Promise<object | null>} 链路实例;失败时 null
|
|
575
256
|
*/
|
|
576
257
|
async function ensureDirectLinkToNode(remoteNodeHash) {
|
|
577
|
-
await
|
|
258
|
+
await bootstrap.ensureRuntime()
|
|
259
|
+
await bootstrap.whenSignalListening()
|
|
578
260
|
const normalized = normalizeHex64(remoteNodeHash)
|
|
579
261
|
if (!normalized || normalized === localIdentity.nodeHash) return null
|
|
580
262
|
if (links.has(normalized)) return links.get(normalized)
|
|
581
263
|
if (inflights.has(normalized)) return await inflights.get(normalized)
|
|
582
264
|
const task = (async () => {
|
|
583
|
-
const providers =
|
|
584
|
-
for (const provider of providers)
|
|
265
|
+
const providers = listLinkProviders()
|
|
266
|
+
for (const provider of providers)
|
|
585
267
|
try {
|
|
586
268
|
if (typeof provider.canReach === 'function') {
|
|
587
269
|
const reachable = await Promise.resolve(provider.canReach({ nodeHash: normalized }))
|
|
588
270
|
if (!reachable) continue
|
|
589
271
|
}
|
|
272
|
+
if (typeof provider.isAvailable === 'function') {
|
|
273
|
+
const available = await Promise.resolve(provider.isAvailable())
|
|
274
|
+
if (!available) continue
|
|
275
|
+
}
|
|
590
276
|
if (provider.caps?.needsOfferAnswer) {
|
|
591
|
-
// soft-fail 返回 null(不 throw);必须 continue 才能落到更低 level。
|
|
592
277
|
const link = await dialOfferAnswer(provider, normalized)
|
|
593
278
|
if (link) return link
|
|
594
279
|
continue
|
|
@@ -597,7 +282,7 @@ export function createLinkRegistry(options = {}) {
|
|
|
597
282
|
if (link) return link
|
|
598
283
|
}
|
|
599
284
|
catch { /* dial failed — try next provider */ }
|
|
600
|
-
|
|
285
|
+
|
|
601
286
|
return null
|
|
602
287
|
})().finally(() => inflights.delete(normalized))
|
|
603
288
|
inflights.set(normalized, task)
|
|
@@ -632,10 +317,7 @@ export function createLinkRegistry(options = {}) {
|
|
|
632
317
|
overlayRouter = createOverlayRouter({
|
|
633
318
|
localIdentity,
|
|
634
319
|
sendToNodeLink: sendDirectToNodeLink,
|
|
635
|
-
/**
|
|
636
|
-
* 列出当前所有活跃链路。
|
|
637
|
-
* @returns {Array<{ nodeHash: string, link: object }>} 链路列表
|
|
638
|
-
*/
|
|
320
|
+
/** @returns {Array<{ nodeHash: string, link: object }>} 当前活跃链路列表 */
|
|
639
321
|
listLinks() {
|
|
640
322
|
return [...links.entries()].map(([nodeHash, link]) => ({ nodeHash, link }))
|
|
641
323
|
},
|
|
@@ -656,8 +338,9 @@ export function createLinkRegistry(options = {}) {
|
|
|
656
338
|
async function relayEnvelopeToNode(remoteNodeHash, envelope) {
|
|
657
339
|
if (!links.size || envelope?.scope === 'overlay') return false
|
|
658
340
|
try {
|
|
659
|
-
const
|
|
660
|
-
await
|
|
341
|
+
const overlay = getOverlayRouter()
|
|
342
|
+
const path = await overlay.discoverRoute(remoteNodeHash)
|
|
343
|
+
await overlay.relay(path, envelope)
|
|
661
344
|
return true
|
|
662
345
|
}
|
|
663
346
|
catch {
|
|
@@ -665,15 +348,6 @@ export function createLinkRegistry(options = {}) {
|
|
|
665
348
|
}
|
|
666
349
|
}
|
|
667
350
|
|
|
668
|
-
/**
|
|
669
|
-
* 确保到远端节点的链路(直连或被动应答)。
|
|
670
|
-
* @param {string} remoteNodeHash 远端节点 64 hex
|
|
671
|
-
* @returns {Promise<object | null>} 链路实例
|
|
672
|
-
*/
|
|
673
|
-
async function ensureLinkToNode(remoteNodeHash) {
|
|
674
|
-
return await ensureDirectLinkToNode(remoteNodeHash)
|
|
675
|
-
}
|
|
676
|
-
|
|
677
351
|
/**
|
|
678
352
|
* 发送 envelope:优先直连,失败则 overlay relay。
|
|
679
353
|
* @param {string} remoteNodeHash 远端节点 64 hex
|
|
@@ -685,6 +359,17 @@ export function createLinkRegistry(options = {}) {
|
|
|
685
359
|
|| await relayEnvelopeToNode(remoteNodeHash, envelope)
|
|
686
360
|
}
|
|
687
361
|
|
|
362
|
+
/**
|
|
363
|
+
* 同步结果直接返回;thenable 才 await(避免每条 envelope 造 microtask)。
|
|
364
|
+
* @param {unknown} value 可能为 Promise 的返回值
|
|
365
|
+
* @returns {Promise<unknown>} 已 resolve 的值
|
|
366
|
+
*/
|
|
367
|
+
async function maybeAwait(value) {
|
|
368
|
+
if (value != null && typeof /** @type {{ then?: unknown }} */ value.then === 'function')
|
|
369
|
+
return await value
|
|
370
|
+
return value
|
|
371
|
+
}
|
|
372
|
+
|
|
688
373
|
/**
|
|
689
374
|
* 将入站 envelope 派发到 scope 监听器(经 authorizer 校验)。
|
|
690
375
|
* @param {string} senderNodeHash 发送方节点 64 hex
|
|
@@ -693,45 +378,46 @@ export function createLinkRegistry(options = {}) {
|
|
|
693
378
|
* @returns {Promise<void>}
|
|
694
379
|
*/
|
|
695
380
|
async function dispatchEnvelope(senderNodeHash, envelope, link) {
|
|
696
|
-
const scope =
|
|
381
|
+
const scope = envelope.scope || ''
|
|
697
382
|
for (const [prefix, authorizer] of scopeAuthorizers.entries())
|
|
698
383
|
if (scope.startsWith(prefix)) {
|
|
699
|
-
const allowed = await
|
|
384
|
+
const allowed = await maybeAwait(authorizer(scope, senderNodeHash, envelope, link))
|
|
700
385
|
if (!allowed) return
|
|
701
386
|
}
|
|
702
387
|
for (const [prefix, listeners] of scopeListeners.entries())
|
|
703
388
|
if (scope.startsWith(prefix))
|
|
704
389
|
for (const listener of listeners)
|
|
705
|
-
await
|
|
390
|
+
await maybeAwait(listener(senderNodeHash, envelope, link))
|
|
706
391
|
}
|
|
707
392
|
|
|
708
393
|
/**
|
|
709
394
|
* 订阅指定 scope 前缀的 envelope。
|
|
710
395
|
* @param {string} prefix scope 前缀
|
|
711
|
-
* @param {
|
|
396
|
+
* @param {Function} listener 监听器
|
|
712
397
|
* @returns {() => void} 取消订阅函数
|
|
713
398
|
*/
|
|
714
399
|
function subscribeScope(prefix, listener) {
|
|
715
|
-
return subscribeBucket(scopeListeners,
|
|
400
|
+
return subscribeBucket(scopeListeners, prefix, listener)
|
|
716
401
|
}
|
|
717
402
|
|
|
718
403
|
/**
|
|
719
404
|
* 注册 scope 前缀的 authorizer(入站校验)。
|
|
720
405
|
* @param {string} prefix scope 前缀
|
|
721
|
-
* @param {
|
|
406
|
+
* @param {Function} authorizer 校验函数
|
|
722
407
|
* @returns {() => void} 取消注册函数
|
|
723
408
|
*/
|
|
724
409
|
function registerScopeAuthorizer(prefix, authorizer) {
|
|
725
|
-
scopeAuthorizers.set(
|
|
726
|
-
return () => scopeAuthorizers.delete(
|
|
410
|
+
scopeAuthorizers.set(prefix, authorizer)
|
|
411
|
+
return () => scopeAuthorizers.delete(prefix)
|
|
727
412
|
}
|
|
728
413
|
|
|
729
414
|
return {
|
|
730
415
|
localIdentity,
|
|
731
|
-
buildLocalAdvert,
|
|
732
|
-
lanTcpPort:
|
|
733
|
-
|
|
734
|
-
|
|
416
|
+
buildLocalAdvert: bootstrap.buildLocalAdvert,
|
|
417
|
+
lanTcpPort: bootstrap.lanTcpPort,
|
|
418
|
+
whenListening: bootstrap.whenListening,
|
|
419
|
+
ensureRuntime: bootstrap.ensureRuntime,
|
|
420
|
+
ensureLinkToNode: ensureDirectLinkToNode,
|
|
735
421
|
/**
|
|
736
422
|
* 获取到指定节点的活跃链路。
|
|
737
423
|
* @param {string} nodeHash 节点 64 hex
|
|
@@ -785,7 +471,7 @@ export function createLinkRegistry(options = {}) {
|
|
|
785
471
|
* @returns {void}
|
|
786
472
|
*/
|
|
787
473
|
registerScopeInterest(scope, nodeHashes) {
|
|
788
|
-
scopeInterests.set(
|
|
474
|
+
scopeInterests.set(scope, new Set((nodeHashes || []).map(normalizeHex64).filter(Boolean)))
|
|
789
475
|
},
|
|
790
476
|
/**
|
|
791
477
|
* 释放 scope 兴趣。
|
|
@@ -793,7 +479,7 @@ export function createLinkRegistry(options = {}) {
|
|
|
793
479
|
* @returns {void}
|
|
794
480
|
*/
|
|
795
481
|
releaseScopeInterest(scope) {
|
|
796
|
-
scopeInterests.delete(
|
|
482
|
+
scopeInterests.delete(scope)
|
|
797
483
|
},
|
|
798
484
|
registerScopeAuthorizer,
|
|
799
485
|
subscribeScope,
|
|
@@ -806,13 +492,10 @@ export function createLinkRegistry(options = {}) {
|
|
|
806
492
|
async subscribeNodeAdvert(nodeHash, onAdvert) {
|
|
807
493
|
const topic = nodeRendezvousTopic(nodeHash)
|
|
808
494
|
return await subscribeTopic(topic, async (bytes, meta) => {
|
|
809
|
-
const
|
|
810
|
-
if (
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
noteAdvertPeerHints(verifiedNodeHash, packet.body, meta)
|
|
814
|
-
recentAdverts.touch(verifiedNodeHash, Date.now())
|
|
815
|
-
await Promise.resolve(onAdvert(verifiedNodeHash, packet.body))
|
|
495
|
+
const ingested = await ingestSignedAdvert(topic, bytes, meta)
|
|
496
|
+
if (!ingested) return
|
|
497
|
+
recentAdverts.touch(ingested.verifiedNodeHash, Date.now())
|
|
498
|
+
await Promise.resolve(onAdvert(ingested.verifiedNodeHash, ingested.body))
|
|
816
499
|
})
|
|
817
500
|
},
|
|
818
501
|
recentAdverts,
|
|
@@ -822,10 +505,7 @@ export function createLinkRegistry(options = {}) {
|
|
|
822
505
|
* @returns {Promise<void>}
|
|
823
506
|
*/
|
|
824
507
|
async shutdown() {
|
|
825
|
-
|
|
826
|
-
stopSignalListener?.()
|
|
827
|
-
for (const stop of stopLinkListeners.splice(0))
|
|
828
|
-
try { stop() } catch { /* ignore */ }
|
|
508
|
+
await bootstrap.shutdown()
|
|
829
509
|
overlayRouter?.close()
|
|
830
510
|
overlayRouter = null
|
|
831
511
|
for (const link of links.values())
|
|
@@ -834,99 +514,77 @@ export function createLinkRegistry(options = {}) {
|
|
|
834
514
|
inflights.clear()
|
|
835
515
|
for (const session of signalSessions.values()) session.clear()
|
|
836
516
|
signalSessions.clear()
|
|
837
|
-
if (ownedLanTcp) {
|
|
838
|
-
unregisterLinkProvider(ownedLanTcp.id)
|
|
839
|
-
ownedLanTcp = null
|
|
840
|
-
}
|
|
841
|
-
if (ownedBleGatt) {
|
|
842
|
-
unregisterLinkProvider(ownedBleGatt.id)
|
|
843
|
-
ownedBleGatt = null
|
|
844
|
-
}
|
|
845
|
-
runtimeStarted = false
|
|
846
517
|
},
|
|
847
518
|
}
|
|
848
519
|
}
|
|
849
520
|
|
|
850
521
|
let defaultRegistry = null
|
|
851
522
|
|
|
523
|
+
/**
|
|
524
|
+
* 默认 registry 尚未创建时暂存的 scope authorizer 条目。
|
|
525
|
+
* 注册阶段不应触发 createLinkRegistry / resolveLocalIdentity;绑定在 getLinkRegistry flush 时完成。
|
|
526
|
+
* @type {Array<{ prefix: string, authorizer: Function, unregister: (() => void) | null }>}
|
|
527
|
+
*/
|
|
528
|
+
const pendingScopeAuthorizers = []
|
|
529
|
+
|
|
852
530
|
/**
|
|
853
531
|
* 获取进程级默认 link registry 单例。
|
|
854
532
|
* @returns {ReturnType<typeof createLinkRegistry>} 默认 registry
|
|
855
533
|
*/
|
|
856
534
|
export function getLinkRegistry() {
|
|
857
|
-
if (
|
|
535
|
+
if (defaultRegistry) return defaultRegistry
|
|
536
|
+
defaultRegistry = createLinkRegistry()
|
|
537
|
+
for (const entry of pendingScopeAuthorizers)
|
|
538
|
+
entry.unregister = defaultRegistry.registerScopeAuthorizer(entry.prefix, entry.authorizer)
|
|
539
|
+
pendingScopeAuthorizers.length = 0
|
|
858
540
|
return defaultRegistry
|
|
859
541
|
}
|
|
860
542
|
|
|
861
543
|
/**
|
|
862
|
-
* 默认 registry
|
|
863
|
-
* @param {
|
|
864
|
-
* @returns {
|
|
865
|
-
*/
|
|
866
|
-
export const ensureLinkToNode = (...args) => getLinkRegistry().ensureLinkToNode(...args)
|
|
867
|
-
/**
|
|
868
|
-
* 默认 registry 的 getLink 代理。
|
|
869
|
-
* @param {...any} args 转发参数
|
|
870
|
-
* @returns {ReturnType<ReturnType<typeof createLinkRegistry>['getLink']>} 链路实例
|
|
871
|
-
*/
|
|
872
|
-
export const getLink = (...args) => getLinkRegistry().getLink(...args)
|
|
873
|
-
/**
|
|
874
|
-
* 默认 registry 的 listLinks 代理。
|
|
875
|
-
* @param {...any} args 转发参数
|
|
876
|
-
* @returns {ReturnType<ReturnType<typeof createLinkRegistry>['listLinks']>} 链路列表
|
|
877
|
-
*/
|
|
878
|
-
export const listLinks = (...args) => getLinkRegistry().listLinks(...args)
|
|
879
|
-
/**
|
|
880
|
-
* 默认 registry 的 closeLink 代理。
|
|
881
|
-
* @param {...any} args 转发参数
|
|
882
|
-
* @returns {ReturnType<ReturnType<typeof createLinkRegistry>['closeLink']>} 关闭完成
|
|
883
|
-
*/
|
|
884
|
-
export const closeLink = (...args) => getLinkRegistry().closeLink(...args)
|
|
885
|
-
/**
|
|
886
|
-
* 默认 registry 的 sendToNodeLink 代理。
|
|
887
|
-
* @param {...any} args 转发参数
|
|
888
|
-
* @returns {ReturnType<ReturnType<typeof createLinkRegistry>['sendToNodeLink']>} 是否成功
|
|
889
|
-
*/
|
|
890
|
-
export const sendToNodeLink = (...args) => getLinkRegistry().sendToNodeLink(...args)
|
|
891
|
-
/**
|
|
892
|
-
* 默认 registry 的 relayEnvelopeToNode 代理。
|
|
893
|
-
* @param {...any} args 转发参数
|
|
894
|
-
* @returns {ReturnType<ReturnType<typeof createLinkRegistry>['relayEnvelopeToNode']>} 是否成功
|
|
895
|
-
*/
|
|
896
|
-
export const relayEnvelopeToNode = (...args) => getLinkRegistry().relayEnvelopeToNode(...args)
|
|
897
|
-
/**
|
|
898
|
-
* 默认 registry 的 onLinkUp 代理。
|
|
899
|
-
* @param {...any} args 转发参数
|
|
900
|
-
* @returns {ReturnType<ReturnType<typeof createLinkRegistry>['onLinkUp']>} 取消订阅函数
|
|
901
|
-
*/
|
|
902
|
-
export const onLinkUp = (...args) => getLinkRegistry().onLinkUp(...args)
|
|
903
|
-
/**
|
|
904
|
-
* 默认 registry 的 onLinkDown 代理。
|
|
905
|
-
* @param {...any} args 转发参数
|
|
906
|
-
* @returns {ReturnType<ReturnType<typeof createLinkRegistry>['onLinkDown']>} 取消订阅函数
|
|
907
|
-
*/
|
|
908
|
-
export const onLinkDown = (...args) => getLinkRegistry().onLinkDown(...args)
|
|
909
|
-
/**
|
|
910
|
-
* 默认 registry 的 registerScopeInterest 代理。
|
|
911
|
-
* @param {...any} args 转发参数
|
|
912
|
-
* @returns {ReturnType<ReturnType<typeof createLinkRegistry>['registerScopeInterest']>} 无返回值
|
|
913
|
-
*/
|
|
914
|
-
export const registerScopeInterest = (...args) => getLinkRegistry().registerScopeInterest(...args)
|
|
915
|
-
/**
|
|
916
|
-
* 默认 registry 的 releaseScopeInterest 代理。
|
|
917
|
-
* @param {...any} args 转发参数
|
|
918
|
-
* @returns {ReturnType<ReturnType<typeof createLinkRegistry>['releaseScopeInterest']>} 无返回值
|
|
919
|
-
*/
|
|
920
|
-
export const releaseScopeInterest = (...args) => getLinkRegistry().releaseScopeInterest(...args)
|
|
921
|
-
/**
|
|
922
|
-
* 默认 registry 的 registerScopeAuthorizer 代理。
|
|
923
|
-
* @param {...any} args 转发参数
|
|
924
|
-
* @returns {ReturnType<ReturnType<typeof createLinkRegistry>['registerScopeAuthorizer']>} 取消注册函数
|
|
544
|
+
* 默认 registry 方法代理。
|
|
545
|
+
* @param {string} name registry 方法名
|
|
546
|
+
* @returns {(...args: unknown[]) => unknown} 绑定到 getLinkRegistry()[name] 的函数
|
|
925
547
|
*/
|
|
926
|
-
|
|
548
|
+
const bindRegistryMethod = name => (...args) => getLinkRegistry()[name](...args)
|
|
549
|
+
|
|
550
|
+
/** @type {(...args: unknown[]) => unknown} */
|
|
551
|
+
export const ensureLinkToNode = bindRegistryMethod('ensureLinkToNode')
|
|
552
|
+
/** @type {(...args: unknown[]) => unknown} */
|
|
553
|
+
export const getLink = bindRegistryMethod('getLink')
|
|
554
|
+
/** @type {(...args: unknown[]) => unknown} */
|
|
555
|
+
export const listLinks = bindRegistryMethod('listLinks')
|
|
556
|
+
/** @type {(...args: unknown[]) => unknown} */
|
|
557
|
+
export const closeLink = bindRegistryMethod('closeLink')
|
|
558
|
+
/** @type {(...args: unknown[]) => unknown} */
|
|
559
|
+
export const sendToNodeLink = bindRegistryMethod('sendToNodeLink')
|
|
560
|
+
/** @type {(...args: unknown[]) => unknown} */
|
|
561
|
+
export const relayEnvelopeToNode = bindRegistryMethod('relayEnvelopeToNode')
|
|
562
|
+
/** @type {(...args: unknown[]) => unknown} */
|
|
563
|
+
export const onLinkUp = bindRegistryMethod('onLinkUp')
|
|
564
|
+
/** @type {(...args: unknown[]) => unknown} */
|
|
565
|
+
export const onLinkDown = bindRegistryMethod('onLinkDown')
|
|
566
|
+
/** @type {(...args: unknown[]) => unknown} */
|
|
567
|
+
export const registerScopeInterest = bindRegistryMethod('registerScopeInterest')
|
|
568
|
+
/** @type {(...args: unknown[]) => unknown} */
|
|
569
|
+
export const releaseScopeInterest = bindRegistryMethod('releaseScopeInterest')
|
|
570
|
+
/** @type {(...args: unknown[]) => unknown} */
|
|
571
|
+
export const subscribeScope = bindRegistryMethod('subscribeScope')
|
|
572
|
+
|
|
927
573
|
/**
|
|
928
|
-
*
|
|
929
|
-
*
|
|
930
|
-
* @
|
|
574
|
+
* 注册默认 registry 的 scope authorizer。
|
|
575
|
+
* 不急切创建 registry(不必 resolveLocalIdentity);首次 getLinkRegistry 时 flush。
|
|
576
|
+
* @param {string} prefix scope 前缀
|
|
577
|
+
* @param {Function} authorizer 校验函数
|
|
578
|
+
* @returns {() => void} 取消注册函数
|
|
931
579
|
*/
|
|
932
|
-
export
|
|
580
|
+
export function registerScopeAuthorizer(prefix, authorizer) {
|
|
581
|
+
if (defaultRegistry) return defaultRegistry.registerScopeAuthorizer(prefix, authorizer)
|
|
582
|
+
|
|
583
|
+
const entry = { prefix, authorizer, unregister: null }
|
|
584
|
+
pendingScopeAuthorizers.push(entry)
|
|
585
|
+
return () => {
|
|
586
|
+
const index = pendingScopeAuthorizers.indexOf(entry)
|
|
587
|
+
if (index !== -1) pendingScopeAuthorizers.splice(index, 1)
|
|
588
|
+
entry.unregister?.()
|
|
589
|
+
}
|
|
590
|
+
}
|