@steve02081504/fount-p2p 0.0.5 → 0.0.7

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.
Files changed (47) hide show
  1. package/core/tcp_port.mjs +11 -0
  2. package/dag/canonicalize_row.mjs +5 -5
  3. package/discovery/advert_peer_hints.mjs +21 -0
  4. package/discovery/{bt.mjs → bt/index.mjs} +151 -55
  5. package/discovery/bt/peer_hints.mjs +41 -0
  6. package/discovery/bt/runtime.mjs +46 -0
  7. package/discovery/lan_peer_hints.mjs +43 -0
  8. package/discovery/mdns.mjs +10 -6
  9. package/discovery/nostr.mjs +3 -3
  10. package/federation/chunk_fetch_pending.mjs +3 -3
  11. package/federation/chunk_fetch_scheduler.mjs +3 -3
  12. package/federation/dag_order_cache.mjs +3 -3
  13. package/federation/topo_order_memo.mjs +3 -3
  14. package/files/chunk_responder.mjs +1 -1
  15. package/files/evfs.mjs +26 -26
  16. package/files/transfer_key.mjs +9 -9
  17. package/files/transfer_key_registry.mjs +9 -9
  18. package/governance/join_pow.mjs +17 -17
  19. package/index.mjs +3 -2
  20. package/link/channel_mux.mjs +7 -7
  21. package/link/frame.mjs +5 -5
  22. package/link/handshake.mjs +63 -36
  23. package/link/pipe.mjs +404 -0
  24. package/link/providers/ble_gatt.mjs +339 -0
  25. package/link/providers/index.mjs +90 -0
  26. package/link/providers/lan_tcp.mjs +342 -0
  27. package/link/providers/levels.mjs +9 -0
  28. package/link/providers/webrtc.mjs +385 -0
  29. package/mailbox/deliver_or_store.mjs +13 -13
  30. package/mailbox/wire.mjs +4 -4
  31. package/node/reputation_store.mjs +5 -5
  32. package/node/retention_policy.mjs +8 -8
  33. package/overlay/index.mjs +6 -6
  34. package/package.json +3 -2
  35. package/registries/inbound.mjs +8 -8
  36. package/rooms/scoped_link.mjs +23 -21
  37. package/timeline/append_core.mjs +3 -3
  38. package/transport/group_link_set.mjs +35 -33
  39. package/transport/link_registry.mjs +250 -63
  40. package/transport/peer_pool.mjs +4 -4
  41. package/transport/user_room.mjs +12 -14
  42. package/utils/ttl_map.mjs +41 -0
  43. package/wire/group_part.mjs +3 -3
  44. package/wire/part_ingress.mjs +14 -14
  45. package/wire/part_query.mjs +81 -82
  46. package/wire/part_query_cache.mjs +7 -0
  47. package/link/link.mjs +0 -617
