@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
@@ -0,0 +1,140 @@
1
+
2
+ import { isNodeInitialized } from '../node/instance.mjs'
3
+ import { setOverlayRateGate, clearOverlayRateGate } from '../overlay/index.mjs'
4
+ import { getLinkRegistry } from '../transport/link_registry.mjs'
5
+ import { attachNodeScopeMailbox } from '../transport/node_scope.mjs'
6
+
7
+ import { attachInfraDebugLog, detachInfraDebugLog } from './debug_log.mjs'
8
+ import {
9
+ applyPriorityToRegistry,
10
+ clearInfraPriorityFromRegistry,
11
+ getInfraPriority,
12
+ setInfraPriority,
13
+ } from './priority.mjs'
14
+ import infraTunables from './tunables.json' with { type: 'json' }
15
+
16
+ /** @type {Map<string, { tokens: number, updatedAt: number }>} */
17
+ const overlayRateBuckets = new Map()
18
+
19
+ /**
20
+ * Token bucket:桶容量 = burst,补充速率 = perMin/min。
21
+ * @param {Map<string, { tokens: number, updatedAt: number }>} buckets - 每 sender 桶状态
22
+ * @param {string} sender - 发送方 nodeHash
23
+ * @param {number} now - 当前时间戳(ms)
24
+ * @param {{ perMin: number, burst: number }} limits - 限速参数
25
+ * @returns {boolean} 是否允许本次 overlay 动作
26
+ */
27
+ export function consumeOverlayRateToken(buckets, sender, now, limits) {
28
+ const perMin = Math.max(1, limits.perMin)
29
+ const burst = Math.max(1, limits.burst)
30
+ const refillPerMs = perMin / 60_000
31
+ let bucket = buckets.get(sender)
32
+ if (!bucket) bucket = { tokens: burst, updatedAt: now }
33
+ const elapsed = Math.max(0, now - bucket.updatedAt)
34
+ bucket.tokens = Math.min(burst, bucket.tokens + elapsed * refillPerMs)
35
+ bucket.updatedAt = now
36
+ if (bucket.tokens < 1) {
37
+ buckets.set(sender, bucket)
38
+ return false
39
+ }
40
+ bucket.tokens -= 1
41
+ buckets.set(sender, bucket)
42
+ return true
43
+ }
44
+
45
+ /**
46
+ * @returns {void}
47
+ */
48
+ function installOverlayRateLimit() {
49
+ const limits = {
50
+ perMin: Math.max(1, Number(infraTunables.overlayRatePerMin) || 120),
51
+ burst: Math.max(1, Number(infraTunables.overlayRateBurst) || 30),
52
+ }
53
+ setOverlayRateGate((sender, action) => {
54
+ if (action !== 'route_req' && action !== 'relay') return true
55
+ return consumeOverlayRateToken(overlayRateBuckets, sender, Date.now(), limits)
56
+ })
57
+ }
58
+
59
+ /**
60
+ * @returns {void}
61
+ */
62
+ function removeOverlayRateLimit() {
63
+ clearOverlayRateGate()
64
+ overlayRateBuckets.clear()
65
+ }
66
+
67
+ /** @type {boolean} */
68
+ let infraRunning = false
69
+
70
+ /** @type {(() => void) | null} */
71
+ let mailboxDispose = null
72
+
73
+ /** @type {(() => void) | null} */
74
+ let debugDispose = null
75
+
76
+ /** @type {number | null} */
77
+ let savedMaxActive = null
78
+
79
+ /**
80
+ * @returns {boolean} infra relay 是否在运行
81
+ */
82
+ export function isInfraRunning() {
83
+ return infraRunning
84
+ }
85
+
86
+ /**
87
+ * 启动 public-good infra:overlay、mailbox、rate limit、priority、debug log。
88
+ * @param {{ maxActive?: number, logger?: { info?: Function, warn?: Function, error?: Function, log?: Function } | null }} [options] - maxActive 与 debug logger
89
+ * @returns {Promise<void>}
90
+ */
91
+ export async function startInfra(options = {}) {
92
+ if (!isNodeInitialized()) throw new Error('p2p: startInfra requires initNode')
93
+ if (infraRunning) {
94
+ if (options.maxActive != null) await getLinkRegistry().setMaxActive(options.maxActive)
95
+ if (options.logger !== undefined) {
96
+ detachInfraDebugLog()
97
+ debugDispose = attachInfraDebugLog(options.logger ?? null)
98
+ }
99
+ applyPriorityToRegistry()
100
+ return
101
+ }
102
+ const registry = getLinkRegistry()
103
+ await registry.ensureRuntime()
104
+ registry.ensureOverlayRouter()
105
+ installOverlayRateLimit()
106
+ savedMaxActive = registry.getMaxActive()
107
+ if (options.maxActive != null)
108
+ await registry.setMaxActive(options.maxActive)
109
+ else
110
+ await registry.setMaxActive(infraTunables.defaultMaxActive ?? savedMaxActive)
111
+ if (!mailboxDispose) mailboxDispose = attachNodeScopeMailbox()
112
+ const logger = options.logger === undefined ? console : options.logger ?? null
113
+ debugDispose = attachInfraDebugLog(logger)
114
+ applyPriorityToRegistry()
115
+ infraRunning = true
116
+ }
117
+
118
+ /**
119
+ * 卸掉 infra 自己挂的面(mailbox / rate / debug / priority / maxActive 恢复)。
120
+ * 不碰用户另行 attach 的 wires,不绑信誉同步。
121
+ * @returns {Promise<void>}
122
+ */
123
+ export async function stopInfra() {
124
+ if (!infraRunning) return
125
+ debugDispose?.()
126
+ debugDispose = null
127
+ detachInfraDebugLog()
128
+ clearInfraPriorityFromRegistry()
129
+ removeOverlayRateLimit()
130
+ mailboxDispose?.()
131
+ mailboxDispose = null
132
+ const registry = getLinkRegistry()
133
+ if (savedMaxActive != null)
134
+ await registry.setMaxActive(savedMaxActive)
135
+ savedMaxActive = null
136
+ infraRunning = false
137
+ }
138
+
139
+ /** 再导出:infra 路由加权配置(见 `priority.mjs`)。 */
140
+ export { getInfraPriority, setInfraPriority }
@@ -0,0 +1,5 @@
1
+ {
2
+ "defaultMaxActive": 96,
3
+ "overlayRatePerMin": 120,
4
+ "overlayRateBurst": 30
5
+ }
package/link/frame.mjs CHANGED
@@ -2,12 +2,10 @@ import { randomBytes } from 'node:crypto'
2
2
 
