@steve02081504/fount-p2p 0.0.26 → 0.0.27

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 (99) hide show
  1. package/README.md +1 -0
  2. package/core/bytes_codec.mjs +5 -6
  3. package/core/composite_key.mjs +0 -79
  4. package/core/entity_id_parse.mjs +2 -3
  5. package/core/evfs_logical_path.mjs +2 -3
  6. package/core/hexIds.mjs +5 -5
  7. package/core/logical_entity.mjs +3 -4
  8. package/crypto/channel.mjs +2 -25
  9. package/crypto/checkpoint_sign.mjs +5 -4
  10. package/crypto/crypto.mjs +0 -8
  11. package/crypto/key.mjs +9 -10
  12. package/dag/canonicalize_row.mjs +1 -1
  13. package/discovery/advert_peer_hints.mjs +1 -1
  14. package/discovery/adverts.mjs +7 -12
  15. package/discovery/bt/index.mjs +3 -11
  16. package/discovery/bt/peer_hints.mjs +2 -3
  17. package/discovery/bt/runtime.mjs +1 -1
  18. package/discovery/index.mjs +14 -14
  19. package/discovery/lan.mjs +3 -3
  20. package/discovery/lan_interfaces.mjs +2 -2
  21. package/discovery/lan_peer_hints.mjs +1 -1
  22. package/discovery/nostr.mjs +16 -21
  23. package/federation/entity_key_chain.mjs +5 -24
  24. package/federation/message_rate_limit.mjs +3 -2
  25. package/federation/part_query/runtime.mjs +9 -20
  26. package/files/assemble.mjs +3 -3
  27. package/files/assemble_stream.mjs +1 -1
  28. package/files/chunk/fetch.mjs +1 -2
  29. package/files/chunk/responder.mjs +4 -41
  30. package/files/chunk/store.mjs +2 -3
  31. package/files/chunk/verify.mjs +2 -3
  32. package/files/evfs.mjs +1 -1
  33. package/files/evfs_ref.mjs +3 -4
  34. package/files/fetch_fanout.mjs +3 -3
  35. package/files/manifest/fetch.mjs +2 -3
  36. package/files/manifest/normalize.mjs +9 -9
  37. package/files/manifest/pending.mjs +1 -4
  38. package/files/manifest/public.mjs +8 -8
  39. package/files/transfer_key.mjs +3 -3
  40. package/files/transfer_key_registry.mjs +6 -6
  41. package/governance/branch.mjs +2 -1
  42. package/governance/join_pow.mjs +7 -5
  43. package/governance/owner_succession_ballot.mjs +6 -6
  44. package/index.mjs +2 -2
  45. package/infra/default_node_dir.mjs +2 -2
  46. package/infra/priority.mjs +2 -2
  47. package/link/frame.mjs +2 -3
  48. package/link/handshake.mjs +6 -6
  49. package/link/pipe.mjs +1 -2
  50. package/link/providers/ble_gatt.mjs +1 -9
  51. package/link/providers/index.mjs +2 -2
  52. package/link/providers/nostr.mjs +1 -3
  53. package/link/providers/webrtc.mjs +1 -1
  54. package/link/rtc/ice_local_hostname.mjs +6 -5
  55. package/link/rtc/w3c_bridge.mjs +1 -1
  56. package/link/sdp_fingerprint.mjs +1 -1
  57. package/mailbox/consumer_registry.mjs +2 -2
  58. package/mailbox/deliver_or_store.mjs +4 -4
  59. package/mailbox/parse.mjs +1 -1
  60. package/mailbox/prune.mjs +2 -2
  61. package/mailbox/rate.mjs +3 -10
  62. package/mailbox/store.mjs +7 -7
  63. package/node/denylist.mjs +38 -38
  64. package/node/entity_store.mjs +5 -5
  65. package/node/identity.mjs +7 -8
  66. package/node/instance.mjs +1 -1
  67. package/node/log.mjs +1 -1
  68. package/node/network.mjs +14 -16
  69. package/node/personal_block.mjs +22 -20
  70. package/node/reputation_store.mjs +11 -19
  71. package/node/reputation_sync.mjs +0 -7
  72. package/node/signaling_config.mjs +1 -1
  73. package/node/storage_plugins.mjs +2 -2
  74. package/overlay/index.mjs +1 -1
  75. package/package.json +1 -1
  76. package/registries/event_type.mjs +2 -2
  77. package/registries/room_provider.mjs +2 -2
  78. package/reputation/engine.mjs +17 -15
  79. package/schemas/discovery.mjs +5 -5
  80. package/schemas/federation_pull.mjs +16 -24
  81. package/schemas/part_query.mjs +4 -4
  82. package/timeline/verify_remote.mjs +4 -4
  83. package/transport/group_link_set.mjs +2 -2
  84. package/transport/ice_servers.mjs +2 -2
  85. package/transport/link_registry.mjs +6 -18
  86. package/transport/mesh_keepalive.mjs +1 -1
  87. package/transport/node_scope/wire.mjs +1 -1
  88. package/transport/remote_user_room.mjs +13 -15
  89. package/transport/rtc_connection_budget.mjs +6 -6
  90. package/transport/runtime_bootstrap.mjs +12 -12
  91. package/trust_graph/cache.mjs +2 -3
  92. package/trust_graph/engine.mjs +1 -1
  93. package/trust_graph/registry.mjs +2 -2
  94. package/trust_graph/send.mjs +1 -1
  95. package/utils/async_mutex.mjs +1 -1
  96. package/wire/ingress.mjs +1 -1
  97. package/wire/part/ingress.mjs +6 -6
  98. package/wire/part/query.mjs +3 -3
  99. package/wire/volatile_signature.mjs +4 -9
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * 信誉纯内存算子(模拟器与磁盘 store 共用)。
3
3
  */
