@steve02081504/fount-p2p 0.0.11 → 0.0.13

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 (102) hide show
  1. package/README.md +42 -11
  2. package/core/bytes_codec.mjs +65 -10
  3. package/core/composite_key.mjs +5 -5
  4. package/core/tcp_port.mjs +1 -1
  5. package/crypto/checkpoint_sign.mjs +2 -2
  6. package/crypto/crypto.mjs +3 -3
  7. package/crypto/key.mjs +12 -12
  8. package/dag/canonicalize_row.mjs +3 -3
  9. package/dag/index.mjs +2 -3
  10. package/dag/storage.mjs +36 -36
  11. package/discovery/advert_peer_hints.mjs +1 -1
  12. package/discovery/adverts.mjs +109 -0
  13. package/discovery/bt/index.mjs +154 -113
  14. package/discovery/bt/probe_child.mjs +2 -1
  15. package/discovery/bt/runtime.mjs +5 -12
  16. package/discovery/index.mjs +267 -62
  17. package/discovery/internal/signal_crypto.mjs +109 -0
  18. package/discovery/lan.mjs +228 -0
  19. package/discovery/nostr.mjs +430 -141
  20. package/federation/chunk_fetch_pending.mjs +14 -18
  21. package/federation/dag_order_cache.mjs +1 -1
  22. package/federation/entity_key_chain.mjs +3 -4
  23. package/federation/manifest_fetch_pending.mjs +1 -3
  24. package/federation/topo_order_memo.mjs +11 -4
  25. package/files/assemble.mjs +14 -14
  26. package/files/assemble_stream.mjs +6 -6
  27. package/files/chunk_fetch.mjs +2 -2
  28. package/files/chunk_responder.mjs +29 -20
  29. package/files/evfs.mjs +31 -30
  30. package/files/manifest_fetch.mjs +16 -3
  31. package/files/public_manifest.mjs +11 -11
  32. package/files/transfer_key_registry.mjs +2 -2
  33. package/index.mjs +86 -11
  34. package/infra/cli.mjs +62 -0
  35. package/infra/debug_log.mjs +56 -0
  36. package/infra/default_node_dir.mjs +22 -0
  37. package/infra/priority.mjs +56 -0
  38. package/infra/service.mjs +140 -0
  39. package/infra/tunables.json +5 -0
  40. package/link/channel_mux.mjs +10 -10
  41. package/link/frame.mjs +76 -131
  42. package/link/handshake.mjs +19 -20
  43. package/link/pipe.mjs +53 -79
  44. package/link/providers/ble_gatt.mjs +19 -31
  45. package/link/providers/lan_tcp.mjs +17 -32
  46. package/link/providers/link_id_pipe.mjs +46 -0
  47. package/link/providers/webrtc.mjs +42 -51
  48. package/link/rtc.mjs +24 -12
  49. package/mailbox/consumer_registry.mjs +2 -2
  50. package/mailbox/deliver_or_store.mjs +6 -6
  51. package/mailbox/wire.mjs +28 -24
  52. package/node/entity_store.mjs +6 -6
  53. package/node/identity.mjs +4 -4
  54. package/node/instance.mjs +46 -27
  55. package/node/local_data_revision.mjs +26 -0
  56. package/node/log.mjs +56 -0
  57. package/node/network.mjs +46 -14
  58. package/node/reputation_store.mjs +4 -4
  59. package/node/reputation_sync.mjs +318 -0
  60. package/node/routing_profile.mjs +21 -0
  61. package/node/signaling_config.mjs +30 -11
  62. package/overlay/index.mjs +35 -14
  63. package/package.json +13 -2
  64. package/rooms/scoped_link.mjs +17 -182
  65. package/schemas/discovery.mjs +1 -2
  66. package/schemas/federation_pull.mjs +2 -2
  67. package/schemas/part_query.mjs +1 -1
  68. package/schemas/remote_event.mjs +1 -1
  69. package/transport/advert_ingest.mjs +17 -0
  70. package/transport/group_link_set.mjs +165 -134
  71. package/transport/ice_servers.mjs +2 -2
  72. package/transport/link_registry.mjs +283 -124
  73. package/transport/mesh_keepalive.mjs +217 -0
  74. package/transport/node_scope.mjs +289 -0
  75. package/transport/offer_answer.mjs +48 -47
  76. package/transport/peer_pool.mjs +169 -78
  77. package/transport/remote_user_room.mjs +18 -23
  78. package/transport/rtc_connection_budget.mjs +18 -4
  79. package/transport/{rtc_mdns_filter.mjs → rtc_ice_local_hostname.mjs} +13 -13
  80. package/transport/runtime_bootstrap.mjs +172 -104
  81. package/transport/tunables.json +11 -0
  82. package/transport/tunables.mjs +18 -0
  83. package/transport/user_room.mjs +93 -161
  84. package/trust_graph/build.mjs +0 -2
  85. package/trust_graph/cache.mjs +12 -3
  86. package/trust_graph/registry.mjs +6 -6
  87. package/trust_graph/send.mjs +1 -4
  88. package/utils/async_mutex.mjs +4 -4
  89. package/utils/emit_safe.mjs +11 -0
  90. package/utils/json_io.mjs +12 -12
  91. package/utils/map_pool.mjs +5 -5
  92. package/utils/shuffle.mjs +13 -0
  93. package/utils/ttl_map.mjs +29 -4
  94. package/wire/ingress.mjs +1 -1
  95. package/wire/part_ingress.mjs +38 -36
  96. package/wire/part_invoke.mjs +4 -4
  97. package/wire/part_query.mjs +28 -23
  98. package/wire/part_query.tunables.json +6 -1
  99. package/wire/part_query_cache.mjs +1 -1
  100. package/wire/volatile_signature.mjs +1 -1
  101. package/discovery/mdns.mjs +0 -191
  102. package/transport/signal_crypto.mjs +0 -82