@@ -0,0 +1,385 @@
1
+ import { getSignalingRuntimeConfig } from '../../node/instance.mjs'
2
+ import { ms } from '../../utils/duration.mjs'
3
+ import { createLruMap } from '../../utils/lru.mjs'
4
+ import {
5
+ CHANNEL_BULK,
6
+ CHANNEL_CONTROL,
7
+ CHANNEL_LOW_THRESHOLD_BYTES,
8
+ configureBufferedAmountLowThreshold,
9
+ createChannelSendQueues,
10
+ onBufferedAmountLow,
11
+ } from '../channel_mux.mjs'
12
+ import { asLinkHandle, createLinkPipe } from '../pipe.mjs'
13
+ import {
14
+ attachDataChannelListener,
15
+ attachIceCandidateListener,
16
+ loadNodeRtcPolyfill,
17
+ signalDataToBytes,
18
+ waitForChannelState,
19
+ } from '../rtc.mjs'
20
+ import { extractDtlsFingerprint } from '../sdp_fingerprint.mjs'
21
+
22
+ import { LINK_LEVEL_WEBRTC } from './levels.mjs'
23
+
24
+ /**
25
+ * 将错误对象格式化为短字符串。
26
+ * @param {unknown} error 原始错误
27
+ * @returns {string} 短 reason
28
+ */
29
+ function formatErrorReason(error) {
30
+ return String(error?.message ?? error ?? 'unknown-error').replace(/\s+/g, ' ').slice(0, 240)
31
+ }
32
+
33
+ /**
34
+ * 绑定 data channel message 回调。
35
+ * @param {RTCDataChannel} channel RTC 数据通道
36
+ * @param {(data: unknown) => void} handler 消息处理器
37
+ * @returns {void}
38
+ */
39
+ function attachChannelMessageListener(channel, handler) {
40
+ channel.addEventListener?.('message', event => handler(event?.data))
41
+ /**
42
+ * @param {MessageEvent} event 消息事件
43
+ * @returns {void}
44
+ */
45
+ channel.onmessage = event => handler(event?.data)
46
+ channel.onMessage?.subscribe(message => handler(message))
47
+ }
48
+
49
+ let cachedAvailable = null
50
+
51
+ /**
52
+ * 探测 WebRTC(node-datachannel)是否可用。
53
+ * @returns {Promise<boolean>} 可用为 true
54
+ */
55
+ export async function canUseWebRtcLink() {
56
+ if (cachedAvailable !== null) return cachedAvailable
57
+ try {
58
+ await loadNodeRtcPolyfill()
59
+ cachedAvailable = true
60
+ }
61
+ catch {
62
+ cachedAvailable = false
63
+ }
64
+ return cachedAvailable
65
+ }
66
+
67
+ /**
68
+ * 建立 WebRTC link(双 DataChannel + discovery 信令)。
69
+ * @param {object} options link 配置
70
+ * @param {string | null} [options.nodeHash] 期望的对端 nodeHash
71
+ * @param {boolean} options.initiator 是否为连接发起方
72
+ * @param {{ send: (message: unknown) => void | Promise<void>, onRemote: (handler: (message: unknown) => void) => (() => void) | void }} options.signal 信令收发接口
73
+ * @param {RTCConfiguration['iceServers']} [options.iceServers] ICE 服务器列表
74
+ * @param {number} [options.heartbeatMs] 心跳间隔
75
+ * @param {number} [options.idleTimeoutMs] 无入站流量超时
76
+ * @param {number} [options.handshakeTimeoutMs] 握手超时
77
+ * @param {{ RTCPeerConnection: typeof RTCPeerConnection } | null} [options.rtc] RTC 构造器
78
+ * @param {{ nodeHash?: string, nodePubKey?: string, secretKey?: Uint8Array, nonce?: string } | null} [options.localIdentity] 本地握手身份
79
+ * @returns {Promise<import('./index.mjs').LinkHandle>} link 句柄
80
+ */
81
+ export async function createWebRtcLink(options) {
82
+ const handshakeTimeoutMs = Number(options.handshakeTimeoutMs) || ms('10s')
83
+ const channelOpenTimeoutMs = Math.max(handshakeTimeoutMs, ms('30s'))
84
+ const trickleIceOff = getSignalingRuntimeConfig().trickleIceOff === true
85
+ const rtc = options.rtc ?? await loadNodeRtcPolyfill()
86
+ const pc = new rtc.RTCPeerConnection(options.iceServers?.length ? { iceServers: options.iceServers } : undefined)
87
+ const remoteSignalQueue = []
88
+ const seenRemoteSignals = createLruMap(1024)
89
+ let remoteDescriptionSet = false
90
+ let controlChannel = null
91
+ let bulkChannel = null
92
+ let unlistenRemote = null
93
+ let sendQueues = null
94
+ let controlLowEvents = 0
95
+ let bulkLowEvents = 0
96
+ let reconnectCount = 0
97
+
98
+ /**
99
+ * @param {unknown} message 信令载荷
100
+ * @returns {Promise<void>}
101
+ */
102
+ async function sendSignal(message) {
103
+ await Promise.resolve(options.signal.send(message))
104
+ }
105
+
106
+ const pipe = createLinkPipe({
107
+ providerId: 'webrtc',
108
+ level: LINK_LEVEL_WEBRTC,
109
+ initiator: !!options.initiator,
110
+ nodeHash: options.nodeHash,
111
+ localIdentity: options.localIdentity,
112
+ heartbeatMs: options.heartbeatMs,
113
+ idleTimeoutMs: options.idleTimeoutMs,
114
+ handshakeTimeoutMs,
115
+ /** @returns {string} 本端 DTLS fingerprint */
116
+ getLocalBinding: () => extractDtlsFingerprint(pc.localDescription?.sdp || ''),
117
+ /** @returns {string} 对端 DTLS fingerprint */
118
+ getRemoteBinding: () => extractDtlsFingerprint(pc.remoteDescription?.sdp || ''),
119
+ /**
120
+ * @param {string} text control JSON
121
+ * @returns {void}
122
+ */
123
+ sendControlText(text) {
124
+ if (!controlChannel) throw new Error('p2p: control channel unavailable')
125
+ controlChannel.send(text)
126
+ },
127
+ /**
128
+ * @param {string} action envelope action
129
+ * @param {Uint8Array} frame 帧字节
130
+ * @returns {void}
131
+ */
132
+ sendFrame(action, frame) {
133
+ if (!sendQueues) throw new Error('p2p: send queues unavailable')
134
+ sendQueues.enqueue(action, frame)
135
+ },
136
+ /**
137
+ * @returns {Promise<void>}
138
+ */
139
+ async closeTransport() {
140
+ unlistenRemote?.()
141
+ sendQueues?.clear()
142
+ try { controlChannel?.close() } catch { /* ignore */ }
143
+ try { bulkChannel?.close() } catch { /* ignore */ }
144
+ try { await pc.close() } catch { /* ignore */ }
145
+ },
146
+ /**
147
+ * @returns {object} WebRTC 附加 stats
148
+ */
149
+ extraStats() {
150
+ return {
151
+ connectionState: pc.connectionState,
152
+ iceConnectionState: pc.iceConnectionState,
153
+ reconnectCount,
154
+ pending: sendQueues?.pending() ?? { control: 0, bulk: 0 },
155
+ controlBufferedAmount: controlChannel?.bufferedAmount ?? 0,
156
+ bulkBufferedAmount: bulkChannel?.bufferedAmount ?? 0,
157
+ controlLowEvents,
158
+ bulkLowEvents,
159
+ }
160
+ },
161
+ })
162
+
163
+ /**
164
+ * @param {RTCDataChannel} channel RTC 数据通道
165
+ * @returns {void}
166
+ */
167
+ function attachBackpressurePump(channel) {
168
+ configureBufferedAmountLowThreshold(channel, CHANNEL_LOW_THRESHOLD_BYTES)
169
+ onBufferedAmountLow(channel, () => {
170
+ if (channel.label === CHANNEL_CONTROL) controlLowEvents++
171
+ if (channel.label === CHANNEL_BULK) bulkLowEvents++
172
+ if (channel.label === CHANNEL_CONTROL) sendQueues?.flush(CHANNEL_CONTROL)
173
+ if (channel.label === CHANNEL_BULK) sendQueues?.flush(CHANNEL_BULK)
174
+ })
175
+ }
176
+
177
+ /**
178
+ * @param {RTCSessionDescriptionInit | RTCSessionDescription} description 远端会话描述
179
+ * @returns {Promise<void>}
180
+ */
181
+ async function applyRemoteDescription(description) {
182
+ await pc.setRemoteDescription(description)
183
+ remoteDescriptionSet = true
184
+ await flushQueuedIceCandidates()
185
+ }
186
+
187
+ /**
188
+ * @param {unknown} error addIceCandidate 错误
189
+ * @returns {boolean} 是否应暂存后重试
190
+ */
191
+ function shouldRetryQueuedIce(error) {
192
+ return /without ice transport/i.test(String(error?.message ?? error ?? ''))
193
+ }
194
+
195
+ /**
196
+ * @returns {Promise<void>}
197
+ */
198
+ async function waitForIceGatheringComplete() {
199
+ if (!trickleIceOff || pc.iceGatheringState === 'complete') return
200
+ const deadline = Date.now() + handshakeTimeoutMs
201
+ while (pc.iceGatheringState !== 'complete' && Date.now() < deadline)
202
+ await new Promise(resolve => setTimeout(resolve, 50))
203
+ }
204
+
205
+ /**
206
+ * @returns {Promise<void>}
207
+ */
208
+ async function flushQueuedIceCandidates() {
209
+ if (!remoteDescriptionSet || !pc.localDescription) return
210
+ while (remoteSignalQueue.length) {
211
+ const candidate = remoteSignalQueue.shift()
212
+ try {
213
+ await pc.addIceCandidate(candidate)
214
+ }
215
+ catch (error) {
216
+ if (shouldRetryQueuedIce(error)) {
217
+ remoteSignalQueue.unshift(candidate)
218
+ return
219
+ }
220
+ throw error
221
+ }
222
+ }
223
+ }
224
+
225
+ /**
226
+ * @param {unknown} message 信令消息
227
+ * @returns {Promise<void>}
228
+ */
229
+ async function handleRemoteSignal(message) {
230
+ if (!message || typeof message !== 'object') return
231
+ const signalKey = JSON.stringify(message)
232
+ if (seenRemoteSignals.has(signalKey)) return
233
+ seenRemoteSignals.touch(signalKey, true)
234
+ if (message.type === 'description' && message.description) {
235
+ if (message.description.type === 'answer' && pc.signalingState === 'stable') return
236
+ await applyRemoteDescription(message.description)
237
+ if (message.description.type === 'offer') {
238
+ const answer = await pc.createAnswer()
239
+ await pc.setLocalDescription(answer)
240
+ await flushQueuedIceCandidates()
241
+ await waitForIceGatheringComplete()
242
+ await sendSignal({
243
+ type: 'description',
244
+ description: pc.localDescription?.toJSON?.() ?? pc.localDescription ?? answer,
245
+ })
246
+ await pipe.maybeSendAuth()
247
+ }
248
+ return
249
+ }
250
+ if (message.type === 'ice' && message.candidate) {
251
+ if (!remoteDescriptionSet || !pc.localDescription || pc.signalingState !== 'stable') {
252
+ remoteSignalQueue.push(message.candidate)
253
+ return
254
+ }
255
+ try {
256
+ await pc.addIceCandidate(message.candidate)
257
+ }
258
+ catch (error) {
259
+ if (shouldRetryQueuedIce(error)) {
260
+ remoteSignalQueue.push(message.candidate)
261
+ return
262
+ }
263
+ throw error
264
+ }
265
+ }
266
+ }
267
+
268
+ /**
269
+ * @param {RTCDataChannel} channel RTC 数据通道
270
+ * @returns {Promise<void>}
271
+ */
272
+ async function attachChannel(channel) {
273
+ if (channel.label === CHANNEL_CONTROL) controlChannel = channel
274
+ else if (channel.label === CHANNEL_BULK) bulkChannel = channel
275
+ else return
276
+ attachChannelMessageListener(channel, data => {
277
+ try {
278
+ if (typeof data === 'string') pipe.handleInbound(data)
279
+ else pipe.handleInbound(signalDataToBytes(data))
280
+ }
281
+ catch { /* drop */ }
282
+ })
283
+ attachBackpressurePump(channel)
284
+ }
285
+
286
+ /**
287
+ * @returns {Promise<void>}
288
+ */
289
+ async function maybeStartPostOpenFlow() {
290
+ if (!controlChannel || !bulkChannel) return
291
+ await Promise.all([
292
+ waitForChannelState(controlChannel, 'open', channelOpenTimeoutMs),
293
+ waitForChannelState(bulkChannel, 'open', channelOpenTimeoutMs),
294
+ ])
295
+ if (!sendQueues)
296
+ sendQueues = createChannelSendQueues({
297
+ /**
298
+ * @param {'control' | 'bulk'} name 通道名
299
+ * @returns {RTCDataChannel | null | undefined} 对应通道
300
+ */
301
+ getChannel: name => name === CHANNEL_CONTROL ? controlChannel : bulkChannel,
302
+ })
303
+ await pipe.startHandshake()
304
+ }
305
+
306
+ unlistenRemote = options.signal.onRemote(message => {
307
+ void handleRemoteSignal(message).catch(error => pipe.close(`signal-error:${formatErrorReason(error)}`))
308
+ }) ?? null
309
+
310
+ attachIceCandidateListener(pc, event => {
311
+ if (trickleIceOff || !event.candidate) return
312
+ void sendSignal({
313
+ type: 'ice',
314
+ candidate: typeof event.candidate.toJSON === 'function' ? event.candidate.toJSON() : event.candidate,
315
+ }).catch(error => pipe.close(`signal-send-failed:${formatErrorReason(error)}`))
316
+ })
317
+ attachDataChannelListener(pc, event => {
318
+ void attachChannel(event.channel)
319
+ .then(() => maybeStartPostOpenFlow())
320
+ .catch(error => pipe.close(`channel-attach-failed:${error?.message ?? error}`))
321
+ })
322
+
323
+ /** 连接失败/关闭时关掉 pipe。 */
324
+ pc.onconnectionstatechange = () => {
325
+ if (['failed', 'closed', 'disconnected'].includes(pc.connectionState)) {
326
+ reconnectCount++
327
+ void pipe.close(`connection-${pc.connectionState}`)
328
+ }
329
+ }
330
+
331
+ if (options.initiator) {
332
+ await attachChannel(pc.createDataChannel(CHANNEL_CONTROL))
333
+ await attachChannel(pc.createDataChannel(CHANNEL_BULK))
334
+ const offer = await pc.createOffer()
335
+ await pc.setLocalDescription(offer)
336
+ await waitForIceGatheringComplete()
337
+ await sendSignal({
338
+ type: 'description',
339
+ description: pc.localDescription?.toJSON?.() ?? pc.localDescription ?? offer,
340
+ })
341
+ }
342
+
343
+ void maybeStartPostOpenFlow().catch(error => pipe.close(`open-flow-failed:${error?.message ?? error}`))
344
+
345
+ return asLinkHandle(pipe, {
346
+ /**
347
+ * @internal 仅 live 背压测试用,不属公开 LinkHandle
348
+ * @param {'control' | 'bulk'} name 通道名
349
+ * @returns {RTCDataChannel | null} 测试用通道
350
+ */
351
+ _channelForTest(name) {
352
+ return name === CHANNEL_CONTROL ? controlChannel : bulkChannel
353
+ },
354
+ })
355
+ }
356
+
357
+ /**
358
+ * 创建 WebRTC LinkProvider。
359
+ * @param {object} [options] 可选覆盖
360
+ * @param {typeof createWebRtcLink} [options.createWebRtcLink] 链路工厂(测试注入)
361
+ * @returns {import('./index.mjs').LinkProvider} WebRTC provider
362
+ */
363
+ export function createWebRtcLinkProvider(options = {}) {
364
+ const createImpl = options.createWebRtcLink ?? createWebRtcLink
365
+ return {
366
+ id: 'webrtc',
367
+ level: LINK_LEVEL_WEBRTC,
368
+ caps: { needsOfferAnswer: true, needsDiscoverySignal: true },
369
+ isAvailable: canUseWebRtcLink,
370
+ /**
371
+ * @param {object} dialOptions dial 参数(含 signal / iceServers 等)
372
+ * @returns {Promise<import('./index.mjs').LinkHandle>} 已建链句柄
373
+ */
374
+ async dial(dialOptions) {
375
+ return createImpl({ ...dialOptions, initiator: true })
376
+ },
377
+ /**
378
+ * @param {object} acceptOptions accept 参数
379
+ * @returns {Promise<import('./index.mjs').LinkHandle>} 已建链句柄
380
+ */
381
+ async accept(acceptOptions) {
382
+ return createImpl({ ...acceptOptions, initiator: false })
383
+ },
384
+ }
385
+ }
@@ -59,26 +59,26 @@ async function resolveRouting(username) {
59
59
 
60
60
  /**
61
61
  * @param {string} username 副本用户名(trust graph 投递上下文)
62
- * @param {object} opts 投递选项
62
+ * @param {object} options 投递选项
63
63
  * @returns {Promise<{ stored: boolean, delivered: boolean, relayed: number }>} 存转结果
64
64
  */
65
- export async function deliverOrStoreMailboxPut(username, opts) {
65
+ export async function deliverOrStoreMailboxPut(username, options) {
66
66
  const routing = await resolveRouting(username)
67
- const toPubKeyHash = normalizeHex64(opts.toPubKeyHash)
67
+ const toPubKeyHash = normalizeHex64(options.toPubKeyHash)
68
68
  if (!toPubKeyHash) return { stored: false, delivered: false, relayed: 0 }
69
- const hop = normalizeMailboxHop(opts.hop)
69
+ const hop = normalizeMailboxHop(options.hop)
70
70
  if (hop >= routing.maxHop) return { stored: false, delivered: false, relayed: 0 }
71
71
  const tier = mailboxTierFromHop(hop)
72
72
  const nodeHash = getNodeHash()
73
73
  const record = {
74
- ...opts.record,
74
+ ...options.record,
75
75
  toPubKeyHash,
76
76
  hop,
77
77
  tier,
78
- fromNodeHash: opts.record?.fromNodeHash || nodeHash,
78
+ fromNodeHash: options.record?.fromNodeHash || nodeHash,
79
79
  }
80
80
  const stored = await storeMailboxRecord(record)
81
- const toNodeHash = opts.toNodeHash?.trim().toLowerCase()
81
+ const toNodeHash = options.toNodeHash?.trim().toLowerCase()
82
82
  const delivered = toNodeHash && isMailboxRecordWithinSizeLimit(record)
83
83
  ? await requireTrustGraphProvider(DEFAULT_TRUST_GRAPH_OWNER).sendToNode(username, toNodeHash, 'mailbox_put', { nodeHash, record })
84
84
  : false
@@ -108,17 +108,17 @@ export async function publishMailboxRecord(username, toPubKeyHash, record, toNod
108
108
  }
109
109
 
110
110
  /**
111
- * @param {{ replicaUsername?: string }} ctx 入站上下文
111
+ * @param {{ replicaUsername?: string }} wireContext 入站上下文
112
112
  * @param {object} put 入站 mailbox_put
113
113
  * @param {string} [peerId] Trystero 对端 id(有则校验 nodeHash 绑定)
114
114
  * @returns {Promise<void>}
115
115
  */
116
- export async function ingestMailboxPut(ctx, put, peerId = '') {
116
+ export async function ingestMailboxPut(wireContext, put, peerId = '') {
117
117
  const { record } = put
118
118
  if (!record?.envelope || !record?.toPubKeyHash) return
119
119
  const fromNode = normalizeHex64(put.nodeHash)
120
120
  if (!fromNode || !takeIncomingMailboxPutSlot(fromNode)) return
121
- const username = String(ctx?.replicaUsername || '').trim()
121
+ const username = String(wireContext.replicaUsername || '').trim()
122
122
  if (!username) return
123
123
  if (peerId) {
124
124
  const remote = await resolveRemoteNodeHashForPeer(username, peerId)
@@ -157,14 +157,14 @@ export async function respondMailboxWant(want, sendGive, peerId) {
157
157
  }
158
158
 
159
159
  /**
160
- * @param {{ replicaUsername?: string }} ctx 入站上下文
160
+ * @param {{ replicaUsername?: string }} wireContext 入站上下文
161
161
  * @param {object} give mailbox_give 载荷
162
162
  * @returns {Promise<number>} 投递给消费者的记录数
163
163
  */
164
- export async function ingestMailboxGive(ctx, give) {
164
+ export async function ingestMailboxGive(wireContext, give) {
165
165
  const records = (give.records || []).filter(isDeliverableMailboxRecord)
166
166
  if (!records.length) return 0
167
- const username = String(ctx?.replicaUsername || '').trim()
167
+ const username = String(wireContext.replicaUsername || '').trim()
168
168
  if (!username) return 0
169
169
  const { dispatchMailboxRecordsToConsumers } = await import('./consumer_registry.mjs')
170
170
  const { deleteMailboxRecords } = await import('./store.mjs')
package/mailbox/wire.mjs CHANGED
@@ -10,15 +10,15 @@ import { parseMailboxGive, parseMailboxPut, parseMailboxWant } from './parse.mjs
10
10
  */
11
11
 
12
12
  /**
13
- * @param {MailboxWireContext} ctx 入站上下文
13
+ * @param {MailboxWireContext} wireContext 入站上下文
14
14
  * @param {{ on: (name: string, handler: (payload: unknown, peerId: string) => void) => void, send: (name: string, payload: unknown, peerId: string | null) => void }} wire Trystero 适配器
15
15
  * @returns {void}
16
16
  */
17
- export function attachMailboxWire(ctx, wire) {
17
+ export function attachMailboxWire(wireContext, wire) {
18
18
  wire.on('mailbox_put', (payload, peerId) => {
19
19
  const put = parseMailboxPut(payload)
20
20
  if (!put.ok) return
21
- void ingestMailboxPut(ctx, put.value, peerId).catch(err => console.error('mailbox: put ingest failed', err))
21
+ void ingestMailboxPut(wireContext, put.value, peerId).catch(err => console.error('mailbox: put ingest failed', err))
22
22
  })
23
23
 
24
24
  wire.on('mailbox_want', (payload, peerId) => {
@@ -35,6 +35,6 @@ export function attachMailboxWire(ctx, wire) {
35
35
  wire.on('mailbox_give', payload => {
36
36
  const give = parseMailboxGive(payload)
37
37
  if (!give.ok) return
38
- void ingestMailboxGive(ctx, give.value).catch(err => console.error('mailbox: give ingest failed', err))
38
+ void ingestMailboxGive(wireContext, give.value).catch(err => console.error('mailbox: give ingest failed', err))
39
39
  })
40
40
  }
@@ -26,12 +26,12 @@ import { readNodeJsonSync, writeNodeJsonSync } from './storage.mjs'
26
26
 
27
27
  const DATA_NAME = 'reputation'
28
28
 
29
- /** @type {((opts: object) => Promise<boolean>) | null} */
29
+ /** @type {((options: object) => Promise<boolean>) | null} */
30
30
  let blockReputationHandler = null
31
31
 
32
32
  /**
33
33
  * Shell Load 时注册:公开 block/unblock → 信誉传导。
34
- * @param {(opts: object) => Promise<boolean>} handler block/unblock 信誉传导回调
34
+ * @param {(options: object) => Promise<boolean>} handler block/unblock 信誉传导回调
35
35
  * @returns {void}
36
36
  */
37
37
  export function registerBlockReputationHandler(handler) {
@@ -308,10 +308,10 @@ export function pickNodeScore(nodeId) {
308
308
  }
309
309
 
310
310
  /**
311
- * @param {object} opts applyFollowedBlockSignal 参数
311
+ * @param {object} options applyFollowedBlockSignal 参数
312
312
  * @returns {Promise<boolean>} 是否已应用
313
313
  */
314
- export async function applyBlockReputationSignal(opts) {
314
+ export async function applyBlockReputationSignal(options) {
315
315
  if (!blockReputationHandler) return false
316
- return blockReputationHandler(opts)
316
+ return blockReputationHandler(options)
317
317
  }
@@ -8,16 +8,16 @@ import {
8
8
  * 在共识分支上计算须保留的事件 id(连通子图,不用拓扑下标切片)。
9
9
  * @param {string[]} order 规范拓扑序
10
10
  * @param {Map<string, object>} byId id → 事件
11
- * @param {object} opts 保留策略
12
- * @param {number} opts.maxDepth 分支上最大事件深度
13
- * @param {number} opts.cutoffWall 最早保留的 HLC wall
14
- * @param {Set<string>} opts.anchorTypes 权限锚点事件类型
15
- * @param {string | null} [opts.checkpointTipId] checkpoint 尖
16
- * @param {string | null} [opts.branchTipId] 共识分支尖
11
+ * @param {object} options 保留策略
12
+ * @param {number} options.maxDepth 分支上最大事件深度
13
+ * @param {number} options.cutoffWall 最早保留的 HLC wall
14
+ * @param {Set<string>} options.anchorTypes 权限锚点事件类型
15
+ * @param {string | null} [options.checkpointTipId] checkpoint 尖
16
+ * @param {string | null} [options.branchTipId] 共识分支尖
17
17
  * @returns {Set<string>} 保留 id
18
18
  */
19
- export function computeRetentionKeepIds(order, byId, opts) {
20
- const { maxDepth, cutoffWall, anchorTypes, checkpointTipId, branchTipId } = opts
19
+ export function computeRetentionKeepIds(order, byId, options) {
20
+ const { maxDepth, cutoffWall, anchorTypes, checkpointTipId, branchTipId } = options
21
21
  const branchOrder = authzFoldOrderIds(order, byId, branchTipId)
22
22
  const branchSet = new Set(branchOrder)
23
23
  if (!branchSet.size) return new Set()
package/overlay/index.mjs CHANGED
@@ -131,15 +131,15 @@ export function createOverlayRouter(registry, ttl = 3) {
131
131
  /**
132
132
  * 发现到目标节点的签名路由路径。
133
133
  * @param {string} targetNodeHash 目标节点 64 hex
134
- * @param {object} [opts] 选项
135
- * @param {number} [opts.ttl] 路由 TTL
136
- * @param {number} [opts.timeoutMs] 超时毫秒
134
+ * @param {object} [options] 选项
135
+ * @param {number} [options.ttl] 路由 TTL
136
+ * @param {number} [options.timeoutMs] 超时毫秒
137
137
  * @returns {Promise<string[]>} 从本节点到目标的 nodeHash 路径
138
138
  */
139
- async discoverRoute(targetNodeHash, opts = {}) {
139
+ async discoverRoute(targetNodeHash, options = {}) {
140
140
  const reqId = randomMsgIdHex()
141
- const maxTtl = Number(opts.ttl) || ttl
142
- const timeoutMs = Number(opts.timeoutMs) || 10_000
141
+ const maxTtl = Number(options.ttl) || ttl
142
+ const timeoutMs = Number(options.timeoutMs) || 10_000
143
143
  const promise = new Promise((resolve, reject) => {
144
144
  const timer = setTimeout(() => {
145
145
  pendingRoutes.delete(reqId)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steve02081504/fount-p2p",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "fount federation P2P layer — link, trust graph, mailbox, DAG, EVFS.",
5
5
  "keywords": [
6
6
  "network",
@@ -39,8 +39,9 @@
39
39
  "./schemas/*": "./schemas/*.mjs",
40
40
  "./node/*": "./node/*.mjs",
41
41
  "./discovery": "./discovery/index.mjs",
42
+ "./discovery/bt": "./discovery/bt/index.mjs",
43
+ "./discovery/bt/*": "./discovery/bt/*.mjs",
42
44
  "./discovery/*": "./discovery/*.mjs",
43
- "./link/*": "./link/*.mjs",
44
45
  "./transport/*": "./transport/*.mjs",
45
46
  "./rooms/*": "./rooms/*.mjs",
46
47
  "./overlay": "./overlay/index.mjs",
@@ -10,11 +10,11 @@
10
10
  */
11
11
 
12
12
  /**
13
- * @typedef {(ctx: InboundContext, message: object) => Promise<PartInvokeResponse | null>} RpcInboundHandler
13
+ * @typedef {(inboundContext: InboundContext, message: object) => Promise<PartInvokeResponse | null>} RpcInboundHandler
14
14
  */
15
15
 
16
16
  /**
17
- * @typedef {(ctx: InboundContext, message: object) => Promise<void>} DeliveryInboundHandler
17
+ * @typedef {(inboundContext: InboundContext, message: object) => Promise<void>} DeliveryInboundHandler
18
18
  */
19
19
 
20
20
  /** @type {Map<string, RpcInboundHandler>} */
@@ -42,27 +42,27 @@ export function registerDeliveryInboundHandler(type, handler) {
42
42
  }
43
43
 
44
44
  /**
45
- * @param {InboundContext} ctx 入站上下文
45
+ * @param {InboundContext} inboundContext 入站上下文
46
46
  * @param {object} message 已校验的线载荷(含 type)
47
47
  * @returns {Promise<PartInvokeResponse | null>} 处理器返回值
48
48
  */
49
- export async function dispatchRpcInbound(ctx, message) {
49
+ export async function dispatchRpcInbound(inboundContext, message) {
50
50
  const type = String(message?.type || '').trim()
51
51
  if (!type) return null
52
52
  const handler = rpcHandlers.get(type)
53
53
  if (!handler) return null
54
- return handler(ctx, message)
54
+ return handler(inboundContext, message)
55
55
  }
56
56
 
57
57
  /**
58
- * @param {InboundContext} ctx 入站上下文
58
+ * @param {InboundContext} inboundContext 入站上下文
59
59
  * @param {object} message 已校验的线载荷(含 type)
60
60
  * @returns {Promise<void>}
61
61
  */
62
- export async function dispatchDeliveryInbound(ctx, message) {
62
+ export async function dispatchDeliveryInbound(inboundContext, message) {
63
63
  const type = String(message?.type || '').trim()
64
64
  if (!type) return
65
65
  const handler = deliveryHandlers.get(type)
66
66
  if (!handler) return
67
- await handler(ctx, message)
67
+ await handler(inboundContext, message)
68
68
  }