@steve02081504/fount-p2p 0.0.12 → 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 (75) hide show
  1. package/README.md +41 -10
  2. package/core/composite_key.mjs +5 -5
  3. package/crypto/crypto.mjs +3 -3
  4. package/crypto/key.mjs +12 -12
  5. package/dag/storage.mjs +36 -36
  6. package/discovery/advert_peer_hints.mjs +1 -1
  7. package/discovery/adverts.mjs +109 -0
  8. package/discovery/bt/index.mjs +153 -112
  9. package/discovery/bt/probe_child.mjs +2 -1
  10. package/discovery/bt/runtime.mjs +2 -9
  11. package/discovery/index.mjs +267 -62
  12. package/discovery/internal/signal_crypto.mjs +109 -0
  13. package/discovery/lan.mjs +228 -0
  14. package/discovery/nostr.mjs +394 -59
  15. package/federation/chunk_fetch_pending.mjs +10 -13
  16. package/federation/manifest_fetch_pending.mjs +1 -3
  17. package/files/assemble.mjs +14 -14
  18. package/files/assemble_stream.mjs +6 -6
  19. package/files/chunk_responder.mjs +29 -20
  20. package/files/evfs.mjs +29 -28
  21. package/files/manifest_fetch.mjs +16 -3
  22. package/files/public_manifest.mjs +11 -11
  23. package/files/transfer_key_registry.mjs +2 -2
  24. package/index.mjs +86 -11
  25. package/infra/cli.mjs +62 -0
  26. package/infra/debug_log.mjs +56 -0
  27. package/infra/default_node_dir.mjs +22 -0
  28. package/infra/priority.mjs +56 -0
  29. package/infra/service.mjs +140 -0
  30. package/infra/tunables.json +5 -0
  31. package/link/frame.mjs +9 -16
  32. package/link/handshake.mjs +14 -15
  33. package/link/pipe.mjs +8 -8
  34. package/link/providers/ble_gatt.mjs +6 -6
  35. package/link/rtc.mjs +3 -3
  36. package/mailbox/consumer_registry.mjs +2 -2
  37. package/mailbox/deliver_or_store.mjs +5 -5
  38. package/mailbox/wire.mjs +28 -24
  39. package/node/entity_store.mjs +6 -6
  40. package/node/instance.mjs +46 -27
  41. package/node/local_data_revision.mjs +26 -0
  42. package/node/log.mjs +56 -0
  43. package/node/network.mjs +37 -5
  44. package/node/reputation_store.mjs +4 -4
  45. package/node/reputation_sync.mjs +318 -0
  46. package/node/routing_profile.mjs +21 -0
  47. package/node/signaling_config.mjs +30 -11
  48. package/overlay/index.mjs +22 -1
  49. package/package.json +13 -2
  50. package/rooms/scoped_link.mjs +17 -166
  51. package/transport/advert_ingest.mjs +11 -14
  52. package/transport/group_link_set.mjs +149 -99
  53. package/transport/link_registry.mjs +211 -60
  54. package/transport/mesh_keepalive.mjs +217 -0
  55. package/transport/node_scope.mjs +289 -0
  56. package/transport/offer_answer.mjs +45 -48
  57. package/transport/peer_pool.mjs +116 -14
  58. package/transport/remote_user_room.mjs +6 -9
  59. package/transport/{rtc_mdns_filter.mjs → rtc_ice_local_hostname.mjs} +13 -13
  60. package/transport/runtime_bootstrap.mjs +170 -108
  61. package/transport/tunables.json +11 -0
  62. package/transport/tunables.mjs +18 -0
  63. package/transport/user_room.mjs +83 -158
  64. package/trust_graph/build.mjs +0 -2
  65. package/trust_graph/cache.mjs +12 -3
  66. package/trust_graph/registry.mjs +6 -6
  67. package/trust_graph/send.mjs +0 -3
  68. package/utils/async_mutex.mjs +4 -4
  69. package/utils/emit_safe.mjs +3 -3
  70. package/utils/json_io.mjs +12 -12
  71. package/utils/map_pool.mjs +5 -5
  72. package/wire/part_ingress.mjs +32 -28
  73. package/wire/part_query.mjs +26 -20
  74. package/discovery/mdns.mjs +0 -197
  75. package/transport/signal_crypto.mjs +0 -104
@@ -1,13 +1,14 @@
1
1
  import { createHash } from 'node:crypto'
2
2
 
3
- import { attachMailboxWire } from '../mailbox/wire.mjs'
4
3
  import { ensureNodeDefaults, getNodeHash } from '../node/identity.mjs'