@@ -1,187 +1,22 @@
1
- import { noteAdvertPeerHints } from '../discovery/advert_peer_hints.mjs'
2
- import { advertiseTopic, subscribeTopic } from '../discovery/index.mjs'
3
- import { verifySignedAdvert } from '../link/handshake.mjs'
4
- import { getLinkRegistry } from '../transport/link_registry.mjs'
5
- import {
6
- decryptSignalPacket,
7
- encryptSignalPacket,
8
- groupRendezvousTopic,
9
- } from '../transport/signal_crypto.mjs'
1
+ import { createGroupLinkSet } from '../transport/group_link_set.mjs'
10
2
 
11
3
  /**
12
- * 在指定 scope roomSecret 下创建 link 层房间(discovery + registry 转发)。
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> }} 房间句柄
4
+ * scoped 房间:group_link_set 的薄预设(任意 scope + allowNode + 发现即拨)。
5
+ * @param {object} options - 房间选项
6
+ * @param {string} options.scope - link registry scope
7
+ * @param {string} options.roomSecret - rendezvous 密钥
8
+ * @param {(nodeHash: string) => boolean} [options.allowNode] - 节点准入过滤
9
+ * @returns {ReturnType<typeof createGroupLinkSet>} scoped link 房间句柄
18
10
  */
