@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,3 +1,4 @@
1
+ import { normalizeHex64 } from '../core/hexIds.mjs'
1
2
  import { sortedPrevEventIds } from '../dag/index.mjs'
2
3
  import { getGovernanceAuthzTypes } from '../registries/event_type.mjs'
3
4
 
@@ -104,7 +105,7 @@ function governanceAuthzScoreForTip(tipId, byId, reputationBySender) {
104
105
  for (const eventId of ancestorClosureFromTip(tipId, byId)) {
105
106
  const event = byId.get(eventId)
106
107
  if (!event || !getGovernanceAuthzTypes().has(event.type)) continue
107
- const sender = String(event.sender).trim().toLowerCase()
108
+ const sender = normalizeHex64(event.sender)
108
109
  if (sender) score += Number(reputationBySender[sender] ?? 0)
109
110
  }
110
111
  return score
@@ -3,6 +3,8 @@
3
3
  */
4
4
  import { createHash } from 'node:crypto'
5
5
 
6
+ import { normalizeHex64 } from '../core/hexIds.mjs'
7
+
6
8
  import admissionTunables from './tunables.json' with { type: 'json' }
7
9
 
8
10
  /** 默认 epoch 窗口(1 小时) */
@@ -21,7 +23,7 @@ export const JOIN_POW_DEFAULT_EPOCH_SKEW = 1
21
23
  * @returns {string} SHA-256 十六进制
22
24
  */
23
25
  export function computeJoinPowHash({ groupId, anchorRef, joinerNodeHash, epoch, nonce }) {
24
- const preimage = `${String(groupId)}:${String(anchorRef)}:${String(joinerNodeHash)}:${String(epoch)}:${String(nonce)}`
26
+ const preimage = `${groupId}:${anchorRef}:${joinerNodeHash}:${String(epoch)}:${String(nonce)}`
25
27
  return createHash('sha256').update(preimage, 'utf8').digest('hex')
26
28
  }
27
29
 
@@ -100,16 +102,16 @@ export function verifyJoinPow(powSolution, options) {
100
102
  if (floorBits <= 0) return { ok: true, achievedBits: 0 }
101
103
  if (!powSolution || typeof powSolution !== 'object') return { ok: false, achievedBits: 0 }
102
104
 
103
- const anchorRef = String(powSolution.anchorRef ?? '').trim()
105
+ const anchorRef = String(powSolution.anchorRef ?? '')
104
106
  const { nonce } = powSolution
105
107
  const epoch = Number(powSolution.epoch)
106
- const joinerNodeHash = String(powSolution.joinerNodeHash ?? options.senderNodeHash ?? '').trim().toLowerCase()
107
- const senderNodeHash = String(options.senderNodeHash ?? '').trim().toLowerCase()
108
+ const joinerNodeHash = normalizeHex64(powSolution.joinerNodeHash ?? options.senderNodeHash)
109
+ const senderNodeHash = normalizeHex64(options.senderNodeHash)
108
110
 
109
111
  if (!anchorRef || nonce == null || !Number.isFinite(epoch)) return { ok: false, achievedBits: 0 }
110
112
  if (!joinerNodeHash || joinerNodeHash !== senderNodeHash) return { ok: false, achievedBits: 0 }
111
113
 
112
- const anchors = (options.knownAnchors ?? []).map(a => String(a).trim()).filter(Boolean)
114
+ const anchors = (options.knownAnchors ?? []).filter(Boolean)
113
115
  if (!anchors.length || !anchors.includes(anchorRef)) return { ok: false, achievedBits: 0 }
114
116
 
115
117
  const epochMs = Math.max(60_000, Number(options.epochMs) || JOIN_POW_DEFAULT_EPOCH_MS)
@@ -4,7 +4,7 @@
4
4
  import { Buffer } from 'node:buffer'
5
5
 
6
6
  import { canonicalStringify } from '../core/canonical_json.mjs'
7
- import { isHex64, isSignatureHex128 } from '../core/hexIds.mjs'
7
+ import { isHex64, isSignatureHex128, normalizeHex64 } from '../core/hexIds.mjs'
8
8
  import { pubKeyHash, verify } from '../crypto/crypto.mjs'
9
9
 
10
10
  const BALLOT_DOMAIN = 'fount-owner-succession'
@@ -15,9 +15,9 @@ const BALLOT_DOMAIN = 'fount-owner-succession'
15
15
  */
16
16
  export function ownerSuccessionBallotSignBytes(ballot) {
17
17
  const body = {
18
- proposedOwnerPubKeyHash: String(ballot.proposedOwnerPubKeyHash ?? '').trim().toLowerCase(),
19
- groupId: String(ballot.groupId ?? '').trim(),
20
- ballotId: String(ballot.ballotId ?? '').trim(),
18
+ proposedOwnerPubKeyHash: normalizeHex64(ballot.proposedOwnerPubKeyHash),
19
+ groupId: ballot.groupId,
20
+ ballotId: ballot.ballotId,
21
21
  }
22
22
  return Buffer.from(`${BALLOT_DOMAIN}\0${canonicalStringify(body)}`, 'utf8')
23
23
  }
@@ -42,8 +42,8 @@ export async function verifyOwnerSuccessionThreshold(ballot, adminPubKeyHashes,
42
42
  let valid = 0
43
43
 
44
44
  for (const entry of ballot.adminSignatures || []) {
45
- const pubKeyHex = String(entry?.pubKeyHex ?? '').trim().toLowerCase()
46
- const signatureHex = String(entry?.signature ?? '').trim().toLowerCase()
45
+ const pubKeyHex = normalizeHex64(entry.pubKeyHex)
46
+ const signatureHex = entry.signature
47
47
  if (!isHex64(pubKeyHex) || !isSignatureHex128(signatureHex)) continue
48
48
 
49
49
  const hash = pubKeyHash(Buffer.from(pubKeyHex, 'hex'))
package/index.mjs CHANGED
@@ -22,7 +22,6 @@ import {
22
22
  attachReputationSyncWire,
23
23
  getReputationExportAllowlist,
24
24
  getReputationLocks,
25
- getReputationTable,
26
25
  getTrustSyncDonors,
27
26
  lockReputationMax,
28
27
  pullReputationFromNode,
@@ -31,6 +30,7 @@ import {
31
30
  setTrustSyncDonors,
32
31
  unlockReputationMax,
33
32
  } from './node/reputation_sync.mjs'
33
+ import { loadReputation } from './node/reputation_store.mjs'
34
34
  import { getRoutingProfile, setRoutingProfile } from './node/routing_profile.mjs'
35
35
  import { createGroupLinkSet } from './transport/group_link_set.mjs'
36
36
  import {
@@ -68,13 +68,13 @@ export {
68
68
  getNodeHash,
69
69
  getReputationExportAllowlist,
70
70
  getReputationLocks,
71
- getReputationTable,
72
71
  getRoutingProfile,
73
72
  getTrustSyncDonors,
74
73
  getUserRoomSlot,
75
74
  initNode,
76
75
  isInfraRunning,
77
76
  isNodeInitialized,
77
+ loadReputation,
78
78
  lockReputationMax,
79
79
  pullReputationFromNode,
80
80
  registerDiscoveryProvider,
@@ -17,6 +17,6 @@ export function defaultNodeDir() {
17
17
  * @returns {string} 解析后的绝对 node 目录
18
18
  */
19
19
  export function resolveNodeDir(override) {
20
- const trimmed = String(override || '').trim()
21
- return trimmed ? path.resolve(trimmed) : defaultNodeDir()
20
+ const value = override || ''
21
+ return value ? path.resolve(value) : defaultNodeDir()
22
22
  }
@@ -1,5 +1,5 @@
1
1
  import { normalizeHex64 } from '../core/hexIds.mjs'
2
- import { getReputationTable } from '../node/reputation_sync.mjs'
2
+ import { loadReputation } from '../node/reputation_store.mjs'
3
3
  import { pickNodeScoreFromReputation } from '../reputation/pick_score.mjs'
4
4
  import { getLinkRegistry } from '../transport/link_registry.mjs'
5
5
 
@@ -39,7 +39,7 @@ export function applyPriorityToRegistry() {
39
39
  }
40
40
  registry.setPriorityWeightFunction(nodeHash => {
41
41
  const score = pickNodeScoreFromReputation(
42
- getReputationTable(),
42
+ loadReputation(),
43
43
  normalizeHex64(nodeHash) || nodeHash,
44
44
  )
45
45
  return Math.floor(score * PRIORITY_BOOST)
package/link/frame.mjs CHANGED
@@ -25,11 +25,10 @@ function normalizeFrameIdBytes(frameId) {
25
25
  throw new Error(`p2p: frameId must be ${FRAME_ID_BYTES} bytes`)
26
26
  return frameId
27
27
  }
28
- const text = frameId.trim().toLowerCase()
29
- if (text.length !== FRAME_ID_BYTES * 2)
28
+ if (frameId.length !== FRAME_ID_BYTES * 2)
30
29
  throw new Error('p2p: frameId must be 32 hex characters')
31
30
  try {
32
- return hexToBytes(text)
31
+ return hexToBytes(frameId)
33
32
  }
34
33
  catch {
35
34
  throw new Error('p2p: frameId must be 32 hex characters')
@@ -1,7 +1,7 @@
1
1
  import { Buffer } from 'node:buffer'
2
2
  import { randomBytes } from 'node:crypto'
3
3
 
4
- import { isHex64, normalizeHex64 } from '../core/hexIds.mjs'
4
+ import { isHex64, isSignatureHex128, normalizeHex64 } from '../core/hexIds.mjs'
5
5
  import { normalizeTcpPort } from '../core/tcp_port.mjs'
6
6
  import { keyPairFromSeed, pubKeyHash, sign, verify } from '../crypto/crypto.mjs'
7
7
  import { normalizeLanHosts } from '../discovery/lan_interfaces.mjs'
@@ -117,9 +117,9 @@ export function parseHello(hello) {
117
117
  export async function verifyAuth(hello, auth, expectedNonce, remoteBinding) {
118
118
  const parsedHello = parseHello(hello)
119
119
  if (!parsedHello) return null
120
- const signatureHex = String(auth?.sig ?? '').trim().toLowerCase()
120
+ const signatureHex = String(auth?.sig ?? '')
121
121
  const binding = normalizeLinkBinding(remoteBinding)
122
- if (!/^[\da-f]{128}$/u.test(signatureHex) || !binding) return null
122
+ if (!isSignatureHex128(signatureHex) || !binding) return null
123
123
  const normalizedNonce = normalizeHex64(expectedNonce)
124
124
  if (!isHex64(normalizedNonce)) return null
125
125
  const message = buildAuthMessage(normalizedNonce, binding, parsedHello.nodeHash)
@@ -141,7 +141,7 @@ export async function verifyAuth(hello, auth, expectedNonce, remoteBinding) {
141
141
  * @returns {Uint8Array} 待签名消息字节
142
142
  */
143
143
  export function buildAdvertMessage(rendezvousKey, ts, nodeHash, tcpPort = null, lanHosts = null) {
144
- const base = `fount-advert\0${String(rendezvousKey)}\0${String(ts)}\0${normalizeHex64(nodeHash)}`
144
+ const base = `fount-advert\0${rendezvousKey}\0${ts}\0${normalizeHex64(nodeHash)}`
145
145
  const port = normalizeTcpPort(tcpPort)
146
146
  let message = port ? `${base}\0${port}` : base
147
147
  const hosts = normalizeLanHosts(lanHosts)
@@ -194,8 +194,8 @@ export async function verifySignedAdvert(rendezvousKey, advert, now = Date.now()
194
194
  const parsedHello = parseHello({ nodeHash: advert?.nodeHash, nodePubKey: advert?.nodePubKey, nonce: '0'.repeat(64) })
195
195
  if (!parsedHello) return null
196
196
  const ts = Number(advert?.ts)
197
- const sig = String(advert?.sig ?? '').trim().toLowerCase()
198
- if (!Number.isFinite(ts) || Math.abs(now - ts) > maxSkewMs || !/^[\da-f]{128}$/u.test(sig)) return null
197
+ const sig = String(advert?.sig ?? '')
198
+ if (!Number.isFinite(ts) || Math.abs(now - ts) > maxSkewMs || !isSignatureHex128(sig)) return null
199
199
  const hasTcpPortField = !!advert?.tcpPort
200
200
  const tcpPort = normalizeTcpPort(advert?.tcpPort)
201
201
  if (hasTcpPortField && !tcpPort) return null
package/link/pipe.mjs CHANGED
@@ -299,8 +299,7 @@ export function createLinkPipe(options) {
299
299
  }))
300
300
  for (const frame of encodeFrames(frameId, bytes)) {
301
301
  const sent = options.sendFrame(envelope.action, frame)
302
- if (sent != null && typeof /** @type {{ then?: unknown }} */ sent.then === 'function')
303
- await sent
302
+ if (sent?.then) await sent
304
303
  sentFrames++
305
304
  }
306
305
  lastOutboundAt = Date.now()
@@ -15,14 +15,6 @@ export const BLE_DATA_SERVICE_UUID = 'f017f017f017f017f017f017f017f019'
15
15
  export const BLE_DATA_CHAR_UUID = 'f017f017f017f017f017f017f017f01a'
16
16
  const BT_DEVICE_NAME = 'fount-bt'
17
17
 
18
- /**
19
- * 探测 BLE GATT 数据链路是否可用;失败则回落其它 link provider。
20
- * @returns {Promise<boolean>} 可用为 true
21
- */
22
- export async function canUseBleGattLink() {
23
- return canUseBluetoothRuntime()
24
- }
25
-
26
18
  /**
27
19
  * 在 GATT write/notify 上建立 pipe。
28
20
  * @param {object} options 配置
@@ -279,7 +271,7 @@ export function createBleGattLinkProvider() {
279
271
  id: instanceId,
280
272
  level: LINK_LEVEL_BLE_GATT,
281
273
  caps: { needsOfferAnswer: false, needsDiscoverySignal: false, probe: 'native' },
282
- isAvailable: canUseBleGattLink,
274
+ isAvailable: canUseBluetoothRuntime,
283
275
  /**
284
276
  * @param {{ nodeHash: string }} remote 远端
285
277
  * @returns {boolean} 是否有 BT peer hint
@@ -35,7 +35,7 @@ const providers = new Map()
35
35
  */
36
36
  export function registerLinkProvider(provider) {
37
37
  if (!provider?.id) throw new Error('p2p: link provider requires id')
38
- providers.set(String(provider.id), provider)
38
+ providers.set(provider.id, provider)
39
39
  return () => unregisterLinkProvider(provider.id)
40
40
  }
41
41
 
@@ -45,7 +45,7 @@ export function registerLinkProvider(provider) {
45
45
  * @returns {void}
46
46
  */
47
47
  export function unregisterLinkProvider(id) {
48
- providers.delete(String(id))
48
+ providers.delete(id)
49
49
  }
50
50
 
51
51
  /**
@@ -48,9 +48,7 @@ async function publishLinkPacket(remoteNodeHash, packet) {
48
48
  * @returns {import('./index.mjs').LinkProvider & { deliverPacket: (packet: object) => void }} provider
49
49
  */
50
50
  export function createNostrLinkProvider(options = {}) {
51
- const resolveRelayUrls = typeof options.getRelayUrls === 'function'
52
- ? options.getRelayUrls
53
- : resolveDefaultRelayUrls
51
+ const resolveRelayUrls = options.getRelayUrls || resolveDefaultRelayUrls
54
52
 
55
53
  /** @type {((link: import('./index.mjs').LinkHandle) => void) | null} */
56
54
  let onInbound = null
@@ -304,7 +304,7 @@ export async function createWebRtcLink(options) {
304
304
  if (trickleIceOff || !event.candidate) return
305
305
  void sendSignal({
306
306
  type: 'ice',
307
- candidate: typeof event.candidate.toJSON === 'function' ? event.candidate.toJSON() : event.candidate,
307
+ candidate: event.candidate.toJSON?.() || event.candidate,
308
308
  }).catch(error => pipe.close(`signal-send-failed:${formatErrorReason(error)}`))
309
309
  }
310
310
  /**
@@ -11,7 +11,7 @@
11
11
  * @returns {string | null} 处理后的 SDP;drop 策略下不可用时返回 null
12
12
  */
13
13
  export function applyIceLocalHostnamePolicy(candidateSdp, policy) {
14
- const sdp = String(candidateSdp || '').trim()
14
+ const sdp = candidateSdp || ''
15
15
  if (!sdp || !/\.local/i.test(sdp)) return sdp || null
16
16
  if (!/\btyp host\b/i.test(sdp)) return sdp
17
17
  if (policy === 'drop') return null
@@ -34,9 +34,10 @@ export function filterIceLocalHostnameCandidate(candidate, RTCIceCandidateCtor,
34
34
  const rewritten = applyIceLocalHostnamePolicy(raw, policy)
35
35
  if (!rewritten) return null
36
36
  if (rewritten === raw) return candidate
37
- const init = typeof candidate.toJSON === 'function'
38
- ? { ...candidate.toJSON(), candidate: rewritten }
39
- : { candidate: rewritten, sdpMid: candidate.sdpMid, sdpMLineIndex: candidate.sdpMLineIndex }
37
+ const init = {
38
+ ...(candidate.toJSON?.() || { sdpMid: candidate.sdpMid, sdpMLineIndex: candidate.sdpMLineIndex }),
39
+ candidate: rewritten,
40
+ }
40
41
  return new RTCIceCandidateCtor(init)
41
42
  }
42
43
 
@@ -49,7 +50,7 @@ export function filterIceLocalHostnameCandidate(candidate, RTCIceCandidateCtor,
49
50
  export function wrapRtcPeerConnectionForIceLocalHostname(BaseRTC, RTCIceCandidate, policy = 'drop') {
50
51
  if (policy === 'none') return BaseRTC
51
52
 
52
- const baseRoutesIce = typeof BaseRTC.prototype.prepareIceCandidateEvent === 'function'
53
+ const baseRoutesIce = !!BaseRTC.prototype.prepareIceCandidateEvent
53
54
 
54
55
  return class IceLocalHostnameFilteredRTCPeerConnection extends BaseRTC {
55
56
  /** @type {((event: RTCPeerConnectionIceEvent) => void) | null} */
@@ -36,7 +36,7 @@ function defineEmitterHandler(target, property, eventName, adapt = payload => pa
36
36
  * @returns {RTCDataChannel} 已挂 W3C handler 的通道
37
37
  */
38
38
  export function bridgeDataChannel(channel) {
39
- if (bridgedChannels.has(channel) || typeof channel.on !== 'function') return channel
39
+ if (bridgedChannels.has(channel) || !channel.on) return channel
40
40
  bridgedChannels.add(channel)
41
41
  defineEmitterHandler(channel, 'onmessage', 'message')
42
42
  defineEmitterHandler(channel, 'onopen', 'open')
@@ -20,6 +20,6 @@ export function normalizeDtlsFingerprint(value) {
20
20
  * @returns {string | null} 规范化 fingerprint,未找到时返回 null
21
21
  */
22
22
  export function extractDtlsFingerprint(sdp) {
23
- const line = String(sdp || '').match(/^a=fingerprint:sha-256\s+([\d:A-Fa-f]+)$/mu)?.[1]
23
+ const line = sdp.match(/^a=fingerprint:sha-256\s+([\d:A-Fa-f]+)$/mu)?.[1]
24
24
  return normalizeDtlsFingerprint(line)
25
25
  }
@@ -19,7 +19,7 @@ const consumers = new Map()
19
19
  * @returns {void}
20
20
  */
21
21
  export function registerMailboxConsumer(app, handler) {
22
- consumers.set(String(app), { handler })
22
+ consumers.set(app, { handler })
23
23
  }
24
24
 
25
25
  /**
@@ -27,7 +27,7 @@ export function registerMailboxConsumer(app, handler) {
27
27
  * @returns {void}
28
28
  */
29
29
  export function unregisterMailboxConsumer(app) {
30
- consumers.delete(String(app))
30
+ consumers.delete(app)
31
31
  }
32
32
 
33
33
  /**
@@ -25,7 +25,7 @@ import {
25
25
  async function resolveRemoteNodeHashForPeer(username, peerId) {
26
26
  if (!peerId) return null
27
27
  const entry = activeLinkRoster().find(row => row.peerId === peerId)
28
- const remote = entry?.remoteNodeHash?.trim().toLowerCase()
28
+ const remote = normalizeHex64(entry?.remoteNodeHash)
29
29
  return remote || null
30
30
  }
31
31
 
@@ -76,7 +76,7 @@ export async function deliverOrStoreMailboxPut(username, options) {
76
76
  fromNodeHash: options.record?.fromNodeHash || nodeHash,
77
77
  }
78
78
  const stored = await storeMailboxRecord(record)
79
- const toNodeHash = options.toNodeHash?.trim().toLowerCase()
79
+ const toNodeHash = normalizeHex64(options.toNodeHash)
80
80
  const delivered = toNodeHash && isMailboxRecordWithinSizeLimit(record)
81
81
  ? await requireTrustGraphProvider(DEFAULT_TRUST_GRAPH_OWNER).sendToNode(username, toNodeHash, 'mailbox_put', { nodeHash, record })
82
82
  : false
@@ -116,7 +116,7 @@ export async function ingestMailboxPut(wireContext, put, peerId = '') {
116
116
  if (!record?.envelope || !record?.toPubKeyHash) return
117
117
  const fromNode = normalizeHex64(put.nodeHash)
118
118
  if (!fromNode || !takeIncomingMailboxPutSlot(fromNode)) return
119
- const username = String(wireContext.replicaUsername || '').trim()
119
+ const username = wireContext.replicaUsername || ''
120
120
  if (!username) return
121
121
  if (peerId) {
122
122
  const remote = await resolveRemoteNodeHashForPeer(username, peerId)
@@ -162,7 +162,7 @@ export async function respondMailboxWant(want, sendGive, peerId) {
162
162
  export async function ingestMailboxGive(wireContext, give) {
163
163
  const records = (give.records || []).filter(isDeliverableMailboxRecord)
164
164
  if (!records.length) return 0
165
- const username = String(wireContext.replicaUsername || '').trim()
165
+ const username = wireContext.replicaUsername || ''
166
166
  if (!username) return 0
167
167
  const { dispatchMailboxRecordsToConsumers } = await import('./consumer_registry.mjs')
168
168
  const { deleteMailboxRecords } = await import('./store.mjs')
package/mailbox/parse.mjs CHANGED
@@ -64,7 +64,7 @@ export function parseMailboxGive(payload) {
64
64
  return { ok: false, code: shape.code, field: `records[${i}].${shape.field}` }
65
65
  if (!record.envelope || typeof record.envelope !== 'object')
66
66
  return { ok: false, code: 'required', field: `records[${i}].envelope` }
67
- const app = String(record.app || '').trim()
67
+ const app = String(record.app || '')
68
68
  if (!app)
69
69
  return { ok: false, code: 'required', field: `records[${i}].app` }
70
70
  records.push(record)
package/mailbox/prune.mjs CHANGED
@@ -32,8 +32,8 @@ export function mailboxRecordBytes(record) {
32
32
  */
33
33
  export function mailboxBucketKey(record) {
34
34
  const to = normalizeHex64(record.toPubKeyHash) || ''
35
- const from = record.fromNodeHash?.trim().toLowerCase()
36
- || record.envelope?.sender?.trim().toLowerCase()
35
+ const from = normalizeHex64(record.fromNodeHash)
36
+ || normalizeHex64(record.envelope?.sender)
37
37
  || 'unknown'
38
38
  return `${to}\0${from}`
39
39
  }
package/mailbox/rate.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Mailbox 入站 put 限速(按来源节点,节点级单例)。
3
3
  */
4
+ import { normalizeHex64 } from '../core/hexIds.mjs'
4
5
 
5
6
  const DEFAULT_WINDOW_MS = 60_000
6
7
  const DEFAULT_MAX_PUTS = 20
@@ -14,21 +15,13 @@ const inboundByKey = new Map()
14
15
  * @param {object} [limits] 可选限额
15
16
  * @returns {{ windowMs: number, maxPuts: number }} 生效限额
16
17
  */
17
- export function resolveMailboxRateLimits(limits = {}) {
18
+ function resolveMailboxRateLimits(limits = {}) {
18
19
  return {
19
20
  windowMs: Math.max(1000, Number(limits.windowMs) || DEFAULT_WINDOW_MS),
20
21
  maxPuts: Math.max(1, Math.min(256, Number(limits.maxPuts) || DEFAULT_MAX_PUTS)),
21
22
  }
22
23
  }
23
24
 
24
- /**
25
- * @param {string} fromNodeHash 来源节点
26
- * @returns {string} 限速键
27
- */
28
- export function mailboxRateKey(fromNodeHash) {
29
- return String(fromNodeHash || '').trim()
30
- }
31
-
32
25
  /**
33
26
  * @param {number} now 当前时间戳
34
27
  * @returns {void}
@@ -69,7 +62,7 @@ function evictLruIfNeeded(key) {
69
62
  */
70
63
  export function takeIncomingMailboxPutSlot(fromNodeHash, limits) {
71
64
  const { windowMs, maxPuts } = resolveMailboxRateLimits(limits)
72
- const key = mailboxRateKey(fromNodeHash)
65
+ const key = normalizeHex64(fromNodeHash)
73
66
  const now = Date.now()
74
67
  if (inboundByKey.size >= MAX_KEYS) sweepExpiredEntries(now)
75
68
  evictLruIfNeeded(key)
package/mailbox/store.mjs CHANGED
@@ -73,7 +73,7 @@ export function mailboxTierFromHop(hop) {
73
73
  */
74
74
  export function isDeliverableMailboxRecord(row) {
75
75
  if (!row?.envelope || typeof row.envelope !== 'object') return false
76
- if (!String(row.app || '').trim()) return false
76
+ if (!String(row.app || '')) return false
77
77
  return row.tier === 'trusted' || row.tier === 'normal'
78
78
  }
79
79
 
@@ -139,7 +139,7 @@ async function writeAll(rows) {
139
139
  * @returns {string} 稳定信封 id
140
140
  */
141
141
  export function mailboxEnvelopeId(envelope) {
142
- const id = String(envelope?.id || '').trim().toLowerCase()
142
+ const id = String(envelope?.id || '')
143
143
  if (!id) throw new Error('mailbox envelope id required')
144
144
  return id
145
145
  }
@@ -155,7 +155,7 @@ export async function storeMailboxRecord(record) {
155
155
  const hop = normalizeMailboxHop(record.hop)
156
156
  const { tier } = record
157
157
  if (!['trusted', 'normal', 'quarantine'].includes(tier)) return false
158
- const app = String(record.app || '').trim()
158
+ const app = String(record.app || '')
159
159
  if (!app) return false
160
160
  let id
161
161
  try {
@@ -172,13 +172,13 @@ export async function storeMailboxRecord(record) {
172
172
  id,
173
173
  app,
174
174
  toPubKeyHash,
175
- dmSessionTag: record.dmSessionTag?.trim().toLowerCase() || undefined,
175
+ dmSessionTag: record.dmSessionTag ? String(record.dmSessionTag) : undefined,
176
176
  groupId: record.groupId || undefined,
177
177
  channelId: record.channelId || undefined,
178
178
  envelope: record.envelope,
179
179
  storedAt: Date.now(),
180
180
  expiresAt: Date.now() + ttlMs,
181
- fromNodeHash: String(record.fromNodeHash || '').trim(),
181
+ fromNodeHash: normalizeHex64(record.fromNodeHash),
182
182
  hop,
183
183
  tier,
184
184
  importance: Number.isFinite(Number(record.importance)) ? Number(record.importance) : undefined,
@@ -204,7 +204,7 @@ export async function listMailboxIdsForRecipient(toPubKeyHash) {
204
204
  * @returns {Promise<MailboxRecord[]>} 匹配记录
205
205
  */
206
206
  export async function getMailboxRecords(ids) {
207
- const want = new Set(ids.map(id => String(id).trim().toLowerCase()))
207
+ const want = new Set(ids.map(id => String(id)))
208
208
  return (await readAll()).filter(record => want.has(record.id))
209
209
  }
210
210
 
@@ -213,7 +213,7 @@ export async function getMailboxRecords(ids) {
213
213
  * @returns {Promise<void>}
214
214
  */
215
215
  export async function deleteMailboxRecords(ids) {
216
- const drop = new Set(ids.map(id => String(id).trim().toLowerCase()))
216
+ const drop = new Set(ids.map(id => String(id)))
217
217
  return withAsyncMutex(mailboxStoreMutexKey(), async () => {
218
218
  await writeAll((await readAll()).filter(record => !drop.has(record.id)))
219
219
  })