5
4
  import { registerFederationRoomProvider } from '../registries/room_provider.mjs'
6
5
  import { shuffleInPlace } from '../utils/shuffle.mjs'
7
- import { attachPartWire } from '../wire/part_ingress.mjs'
8
- import { attachPartQueryWire } from '../wire/part_query.mjs'
9
6
 
10
- import { listLinks, sendToNodeLink, subscribeScope, getLinkRegistry } from './link_registry.mjs'
7
+ import { getLinkRegistry, listLinks, sendToNodeLink } from './link_registry.mjs'
8
+ import {
9
+ attachUserRoomDefaultWires,
10
+ ensureNodeScope,
11
+ } from './node_scope.mjs'
11
12
  import { USER_ROOM_SCOPE } from './room_scopes.mjs'
12
13
 
13
14
  /**
@@ -20,117 +21,35 @@ import { USER_ROOM_SCOPE } from './room_scopes.mjs'
20
21
  const sendNodeAction = (peerId, action, payload) =>
21
22
  sendToNodeLink(peerId, { scope: 'node', action, payload })
22
23
 
23
-
24
- /**
25
- * 在 node scope action 表上注册 fed_chunk_get / fed_chunk_data handler,
26
- * 供用户房间(本地 + 远端)双向 chunk 传输使用。
27
- * @param {string} username 副本用户名 用户名
28
- * @param {{ on: (name: string, handler: (payload: unknown, peerId: string) => void) => void, send: (name: string, payload: unknown, peerId: string | null) => void }} wire action 表
29
- * @returns {void}
30
- */
31
- function attachUserRoomChunkHandlers(username, wire) {
32
- import('../files/chunk_responder.mjs').then(({ attachNodeScopeFedChunkResponder }) => {
33
- attachNodeScopeFedChunkResponder(username, wire)
34
- }).catch(error => console.error('p2p: failed to attach chunk handlers to node scope', error))
35
- }
36
-
37
- /** @type {Promise<UserRoomSlot | null> | null} */
24
+ /** @type {Promise<UserRoomSlot> | null} */
38
25
  let userRoomInflight = null
39
26
 
40
27
  /** @type {UserRoomSlot | null} */
41
- export let userRoomSlot = null
28
+ let userRoomSlot = null
42
29
 
43
- /** @type {Map<string, Set<(payload: unknown, peerId: string) => void>>} */
44
- const nodeActionHandlers = new Map()
45
- /** @type {Set<(username: string, wire: { on: (name: string, handler: (payload: unknown, peerId: string) => void) => void, send: (name: string, payload: unknown, peerId: string | null) => void }) => void>} */
46
- const nodeScopeWireHooks = new Set()
47
- /** @type {string} */
48
- let nodeScopeReplicaUsername = ''
49
- /** @type {ReturnType<typeof createNodeScopeWire> | null} */
50
- let nodeScopeWire = null
51
- let nodeScopeCleanup = null
30
+ /** @type {(() => void) | null} */
31
+ let userRoomDefaultWiresDispose = null
52
32
 
53
33
  /**
54
- * Chat 等非 P2P 模块可向 node scope 注册 fed_emoji 等 handler(避免 p2p→shell 硬依赖)。
55
- * @param {(username: string, wire: { on: (name: string, handler: (payload: unknown, peerId: string) => void) => void, send: (name: string, payload: unknown, peerId: string | null) => void }) => void} hook 注册回调
56
- * @returns {() => void} 取消注册
34
+ * @returns {UserRoomSlot | null} 已创建的用户房间槽,未 ensure 时为 null
57
35
  */