19
11
  export function createScopedLinkRoom(options) {
20
- const registry = getLinkRegistry()
21
- const scope = String(options.scope)
22
- const topic = groupRendezvousTopic(options.roomSecret)
23
- const allowNode = typeof options.allowNode === 'function' ? options.allowNode : () => true
24
- /** @type {Set<string>} */
25
- const discoveredPeers = new Set()
26
- /** @type {Set<string>} */
27
- const announcedPeers = new Set()
28
- /** @type {Set<(peerId: string) => void>} */
29
- const joinListeners = new Set()
30
- /** @type {Set<(peerId: string) => void>} */
31
- const leaveListeners = new Set()
32
- /** @type {Set<() => void>} */
33
- const cleanups = new Set()
34
- /** @type {Map<string, { handler: ((payload: unknown, peerId: string) => void) | null, backlog: Array<{ payload: unknown, peerId: string }> }>} */
35
- const actionEntries = new Map()
36
-
37
- /**
38
- * @returns {string[]} 当前已连接且通过 allowNode 过滤的 peer nodeHash 列表
39
- */
40
- function activePeerIds() {
41
- return [...discoveredPeers].filter(nodeHash => allowNode(nodeHash) && registry.getLink(nodeHash))
42
- }
43
-
44
- /**
45
- * @param {Set<(peerId: string) => void>} listeners 回调集合
46
- * @param {string} peerId 节点 hash
47
- * @returns {void}
48
- */
49
- function emit(listeners, peerId) {
50
- for (const listener of listeners)
51
- try { listener(peerId) } catch { /* ignore */ }
52
- }
53
-
54
- /**
55
- * @param {string} peerId 节点 hash
56
- * @returns {void}
57
- */
58
- function notePeerJoin(peerId) {
59
- if (!peerId || announcedPeers.has(peerId)) return
60
- announcedPeers.add(peerId)
61
- emit(joinListeners, peerId)
62
- }
63
-
64
- /**
65
- * @param {string} peerId 节点 hash
66
- * @returns {void}
67
- */
68
- function notePeerLeave(peerId) {
69
- if (!peerId || !announcedPeers.has(peerId)) return
70
- announcedPeers.delete(peerId)
71
- emit(leaveListeners, peerId)
72
- }
73
-
74
- /**
75
- * @param {string} name action 名称
76
- * @returns {{ handler: ((payload: unknown, peerId: string) => void) | null, backlog: Array<{ payload: unknown, peerId: string }> }} action 槽(含待处理 backlog)
77
- */
78
- function getActionEntry(name) {
79
- const key = String(name)
80
- if (!actionEntries.has(key))
81
- actionEntries.set(key, { handler: null, backlog: [] })
82
- return actionEntries.get(key)
83
- }
84
-
85
- return {
86
- /**
87
- * @returns {Promise<void>}
88
- */
89
- async start() {
90
- await registry.ensureRuntime()
91
- cleanups.add(registry.subscribeScope(scope, (senderNodeHash, envelope) => {
92
- if (!allowNode(senderNodeHash)) return
93
- const entry = actionEntries.get(String(envelope?.action || ''))
94
- if (!entry) return
95
- if (entry.handler) entry.handler(envelope.payload, senderNodeHash)
96
- else entry.backlog.push({ payload: envelope.payload, peerId: senderNodeHash })
97
- }))
98
- cleanups.add(registry.onLinkUp(nodeHash => {
99
- if (!discoveredPeers.has(nodeHash) || !allowNode(nodeHash)) return
100
- notePeerJoin(nodeHash)
101
- }))
102
- cleanups.add(registry.onLinkDown(nodeHash => {
103
- if (!discoveredPeers.has(nodeHash)) return
104
- notePeerLeave(nodeHash)
105
- }))
106
- cleanups.add(await subscribeTopic(topic, async (bytes, meta) => {
107
- const packet = decryptSignalPacket(topic, bytes)
108
- if (packet?.type !== 'advert' || !packet.body) return
109
- const verifiedNodeHash = await verifySignedAdvert(topic, packet.body)
110
- if (!verifiedNodeHash || !allowNode(verifiedNodeHash)) return
111
- noteAdvertPeerHints(verifiedNodeHash, packet.body, meta)
112
- discoveredPeers.add(verifiedNodeHash)
113
- await registry.ensureLinkToNode(verifiedNodeHash).catch(() => null)
114
- if (registry.getLink(verifiedNodeHash)) notePeerJoin(verifiedNodeHash)
115
- }))
116
- cleanups.add(await advertiseTopic(topic, encryptSignalPacket(topic, {
117
- type: 'advert',
118
- body: await registry.buildLocalAdvert(topic),
119
- })))
120
- for (const peerId of activePeerIds())
121
- notePeerJoin(peerId)
122
- },
123
- /**
124
- * @returns {Promise<void>}
125
- */
126
- async leave() {
127
- for (const cleanup of cleanups)
128
- try { cleanup() } catch { /* ignore */ }
129
- cleanups.clear()
130
- for (const peerId of [...announcedPeers])
131
- notePeerLeave(peerId)
132
- },
133
- /**
134
- * @param {string} name action 名称
135
- * @returns {[(payload: unknown, peerId?: string | string[] | null) => Promise<void>, (handler: (payload: unknown, peerId: string) => void) => void]} [send, onReceive] 发送与订阅元组
136
- */
137
- makeAction(name) {
138
- const actionName = String(name)
139
- return [
140
- async (payload, peerId = null) => {
141
- if (Array.isArray(peerId)) {
142
- await Promise.all(peerId.map(targetPeerId =>
143
- registry.sendToNodeLink(targetPeerId, { scope, action: actionName, payload })))
144
- return
145
- }
146
- if (peerId)
147
- await registry.sendToNodeLink(peerId, { scope, action: actionName, payload })
148
- else
149
- await Promise.all(activePeerIds().map(targetPeerId =>
150
- registry.sendToNodeLink(targetPeerId, { scope, action: actionName, payload })))
151
- },
152
- handler => {
153
- const entry = getActionEntry(actionName)
154
- entry.handler = handler
155
- for (const pending of entry.backlog.splice(0))
156
- handler(pending.payload, pending.peerId)
157
- },
158
- ]
159
- },
160
- /**
161
- * @param {(peerId: string) => void} callback 新 peer 上线回调
162
- * @returns {() => void} 取消订阅
163
- */
164
- onPeerJoin(callback) {
165
- joinListeners.add(callback)
166
- for (const peerId of activePeerIds())
167
- announcedPeers.add(peerId)
168
- for (const peerId of announcedPeers)
169
- try { callback(peerId) } catch { /* ignore */ }
170
- return () => joinListeners.delete(callback)
171
- },
172
- /**
173
- * @param {(peerId: string) => void} callback peer 离线回调
174
- * @returns {() => void} 取消订阅
175
- */
176
- onPeerLeave(callback) {
177
- leaveListeners.add(callback)
178
- return () => leaveListeners.delete(callback)
179
- },
180
- /**
181
- * @returns {Record<string, true>} 当前活跃 peer 的 nodeHash 集合
182
- */
183
- getPeers() {
184
- return Object.fromEntries(activePeerIds().map(peerId => [peerId, true]))
185
- },
186
- }
12
+ const { scope, roomSecret, allowNode } = options
13
+ return createGroupLinkSet({
14
+ groupId: scope,
15
+ scope,
16
+ roomSecret,
17
+ members: [],
18
+ allowNode,
19
+ dialAll: true,
20
+ autoconnect: true,
21
+ })
187
22
  }