3
3
  import { bytesToHex, hexToBytes, toBytes } from '../core/bytes_codec.mjs'
4
4
 
5
- /** 二进制帧协议版本号。 */
6
- export const FRAME_VERSION = 1
7
5
  /** frameId 字段字节长度(128 位)。 */
8
6
  export const FRAME_ID_BYTES = 16
9
- /** 帧头:version(1) + frameId(16) + seq(4) + total(4)。 */
10
- export const FRAME_HEADER_BYTES = 1 + FRAME_ID_BYTES + 4 + 4
7
+ /** 帧头:frameId(16) + seq(4) + total(4)。 */
8
+ export const FRAME_HEADER_BYTES = FRAME_ID_BYTES + 4 + 4
11
9
  /** 默认单帧最大 chunk 大小(15 KiB)。 */
12
10
  export const DEFAULT_MAX_FRAME_CHUNK_BYTES = 15 * 1024
13
11
  /** 重组后消息最大字节数(8 MiB)。 */
@@ -62,11 +60,10 @@ export function encodeFrames(frameId, bytes, maxChunkBytes = DEFAULT_MAX_FRAME_C
62
60
  const end = Math.min(body.byteLength, start + chunkBytes)
63
61
  const chunk = body.subarray(start, end)
64
62
  const frame = new Uint8Array(FRAME_HEADER_BYTES + chunk.byteLength)
65
- frame[0] = FRAME_VERSION
66
- frame.set(idBytes, 1)
63
+ frame.set(idBytes, 0)
67
64
  const view = new DataView(frame.buffer, frame.byteOffset, frame.byteLength)
68
- view.setUint32(1 + FRAME_ID_BYTES, seq, false)
69
- view.setUint32(1 + FRAME_ID_BYTES + 4, total, false)
65
+ view.setUint32(FRAME_ID_BYTES, seq, false)
66
+ view.setUint32(FRAME_ID_BYTES + 4, total, false)
70
67
  frame.set(chunk, FRAME_HEADER_BYTES)
71
68
  frames.push(frame)
72
69
  }