58
- export function registerUserRoomNodeScopeHook(hook) {
59
- nodeScopeWireHooks.add(hook)
60
- if (nodeScopeWire)
61
- try { hook(nodeScopeReplicaUsername, nodeScopeWire) } catch { /* ignore */ }
62
- return () => nodeScopeWireHooks.delete(hook)
63
- }
64
-
65
- /**
66
- * 创建 node scope 的 on/send wire 表。
67
- * @returns {{ on: (name: string, handler: (payload: unknown, peerId: string) => void) => void, send: (name: string, payload: unknown, peerId: string | null) => void }} wire 接口
68
- */
69
- function createNodeScopeWire() {
70
- return {
71
- /**
72
- * 注册 node scope action handler。
73
- * @param {string} name action 名称
74
- * @param {(payload: unknown, peerId: string) => void} handler 入站回调
75
- * @returns {void}
76
- */
77
- on(name, handler) {
78
- if (!nodeActionHandlers.has(name)) nodeActionHandlers.set(name, new Set())
79
- nodeActionHandlers.get(name).add(handler)
80
- },
81
- /**
82
- * 向指定 peer 发送 node scope action。
83
- * @param {string} name action 名称
84
- * @param {unknown} payload 载荷
85
- * @param {string | null} peerId 目标 peer id
86
- * @returns {void}
87
- */
88
- send(name, payload, peerId) {
89
- if (!peerId) return
90
- void sendNodeAction(peerId, name, payload).catch(() => { })
91
- },
92
- }
36
+ export function getUserRoomSlot() {
37
+ return userRoomSlot
93
38
  }
94
39
 
95
40
  /**
96
41
  * 返回当前所有活跃链路的 roster。
97
- * @returns {Array<{ peerId: string, remoteNodeHash: string }>} 在线 peer 列表
42
+ * @returns {Array<{ peerId: string, remoteNodeHash: string }>} 活跃链路列表
98
43
  */
99
- function activeLinkRoster() {
44
+ export function activeLinkRoster() {
100
45
  return listLinks().map(({ nodeHash }) => ({ peerId: nodeHash, remoteNodeHash: nodeHash }))
101
46
  }
102
47
 
103
48
  /**
104
- * 初始化 node scope 订阅与 wire 派发(幂等)。
105
- * @param {{ replicaUsername?: string }} wireContext 入站上下文
106
- * @returns {Promise<void>}
107
- */
108
- async function ensureNodeScopeRuntime(wireContext) {
109
- if (nodeScopeCleanup) return
110
- nodeScopeReplicaUsername = wireContext.replicaUsername || nodeScopeReplicaUsername || ''
111
- nodeScopeCleanup = subscribeScope('node', (senderNodeHash, envelope) => {
112
- const handlers = nodeActionHandlers.get(envelope.action)
113
- if (!handlers?.size) return
114
- for (const handler of handlers)
115
- try { handler(envelope.payload, senderNodeHash) } catch { /* ignore */ }
116
- })
117
- const wire = createNodeScopeWire()
118
- nodeScopeWire = wire
119
- attachPartWire({ replicaUsername: wireContext.replicaUsername }, wire)
120
- attachPartQueryWire({ replicaUsername: wireContext.replicaUsername }, wire)
121
- attachMailboxWire({ replicaUsername: wireContext.replicaUsername }, wire)
122
- attachUserRoomChunkHandlers(wireContext.replicaUsername || '', wire)
123
- for (const hook of nodeScopeWireHooks)
124
- try { hook(wireContext.replicaUsername || '', wire) } catch { /* ignore */ }
125
- }
126
-
127
- /**
128
- * 用户级 node scope 房间(`fount-node-{nodeHash}`),单节点单实例。
129
- *
130
49
  * @typedef {{
131
50
  * roomId: string
132
51
  * roomSecret: string
133
- * room: object
52
+ * room: object | null
134
53
  * sendToPeer: (peerId: string, actionName: string, payload: unknown) => void
135
54
  * getRoster: () => Array<{ peerId: string, remoteNodeHash: string | undefined }>
136
55
  * getPeerIdByNodeHash: (nodeHash: string) => string | null
@@ -138,28 +57,31 @@ async function ensureNodeScopeRuntime(wireContext) {
138
57
  */
139
58
 
140
59
  registerFederationRoomProvider('user-room', () => {
141
- if (!userRoomSlot) return []
60
+ const slot = getUserRoomSlot()
61
+ if (!slot) return []
142
62
  return [{
143
63
  groupId: USER_ROOM_SCOPE,
144
- /** @returns {Array<{ peerId: string, remoteNodeHash: string | undefined }>} 房间 roster 列表 */
145
- getRoster: () => userRoomSlot.getRoster(),
146
64
  /**
147
- * @param {string} nodeHash 64 位十六进制
148
- * @returns {string | null} 对端 id
65
+ * @returns {Array<{ peerId: string, remoteNodeHash: string | undefined }>} 当前 roster
66
+ */
67
+ getRoster: () => slot.getRoster(),
68
+ /**
69
+ * @param {string} nodeHash - 远端节点 hash
70
+ * @returns {string | null} 已连接时返回 peerId,否则 null
149
71
  */
150
- getPeerIdByNodeHash: nodeHash => userRoomSlot.getPeerIdByNodeHash(nodeHash),
72
+ getPeerIdByNodeHash: nodeHash => slot.getPeerIdByNodeHash(nodeHash),
151
73
  /**
152
- * @param {string} peerId 目标 peer
153
- * @param {string} actionName 节点 scope action
154
- * @param {unknown} payload 载荷
74
+ * @param {string} peerId - 目标 peer
75
+ * @param {string} actionName - node scope action
76
+ * @param {unknown} payload - 载荷
155
77
  * @returns {void}
156
78
  */
157
- sendToPeer: (peerId, actionName, payload) => userRoomSlot.sendToPeer(peerId, actionName, payload),
79
+ sendToPeer: (peerId, actionName, payload) => slot.sendToPeer(peerId, actionName, payload),
158
80
  }]
159
81
  })
160
82
 
161
83
  /**
162
- * @returns {{ appId: string, password: string, roomId: string, nodeHash: string }} 用户房间参数
84
+ * @returns {{ appId: string, password: string, roomId: string, nodeHash: string }} user room rendezvous 凭据
163
85
  */
164
86
  export function resolveUserRoomCredentials() {
165
87
  const nodeHash = getNodeHash()
@@ -173,61 +95,62 @@ export function resolveUserRoomCredentials() {
173
95
  }
174
96
 
175
97
  /**
176
- * @param {{ replicaUsername?: string }} [wireContext] 入站上下文(part/mailbox 派发用)
177
- * @returns {Promise<UserRoomSlot | null>} 用户级联邦房间槽
98
+ * 用户房间槽 + runtime;默认不挂业务 wire(用 `attachUserRoomDefaultWires` / `attachDefaultWires: true`)。
99
+ * @param {{ replicaUsername?: string, attachDefaultWires?: boolean }} [options] - 副本用户名与是否挂载默认 wire
100
+ * @returns {Promise<UserRoomSlot>} 用户房间槽
178
101
  */
179
- export async function ensureUserRoom(wireContext = {}) {
180
- if (userRoomSlot) return userRoomSlot
102
+ export async function ensureUserRoom(options = {}) {
103
+ const { attachDefaultWires = false } = options
104
+ if (options.replicaUsername != null)
105
+ ensureNodeScope({ replicaUsername: options.replicaUsername })
106
+ if (userRoomSlot) {
107
+ if (attachDefaultWires && !userRoomDefaultWiresDispose)
108
+ userRoomDefaultWiresDispose = attachUserRoomDefaultWires({ replicaUsername: options.replicaUsername })
109
+ return userRoomSlot
110
+ }
181
111
  if (userRoomInflight) return await userRoomInflight
182
112
 
183
113
  userRoomInflight = (async () => {
184
114
  ensureNodeDefaults()
185
- try {
186
- await getLinkRegistry().ensureRuntime()
187
- await ensureNodeScopeRuntime(wireContext)
188
- const creds = resolveUserRoomCredentials()
189
- /** @type {UserRoomSlot} */
190
- userRoomSlot = {
191
- roomId: creds.roomId,
192
- roomSecret: creds.password,
193
- room: null,
194
- /**
195
- * node scope peer 发送 action。
196
- * @param {string} peerId 目标 peer id
197
- * @param {string} actionName action 名称
198
- * @param {unknown} payload 载荷
199
- * @returns {void}
200
- */
201
- sendToPeer(peerId, actionName, payload) {
202
- void sendNodeAction(peerId, actionName, payload).catch(() => { })
203
- },
204
- /**
205
- * 返回当前活跃链路 roster。
206
- * @returns {Array<{ peerId: string, remoteNodeHash: string }>} 在线 peer 列表
207
- */
208
- getRoster: () => activeLinkRoster(),
209
- /**
210
- * 按 nodeHash 查找 peer id。
211
- * @param {string} nodeHash 目标节点 64 hex
212
- * @returns {string | null} 对端 id;无链路时为 null
213
- */
214
- getPeerIdByNodeHash(nodeHash) {
215
- return getLinkRegistry().getLink(nodeHash) ? nodeHash : null
216
- },
217
- }
218
- return userRoomSlot
219
- }
220
- catch (error) {
221
- console.error('p2p: node scope init failed', error)
222
- userRoomSlot = null
223
- return null
224
- }
225
- finally {
226
- userRoomInflight = null
115
+ await getLinkRegistry().ensureRuntime()
116
+ ensureNodeScope({ replicaUsername: options.replicaUsername })
117
+ if (attachDefaultWires && !userRoomDefaultWiresDispose)
118
+ userRoomDefaultWiresDispose = attachUserRoomDefaultWires({ replicaUsername: options.replicaUsername })
119
+ const creds = resolveUserRoomCredentials()
120
+ userRoomSlot = {
121
+ roomId: creds.roomId,
122
+ roomSecret: creds.password,
123
+ room: null,
124
+ /**
125
+ * @param {string} peerId - 目标 peer
126
+ * @param {string} actionName - node scope action 名
127
+ * @param {unknown} payload - 载荷
128
+ * @returns {void}
129
+ */
130
+ sendToPeer(peerId, actionName, payload) {
131
+ void sendNodeAction(peerId, actionName, payload).catch(() => { })
132
+ },
133
+ /**
134
+ * @returns {Array<{ peerId: string, remoteNodeHash: string }>} 当前活跃链路 roster
135
+ */
136
+ getRoster: () => activeLinkRoster(),
137
+ /**
138
+ * @param {string} nodeHash - 远端节点 hash
139
+ * @returns {string | null} 已连接时返回 peerId,否则 null
140
+ */
141
+ getPeerIdByNodeHash(nodeHash) {
142
+ return getLinkRegistry().getLink(nodeHash) ? nodeHash : null
143
+ },
227
144
  }
145
+ return userRoomSlot
228
146
  })()
229
147
 
230
- return await userRoomInflight
148
+ try {
149
+ return await userRoomInflight
150
+ }
151
+ finally {
152
+ userRoomInflight = null
153
+ }
231
154
  }
232
155
 
233
156
  /**
@@ -236,16 +159,15 @@ export async function ensureUserRoom(wireContext = {}) {
236
159
  * @param {unknown} payload 载荷
237
160
  * @param {string | null} [exceptPeerId] 跳过的 peer
238
161
  * @param {number} [limit] 最多转发 peer 数
239
- * @returns {Promise<number>} 实际转发的 peer 数
162
+ * @returns {Promise<number>} 成功转发的 peer 数
240
163
  */
241
164
  export async function deliverToUserRoomPeers(username, actionName, payload, exceptPeerId = null, limit) {
165
+ void username
242
166
  const { USER_ROOM_PEER_FANOUT_DEFAULT } = await import('../wire/part_common.mjs')
243
167
  const fanoutLimit = limit ?? USER_ROOM_PEER_FANOUT_DEFAULT
244
- const slot = await ensureUserRoom({ replicaUsername: username })
245
- if (!slot) return 0
246
168
  const body = { ...payload, nodeHash: getNodeHash() }
247
169
  let sent = 0
248
- const peers = shuffleInPlace(slot.getRoster()
170
+ const peers = shuffleInPlace(activeLinkRoster()
249
171
  .filter(({ peerId }) => peerId && peerId !== exceptPeerId))
250
172
  for (const { peerId } of peers)
251
173
  try {
@@ -257,3 +179,6 @@ export async function deliverToUserRoomPeers(username, actionName, payload, exce
257
179
 
258
180
  return sent
259
181
  }
182
+
183
+ /** 再导出:node-scope 订阅与默认 wires(见 `node_scope.mjs`)。 */
184
+ export { attachUserRoomDefaultWires, ensureNodeScope } from './node_scope.mjs'
@@ -3,7 +3,6 @@ import { loadNetwork } from '../node/network.mjs'
3
3
  import { loadReputation } from '../node/reputation_store.mjs'
4
4
  import { listFederationRoomSlots } from '../registries/room_provider.mjs'
5
5
  import { isQuarantinedPure } from '../reputation/engine.mjs'
6
- import { ensureUserRoom } from '../transport/user_room.mjs'
7
6
 
8
7
  import { getCachedTrustGraph } from './cache.mjs'
9
8
  import { mergeGraph, pickTopFromGraph } from './engine.mjs'
@@ -90,7 +89,6 @@ export async function buildMergedGraph(username) {
90
89
  * @returns {Promise<TrustNode[]>} 按信誉降序
91
90
  */
92
91
  export async function pickTopNodes(username, limit = trustGraphTunables.pickTopNodesDefaultLimit) {
93
- await ensureUserRoom({ replicaUsername: username })
94
92
  const rep = loadReputation()
95
93
  const quarantined = new Set(
96
94
  Object.keys(rep.byNodeHash || {}).filter(id => isQuarantinedPure(rep, id)),
@@ -1,10 +1,13 @@
1
- /** @type {Map<string, { graph: Map<string, object>, builtAt: number, revision: number }>} */
1
+ import { getLocalDataRevision } from '../node/local_data_revision.mjs'
2
+
3
+ /** @type {Map<string, { graph: Map<string, object>, builtAt: number, revision: number, dataRevision: number }>} */
2
4
  const cacheByUsername = new Map()
3
5
  let revision = 0
4
6
 
5
7
  const DEFAULT_TTL_MS = 30_000
6
8
 
7
9
  /**
10
+ * 显式失效(测试 / 特殊路径);常态依赖 local_data_revision。
8
11
  * @returns {void}
9
12
  */
10
13
  export function invalidateTrustGraphCache() {
@@ -21,11 +24,17 @@ export function invalidateTrustGraphCache() {
21
24
  export async function getCachedTrustGraph(username, build, ttlMs = DEFAULT_TTL_MS) {
22
25
  const key = String(username || '')
23
26
  const now = Date.now()
27
+ const dataRevision = getLocalDataRevision()
24
28
  const cached = cacheByUsername.get(key)
25
- if (cached && cached.revision === revision && now - cached.builtAt < ttlMs)
29
+ if (
30
+ cached
31
+ && cached.revision === revision
32
+ && cached.dataRevision === dataRevision
33
+ && now - cached.builtAt < ttlMs
34
+ )
26
35
  return cached.graph
27
36
 
28
37
  const graph = await build()
29
- cacheByUsername.set(key, { graph, builtAt: now, revision })
38
+ cacheByUsername.set(key, { graph, builtAt: now, revision, dataRevision })
30
39
  return graph
31
40
  }
@@ -9,11 +9,11 @@ export const DEFAULT_TRUST_GRAPH_OWNER = 'default'
9
9
 
10
10
  /**
11
11
  * @param {string} ownerId 注册方(如 chat)
12
- * @param {import('./registry.mjs').TrustGraphProvider} impl 信任图实现
12
+ * @param {import('./registry.mjs').TrustGraphProvider} implementation 信任图实现
13
13
  * @returns {void}
14
14
  */
15
- export function registerTrustGraphProvider(ownerId, impl) {
16
- providersByOwner.set(String(ownerId), impl)
15
+ export function registerTrustGraphProvider(ownerId, implementation) {
16
+ providersByOwner.set(String(ownerId), implementation)
17
17
  }
18
18
 
19
19
  /** @returns {void} */
@@ -26,10 +26,10 @@ export function clearTrustGraphProvider() {
26
26
  * @returns {import('./registry.mjs').TrustGraphProvider} 已注册实现
27
27
  */
28
28
  export function requireTrustGraphProvider(ownerId = DEFAULT_TRUST_GRAPH_OWNER) {
29
- const impl = providersByOwner.get(String(ownerId))
30
- if (!impl)
29
+ const implementation = providersByOwner.get(String(ownerId))
30
+ if (!implementation)
31
31
  throw new Error(`p2p: registerTrustGraphProvider('${ownerId}') must run before trust graph fanout`)
32
- return impl
32
+ return implementation
33
33
  }
34
34
 
35
35
  /** @returns {import('./registry.mjs').TrustGraphProvider} 默认信任图实现 */
@@ -5,7 +5,6 @@ import { listFederationRoomSlots } from '../registries/room_provider.mjs'
5
5
  import { isQuarantinedPure } from '../reputation/engine.mjs'
6
6
  import { sendToNodeLink } from '../transport/link_registry.mjs'
7
7
  import { USER_ROOM_SCOPE } from '../transport/room_scopes.mjs'
8
- import { ensureUserRoom } from '../transport/user_room.mjs'
9
8
 
10
9
  import { buildMergedGraph } from './build.mjs'
11
10
  import { pickTopFromGraph } from './engine.mjs'
@@ -23,7 +22,6 @@ import trustGraphTunables from './tunables.json' with { type: 'json' }
23
22
  export async function sendToNode(username, targetNodeHash, actionName, payload, graph) {
24
23
  const target = normalizeHex64(targetNodeHash) || String(targetNodeHash || '').trim().toLowerCase()
25
24
  if (!isHex64(target)) return false
26
- await ensureUserRoom({ replicaUsername: username })
27
25
 
28
26
  // 已直连 peer 不经 trust-graph scope 也应能收发 node scope action(非成员 CAS chunk / follow hint 等)
29
27
  if (await sendToNodeLink(target, { scope: 'node', action: actionName, payload }))
@@ -73,7 +71,6 @@ export async function sendToNode(username, targetNodeHash, actionName, payload,
73
71
  * @returns {Promise<number>} 发送次数
74
72
  */
75
73
  export async function fanoutToTopNodes(username, actionName, payload, limit) {
76
- await ensureUserRoom({ replicaUsername: username })
77
74
  const graph = await buildMergedGraph(username)
78
75
  const k = limit ?? resolveFederationFanoutTopK(graph.size, trustGraphTunables)
79
76
  const rep = loadReputation()
@@ -34,11 +34,11 @@ function releaseMutex(lockKey, state) {
34
34
 
35
35
  /**
36
36
  * @param {string} key 锁键
37
- * @param {() => Promise<T>} fn 临界区
38
- * @returns {Promise<T>} `fn` 的解析结果
37
+ * @param {() => Promise<T>} criticalSection 临界区
38
+ * @returns {Promise<T>} `criticalSection` 的解析结果
39
39
  * @template T
40
40
  */
41
- export async function withAsyncMutex(key, fn) {
41
+ export async function withAsyncMutex(key, criticalSection) {
42
42
  const lockKey = String(key)
43
43
  const state = mutexState(lockKey)
44
44
  return new Promise((resolve, reject) => {
@@ -47,7 +47,7 @@ export async function withAsyncMutex(key, fn) {
47
47
  */
48
48
  const run = () => {
49
49
  Promise.resolve()
50
- .then(fn)
50
+ .then(criticalSection)
51
51
  .then(resolve, reject)
52
52
  .finally(() => releaseMutex(lockKey, state))
53
53
  }
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * 调用一组 listener,单次抛错不影响其余。
3
3
  * @param {Iterable<Function>} listeners 回调集合
4
- * @param {...unknown} args 传给每个 listener 的参数
4
+ * @param {...unknown} listenerArguments 传给每个 listener 的参数
5
5
  * @returns {void}
6
6
  */
7
- export function emitSafe(listeners, ...args) {
7
+ export function emitSafe(listeners, ...listenerArguments) {
8
8
  for (const listener of listeners)
9
- try { listener(...args) }
9
+ try { listener(...listenerArguments) }
10
10
  catch { /* ignore */ }
11
11
  }
package/utils/json_io.mjs CHANGED
@@ -11,9 +11,9 @@ export async function readJsonFile(filePath) {
11
11
  const raw = await fsp.readFile(filePath, 'utf8')
12
12
  return JSON.parse(raw)
13
13
  }
14
- catch (err) {
15
- if (/** @type {NodeJS.ErrnoException} */ err.code === 'ENOENT') return null
16
- throw err
14
+ catch (error) {
15
+ if (/** @type {NodeJS.ErrnoException} */ error.code === 'ENOENT') return null
16
+ throw error
17
17
  }
18
18
  }
19
19
 
@@ -24,9 +24,9 @@ export async function readJsonFile(filePath) {
24
24
  */
25
25
  export async function writeJsonFile(filePath, data) {
26
26
  await fsp.mkdir(path.dirname(filePath), { recursive: true })
27
- const tmp = `${filePath}.tmp`
28
- await fsp.writeFile(tmp, `${JSON.stringify(data, null, 2)}\n`, 'utf8')
29
- await fsp.rename(tmp, filePath)
27
+ const temporaryPath = `${filePath}.tmp`
28
+ await fsp.writeFile(temporaryPath, `${JSON.stringify(data, null, 2)}\n`, 'utf8')
29
+ await fsp.rename(temporaryPath, filePath)
30
30
  }
31
31
 
32
32
  /**
@@ -38,9 +38,9 @@ export function readJsonFileSync(filePath) {
38
38
  const raw = fs.readFileSync(filePath, 'utf8')
39
39
  return JSON.parse(raw)
40
40
  }
41
- catch (err) {
42
- if (/** @type {NodeJS.ErrnoException} */ err.code === 'ENOENT') return null
43
- throw err
41
+ catch (error) {
42
+ if (/** @type {NodeJS.ErrnoException} */ error.code === 'ENOENT') return null
43
+ throw error
44
44
  }
45
45
  }
46
46
 
@@ -51,7 +51,7 @@ export function readJsonFileSync(filePath) {
51
51
  */
52
52
  export function writeJsonFileSync(filePath, data) {
53
53
  fs.mkdirSync(path.dirname(filePath), { recursive: true })
54
- const tmp = `${filePath}.tmp`
55
- fs.writeFileSync(tmp, `${JSON.stringify(data, null, 2)}\n`, 'utf8')
56
- fs.renameSync(tmp, filePath)
54
+ const temporaryPath = `${filePath}.tmp`
55
+ fs.writeFileSync(temporaryPath, `${JSON.stringify(data, null, 2)}\n`, 'utf8')
56
+ fs.renameSync(temporaryPath, filePath)
57
57
  }
@@ -2,11 +2,11 @@
2
2
  * 有限并发 map(进程内,无外部依赖)。
3
3
  * @template T, R
4
4
  * @param {T[]} items 待处理项
5
- * @param {(item: T, index: number) => Promise<R>} fn 异步映射
5
+ * @param {(item: T, index: number) => Promise<R>} mapper 异步映射
6
6
  * @param {number} concurrency 并发上限
7
7
  * @returns {Promise<R[]>} 与 items 同序的结果
8
8
  */
9
- export async function mapPool(items, fn, concurrency) {
9
+ export async function mapPool(items, mapper, concurrency) {
10
10
  if (!items.length) return []
11
11
  const limit = Math.max(1, Math.min(concurrency, items.length))
12
12
  /** @type {R[]} */
@@ -16,9 +16,9 @@ export async function mapPool(items, fn, concurrency) {
16
16
  /** @returns {Promise<void>} */
17
17
  const worker = async () => {
18
18
  for (; ;) {
19
- const i = nextIndex++
20
- if (i >= items.length) return
21
- results[i] = await fn(items[i], i)
19
+ const index = nextIndex++
20
+ if (index >= items.length) return
21
+ results[index] = await mapper(items[index], index)
22
22
  }
23
23
  }
24
24
 
@@ -18,7 +18,7 @@ export const pendingPartInvoke = new Map()
18
18
  /**
19
19
  * @typedef {{
20
20
  * send: (name: string, payload: unknown, peerId: string | null) => void
21
- * on: (name: string, handler: (payload: unknown, peerId: string) => void) => void
21
+ * on: (name: string, handler: (payload: unknown, peerId: string) => void) => (() => void) | void
22
22
  * }} PartWireAdapter
23
23
  */
24
24
 
@@ -73,35 +73,39 @@ async function dispatchPartInvoke(wireContext, payload) {
73
73
  * @param {PartWireContext} wireContext 入站上下文
74
74
  * @param {PartWireAdapter} wire Trystero 适配器
75
75
  * @param {{ allowPartInvoke?: (payload: object) => boolean }} [options] 入站过滤
76
- * @returns {void}
76
+ * @returns {() => void} 取消挂载的 dispose
77
77
  */
78
78
  export function attachPartWire(wireContext, wire, options = {}) {
79
- wire.on('part_timeline_put', data => {
80
- if (!isPlainObject(data)) return
81
- const partpath = normalizePartpath(data.partpath)
82
- if (!partpath) return
83
- const message = parsePartTimelinePut(data, partpath)
84
- if (!message) return
85
- void dispatchDeliveryInbound({
86
- replicaUsername: wireContext.replicaUsername,
87
- requesterNodeHash: data.nodeHash ? String(data.nodeHash).trim() : null,
88
- }, message)
89
- })
90
-
91
- wire.on('part_invoke', (data, peerId) => {
92
- if (!isPlainObject(data)) return
93
- if (options.allowPartInvoke?.(data) === false) return
94
- const payload = { ...data, peerId }
95
- if (payload.requestId)
96
- void handleIncomingPartInvokeRequest(wireContext, payload, wire, peerId)
97
- else
98
- void handleIncomingPartInvokeFireAndForget(wireContext, payload, wire, peerId)
99
- })
100
-
101
- wire.on('part_invoke_response', (data, peerId) => {
102
- if (!isPlainObject(data)) return
103
- handleIncomingPartInvokeResponse(data, peerId)
104
- })
79
+ const offs = [
80
+ wire.on('part_timeline_put', data => {
81
+ if (!isPlainObject(data)) return
82
+ const partpath = normalizePartpath(data.partpath)
83
+ if (!partpath) return
84
+ const message = parsePartTimelinePut(data, partpath)
85
+ if (!message) return
86
+ void dispatchDeliveryInbound({
87
+ replicaUsername: wireContext.replicaUsername,
88
+ requesterNodeHash: data.nodeHash ? String(data.nodeHash).trim() : null,
89
+ }, message)
90
+ }),
91
+ wire.on('part_invoke', (data, peerId) => {
92
+ if (!isPlainObject(data)) return
93
+ if (options.allowPartInvoke?.(data) === false) return
94
+ const payload = { ...data, peerId }
95
+ if (payload.requestId)
96
+ void handleIncomingPartInvokeRequest(wireContext, payload, wire, peerId)
97
+ else
98
+ void handleIncomingPartInvokeFireAndForget(wireContext, payload, wire, peerId)
99
+ }),
100
+ wire.on('part_invoke_response', (data, peerId) => {
101
+ if (!isPlainObject(data)) return
102
+ handleIncomingPartInvokeResponse(data, peerId)
103
+ }),
104
+ ]
105
+ return () => {
106
+ for (const off of offs)
107
+ try { off?.() } catch { /* ignore */ }
108
+ }
105
109
  }
106
110
 
107
111
  /**