4
+ import { normalizeHex64 } from '../core/hexIds.mjs'
5
+
4
6
  import {
5
7
  clampReputationScore,
6
8
  computeRepMaxEff,
@@ -176,9 +178,9 @@ export function pruneReputationFile(data, tunables = reputationTunables, now = D
176
178
  * @returns {boolean} 是否已加分
177
179
  */
178
180
  export function bumpReputationOnRelayPure(data, peerNodeHash, dedupeKey, now = Date.now(), tunables = reputationTunables) {
179
- const id = String(peerNodeHash || '').trim()
181
+ const id = normalizeHex64(peerNodeHash)
180
182
  if (!id) return false
181
- const key = String(dedupeKey || `conn:${id}`).trim()
183
+ const key = dedupeKey || `conn:${id}`
182
184
  data.relayBumpSeen = data.relayBumpSeen.filter(hit => now - hit.t <= tunables.relayBumpDedupeMs)
183
185
  if (relayBumpIsDuplicate(data.relayBumpSeen, id, key, now, tunables.relayBumpDedupeMs)) return false
184
186
  data.relayBumpSeen.push({ peerNodeHash: id, key, t: now })
@@ -213,7 +215,7 @@ export function recordGossipAllUnknownWantPure(data, peerNodeHash, now = Date.no
213
215
  * @returns {void}
214
216
  */
215
217
  export function recordMessageRateViolationPure(data, peerNodeHash, tunables = reputationTunables, excessRatio = 1) {
216
- const id = String(peerNodeHash || '').trim()
218
+ const id = normalizeHex64(peerNodeHash)
217
219
  if (!id) return
218
220
  const ratio = Math.max(0, Math.min(1, Number(excessRatio) || 0))
219
221
  if (ratio <= 0) return
@@ -227,7 +229,7 @@ export function recordMessageRateViolationPure(data, peerNodeHash, tunables = re
227
229
  * @returns {void}
228
230
  */
229
231
  export function bumpChunkStorageReputationPure(data, storagePeerKey, tunables = reputationTunables) {
230
- const id = String(storagePeerKey || '').trim()
232
+ const id = normalizeHex64(storagePeerKey)
231
233
  if (!id) return
232
234
  adjustNodeReputation(data, id, tunables.chunkStoreRepBump)
233
235
  }
@@ -239,7 +241,7 @@ export function bumpChunkStorageReputationPure(data, storagePeerKey, tunables =
239
241
  * @returns {void}
240
242
  */
241
243
  export function penalizeChunkStorageFailurePure(data, blamePeerKey, tunables = reputationTunables) {
242
- const id = String(blamePeerKey || '').trim()
244
+ const id = normalizeHex64(blamePeerKey)
243
245
  if (!id) return
244
246
  adjustNodeReputation(data, id, -tunables.chunkFetchFailPenalty)
245
247
  }
@@ -251,7 +253,7 @@ export function penalizeChunkStorageFailurePure(data, blamePeerKey, tunables = r
251
253
  * @returns {void}
252
254
  */
253
255
  export function penalizeArchiveServeMismatchPure(data, peerNodeHash, tunables = reputationTunables) {
254
- const id = String(peerNodeHash || '').trim()
256
+ const id = normalizeHex64(peerNodeHash)
255
257
  if (!id) return
256
258
  adjustNodeReputation(data, id, -tunables.archiveServeMismatchPenalty)
257
259
  }
@@ -280,7 +282,7 @@ export function applySubjectiveSlashPure(data, target, sender, claim, verified,
280
282
  * @returns {Array<{ hop: number, node: string, dRep: number }>} 已应用的上游惩罚
281
283
  */
282
284
  export function applyDecayCollusionAfterSlashPure(data, targetPubKeyHash, inviteEdges, tunables = reputationTunables) {
283
- const target = targetPubKeyHash.trim().toLowerCase()
285
+ const target = normalizeHex64(targetPubKeyHash)
284
286
  /** @type {Array<{ hop: number, node: string, dRep: number }>} */
285
287
  const applied = []
286
288
  let frontier = new Set([target])
@@ -301,8 +303,8 @@ export function applyDecayCollusionAfterSlashPure(data, targetPubKeyHash, invite
301
303
  for (let hop = 1; hop <= effectiveMaxHop(); hop++) {
302
304
  const upstream = new Set()
303
305
  for (const edge of inviteEdges) {
304
- const to = String(edge.to ?? '').trim().toLowerCase()
305
- const from = String(edge.from ?? '').trim().toLowerCase()
306
+ const to = normalizeHex64(edge.to)
307
+ const from = normalizeHex64(edge.from)
306
308
  if (frontier.has(to) && from) upstream.add(from)
307
309
  }
308
310
  if (!upstream.size) break
@@ -331,7 +333,7 @@ export function applyDecayCollusionAfterSlashPure(data, targetPubKeyHash, invite
331
333
  * @returns {void}
332
334
  */
333
335
  export function applyReputationResetToScoresPure(data, targetPubKeyHash) {
334
- const t = targetPubKeyHash.trim().toLowerCase()
336
+ const t = normalizeHex64(targetPubKeyHash)
335
337
  const row = repRow(data, t)
336
338
  row.score = 0
337
339
  delete row.offenseStreak
@@ -351,8 +353,8 @@ export function applyReputationResetToScoresPure(data, targetPubKeyHash) {
351
353
  * @returns {void}
352
354
  */
353
355
  export function seedMemberReputationFromIntroducerPure(data, memberPubKeyHash, introducerPubKeyHash, repEdge, tunables = reputationTunables, powBonus = 0) {
354
- const memberKey = memberPubKeyHash.trim().toLowerCase()
355
- const introducerKey = introducerPubKeyHash?.trim().toLowerCase() || ''
356
+ const memberKey = normalizeHex64(memberPubKeyHash)
357
+ const introducerKey = normalizeHex64(introducerPubKeyHash)
356
358
  if (data.byNodeHash[memberKey]) return
357
359
  const introducerReputation = introducerKey
358
360
  ? Number(data.byNodeHash[introducerKey]?.score ?? 0)
@@ -392,7 +394,7 @@ function baselineRow(row) {
392
394
  * @returns {{ z: number, anomaly: boolean }} 偏离度
393
395
  */
394
396
  export function observeBehaviorSamplePure(data, peerNodeHash, sample, now = Date.now(), tunables = reputationTunables) {
395
- const id = String(peerNodeHash || '').trim()
397
+ const id = normalizeHex64(peerNodeHash)
396
398
  if (!id) return { z: 0, anomaly: false }
397
399
  const row = repRow(data, id)
398
400
  const baseline = baselineRow(row)
@@ -427,7 +429,7 @@ export function observeBehaviorSamplePure(data, peerNodeHash, sample, now = Date
427
429
  * @returns {void}
428
430
  */
429
431
  export function applyQuarantinePure(data, peerNodeHash, now = Date.now(), tunables = reputationTunables) {
430
- const id = String(peerNodeHash || '').trim()
432
+ const id = normalizeHex64(peerNodeHash)
431
433
  if (!id) return
432
434
  const row = repRow(data, id)
433
435
  row.quarantinedUntil = now + Number(tunables.quarantineTtlMs ?? 900_000)
@@ -441,7 +443,7 @@ export function applyQuarantinePure(data, peerNodeHash, now = Date.now(), tunabl
441
443
  * @returns {boolean} 是否处于本地隔离
442
444
  */
443
445
  export function isQuarantinedPure(data, peerNodeHash, now = Date.now()) {
444
- const id = String(peerNodeHash || '').trim()
446
+ const id = normalizeHex64(peerNodeHash)
445
447
  const until = Number(data.byNodeHash?.[id]?.quarantinedUntil ?? 0)
446
448
  return Number.isFinite(until) && until > now
447
449
  }
@@ -9,9 +9,9 @@ const MAX_DISCOVERY_ADS = 64
9
9
  */
10
10
  function sanitizeDiscoveryAdvertisement(ad) {
11
11
  if (!isPlainObject(ad)) return null
12
- const groupId = String(ad.groupId || '').trim()
12
+ const groupId = String(ad.groupId || '')
13
13
  const advertiserPubKeyHash = normalizeHex64(ad.advertiserPubKeyHash)
14
- const signature = String(ad.signature || '').trim().toLowerCase()
14
+ const signature = String(ad.signature || '')
15
15
  if (!groupId || !isHex64(advertiserPubKeyHash) || !isSignatureHex128(signature)) return null
16
16
  const advertiserNodeHash = normalizeHex64(ad.advertiserNodeHash)
17
17
  const body = {
@@ -19,7 +19,7 @@ function sanitizeDiscoveryAdvertisement(ad) {
19
19
  title: String(ad.title || '').slice(0, 200),
20
20
  blurb: String(ad.blurb || '').slice(0, 500),
21
21
  advertiserPubKeyHash,
22
- advertiserNodeHash: isHex64(advertiserNodeHash) ? advertiserNodeHash : String(ad.advertiserNodeHash || '').trim(),
22
+ advertiserNodeHash: isHex64(advertiserNodeHash) ? advertiserNodeHash : String(ad.advertiserNodeHash || ''),
23
23
  observedAt: Number(ad.observedAt) || 0,
24
24
  signature,
25
25
  }
@@ -47,10 +47,10 @@ export function assertDiscoveryNodeHash(nodeHash) {
47
47
 
48
48
  /**
49
49
  * @param {unknown} requestId 请求 id
50
- * @returns {string} 非空 trimmed 字符串
50
+ * @returns {string} 非空字符串
51
51
  */
52
52
  export function assertDiscoveryRequestId(requestId) {
53
- const id = String(requestId ?? '').trim()
53
+ const id = String(requestId ?? '')
54
54
  if (!id) throw new Error('discovery.requestId required')
55
55
  return id
56
56
  }
@@ -14,7 +14,7 @@ const ENVELOPE_BLOB_MIN_LEN = 2
14
14
  * @returns {boolean} 是否像合法 ECIES blob 段
15
15
  */
16
16
  function isEnvelopeBlob(value) {
17
- return String(value ?? '').trim().length >= ENVELOPE_BLOB_MIN_LEN
17
+ return String(value ?? '').length >= ENVELOPE_BLOB_MIN_LEN
18
18
  }
19
19
 
20
20
  /**
@@ -24,14 +24,14 @@ function isEnvelopeBlob(value) {
24
24
  export function parsePullAttestation(attestation) {
25
25
  if (!isPlainObject(attestation)) return null
26
26
  const requesterPubKeyHash = normalizeHex64(attestation.requesterPubKeyHash)
27
- const groupId = String(attestation.groupId || '').trim()
28
- const requestId = String(attestation.requestId || '').trim()
27
+ const groupId = String(attestation.groupId || '')
28
+ const requestId = String(attestation.requestId || '')
29
29
  const timestamp = Number(attestation.timestamp)
30
- const signature = String(attestation.signature || '').trim().toLowerCase()
30
+ const signature = String(attestation.signature || '')
31
31
  if (!isHex64(requesterPubKeyHash) || !groupId || !Number.isFinite(timestamp) || !isSignatureHex128(signature))
32
32
  return null
33
33
  const wantIds = attestation.wantIds?.length
34
- ? [...new Set(attestation.wantIds.map(id => id.trim().toLowerCase()).filter(id => EVENT_ID_HEX.test(id)))]
34
+ ? [...new Set(attestation.wantIds.map(id => normalizeHex64(id)).filter(id => EVENT_ID_HEX.test(id)))]
35
35
  : undefined
36
36
  return {
37
37
  requesterPubKeyHash,
@@ -49,13 +49,13 @@ export function parsePullAttestation(attestation) {
49
49
  */
50
50
  export function parsePullResponseEnvelope(envelope) {
51
51
  if (!isPlainObject(envelope)) return null
52
- const requestId = String(envelope.requestId || '').trim()
52
+ const requestId = String(envelope.requestId || '')
53
53
  const requesterPubKeyHash = normalizeHex64(envelope.requesterPubKeyHash)
54
- const requesterNodeHash = String(envelope.requesterNodeHash || '').trim()
55
- const ephemPub = String(envelope.ephemPub || '').trim()
56
- const iv = String(envelope.iv || '').trim()
57
- const ciphertext = String(envelope.ciphertext || '').trim()
58
- const authTag = String(envelope.authTag || '').trim()
54
+ const requesterNodeHash = String(envelope.requesterNodeHash || '')
55
+ const ephemPub = String(envelope.ephemPub || '')
56
+ const iv = String(envelope.iv || '')
57
+ const ciphertext = String(envelope.ciphertext || '')
58
+ const authTag = String(envelope.authTag || '')
59
59
  if (!requestId || !isHex64(requesterPubKeyHash) || !requesterNodeHash) return null
60
60
  if (!isEnvelopeBlob(ephemPub) || !isEnvelopeBlob(iv) || !isEnvelopeBlob(ciphertext) || !isEnvelopeBlob(authTag))
61
61
  return null
@@ -76,27 +76,19 @@ export function parsePullResponseEnvelope(envelope) {
76
76
  */
77
77
  export function parseJoinSnapshotRequest(data) {
78
78
  if (!isPlainObject(data)) return null
79
- const requestId = String(data.requestId || '').trim()
80
- const requesterNodeHash = String(data.requesterNodeHash || '').trim()
81
- const groupId = String(data.groupId || '').trim()
79
+ const requestId = String(data.requestId || '')
80
+ const requesterNodeHash = String(data.requesterNodeHash || '')
81
+ const groupId = String(data.groupId || '')
82
82
  const attestation = parsePullAttestation(data.attestation)
83
83
  if (!requestId || !requesterNodeHash || !groupId || !attestation) return null
84
84
  if (attestation.groupId !== groupId || attestation.requestId !== requestId) return null
85
- const tipsHash = String(data.tipsHash || '').trim()
85
+ const tipsHash = String(data.tipsHash || '')
86
86
  return {
87
87
  requestId,
88
88
  requesterNodeHash,
89
89
  requesterPubKeyHash: attestation.requesterPubKeyHash,
90
90
  groupId,
91
- tipsHash: tipsHash ? tipsHash.toLowerCase() : undefined,
91
+ tipsHash: tipsHash || undefined,
92
92
  attestation,
93
93
  }
94
94
  }
95
-
96
- /**
97
- * @param {unknown} data 入群快照响应载荷
98
- * @returns {object | null} 解析结果
99
- */
100
- export function parseJoinSnapshotResponse(data) {
101
- return parsePullResponseEnvelope(data)
102
- }
@@ -82,10 +82,10 @@ const REQUEST_ID_MAX_LENGTH = 128
82
82
 
83
83
  /**
84
84
  * @param {unknown} value 原始 requestId
85
- * @returns {string | null} 修剪后的 requestId;非法 null
85
+ * @returns {string | null} 合法 requestId;非法 null
86
86
  */
87
87
  function normalizeRequestId(value) {
88
- const requestId = String(value || '').trim()
88
+ const requestId = String(value || '')
89
89
  if (!requestId || requestId.length > REQUEST_ID_MAX_LENGTH) return null
90
90
  return requestId
91
91
  }
@@ -103,7 +103,7 @@ export function parsePartQueryReq(value, tunables = partQueryTunables) {
103
103
  if (!isHex64(originNodeHash)) return null
104
104
  const partpath = parsePartpath(value.partpath)
105
105
  if (!partpath) return null
106
- const kind = String(value.kind || '').trim()
106
+ const kind = String(value.kind || '')
107
107
  if (!kind) return null
108
108
  if (!Object.prototype.hasOwnProperty.call(value, 'query')) return null
109
109
  if (measureJsonBytes(value.query) > (tunables.maxQueryBytes ?? 2048)) return null
@@ -145,7 +145,7 @@ export function parsePartQueryRes(value, tunables = partQueryTunables) {
145
145
  */
146
146
  export function normalizePartQueryCacheMaterial(partpath, kind, query) {
147
147
  const path = parsePartpath(partpath)
148
- const k = String(kind || '').trim()
148
+ const k = String(kind || '')
149
149
  if (!path || !k) return null
150
150
  if (measureJsonBytes(query) > partQueryTunables.maxQueryBytes) return null
151
151
  try {
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { Buffer } from 'node:buffer'
5
5
 
6
- import { isHex64, isSignatureHex128 } from '../core/hexIds.mjs'
6
+ import { isHex64, isSignatureHex128, normalizeHex64 } from '../core/hexIds.mjs'
7
7
  import { pubKeyHash, verify } from '../crypto/crypto.mjs'
8
8
  import { eventBodyForSign, signPayloadBytes } from '../dag/index.mjs'
9
9
 
@@ -12,11 +12,11 @@ import { eventBodyForSign, signPayloadBytes } from '../dag/index.mjs'
12
12
  * @returns {Promise<boolean>} 验签是否通过
13
13
  */
14
14
  export async function verifyTimelineRemoteSignature(event) {
15
- const sender = String(event?.sender || '').trim().toLowerCase()
15
+ const sender = normalizeHex64(event?.sender)
16
16
  if (!isHex64(sender)) return false
17
- const signatureHex = String(event?.signature || '').trim()
17
+ const signatureHex = String(event?.signature || '')
18
18
  if (!isSignatureHex128(signatureHex)) return false
19
- const pubKeyHex = String(event?.senderPubKey || '').trim().toLowerCase()
19
+ const pubKeyHex = normalizeHex64(event?.senderPubKey)
20
20
  if (!isHex64(pubKeyHex)) return false
21
21
  const publicKeyBytes = new Uint8Array(Buffer.from(pubKeyHex, 'hex'))
22
22
  if (pubKeyHash(publicKeyBytes) !== sender) return false
@@ -59,7 +59,7 @@ export function createGroupLinkSet(options) {
59
59
  * @returns {void}
60
60
  */
61
61
  function registerCleanup(cleanup) {
62
- if (typeof cleanup !== 'function') return
62
+ if (!cleanup) return
63
63
  cleanups.add(cleanup)
64
64
  }
65
65
 
@@ -182,7 +182,7 @@ export function createGroupLinkSet(options) {
182
182
  }
183
183
  started = true
184
184
  active = true
185
- if (typeof registry.ensureRuntime === 'function')
185
+ if (registry.ensureRuntime)
186
186
  await registry.ensureRuntime()
187
187
  registry.registerScopeInterest(scope, [...members])
188
188
  registerCleanup(registry.subscribeScope(scope, (senderNodeHash, envelope) => {
@@ -27,8 +27,8 @@ function normalizeIceEntry(raw) {
27
27
  if (!raw || typeof raw !== 'object') return null
28
28
  const urlsRaw = raw.urls
29
29
  const urlsList = Array.isArray(urlsRaw)
30
- ? urlsRaw.map(u => String(u).trim()).filter(Boolean)
31
- : [String(urlsRaw || '').trim()].filter(Boolean)
30
+ ? urlsRaw.map(u => String(u || '')).filter(Boolean)
31
+ : [String(urlsRaw || '')].filter(Boolean)
32
32
  if (!urlsList.length) return null
33
33
  for (const u of urlsList)
34
34
  if (!ICE_URL_RE.test(u)) return null
@@ -293,14 +293,14 @@ export function createLinkRegistry(options = {}) {
293
293
  const providers = listLinkProviders()
294
294
  for (const provider of providers)
295
295
  try {
296
- if (typeof provider.canReach === 'function') {
296
+ if (provider.canReach) {
297
297
  const reachable = await Promise.resolve(provider.canReach({ nodeHash: normalized }))
298
298
  if (!reachable) {
299
299
  nodeDebug('p2p:dial skip', { peer: shortHash(normalized), provider: provider.id, reason: 'canReach=false' })
300
300
  continue
301
301
  }
302
302
  }
303
- if (typeof provider.isAvailable === 'function') {
303
+ if (provider.isAvailable) {
304
304
  const available = await Promise.resolve(provider.isAvailable())
305
305
  if (!available) {
306
306
  nodeDebug('p2p:dial skip', { peer: shortHash(normalized), provider: provider.id, reason: 'isAvailable=false' })
@@ -470,17 +470,6 @@ export function createLinkRegistry(options = {}) {
470
470
  || await relayEnvelopeToNode(remoteNodeHash, envelope)
471
471
  }
472
472
 
473
- /**
474
- * 同步结果直接返回;thenable 才 await(避免每条 envelope 造 microtask)。
475
- * @param {unknown} value 可能为 Promise 的返回值
476
- * @returns {Promise<unknown>} 已 resolve 的值
477
- */
478
- async function maybeAwait(value) {
479
- if (value != null && typeof /** @type {{ then?: unknown }} */ value.then === 'function')
480
- return await value
481
- return value
482
- }
483
-
484
473
  /**
485
474
  * 将入站 envelope 派发到 scope 监听器(经 authorizer 校验)。
486
475
  * @param {string} senderNodeHash 发送方节点 64 hex
@@ -489,16 +478,15 @@ export function createLinkRegistry(options = {}) {
489
478
  * @returns {Promise<void>}
490
479
  */
491
480
  async function dispatchEnvelope(senderNodeHash, envelope, link) {
492
- const scope = envelope.scope || ''
481
+ const { scope } = envelope
493
482
  for (const [prefix, authorizer] of scopeAuthorizers.entries())
494
483
  if (scope.startsWith(prefix)) {
495
- const allowed = await maybeAwait(authorizer(scope, senderNodeHash, envelope, link))
496
- if (!allowed) return
484
+ if (!await authorizer(scope, senderNodeHash, envelope, link)) return
497
485
  }
498
486
  for (const [prefix, listeners] of scopeListeners.entries())
499
487
  if (scope.startsWith(prefix))
500
488
  for (const listener of listeners)
501
- await maybeAwait(listener(senderNodeHash, envelope, link))
489
+ await listener(senderNodeHash, envelope, link)
502
490
  }
503
491
 
504
492
  /**
@@ -567,7 +555,7 @@ export function createLinkRegistry(options = {}) {
567
555
  * @returns {void}
568
556
  */
569
557
  setPriorityWeightFunction(weightFunction) {
570
- priorityWeightFunction = typeof weightFunction === 'function' ? weightFunction : null
558
+ priorityWeightFunction = weightFunction || null
571
559
  },
572
560
  /**
573
561
  * @param {string} nodeHash - 节点 64-hex hash
@@ -21,7 +21,7 @@ const INTENTIONAL_CLOSE = new Set([
21
21
  * @returns {boolean} 是否为本机主动关链(不重拨)
22
22
  */
23
23
  export function isMeshIntentionalClose(reason) {
24
- return INTENTIONAL_CLOSE.has(String(reason || ''))
24
+ return INTENTIONAL_CLOSE.has(reason || '')
25
25
  }
26
26
 
27
27
  /**
@@ -103,7 +103,7 @@ export function getNodeScopeContext() {
103
103
  */
104
104
  export function ensureNodeScope(options = {}) {
105
105
  if (options.replicaUsername != null)
106
- nodeScopeContext.replicaUsername = String(options.replicaUsername)
106
+ nodeScopeContext.replicaUsername = options.replicaUsername
107
107
  if (nodeScopeSubscribeCleanup) return nodeScopeSubscribeCleanup
108
108
  nodeScopeSubscribeCleanup = subscribeScope('node', (senderNodeHash, envelope) => {
109
109
  const handlers = nodeActionHandlers.get(envelope.action)
@@ -23,21 +23,19 @@ registerFederationRoomProvider('remote-user-room', () => {
23
23
 
24
24
  /**
25
25
  * 加入目标节点的用户房间(幂等)。
26
- * @param {string} username 本地 replica 用户名
27
26
  * @param {string} targetNodeHash 目标节点 64 hex
28
27
  * @returns {Promise<RemoteUserRoomSlot>} 房间槽
29
28
  */
30
- export async function ensureRemoteUserRoom(username, targetNodeHash) {
31
- const key = targetNodeHash.toLowerCase()
32
- const existing = slots.get(key)
29
+ export async function ensureRemoteUserRoom(targetNodeHash) {
30
+ const existing = slots.get(targetNodeHash)
33
31
  if (existing) return existing
34
- const inflight = inflights.get(key)
32
+ const inflight = inflights.get(targetNodeHash)
35
33
  if (inflight) return await inflight
36
34
 
37
35
  const task = (async () => {
38
36
  try {
39
- if (!await ensureLinkToNode(key))
40
- throw new Error(`p2p: ensureRemoteUserRoom link failed for ${key}`)
37
+ if (!await ensureLinkToNode(targetNodeHash))
38
+ throw new Error(`p2p: ensureRemoteUserRoom link failed for ${targetNodeHash}`)
41
39
 
42
40
  /** @type {import('../registries/room_provider.mjs').FederationRoomSlot} */
43
41
  const roomSlot = {
@@ -46,13 +44,13 @@ export async function ensureRemoteUserRoom(username, targetNodeHash) {
46
44
  * 返回远端用户房间 roster(链路存在时含目标节点)。
47
45
  * @returns {Array<{ peerId: string, remoteNodeHash: string }>} roster 列表
48
46
  */
49
- getRoster: () => getLink(key) ? [{ peerId: key, remoteNodeHash: key }] : [],
47
+ getRoster: () => getLink(targetNodeHash) ? [{ peerId: targetNodeHash, remoteNodeHash: targetNodeHash }] : [],
50
48
  /**
51
49
  * 按 nodeHash 查找 peer id。
52
50
  * @param {string} nh 目标节点 64 hex
53
51
  * @returns {string | null} 对端 id;无链路时为 null
54
52
  */
55
- getPeerIdByNodeHash: nh => getLink(nh) ? String(nh) : null,
53
+ getPeerIdByNodeHash: nh => getLink(nh) ? nh : null,
56
54
  /**
57
55
  * 经 node scope 向远端 peer 发送 action。始终走当前规范链路(`getLink`),
58
56
  * 因为 glare 双 PC 择一后最初返回的链路可能已被关闭,规范链在 registry 内。
@@ -62,7 +60,7 @@ export async function ensureRemoteUserRoom(username, targetNodeHash) {
62
60
  * @returns {void}
63
61
  */
64
62
  sendToPeer(peerId, actionName, payload) {
65
- void getLink(key)?.send({ scope: 'node', action: actionName, payload }).catch(() => { })
63
+ void getLink(targetNodeHash)?.send({ scope: 'node', action: actionName, payload }).catch(() => { })
66
64
  },
67
65
  }
68
66
 
@@ -73,19 +71,19 @@ export async function ensureRemoteUserRoom(username, targetNodeHash) {
73
71
  * @returns {Promise<void>} 关闭完成
74
72
  */
75
73
  leave() {
76
- slots.delete(key)
77
- return closeLink(key, 'remote-user-room-release')
74
+ slots.delete(targetNodeHash)
75
+ return closeLink(targetNodeHash, 'remote-user-room-release')
78
76
  },
79
77
  }
80
- slots.set(key, slot)
78
+ slots.set(targetNodeHash, slot)
81
79
  bumpLocalDataRevision()
82
80
  return slot
83
81
  }
84
82
  finally {
85
- inflights.delete(key)
83
+ inflights.delete(targetNodeHash)
86
84
  }
87
85
  })()
88
86
 
89
- inflights.set(key, task)
87
+ inflights.set(targetNodeHash, task)
90
88
  return await task
91
89
  }
@@ -48,7 +48,7 @@ function bucketFor(roomKey, limits = {}) {
48
48
  budgets.set(roomKey, bucket)
49
49
  }
50
50
  for (const id of resolveRtcBudgetLimits(limits).trustedPeers || [])
51
- bucket.trustedPeers.add(String(id))
51
+ bucket.trustedPeers.add(id)
52
52
  return bucket
53
53
  }
54
54
 
@@ -89,7 +89,7 @@ export function takeRtcJoinSlot(roomKey, peerId, limits = {}, sourceId = 'peer')
89
89
  for (const id of bucket.active)
90
90
  if (!bucket.trustedPeers.has(id)) nonTrustedCount++
91
91
  if (!isTrusted) {
92
- const source = String(sourceId || 'peer')
92
+ const source = sourceId || 'peer'
93
93
  let sameSource = 0
94
94
  for (const peerSource of bucket.sourceByPeer.values())
95
95
  if (peerSource === source) sameSource++
@@ -108,7 +108,7 @@ export function takeRtcJoinSlot(roomKey, peerId, limits = {}, sourceId = 'peer')
108
108
  bucket.joinTimestamps.push(now)
109
109
  if (peerId) {
110
110
  bucket.active.add(peerId)
111
- bucket.sourceByPeer.set(peerId, String(sourceId || 'peer'))
111
+ bucket.sourceByPeer.set(peerId, sourceId || 'peer')
112
112
  }
113
113
  return true
114
114
  }
@@ -123,9 +123,9 @@ export function takeRtcJoinSlot(roomKey, peerId, limits = {}, sourceId = 'peer')
123
123
  export function annotateRtcPeerNodeHash(roomKey, peerId, nodeHash, limits = {}) {
124
124
  const bucket = budgets.get(roomKey)
125
125
  if (!bucket || !peerId || !nodeHash) return
126
- bucket.peerNodeHash.set(peerId, String(nodeHash).trim())
126
+ bucket.peerNodeHash.set(peerId, nodeHash)
127
127
  for (const trusted of resolveRtcBudgetLimits(limits).trustedPeers || [])
128
- if (String(trusted).trim() === String(nodeHash).trim())
128
+ if (trusted === nodeHash)
129
129
  bucket.trustedPeers.add(peerId)
130
130
 
131
131
  }
@@ -139,7 +139,7 @@ export function annotateRtcPeerNodeHash(roomKey, peerId, nodeHash, limits = {})
139
139
  export function setRtcPeerSource(roomKey, peerId, sourceId) {
140
140
  const bucket = budgets.get(roomKey)
141
141
  if (!bucket || !peerId) return
142
- bucket.sourceByPeer.set(peerId, String(sourceId || 'peer'))
142
+ bucket.sourceByPeer.set(peerId, sourceId || 'peer')
143
143
  }
144
144
 
145
145
  /**
@@ -41,14 +41,14 @@ export function collectFastListenProviders(ownedLanTcp) {
41
41
  const listenProviders = []
42
42
  if (ownedLanTcp) listenProviders.push(ownedLanTcp)
43
43
  for (const provider of listLinkProviders()) {
44
- const id = String(provider.id)
44
+ const id = provider.id
45
45
  if (id.startsWith('lan_tcp') || id.startsWith('ble_gatt')) continue
46
- if (typeof provider.ensureListening !== 'function') continue
46
+ if (!provider.ensureListening) continue
47
47
  if (providerHasNativeProbe(provider)) continue
48
- if (typeof provider.isAvailable === 'function')
48
+ if (provider.isAvailable)
49
49
  try {
50
50
  const available = provider.isAvailable()
51
- if (available && typeof available.then === 'function') continue
51
+ if (available?.then) continue
52
52
  if (!available) continue
53
53
  }
54
54
  catch { continue }
@@ -166,7 +166,7 @@ export function createRuntimeBootstrap(deps) {
166
166
  * @returns {number | null} 本机 lan_tcp 监听端口,未就绪为 null
167
167
  */
168
168
  function lanTcpPort() {
169
- const endpoint = typeof ownedLanTcp?.localEndpoint === 'function' ? ownedLanTcp.localEndpoint() : null
169
+ const endpoint = ownedLanTcp?.localEndpoint ? ownedLanTcp.localEndpoint() : null
170
170
  return normalizeTcpPort(endpoint?.port)
171
171
  }
172
172
 
@@ -199,13 +199,13 @@ export function createRuntimeBootstrap(deps) {
199
199
  * @returns {Promise<void>}
200
200
  */
201
201
  async function startProviderListening(provider) {
202
- if (typeof provider.ensureListening !== 'function') return
202
+ if (!provider.ensureListening) return
203
203
  try {
204
204
  const stop = await provider.ensureListening({
205
205
  localIdentity,
206
206
  onInbound: onInboundLink,
207
207
  })
208
- if (typeof stop === 'function') stopLinkListeners.push(stop)
208
+ if (stop) stopLinkListeners.push(stop)
209
209
  }
210
210
  catch { /* provider listen unavailable */ }
211
211
  }
@@ -228,18 +228,18 @@ export function createRuntimeBootstrap(deps) {
228
228
  if (!providerIds.has('bt')) {
229
229
  const bt = await import('../discovery/bt/index.mjs').catch(() => null)
230
230
  if (generation !== gen || !isLive()) return
231
- if (await bt?.canUseBluetoothDiscovery?.()) {
231
+ if (await bt?.canUseBluetoothRuntime?.()) {
232
232
  if (generation !== gen || !isLive()) return
233
233
  const provider = bt.createBluetoothDiscoveryProvider()
234
234
  registerDiscoveryProvider(provider)
235
235
  if (generation !== gen || !isLive()) return
236
- if (typeof provider.startPresence === 'function')
236
+ if (provider.startPresence)
237
237
  try {
238
238
  const stop = await provider.startPresence(async () => ({
239
239
  nodeHash: localIdentity.nodeHash,
240
240
  advertBytes: await buildNetworkAdvertBytes(),
241
241
  }))
242
- if (typeof stop === 'function' && generation === gen && isLive()) {
242
+ if (stop && generation === gen && isLive()) {
243
243
  const prev = stopPresence
244
244
  /**
245
245
  * 停止 presence 广播并链式调用上一轮清理。
@@ -248,12 +248,12 @@ export function createRuntimeBootstrap(deps) {
248
248
  }
249
249
  }
250
250
  catch { /* ignore */ }
251
- if (provider.caps?.canSignal && typeof provider.listenNodeSignals === 'function')
251
+ if (provider.caps?.canSignal && provider.listenNodeSignals)
252
252
  try {
253
253
  const stop = await provider.listenNodeSignals(localIdentity.nodeHash, bytes => {
254
254
  void handleIncomingSignal(bytes).catch(() => { })
255
255
  })
256
- if (typeof stop === 'function' && generation === gen && isLive()) {
256
+ if (stop && generation === gen && isLive()) {
257
257
  const prev = stopSignalListener
258
258
  /**
259
259
  * 停止信令监听并链式调用上一轮清理。
@@ -22,10 +22,9 @@ export function invalidateTrustGraphCache() {
22
22
  * @returns {Promise<Map<string, object>>} 合并后的信任图
23
23
  */
24
24
  export async function getCachedTrustGraph(username, build, ttlMs = DEFAULT_TTL_MS) {
25
- const key = String(username || '')
26
25
  const now = Date.now()
27
26
  const dataRevision = getLocalDataRevision()
28
- const cached = cacheByUsername.get(key)
27
+ const cached = cacheByUsername.get(username)
29
28
  if (
30
29
  cached
31
30
  && cached.revision === revision
@@ -35,6 +34,6 @@ export async function getCachedTrustGraph(username, build, ttlMs = DEFAULT_TTL_M
35
34
  return cached.graph
36
35
 
37
36
  const graph = await build()
38
- cacheByUsername.set(key, { graph, builtAt: now, revision, dataRevision })
37
+ cacheByUsername.set(username, { graph, builtAt: now, revision, dataRevision })
39
38
  return graph
40
39
  }
@@ -89,7 +89,7 @@ export function mergeGraph(inputs, tunables = trustGraphTunables) {
89
89
  const rawWeight = Number(hint.weight ?? tunables.hintDefaultWeight)
90
90
  if (Number.isFinite(rawWeight) && rawWeight > 0)
91
91
  ev.hintWeightSum += rawWeight
92
- ev.hintSources.add(String(hint.source ?? 'unknown'))
92
+ ev.hintSources.add(hint.source ?? 'unknown')
93
93
  }
94
94
 
95
95
  for (const room of inputs.roomRosters || [])
@@ -13,7 +13,7 @@ export const DEFAULT_TRUST_GRAPH_OWNER = 'default'
13
13
  * @returns {void}
14
14
  */
15
15
  export function registerTrustGraphProvider(ownerId, implementation) {
16
- providersByOwner.set(String(ownerId), implementation)
16
+ providersByOwner.set(ownerId, implementation)
17
17
  }
18
18
 
19
19
  /** @returns {void} */
@@ -26,7 +26,7 @@ export function clearTrustGraphProvider() {
26
26
  * @returns {import('./registry.mjs').TrustGraphProvider} 已注册实现
27
27
  */
28
28
  export function requireTrustGraphProvider(ownerId = DEFAULT_TRUST_GRAPH_OWNER) {
29
- const implementation = providersByOwner.get(String(ownerId))
29
+ const implementation = providersByOwner.get(ownerId)
30
30
  if (!implementation)
31
31
  throw new Error(`p2p: registerTrustGraphProvider('${ownerId}') must run before trust graph fanout`)
32
32
  return implementation