@@ -34,8 +34,7 @@ function sanitizeDiscoveryAdvertisement(ad) {
34
34
  * @returns {object[]} 已清扫广告
35
35
  */
36
36
  function sanitizeDiscoveryAdvertisements(ads) {
37
- if (!Array.isArray(ads)) return []
38
- return ads.slice(0, MAX_DISCOVERY_ADS).map(sanitizeDiscoveryAdvertisement).filter(Boolean)
37
+ return (ads || []).slice(0, MAX_DISCOVERY_ADS).map(sanitizeDiscoveryAdvertisement).filter(Boolean)
39
38
  }
40
39
 
41
40
  /**
@@ -30,8 +30,8 @@ export function parsePullAttestation(attestation) {
30
30
  const signature = String(attestation.signature || '').trim().toLowerCase()
31
31
  if (!isHex64(requesterPubKeyHash) || !groupId || !Number.isFinite(timestamp) || !isSignatureHex128(signature))
32
32
  return null
33
- const wantIds = Array.isArray(attestation.wantIds)
34
- ? [...new Set(attestation.wantIds.map(id => String(id).trim().toLowerCase()).filter(id => EVENT_ID_HEX.test(id)))]
33
+ const wantIds = attestation.wantIds?.length
34
+ ? [...new Set(attestation.wantIds.map(id => id.trim().toLowerCase()).filter(id => EVENT_ID_HEX.test(id)))]
35
35
  : undefined
36
36
  return {
37
37
  requesterPubKeyHash,
@@ -107,7 +107,7 @@ export function parsePartQueryReq(value, tunables = partQueryTunables) {
107
107
  if (!Object.prototype.hasOwnProperty.call(value, 'query')) return null
108
108
  if (measureJsonBytes(value.query) > (tunables.maxQueryBytes ?? 2048)) return null
109
109
  const ttl = clampPartQueryTtl(value.ttl, tunables.maxTtl)
110
- if (ttl == null) return null
110
+ if (!ttl) return null
111
111
  const budget = clampPartQueryBudget(value.budget, tunables.maxHits)
112
112
  return {
113
113
  requestId,
@@ -9,7 +9,7 @@ export function validateRemoteEventShape(event) {
9
9
  if (!event?.type || typeof event.type !== 'string')
10
10
  throw new Error('remote event: type required')
11
11
  assertHex64(event.id, 'remote event.id')
12
- if (!Array.isArray(event.prev_event_ids))
12
+ if (!event.prev_event_ids)
13
13
  throw new Error('remote event: prev_event_ids must be array')
14
14
  for (const parentId of event.prev_event_ids)
15
15
  assertHex64(parentId, 'remote event.prev_event_id')
@@ -0,0 +1,17 @@
1
+ import { noteAdvertPeerHints } from '../discovery/advert_peer_hints.mjs'
2
+
3
+ /**
4
+ * 解密并验签 advert(`ingestEncryptedAdvert` 别名)。不写 peer hints;写入需 `applyAdvertPeerHints`。Untrusted ingress。
5
+ */
6
+ export { ingestEncryptedAdvert as ingestSignedAdvert } from '../discovery/adverts.mjs'
7
+
8
+ /**
9
+ * 将已验签 advert 的 peer hints 写入本地(apply 侧;与 `ingestSignedAdvert` 分离)。
10
+ * @param {string} verifiedNodeHash 已验签 nodeHash
11
+ * @param {object} body advert body
12
+ * @param {object} [meta] 元数据
13
+ * @returns {void}
14
+ */
15
+ export function applyAdvertPeerHints(verifiedNodeHash, body, meta) {
16
+ noteAdvertPeerHints(verifiedNodeHash, body, meta)
17
+ }