@@ -76,23 +73,19 @@ export function encodeFrames(frameId, bytes, maxChunkBytes = DEFAULT_MAX_FRAME_C
76
73
  /**
77
74
  * 解析单帧头与 chunk。
78
75
  * @param {Uint8Array | ArrayBuffer | ArrayBufferView} frame 原始帧
79
- * @returns {{ version: number, frameId: string, seq: number, total: number, chunk: Uint8Array }} 帧字段
76
+ * @returns {{ frameId: string, seq: number, total: number, chunk: Uint8Array }} 帧字段
80
77
  */
81
78
  export function decodeFrame(frame) {
82
79
  const bytes = toBytes(frame)
83
80
  if (bytes.byteLength < FRAME_HEADER_BYTES)
84
81
  throw new Error('p2p: frame too short')
85
- const version = bytes[0]
86
- if (version !== FRAME_VERSION)
87
- throw new Error(`p2p: unsupported frame version ${version}`)
88
- const frameId = bytesToHex(bytes.subarray(1, 1 + FRAME_ID_BYTES))
82
+ const frameId = bytesToHex(bytes.subarray(0, FRAME_ID_BYTES))
89
83
  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)
90
- const seq = view.getUint32(1 + FRAME_ID_BYTES, false)
91
- const total = view.getUint32(1 + FRAME_ID_BYTES + 4, false)
84
+ const seq = view.getUint32(FRAME_ID_BYTES, false)
85
+ const total = view.getUint32(FRAME_ID_BYTES + 4, false)
92
86
  if (!total || seq >= total)
93
87
  throw new Error('p2p: invalid frame sequence')
94
88
  return {
95
- version,
96
89
  frameId,
97
90
  seq,
98
91
  total,
@@ -51,7 +51,7 @@ export function buildAuthMessage(peerNonce, localBinding, localNodeHash) {
51
51
  /**
52
52
  * 构造 link hello 握手包。
53
53
  * @param {{ nodeHash?: string, nodePubKey?: string, nonce?: string }} [options] 可选身份字段,省略则从本地节点种子推导
54
- * @returns {{ v: 1, nodeHash: string, nodePubKey: string, nonce: string }} hello 对象
54
+ * @returns {{ nodeHash: string, nodePubKey: string, nonce: string }} hello 对象
55
55
  */
56
56
  export function buildHello(options = {}) {
57
57
  let publicKey = null
@@ -66,7 +66,7 @@ export function buildHello(options = {}) {
66
66
  throw new Error('p2p: invalid hello fields')
67
67
  if (pubKeyHash(Buffer.from(nodePubKey, 'hex')) !== nodeHash)
68
68
  throw new Error('p2p: hello nodePubKey does not match nodeHash')
69
- return { v: 1, nodeHash, nodePubKey, nonce }
69
+ return { nodeHash, nodePubKey, nonce }
70
70
  }
71
71
 
72
72
  /**
@@ -92,13 +92,12 @@ export async function buildAuth(peerNonce, localBinding, options = {}) {
92
92
  /**
93
93
  * 解析并校验 hello 对象,无效时返回 null。
94
94
  * @param {unknown} hello 原始 hello 载荷
95
- * @returns {{ v: 1, nodeHash: string, nodePubKey: string, nonce: string } | null} 规范化 hello 或 null
95
+ * @returns {{ nodeHash: string, nodePubKey: string, nonce: string } | null} 规范化 hello 或 null
96
96
  */
97
97
  export function parseHello(hello) {
98
98
  const nodeHash = normalizeHex64(hello?.nodeHash)
99
99
  const nodePubKey = normalizeHex64(hello?.nodePubKey)
100
100
  const nonce = normalizeHex64(hello?.nonce)
101
- if (Number(hello?.v) !== 1) return null
102
101
  if (!isHex64(nodeHash) || !isHex64(nodePubKey) || !isHex64(nonce)) return null
103
102
  try {
104
103
  if (pubKeyHash(Buffer.from(nodePubKey, 'hex')) !== nodeHash) return null
@@ -106,7 +105,7 @@ export function parseHello(hello) {
106
105
  catch {
107
106
  return null
108
107
  }
109
- return { v: 1, nodeHash, nodePubKey, nonce }
108
+ return { nodeHash, nodePubKey, nonce }
110
109
  }
111
110
 
112
111
  /**
@@ -136,26 +135,26 @@ export async function verifyAuth(hello, auth, expectedNonce, remoteBinding) {
136
135
 
137
136
  /**
138
137
  * 构造 discovery advert 待签名字节串。
139
- * @param {string} topic 广播主题
138
+ * @param {string} rendezvousKey discovery 内部汇合键
140
139
  * @param {number} ts 时间戳(毫秒)
141
140
  * @param {string} nodeHash 节点 nodeHash
142
141
  * @param {number | null} [tcpPort=null] 可选 LAN TCP 监听端口(签入消息)
143
142
  * @returns {Uint8Array} 待签名消息字节
144
143
  */
145
- export function buildAdvertMessage(topic, ts, nodeHash, tcpPort = null) {
146
- const base = `fount-advert\0${String(topic)}\0${String(ts)}\0${normalizeHex64(nodeHash)}`
144
+ export function buildAdvertMessage(rendezvousKey, ts, nodeHash, tcpPort = null) {
145
+ const base = `fount-advert\0${String(rendezvousKey)}\0${String(ts)}\0${normalizeHex64(nodeHash)}`
147
146
  const port = normalizeTcpPort(tcpPort)
148
147
  return Buffer.from(port ? `${base}\0${port}` : base, 'utf8')
149
148
  }
150
149
 
151
150
  /**
152
151
  * 构造带签名的 discovery advert。
153
- * @param {string} topic 广播主题
152
+ * @param {string} rendezvousKey discovery 内部汇合键
154
153
  * @param {number} [ts=Date.now()] 时间戳(毫秒)
155
154
  * @param {{ secretKey?: Uint8Array, nodeHash?: string, nodePubKey?: string, tcpPort?: number } | null} [options] 签名身份与可选 tcpPort
156
155
  * @returns {Promise<{ nodeHash: string, nodePubKey: string, ts: number, sig: string, tcpPort?: number }>} 签名 advert
157
156
  */
158
- export async function buildSignedAdvert(topic, ts = Date.now(), options = null) {
157
+ export async function buildSignedAdvert(rendezvousKey, ts = Date.now(), options = null) {
159
158
  const seed = options?.secretKey
160
159
  ? Buffer.from(options.secretKey)
161
160
  : Buffer.from(ensureNodeSeed(), 'hex')
@@ -167,7 +166,7 @@ export async function buildSignedAdvert(topic, ts = Date.now(), options = null)
167
166
  const tcpPort = normalizeTcpPort(options?.tcpPort)
168
167
  if (options?.tcpPort && !tcpPort)
169
168
  throw new Error('p2p: advert tcpPort invalid')
170
- const message = buildAdvertMessage(topic, ts, nodeHash, tcpPort)
169
+ const message = buildAdvertMessage(rendezvousKey, ts, nodeHash, tcpPort)
171
170
  const sig = await sign(message, secretKey)
172
171
  const advert = {
173
172
  nodeHash,
@@ -181,14 +180,14 @@ export async function buildSignedAdvert(topic, ts = Date.now(), options = null)
181
180
 
182
181
  /**
183
182
  * 验证 discovery advert 签名与时间戳,成功返回发布者 nodeHash。
184
- * @param {string} topic 期望的广播主题
183
+ * @param {string} rendezvousKey 期望的汇合键
185
184
  * @param {unknown} advert 原始 advert 载荷
186
185
  * @param {number} [now=Date.now()] 当前时间(毫秒)
187
186
  * @param {number} [maxSkewMs=10 * 60_000] 允许的最大时钟偏差(毫秒)
188
187
  * @returns {Promise<string | null>} 验证通过的 nodeHash,失败返回 null
189
188
  */
190
- export async function verifySignedAdvert(topic, advert, now = Date.now(), maxSkewMs = 10 * 60_000) {
191
- const parsedHello = parseHello({ v: 1, nodeHash: advert?.nodeHash, nodePubKey: advert?.nodePubKey, nonce: '0'.repeat(64) })
189
+ export async function verifySignedAdvert(rendezvousKey, advert, now = Date.now(), maxSkewMs = 10 * 60_000) {
190
+ const parsedHello = parseHello({ nodeHash: advert?.nodeHash, nodePubKey: advert?.nodePubKey, nonce: '0'.repeat(64) })
192
191
  if (!parsedHello) return null
193
192
  const ts = Number(advert?.ts)
194
193
  const sig = String(advert?.sig ?? '').trim().toLowerCase()
@@ -196,7 +195,7 @@ export async function verifySignedAdvert(topic, advert, now = Date.now(), maxSke
196
195
  const hasTcpPortField = !!advert?.tcpPort
197
196
  const tcpPort = normalizeTcpPort(advert?.tcpPort)
198
197
  if (hasTcpPortField && !tcpPort) return null
199
- const message = buildAdvertMessage(topic, ts, parsedHello.nodeHash, tcpPort)
198
+ const message = buildAdvertMessage(rendezvousKey, ts, parsedHello.nodeHash, tcpPort)
200
199
  const ok = await verify(Buffer.from(sig, 'hex'), message, Buffer.from(parsedHello.nodePubKey, 'hex'))
201
200
  return ok ? parsedHello.nodeHash : null
202
201
  }
package/link/pipe.mjs CHANGED
@@ -28,25 +28,25 @@ export function asLinkHandle(pipe, extras = {}) {
28
28
  /** @returns {number} 提供者 level */
29
29
  get level() { return pipe.level },
30
30
  /**
31
- * @param {...any} args 透传 send
31
+ * @param {...any} callArguments 透传 send
32
32
  * @returns {ReturnType<typeof pipe.send>} 是否发送成功
33
33
  */
34
- send: (...args) => pipe.send(...args),
34
+ send: (...callArguments) => pipe.send(...callArguments),
35
35
  /**
36
- * @param {...any} args 透传 onEnvelope
36
+ * @param {...any} callArguments 透传 onEnvelope
37
37
  * @returns {ReturnType<typeof pipe.onEnvelope>} 取消订阅
38
38
  */
39
- onEnvelope: (...args) => pipe.onEnvelope(...args),
39
+ onEnvelope: (...callArguments) => pipe.onEnvelope(...callArguments),
40
40
  /**
41
- * @param {...any} args 透传 onDown
41
+ * @param {...any} callArguments 透传 onDown
42
42
  * @returns {ReturnType<typeof pipe.onDown>} 取消订阅
43
43
  */
44
- onDown: (...args) => pipe.onDown(...args),
44
+ onDown: (...callArguments) => pipe.onDown(...callArguments),
45
45
  /**
46
- * @param {...any} args 透传 close
46
+ * @param {...any} callArguments 透传 close
47
47
  * @returns {ReturnType<typeof pipe.close>} 关闭完成
48
48
  */
49
- close: (...args) => pipe.close(...args),
49
+ close: (...callArguments) => pipe.close(...callArguments),
50
50
  /** @returns {ReturnType<typeof pipe.stats>} 运行时统计 */
51
51
  stats: () => pipe.stats(),
52
52
  ...extras,
@@ -207,9 +207,9 @@ export function createBleGattLinkProvider() {
207
207
  * @returns {void}
208
208
  */
209
209
  onWriteRequest(_connection, data, _offset, _withoutResponse, callback) {
210
- const buf = Buffer.from(data)
211
- if (sessionInbound) sessionInbound(buf)
212
- else void acceptPeripheralWrite(buf).catch(() => { })
210
+ const buffer = Buffer.from(data)
211
+ if (sessionInbound) sessionInbound(buffer)
212
+ else void acceptPeripheralWrite(buffer).catch(() => { })
213
213
  callback(bleno.Characteristic.RESULT_SUCCESS)
214
214
  },
215
215
  })
@@ -228,12 +228,12 @@ export function createBleGattLinkProvider() {
228
228
  }
229
229
 
230
230
  /**
231
- * @param {Buffer} buf 入站写(首包应为 link-open)
231
+ * @param {Buffer} buffer 入站写(首包应为 link-open)
232
232
  * @returns {Promise<void>}
233
233
  */
234
- async function acceptPeripheralWrite(buf) {
234
+ async function acceptPeripheralWrite(buffer) {
235
235
  if (activeInbound || acceptInflight || !onInbound || !localIdentity) return
236
- const opened = parseLinkOpen(buf)
236
+ const opened = parseLinkOpen(buffer)
237
237
  if (!opened) return
238
238
  acceptInflight = true
239
239
  try {
package/link/rtc.mjs CHANGED
@@ -2,7 +2,7 @@ import process from 'node:process'
2
2
 
3
3
  import { toBytes } from '../core/bytes_codec.mjs'
4
4
  import { getSignalingRuntimeConfig } from '../node/instance.mjs'
5
- import { wrapRtcPeerConnectionForMdns } from '../transport/rtc_mdns_filter.mjs'
5
+ import { wrapRtcPeerConnectionForIceLocalHostname } from '../transport/rtc_ice_local_hostname.mjs'
6
6
 
7
7
  /**
8
8
  * 注册进程退出时销毁 libdatachannel 全部原生资源(仅一次)。
@@ -20,9 +20,9 @@ process.on('exit', () => {
20
20
  */
21
21
  export async function loadNodeRtcPolyfill() {
22
22
  const mod = await import('node-datachannel/polyfill')
23
- const { mdnsPolicy } = getSignalingRuntimeConfig()
23
+ const { iceLocalHostnamePolicy } = getSignalingRuntimeConfig()
24
24
  return {
25
- RTCPeerConnection: wrapRtcPeerConnectionForMdns(mod.RTCPeerConnection, mod.RTCIceCandidate, mdnsPolicy),
25
+ RTCPeerConnection: wrapRtcPeerConnectionForIceLocalHostname(mod.RTCPeerConnection, mod.RTCIceCandidate, iceLocalHostnamePolicy),
26
26
  RTCIceCandidate: mod.RTCIceCandidate,
27
27
  }
28
28
  }
@@ -54,8 +54,8 @@ export async function dispatchMailboxRecordsToConsumers(username, records) {
54
54
  const ids = await handler(username, scoped)
55
55
  for (const id of ids || []) delivered.add(String(id))
56
56
  }
57
- catch (err) {
58
- console.error('mailbox: consumer batch failed, retry per record', err)
57
+ catch (error) {
58
+ console.error('mailbox: consumer batch failed, retry per record', error)
59
59
  for (const row of scoped)
60
60
  try {
61
61
  const ids = await handler(username, [row])
@@ -1,6 +1,6 @@
1
1
  import { normalizeHex64 } from '../core/hexIds.mjs'
2
2
  import { getNodeTransportSettings, getNodeHash } from '../node/identity.mjs'
3
- import { deliverToUserRoomPeers, ensureUserRoom } from '../transport/user_room.mjs'
3
+ import { activeLinkRoster, deliverToUserRoomPeers } from '../transport/user_room.mjs'
4
4
  import { DEFAULT_TRUST_GRAPH_OWNER, requireTrustGraphProvider } from '../trust_graph/registry.mjs'
5
5
 
6
6
  import { allowMailboxRelayForTier } from './importance.mjs'
@@ -23,9 +23,9 @@ import {
23
23
  * @returns {Promise<string | null>} 已验证的 remote nodeHash
24
24
  */
25
25
  async function resolveRemoteNodeHashForPeer(username, peerId) {
26
+ void username
26
27
  if (!peerId) return null
27
- const slot = await ensureUserRoom({ replicaUsername: username })
28
- const entry = slot?.getRoster()?.find(row => row.peerId === peerId)
28
+ const entry = activeLinkRoster().find(row => row.peerId === peerId)
29
29
  const remote = entry?.remoteNodeHash?.trim().toLowerCase()
30
30
  return remote || null
31
31
  }
@@ -51,9 +51,9 @@ async function resolveRelayHopForIngress(record) {
51
51
  * @returns {Promise<{ maxHop: number, relayFanoutTrusted: number, relayFanoutNormal: number, wantFanout: number, batterySaver: boolean }>} 按在线 peer 数缩放的路由
52
52
  */
53
53
  async function resolveRouting(username) {
54
+ void username
54
55
  const { batterySaver, mailbox } = getNodeTransportSettings()
55
- const slot = await ensureUserRoom({ replicaUsername: username })
56
- const peerCount = slot?.getRoster()?.length ?? 0
56
+ const peerCount = activeLinkRoster().length
57
57
  return resolveMailboxRoutingForPeerCount(peerCount, mailbox, batterySaver)
58
58
  }
59
59
 
package/mailbox/wire.mjs CHANGED
@@ -11,30 +11,34 @@ import { parseMailboxGive, parseMailboxPut, parseMailboxWant } from './parse.mjs
11
11
 
12
12
  /**
13
13
  * @param {MailboxWireContext} wireContext 入站上下文
14
- * @param {{ on: (name: string, handler: (payload: unknown, peerId: string) => void) => void, send: (name: string, payload: unknown, peerId: string | null) => void }} wire Trystero 适配器
15
- * @returns {void}
14
+ * @param {{ on: (name: string, handler: (payload: unknown, peerId: string) => void) => (() => void) | void, send: (name: string, payload: unknown, peerId: string | null) => void }} wire Trystero 适配器
15
+ * @returns {() => void} 取消挂载的 dispose
16
16
  */
17
17
  export function attachMailboxWire(wireContext, wire) {
18
- wire.on('mailbox_put', (payload, peerId) => {
19
- const put = parseMailboxPut(payload)
20
- if (!put.ok) return
21
- void ingestMailboxPut(wireContext, put.value, peerId).catch(err => console.error('mailbox: put ingest failed', err))
22
- })
23
-
24
- wire.on('mailbox_want', (payload, peerId) => {
25
- const want = parseMailboxWant(payload)
26
- if (!want.ok) return
27
- void respondMailboxWant(want.value, (giveWire, targetPeerId) => {
28
- try {
29
- wire.send('mailbox_give', giveWire, targetPeerId)
30
- }
31
- catch { /* disconnected */ }
32
- }, peerId).catch(err => console.error('mailbox: want failed', err))
33
- })
34
-
35
- wire.on('mailbox_give', payload => {
36
- const give = parseMailboxGive(payload)
37
- if (!give.ok) return
38
- void ingestMailboxGive(wireContext, give.value).catch(err => console.error('mailbox: give ingest failed', err))
39
- })
18
+ const offs = [
19
+ wire.on('mailbox_put', (payload, peerId) => {
20
+ const put = parseMailboxPut(payload)
21
+ if (!put.ok) return
22
+ void ingestMailboxPut(wireContext, put.value, peerId).catch(error => console.error('mailbox: put ingest failed', error))
23
+ }),
24
+ wire.on('mailbox_want', (payload, peerId) => {
25
+ const want = parseMailboxWant(payload)
26
+ if (!want.ok) return
27
+ void respondMailboxWant(want.value, (giveWire, targetPeerId) => {
28
+ try {
29
+ wire.send('mailbox_give', giveWire, targetPeerId)
30
+ }
31
+ catch { /* disconnected */ }
32
+ }, peerId).catch(error => console.error('mailbox: want failed', error))
33
+ }),
34
+ wire.on('mailbox_give', payload => {
35
+ const give = parseMailboxGive(payload)
36
+ if (!give.ok) return
37
+ void ingestMailboxGive(wireContext, give.value).catch(error => console.error('mailbox: give ingest failed', error))
38
+ }),
39
+ ]
40
+ return () => {
41
+ for (const off of offs)
42
+ try { off?.() } catch { /* ignore */ }
43
+ }
40
44
  }
@@ -80,9 +80,9 @@ export function createFsEntityStore(baseDir) {
80
80
  const entries = await fsp.readdir(root, { withFileTypes: true })
81
81
  return entries.filter(e => e.isDirectory()).map(e => e.name.toLowerCase()).filter(h => parseEntityHash(h))
82
82
  }
83
- catch (err) {
84
- if (/** @type {NodeJS.ErrnoException} */ err.code === 'ENOENT') return []
85
- throw err
83
+ catch (error) {
84
+ if (/** @type {NodeJS.ErrnoException} */ error.code === 'ENOENT') return []
85
+ throw error
86
86
  }
87
87
  },
88
88
 
@@ -115,9 +115,9 @@ export function createFsEntityStore(baseDir) {
115
115
  try {
116
116
  return await fsp.readFile(filePath)
117
117
  }
118
- catch (err) {
119
- if (/** @type {NodeJS.ErrnoException} */ err.code === 'ENOENT') return null
120
- throw err
118
+ catch (error) {
119
+ if (/** @type {NodeJS.ErrnoException} */ error.code === 'ENOENT') return null
120
+ throw error
121
121
  }
122
122
  },
123
123