@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
@@ -1,5 +1,6 @@
1
+ import { noteAdvertPeerHints } from '../discovery/advert_peer_hints.mjs'
1
2
  import { advertiseTopic, subscribeTopic } from '../discovery/index.mjs'
2
- import { buildSignedAdvert, verifySignedAdvert } from '../link/handshake.mjs'
3
+ import { verifySignedAdvert } from '../link/handshake.mjs'
3
4
  import {
4
5
  groupRendezvousTopic,
5
6
  decryptSignalPacket,
@@ -9,17 +10,17 @@ import {
9
10
 
10
11
  /**
11
12
  * 在指定 scope 与 roomSecret 下创建 link 层房间(discovery + registry 转发)。
12
- * @param {object} opts 房间选项
13
- * @param {string} opts.scope link registry scope(如 `group:{id}`)
14
- * @param {string} opts.roomSecret 房间 rendezvous 密钥
15
- * @param {(nodeHash: string) => boolean} [opts.allowNode] 是否允许与某 nodeHash 通信
16
- * @returns {{ start: () => Promise<void>, leave: () => Promise<void>, makeAction: (name: string) => [(payload: unknown, peerId?: string | string[] | null) => Promise<void>, (handler: (payload: unknown, peerId: string) => void) => void], onPeerJoin: (cb: (peerId: string) => void) => () => void, onPeerLeave: (cb: (peerId: string) => void) => () => void, getPeers: () => Record<string, true> }} 房间句柄
13
+ * @param {object} options 房间选项
14
+ * @param {string} options.scope link registry scope(如 `group:{id}`)
15
+ * @param {string} options.roomSecret 房间 rendezvous 密钥
16
+ * @param {(nodeHash: string) => boolean} [options.allowNode] 是否允许与某 nodeHash 通信
17
+ * @returns {{ start: () => Promise<void>, leave: () => Promise<void>, makeAction: (name: string) => [(payload: unknown, peerId?: string | string[] | null) => Promise<void>, (handler: (payload: unknown, peerId: string) => void) => void], onPeerJoin: (callback: (peerId: string) => void) => () => void, onPeerLeave: (callback: (peerId: string) => void) => () => void, getPeers: () => Record<string, true> }} 房间句柄
17
18
  */
18
- export function createScopedLinkRoom(opts) {
19
+ export function createScopedLinkRoom(options) {
19
20
  const registry = getLinkRegistry()
20
- const scope = String(opts.scope)
21
- const topic = groupRendezvousTopic(opts.roomSecret)
22
- const allowNode = typeof opts.allowNode === 'function' ? opts.allowNode : () => true
21
+ const scope = String(options.scope)
22
+ const topic = groupRendezvousTopic(options.roomSecret)
23
+ const allowNode = typeof options.allowNode === 'function' ? options.allowNode : () => true
23
24
  /** @type {Set<string>} */
24
25
  const discoveredPeers = new Set()
25
26
  /** @type {Set<string>} */
@@ -102,18 +103,19 @@ export function createScopedLinkRoom(opts) {
102
103
  if (!discoveredPeers.has(nodeHash)) return
103
104
  notePeerLeave(nodeHash)
104
105
  }))
105
- cleanups.add(await subscribeTopic(topic, async bytes => {
106
+ cleanups.add(await subscribeTopic(topic, async (bytes, meta) => {
106
107
  const packet = decryptSignalPacket(topic, bytes)
107
108
  if (packet?.type !== 'advert' || !packet.body) return
108
109
  const verifiedNodeHash = await verifySignedAdvert(topic, packet.body)
109
110
  if (!verifiedNodeHash || !allowNode(verifiedNodeHash)) return
111
+ noteAdvertPeerHints(verifiedNodeHash, packet.body, meta)
110
112
  discoveredPeers.add(verifiedNodeHash)
111
113
  await registry.ensureLinkToNode(verifiedNodeHash).catch(() => null)
112
114
  if (registry.getLink(verifiedNodeHash)) notePeerJoin(verifiedNodeHash)
113
115
  }))
114
116
  cleanups.add(await advertiseTopic(topic, encryptSignalPacket(topic, {
115
117
  type: 'advert',
116
- body: await buildSignedAdvert(topic, Date.now(), registry.localIdentity),
118
+ body: await registry.buildLocalAdvert(topic),
117
119
  })))
118
120
  for (const peerId of activePeerIds())
119
121
  notePeerJoin(peerId)
@@ -156,24 +158,24 @@ export function createScopedLinkRoom(opts) {
156
158
  ]
157
159
  },
158
160
  /**
159
- * @param {(peerId: string) => void} cb 新 peer 上线回调
161
+ * @param {(peerId: string) => void} callback 新 peer 上线回调
160
162
  * @returns {() => void} 取消订阅
161
163
  */
162
- onPeerJoin(cb) {
163
- joinListeners.add(cb)
164
+ onPeerJoin(callback) {
165
+ joinListeners.add(callback)
164
166
  for (const peerId of activePeerIds())
165
167
  announcedPeers.add(peerId)
166
168
  for (const peerId of announcedPeers)
167
- try { cb(peerId) } catch { /* ignore */ }
168
- return () => joinListeners.delete(cb)
169
+ try { callback(peerId) } catch { /* ignore */ }
170
+ return () => joinListeners.delete(callback)
169
171
  },
170
172
  /**
171
- * @param {(peerId: string) => void} cb peer 离线回调
173
+ * @param {(peerId: string) => void} callback peer 离线回调
172
174
  * @returns {() => void} 取消订阅
173
175
  */
174
- onPeerLeave(cb) {
175
- leaveListeners.add(cb)
176
- return () => leaveListeners.delete(cb)
176
+ onPeerLeave(callback) {
177
+ leaveListeners.add(callback)
178
+ return () => leaveListeners.delete(callback)
177
179
  },
178
180
  /**
179
181
  * @returns {Record<string, true>} 当前活跃 peer 的 nodeHash 集合
@@ -14,17 +14,17 @@ import { computeDagTipIdsFromEvents } from '../governance/branch.mjs'
14
14
  * 为追加事件计算 HLC 与 DAG 前驱(chat 群与 social 时间线共用)。
15
15
  * @param {object[]} previous 已有事件
16
16
  * @param {object} event 待追加事件
17
- * @param {{ multiTip?: boolean }} [opts] chat 在多 tip 时连接全部 tip
17
+ * @param {{ multiTip?: boolean }} [options] chat 在多 tip 时连接全部 tip
18
18
  * @returns {{ hlc: object, prev_event_ids: string[], last: object | undefined }} HLC、前驱与末条事件
19
19
  */
20
- export function computeAppendHlcAndPrev(previous, event, opts = {}) {
20
+ export function computeAppendHlcAndPrev(previous, event, options = {}) {
21
21
  const last = previous[previous.length - 1]
22
22
  const hlc = nextHlc(last?.hlc, event.timestamp ?? Date.now())
23
23
  const tips = computeDagTipIdsFromEvents(previous)
24
24
  let prev_event_ids
25
25
  if (event.prev_event_ids?.length)
26
26
  prev_event_ids = sortedPrevEventIds(event.prev_event_ids)
27
- else if (opts.multiTip && tips.length > 1)
27
+ else if (options.multiTip && tips.length > 1)
28
28
  prev_event_ids = sortedPrevEventIds(tips)
29
29
  else if (tips.length)
30
30
  prev_event_ids = sortedPrevEventIds(tips)
@@ -1,5 +1,6 @@
1
+ import { noteAdvertPeerHints } from '../discovery/advert_peer_hints.mjs'
1
2
  import { advertiseTopic, subscribeTopic } from '../discovery/index.mjs'
2
- import { buildSignedAdvert, verifySignedAdvert } from '../link/handshake.mjs'
3
+ import { verifySignedAdvert } from '../link/handshake.mjs'
3
4
  import { loadPeerPoolView } from '../node/network.mjs'
4
5
  import { loadReputation } from '../node/reputation_store.mjs'
5
6
 
@@ -14,22 +15,22 @@ import { resolveFederationPoolLimits, selectLinkTargetsFromMembers } from './pee
14
15
 
15
16
  /**
16
17
  * 创建基于 link registry 的群组联邦房间。
17
- * @param {object} opts 选项
18
- * @param {string} opts.groupId 群组 id
19
- * @param {string} opts.roomSecret 房间密钥(用于 rendezvous topic)
20
- * @param {string[]} opts.members 初始成员 nodeHash 列表
21
- * @param {object} [opts.groupSettings] 群设置(连接预算:trustedPeerSlots/explorePeerSlots/maxPeers 等)
22
- * @returns {{ groupId: string, scope: string, start: () => Promise<void>, leave: () => Promise<void>, getRoster: () => Array<{ peerId: string, remoteNodeHash: string }>, getPeerIdByNodeHash: (nodeHash: string) => string | null, sendToPeer: (peerId: string, actionName: string, payload: unknown) => Promise<boolean>, send: (actionName: string, payload: unknown, peerId?: string | null) => Promise<number>, onEnvelope: (cb: (senderNodeHash: string, envelope: object) => void) => () => void, onPeerJoin: (cb: (peerId: string) => void) => () => void, onPeerLeave: (cb: (peerId: string) => void) => () => void, getPeers: () => Record<string, true>, makeAction: (name: string) => [(payload: unknown, peerId?: string | string[] | null) => Promise<void>, (handler: (payload: unknown, peerId: string) => void) => void], registerCleanup: (fn: () => void) => void, isActive: () => boolean }} 群组 link set 接口
18
+ * @param {object} options 选项
19
+ * @param {string} options.groupId 群组 id
20
+ * @param {string} options.roomSecret 房间密钥(用于 rendezvous topic)
21
+ * @param {string[]} options.members 初始成员 nodeHash 列表
22
+ * @param {object} [options.groupSettings] 群设置(连接预算:trustedPeerSlots/explorePeerSlots/maxPeers 等)
23
+ * @returns {{ groupId: string, scope: string, start: () => Promise<void>, leave: () => Promise<void>, getRoster: () => Array<{ peerId: string, remoteNodeHash: string }>, getPeerIdByNodeHash: (nodeHash: string) => string | null, sendToPeer: (peerId: string, actionName: string, payload: unknown) => Promise<boolean>, send: (actionName: string, payload: unknown, peerId?: string | null) => Promise<number>, onEnvelope: (callback: (senderNodeHash: string, envelope: object) => void) => () => void, onPeerJoin: (callback: (peerId: string) => void) => () => void, onPeerLeave: (callback: (peerId: string) => void) => () => void, getPeers: () => Record<string, true>, makeAction: (name: string) => [(payload: unknown, peerId?: string | string[] | null) => Promise<void>, (handler: (payload: unknown, peerId: string) => void) => void], registerCleanup: (cleanup: () => void) => void, isActive: () => boolean }} 群组 link set 接口
23
24
  */
24
- export function createGroupLinkSet(opts) {
25
- const registry = opts.registry ?? getLinkRegistry()
26
- const autoconnect = opts.autoconnect !== false
27
- const groupId = String(opts.groupId)
25
+ export function createGroupLinkSet(options) {
26
+ const registry = options.registry ?? getLinkRegistry()
27
+ const autoconnect = options.autoconnect !== false
28
+ const groupId = String(options.groupId)
28
29
  const scope = `group:${groupId}`
29
- const topic = groupRendezvousTopic(opts.roomSecret)
30
- const members = new Set((Array.isArray(opts.members) ? opts.members : []).map(String))
30
+ const topic = groupRendezvousTopic(options.roomSecret)
31
+ const members = new Set((Array.isArray(options.members) ? options.members : []).map(String))
31
32
  const selfNodeHash = registry.localIdentity.nodeHash
32
- const groupSettings = opts.groupSettings ?? {}
33
+ const groupSettings = options.groupSettings ?? {}
33
34
  // 初始成员是调用方明确知道的引导集合(如 introducer/creator/seed),作为必连锚点保证引导期连通。
34
35
  const initialAnchors = new Set(members)
35
36
  /** @type {ReturnType<typeof setTimeout> | null} */
@@ -50,12 +51,12 @@ export function createGroupLinkSet(opts) {
50
51
 
51
52
  /**
52
53
  * 注册 leave 时执行的清理回调。
53
- * @param {() => void} fn 清理函数
54
+ * @param {() => void} cleanup 清理函数
54
55
  * @returns {void}
55
56
  */
56
- function registerCleanup(fn) {
57
- if (typeof fn !== 'function') return
58
- cleanups.add(fn)
57
+ function registerCleanup(cleanup) {
58
+ if (typeof cleanup !== 'function') return
59
+ cleanups.add(cleanup)
59
60
  }
60
61
 
61
62
  /**
@@ -186,17 +187,18 @@ export function createGroupLinkSet(opts) {
186
187
  if (!members.has(nodeHash) || nodeHash === selfNodeHash) return
187
188
  notePeerLeave(nodeHash)
188
189
  }))
189
- registerCleanup(await subscribeTopic(topic, async bytes => {
190
+ registerCleanup(await subscribeTopic(topic, async (bytes, meta) => {
190
191
  const packet = decryptSignalPacket(topic, bytes)
191
192
  if (packet?.type !== 'advert' || !packet.body) return
192
193
  const verifiedNodeHash = await verifySignedAdvert(topic, packet.body)
193
194
  if (!verifiedNodeHash || verifiedNodeHash === selfNodeHash) return
195
+ noteAdvertPeerHints(verifiedNodeHash, packet.body, meta)
194
196
  // 发现新成员:并入成员集并去抖重算稀疏建链目标(notePeerCandidate 内部会 scheduleDial)。
195
197
  notePeerCandidate(verifiedNodeHash)
196
198
  }))
197
199
  registerCleanup(await advertiseTopic(topic, encryptSignalPacket(topic, {
198
200
  type: 'advert',
199
- body: await buildSignedAdvert(topic, Date.now(), registry.localIdentity),
201
+ body: await registry.buildLocalAdvert(topic),
200
202
  })))
201
203
  if (autoconnect) selectAndDial()
202
204
  for (const { peerId } of activeRoster())
@@ -255,34 +257,34 @@ export function createGroupLinkSet(opts) {
255
257
  },
256
258
  /**
257
259
  * 订阅入站 envelope。
258
- * @param {(senderNodeHash: string, envelope: object) => void} cb 回调
260
+ * @param {(senderNodeHash: string, envelope: object) => void} callback 回调
259
261
  * @returns {() => void} 取消订阅函数
260
262
  */
261
- onEnvelope(cb) {
262
- envelopeListeners.add(cb)
263
- return () => envelopeListeners.delete(cb)
263
+ onEnvelope(callback) {
264
+ envelopeListeners.add(callback)
265
+ return () => envelopeListeners.delete(callback)
264
266
  },
265
267
  /**
266
268
  * 订阅 peer 加入事件(含当前已在线 peer 的即时回调)。
267
- * @param {(peerId: string) => void} cb 回调
269
+ * @param {(peerId: string) => void} callback 回调
268
270
  * @returns {() => void} 取消订阅函数
269
271
  */
270
- onPeerJoin(cb) {
271
- peerJoinListeners.add(cb)
272
+ onPeerJoin(callback) {
273
+ peerJoinListeners.add(callback)
272
274
  for (const { peerId } of activeRoster())
273
275
  if (peerId) announcedPeers.add(peerId)
274
276
  for (const peerId of announcedPeers)
275
- try { cb(peerId) } catch { /* ignore */ }
276
- return () => peerJoinListeners.delete(cb)
277
+ try { callback(peerId) } catch { /* ignore */ }
278
+ return () => peerJoinListeners.delete(callback)
277
279
  },
278
280
  /**
279
281
  * 订阅 peer 离开事件。
280
- * @param {(peerId: string) => void} cb 回调
282
+ * @param {(peerId: string) => void} callback 回调
281
283
  * @returns {() => void} 取消订阅函数
282
284
  */
283
- onPeerLeave(cb) {
284
- peerLeaveListeners.add(cb)
285
- return () => peerLeaveListeners.delete(cb)
285
+ onPeerLeave(callback) {
286
+ peerLeaveListeners.add(callback)
287
+ return () => peerLeaveListeners.delete(callback)
286
288
  },
287
289
  /**
288
290
  * 返回当前在线 peer 的 Record 映射。