@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
@@ -147,18 +147,18 @@ export async function encryptPlaintextToMultiPartsAsync(plaintext, ceMode = 'con
147
147
  }
148
148
 
149
149
  /**
150
- * @param {object} params 参数
151
- * @param {string} params.ownerEntityHash 128 hex
152
- * @param {string} params.logicalPath EVFS 路径
153
- * @param {Buffer | Uint8Array} params.plaintext 明文
154
- * @param {string} [params.name] 文件名
155
- * @param {string} [params.mimeType] MIME
156
- * @param {CeMode} [params.ceMode] 加密模式
157
- * @param {import('./manifest.mjs').TransferKeyDescriptor} [params.transferKeyDescriptor] 传递密钥
158
- * @param {object} [params.meta] 元数据
150
+ * @param {object} parameters 参数
151
+ * @param {string} parameters.ownerEntityHash 128 hex
152
+ * @param {string} parameters.logicalPath EVFS 路径
153
+ * @param {Buffer | Uint8Array} parameters.plaintext 明文
154
+ * @param {string} [parameters.name] 文件名
155
+ * @param {string} [parameters.mimeType] MIME
156
+ * @param {CeMode} [parameters.ceMode] 加密模式
157
+ * @param {import('./manifest.mjs').TransferKeyDescriptor} [parameters.transferKeyDescriptor] 传递密钥
158
+ * @param {object} [parameters.meta] 元数据
159
159
  * @returns {FileManifest} manifest(未写盘)
160
160
  */
161
- export function buildFileManifest(params) {
161
+ export function buildFileManifest(parameters) {
162
162
  const {
163
163
  ownerEntityHash,
164
164
  logicalPath,
@@ -168,7 +168,7 @@ export function buildFileManifest(params) {
168
168
  ceMode = 'convergent',
169
169
  transferKeyDescriptor,
170
170
  meta,
171
- } = params
171
+ } = parameters
172
172
  const enc = encryptPlaintextToParts(plaintext, ceMode)
173
173
  const manifest = normalizeFileManifest({
174
174
  ownerEntityHash: ownerEntityHash.toLowerCase(),
@@ -188,11 +188,11 @@ export function buildFileManifest(params) {
188
188
 
189
189
  /**
190
190
  * 由已加密分块构建 manifest(vault / file-master-key-wrap 等需自定义 transferKeyDescriptor)。
191
- * @param {object} params 与 buildFileManifest 相同字段(不含 plaintext 重加密)
191
+ * @param {object} parameters 与 buildFileManifest 相同字段(不含 plaintext 重加密)
192
192
  * @param {{ contentHash: string, parts: Array<{ hash: string, size: number, raw?: Buffer, contentHash?: string }> }} enc 加密结果
193
193
  * @returns {FileManifest} manifest
194
194
  */
195
- export function buildFileManifestFromEnc(params, enc) {
195
+ export function buildFileManifestFromEnc(parameters, enc) {
196
196
  const {
197
197
  ownerEntityHash,
198
198
  logicalPath,
@@ -202,7 +202,7 @@ export function buildFileManifestFromEnc(params, enc) {
202
202
  ceMode = 'convergent',
203
203
  transferKeyDescriptor,
204
204
  meta,
205
- } = params
205
+ } = parameters
206
206
  const manifest = normalizeFileManifest({
207
207
  ownerEntityHash: ownerEntityHash.toLowerCase(),
208
208
  logicalPath: logicalPath.replace(/^\/+/, ''),
@@ -44,11 +44,11 @@ export async function encryptReadableToParts(readable, ceMode = 'convergent', on
44
44
  }
45
45
 
46
46
  for await (const chunk of readable) {
47
- const buf = Buffer.from(chunk)
48
- total += buf.length
47
+ const buffer = Buffer.from(chunk)
48
+ total += buffer.length
49
49
  if (total > maxBytes)
50
50
  throw new Error('plaintext exceeds max upload size')
51
- pending = Buffer.concat([pending, buf])
51
+ pending = Buffer.concat([pending, buffer])
52
52
  while (pending.length >= FEDERATION_CHUNK_MAX_BYTES) {
53
53
  const slice = pending.subarray(0, FEDERATION_CHUNK_MAX_BYTES)
54
54
  pending = pending.subarray(FEDERATION_CHUNK_MAX_BYTES)
@@ -91,11 +91,11 @@ function readStreamChunk(stream) {
91
91
  resolve(null)
92
92
  }
93
93
  /**
94
- * @param {Error} err 错误
94
+ * @param {Error} error 错误
95
95
  */
96
- const onError = err => {
96
+ const onError = error => {
97
97
  cleanup()
98
- reject(err)
98
+ reject(error)
99
99
  }
100
100
  /**
101
101
  *
@@ -44,27 +44,36 @@ export async function handleFedManifestDataIngress(data) {
44
44
 
45
45
  /**
46
46
  * node scope user-room wire:注册 fed_chunk_* + fed_manifest_*。
47
- * @param {string} username 副本用户名 用户名
48
- * @param {{ on: (name: string, handler: (payload: unknown, peerId: string) => void) => void, send: (name: string, payload: unknown, peerId: string | null) => void }} wire action 表
49
- * @returns {void}
47
+ * @param {string | (() => string)} usernameOrGetter 副本用户名或 live getter
48
+ * @param {{ on: (name: string, handler: (payload: unknown, peerId: string) => void) => (() => void) | void, send: (name: string, payload: unknown, peerId: string | null) => void }} wire action 表
49
+ * @returns {() => void} 取消挂载的 dispose
50
50
  */
51
- export function attachNodeScopeFedChunkResponder(username, wire) {
52
- wire.on('fed_chunk_get', (data, peerId) => {
53
- void handleFedChunkGetIngress(username, data, peerId, (resp, pid) => {
54
- try { wire.send('fed_chunk_data', resp, pid) }
55
- catch { /* disconnected */ }
56
- })
57
- })
58
- wire.on('fed_chunk_data', handleFedChunkDataIngress)
59
- wire.on('fed_manifest_get', (data, peerId) => {
60
- void handleFedManifestGetIngress(username, data, peerId, (resp, pid) => {
61
- try { wire.send('fed_manifest_data', resp, pid) }
62
- catch { /* disconnected */ }
63
- })
64
- })
65
- wire.on('fed_manifest_data', data => {
66
- void handleFedManifestDataIngress(data)
67
- })
51
+ export function attachNodeScopeFedChunkResponder(usernameOrGetter, wire) {
52
+ const resolveUsername = typeof usernameOrGetter === 'function'
53
+ ? usernameOrGetter
54
+ : () => usernameOrGetter
55
+ const offs = [
56
+ wire.on('fed_chunk_get', (data, peerId) => {
57
+ void handleFedChunkGetIngress(resolveUsername(), data, peerId, (resp, pid) => {
58
+ try { wire.send('fed_chunk_data', resp, pid) }
59
+ catch { /* disconnected */ }
60
+ })
61
+ }),
62
+ wire.on('fed_chunk_data', handleFedChunkDataIngress),
63
+ wire.on('fed_manifest_get', (data, peerId) => {
64
+ void handleFedManifestGetIngress(resolveUsername(), data, peerId, (resp, pid) => {
65
+ try { wire.send('fed_manifest_data', resp, pid) }
66
+ catch { /* disconnected */ }
67
+ })
68
+ }),
69
+ wire.on('fed_manifest_data', data => {
70
+ void handleFedManifestDataIngress(data)
71
+ }),
72
+ ]
73
+ return () => {
74
+ for (const off of offs)
75
+ try { off?.() } catch { /* ignore */ }
76
+ }
68
77
  }
69
78
 
70
79
  /**
package/files/evfs.mjs CHANGED
@@ -128,18 +128,18 @@ export async function readManifestPlaintextStream(replicaUsername, manifest, opt
128
128
  }
129
129
 
130
130
  /**
131
- * @param {object} params 参数
132
- * @param {string} params.ownerEntityHash owner
133
- * @param {string} params.logicalPath 路径
134
- * @param {Buffer | Uint8Array} params.plaintext 明文
135
- * @param {string} [params.name] 文件名
136
- * @param {string} [params.mimeType] MIME
137
- * @param {import('./manifest.mjs').CeMode} [params.ceMode] 模式
138
- * @param {import('./manifest.mjs').TransferKeyDescriptor} [params.transferKeyDescriptor] 传递密钥
139
- * @param {object} [params.meta] meta
131
+ * @param {object} parameters 参数
132
+ * @param {string} parameters.ownerEntityHash owner
133
+ * @param {string} parameters.logicalPath 路径
134
+ * @param {Buffer | Uint8Array} parameters.plaintext 明文
135
+ * @param {string} [parameters.name] 文件名
136
+ * @param {string} [parameters.mimeType] MIME
137
+ * @param {import('./manifest.mjs').CeMode} [parameters.ceMode] 模式
138
+ * @param {import('./manifest.mjs').TransferKeyDescriptor} [parameters.transferKeyDescriptor] 传递密钥
139
+ * @param {object} [parameters.meta] meta
140
140
  * @returns {Promise<import('./manifest.mjs').FileManifest>} 写入后的 manifest
141
141
  */
142
- export async function putFileManifest(params) {
142
+ export async function putFileManifest(parameters) {
143
143
  const {
144
144
  ownerEntityHash,
145
145
  logicalPath,
@@ -149,15 +149,15 @@ export async function putFileManifest(params) {
149
149
  ceMode = 'convergent',
150
150
  transferKeyDescriptor,
151
151
  meta,
152
- } = params
153
- const plainBuf = Buffer.from(plaintext)
154
- const enc = plainBuf.length > FEDERATION_CHUNK_MAX_BYTES
155
- ? await encryptPlaintextToMultiPartsAsync(plainBuf, ceMode)
156
- : encryptPlaintextToParts(plainBuf, ceMode)
152
+ } = parameters
153
+ const plaintextBuffer = Buffer.from(plaintext)
154
+ const enc = plaintextBuffer.length > FEDERATION_CHUNK_MAX_BYTES
155
+ ? await encryptPlaintextToMultiPartsAsync(plaintextBuffer, ceMode)
156
+ : encryptPlaintextToParts(plaintextBuffer, ceMode)
157
157
  const manifest = buildFileManifestFromEnc({
158
158
  ownerEntityHash,
159
159
  logicalPath,
160
- plaintext: plainBuf,
160
+ plaintext: plaintextBuffer,
161
161
  name,
162
162
  mimeType,
163
163
  ceMode,
@@ -171,19 +171,19 @@ export async function putFileManifest(params) {
171
171
 
172
172
  /**
173
173
  * 流式写入文件(请求流 -> 加密分块 -> chunk store)。
174
- * @param {object} params 参数
175
- * @param {string} params.ownerEntityHash owner
176
- * @param {string} params.logicalPath 路径
177
- * @param {import('node:stream').Readable} params.readable 明文流
178
- * @param {number} params.plainSize 明文字节数
179
- * @param {string} [params.name] 文件名
180
- * @param {string} [params.mimeType] MIME
181
- * @param {import('./manifest.mjs').CeMode} [params.ceMode] 模式
182
- * @param {import('./manifest.mjs').TransferKeyDescriptor} [params.transferKeyDescriptor] 传递密钥
183
- * @param {object} [params.meta] meta
174
+ * @param {object} parameters 参数
175
+ * @param {string} parameters.ownerEntityHash owner
176
+ * @param {string} parameters.logicalPath 路径
177
+ * @param {import('node:stream').Readable} parameters.readable 明文流
178
+ * @param {number} parameters.plainSize 明文字节数
179
+ * @param {string} [parameters.name] 文件名
180
+ * @param {string} [parameters.mimeType] MIME
181
+ * @param {import('./manifest.mjs').CeMode} [parameters.ceMode] 模式
182
+ * @param {import('./manifest.mjs').TransferKeyDescriptor} [parameters.transferKeyDescriptor] 传递密钥
183
+ * @param {object} [parameters.meta] meta
184
184
  * @returns {Promise<import('./manifest.mjs').FileManifest>} 写入后的 manifest
185
185
  */
186
- export async function putFileManifestFromStream(params) {
186
+ export async function putFileManifestFromStream(parameters) {
187
187
  const {
188
188
  ownerEntityHash,
189
189
  logicalPath,
@@ -194,7 +194,7 @@ export async function putFileManifestFromStream(params) {
194
194
  ceMode = 'convergent',
195
195
  transferKeyDescriptor,
196
196
  meta,
197
- } = params
197
+ } = parameters
198
198
  const enc = await encryptReadableToParts(readable, ceMode, async part =>
199
199
  putChunk(part.hash, part.raw), plainSize)
200
200
  const manifest = normalizeFileManifest({
@@ -228,6 +228,7 @@ export async function readPublicFile(replicaUsername, entityHash, logicalPath, o
228
228
  username: options.username || replicaUsername,
229
229
  ownerEntityHash: entityHash,
230
230
  logicalPath,
231
+ cache: true,
231
232
  })
232
233
  if (!manifest) return null
233
234
  return readManifestPlaintext(replicaUsername, manifest, options)
@@ -16,8 +16,9 @@ import { normalizeFileManifest } from './manifest.mjs'
16
16
  import { shouldPreferIncomingPublicManifest } from './public_manifest.mjs'
17
17
 
18
18
  /**
19
- * @param {{ username: string, ownerEntityHash: string, logicalPath: string }} context 上下文
20
- * @returns {Promise<import('./manifest.mjs').FileManifest | null>} 验签后的公开清单
19
+ * 拉取公开 manifest;默认不写盘。`cache: true` `cachePublicManifest` 才缓存。
20
+ * @param {{ username: string, ownerEntityHash: string, logicalPath: string, cache?: boolean }} context - 拉取上下文
21
+ * @returns {Promise<import('./manifest.mjs').FileManifest | null>} 验签后的 manifest,失败为 null
21
22
  */
22
23
  export async function fetchPublicManifest(context) {
23
24
  const ownerEntityHash = String(context.ownerEntityHash || '').trim().toLowerCase()
@@ -48,10 +49,22 @@ export async function fetchPublicManifest(context) {
48
49
  const result = await done
49
50
  if (!result) return null
50
51
 
51
- await maybeCacheIncomingPublicManifest(ownerEntityHash, logicalPath, result)
52
+ if (context.cache === true)
53
+ await cachePublicManifest(ownerEntityHash, logicalPath, result)
52
54
  return result
53
55
  }
54
56
 
57
+ /**
58
+ * 将已验签公开 manifest 写入本地缓存(显式 apply;`fetchPublicManifest` 默认不调用)。
59
+ * @param {string} ownerEntityHash owner
60
+ * @param {string} logicalPath 路径
61
+ * @param {import('./manifest.mjs').FileManifest} incoming 已验签入站清单
62
+ * @returns {Promise<void>}
63
+ */
64
+ export async function cachePublicManifest(ownerEntityHash, logicalPath, incoming) {
65
+ await maybeCacheIncomingPublicManifest(ownerEntityHash, logicalPath, incoming)
66
+ }
67
+
55
68
  /**
56
69
  * @param {string} ownerEntityHash owner
57
70
  * @param {string} logicalPath 路径
@@ -116,18 +116,18 @@ export function shouldPreferIncomingPublicManifest(localManifest, incoming) {
116
116
  }
117
117
 
118
118
  /**
119
- * @param {object} params 参数
120
- * @param {string} params.ownerEntityHash owner
121
- * @param {string} params.logicalPath 路径
122
- * @param {Buffer | Uint8Array} params.plaintext 明文
123
- * @param {string} [params.name] 文件名
124
- * @param {string} [params.mimeType] MIME
125
- * @param {Uint8Array | Buffer} params.entitySecretKey recovery 私钥种子
126
- * @param {string} params.entityPubKeyHex recovery 公钥 hex
127
- * @param {number} [params.publishedAt] 发布时间(默认 Date.now)
119
+ * @param {object} parameters 参数
120
+ * @param {string} parameters.ownerEntityHash owner
121
+ * @param {string} parameters.logicalPath 路径
122
+ * @param {Buffer | Uint8Array} parameters.plaintext 明文
123
+ * @param {string} [parameters.name] 文件名
124
+ * @param {string} [parameters.mimeType] MIME
125
+ * @param {Uint8Array | Buffer} parameters.entitySecretKey recovery 私钥种子
126
+ * @param {string} parameters.entityPubKeyHex recovery 公钥 hex
127
+ * @param {number} [parameters.publishedAt] 发布时间(默认 Date.now)
128
128
  * @returns {Promise<import('./manifest.mjs').FileManifest>} 已签名并落盘的公开清单
129
129
  */
130
- export async function publishPublicFile(params) {
130
+ export async function publishPublicFile(parameters) {
131
131
  const {
132
132
  ownerEntityHash,
133
133
  logicalPath,
@@ -137,7 +137,7 @@ export async function publishPublicFile(params) {
137
137
  entitySecretKey,
138
138
  entityPubKeyHex,
139
139
  publishedAt = Date.now(),
140
- } = params
140
+ } = parameters
141
141
  const base = await putFileManifest({
142
142
  ownerEntityHash,
143
143
  logicalPath,
@@ -90,8 +90,8 @@ export async function readDagManifestPlaintext(replicaUsername, manifest) {
90
90
  const reader = dagPlaintextReadersByOwner.get(ownerId)
91
91
  if (reader)
92
92
  try {
93
- const buf = await reader(replicaUsername, manifest)
94
- if (buf?.length) return buf
93
+ const buffer = await reader(replicaUsername, manifest)
94
+ if (buffer?.length) return buffer
95
95
  }
96
96
  catch { /* fall through */ }
97
97
  return null
package/index.mjs CHANGED
@@ -1,40 +1,115 @@
1
1
  /**
2
2
  * Federation P2P 门面:fount 网络引导与房间/发现入口。
3
- * 上层只面对 nodeHash + envelope,不选择 WebRTC/BLE 等传输。
4
- * 重型子系统请从子路径导入(如 `./dag`);勿导入未导出的 `link/`。
5
3
  */
6
4
  import { registerDiscoveryProvider } from './discovery/index.mjs'
5
+ import {
6
+ isInfraRunning,
7
+ setInfraPriority,
8
+ startInfra,
9
+ stopInfra,
10
+ } from './infra/service.mjs'
11
+ import { registerLinkProvider } from './link/providers/index.mjs'
7
12
  import { ensureNodeDefaults, getNodeHash } from './node/identity.mjs'
8
- import { getNodeDir, initNode, isNodeInitialized } from './node/instance.mjs'
13
+ import {
14
+ getNodeDir,
15
+ initNode,
16
+ isNodeInitialized,
17
+ setNodeLogger,
18
+ setSignalingRuntimeConfig,
19
+ } from './node/instance.mjs'
20
+ import { setConnectivityDebug } from './node/log.mjs'
21
+ import {
22
+ attachReputationSyncWire,
23
+ getReputationExportAllowlist,
24
+ getReputationLocks,
25
+ getReputationTable,
26
+ getTrustSyncDonors,
27
+ lockReputationMax,
28
+ pullReputationFromNode,
29
+ setReputationExportAllowlist,
30
+ setReputationTable,
31
+ setTrustSyncDonors,
32
+ unlockReputationMax,
33
+ } from './node/reputation_sync.mjs'
34
+ import { getRoutingProfile, setRoutingProfile } from './node/routing_profile.mjs'
9
35
  import { createScopedLinkRoom } from './rooms/scoped_link.mjs'
10
36
  import { createGroupLinkSet } from './transport/group_link_set.mjs'
11
- import { getLinkRegistry } from './transport/link_registry.mjs'
12
- import { ensureUserRoom } from './transport/user_room.mjs'
37
+ import {
38
+ configureLinkRegistry,
39
+ ensureLinkToNode,
40
+ ensureOverlayRouter,
41
+ getLinkRegistry,
42
+ reloadDiscoveryRelays,
43
+ sendToNodeLink,
44
+ } from './transport/link_registry.mjs'
45
+ import {
46
+ attachUserRoomDefaultWires,
47
+ ensureNodeScope,
48
+ ensureUserRoom,
49
+ getUserRoomSlot,
50
+ } from './transport/user_room.mjs'
13
51
 
14
52
  /**
15
- *
53
+ * 包门面:节点、infra、mesh/registry、信誉同步、node-scope 等公开导出。
16
54
  */
17
55
  export {
56
+ attachReputationSyncWire,
57
+ attachUserRoomDefaultWires,
58
+ configureLinkRegistry,
18
59
  createGroupLinkSet,
19
60
  createScopedLinkRoom,
61
+ ensureLinkToNode,
20
62
  ensureNodeDefaults,
63
+ ensureNodeScope,
64
+ ensureOverlayRouter,
21
65
  ensureUserRoom,
22
66
  getLinkRegistry,
23
67
  getNodeDir,
24
68
  getNodeHash,
69
+ getReputationExportAllowlist,
70
+ getReputationLocks,
71
+ getReputationTable,
72
+ getRoutingProfile,
73
+ getTrustSyncDonors,
74
+ getUserRoomSlot,
25
75
  initNode,
76
+ isInfraRunning,
26
77
  isNodeInitialized,
78
+ lockReputationMax,
79
+ pullReputationFromNode,
27
80
  registerDiscoveryProvider,
81
+ registerLinkProvider,
82
+ reloadDiscoveryRelays,
83
+ sendToNodeLink,
84
+ setConnectivityDebug,
85
+ setInfraPriority,
86
+ setNodeLogger,
87
+ setReputationExportAllowlist,
88
+ setReputationTable,
89
+ setRoutingProfile,
90
+ setSignalingRuntimeConfig,
91
+ setTrustSyncDonors,
92
+ startInfra,
93
+ stopInfra,
94
+ unlockReputationMax,
28
95
  }
29
96
 
30
97
  /**
31
- * 初始化并启动 P2P 节点运行时(身份、链路网、link registry)。
32
- * @param {{ nodeDir: string, entityStore?: import('./node/entity_store.mjs').EntityStore, logger?: object, signaling?: import('./node/signaling_config.mjs').SignalingRuntimeConfig }} options 节点配置
98
+ * @param {{ nodeDir?: string, entityStore?: import('./node/entity_store.mjs').EntityStore, logger?: object | null, signaling?: import('./node/signaling_config.mjs').SignalingRuntimeConfig }} [options] - 首次 init 时的节点选项
33
99
  * @returns {Promise<void>}
34
100
  */
35
- export async function startNode(options) {
36
- if (!isNodeInitialized())
37
- initNode(options)
101
+ export async function startNode(options = {}) {
102
+ if (!isNodeInitialized()) {
103
+ const { nodeDir, entityStore, logger, signaling, ...rest } = options
104
+ if (Object.keys(rest).length)
105
+ throw new Error('p2p: startNode unknown options')
106
+ initNode({ nodeDir, entityStore })
107
+ if (logger !== undefined) setNodeLogger(logger)
108
+ if (signaling !== undefined) setSignalingRuntimeConfig(signaling)
109
+ }
110
+ else if (options?.nodeDir || options?.entityStore || options?.logger !== undefined || options?.signaling)
111
+ throw new Error('p2p: startNode options ignored after initNode — use setNodeLogger / setSignalingRuntimeConfig')
112
+
38
113
  ensureNodeDefaults()
39
114
  await getLinkRegistry().ensureRuntime()
40
115
  }
package/infra/cli.mjs ADDED
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { on_shutdown } from 'on-shutdown'
4
+
5
+ import { ensureNodeDefaults, getNodeHash } from '../node/identity.mjs'
6
+ import { initNode, setNodeLogger } from '../node/instance.mjs'
7
+ import { setConnectivityDebug } from '../node/log.mjs'
8
+ import { getLinkRegistry } from '../transport/link_registry.mjs'
9
+
10
+ import { resolveNodeDir } from './default_node_dir.mjs'
11
+ import { setInfraPriority, startInfra, stopInfra } from './service.mjs'
12
+
13
+ /**
14
+ * @param {string[]} argv - 命令行参数(不含 node 路径)
15
+ * @returns {{ nodeDir?: string, quiet?: boolean, useLocalReputation?: boolean, help?: boolean }} 解析结果
16
+ */
17
+ function parseArgs(argv) {
18
+ /** @type {ReturnType<typeof parseArgs>} */
19
+ const out = {}
20
+ for (let i = 0; i < argv.length; i++) {
21
+ const arg = argv[i]
22
+ if (arg === '--help' || arg === '-h') out.help = true
23
+ else if (arg === '--quiet') out.quiet = true
24
+ else if (arg === '--use-local-reputation') out.useLocalReputation = true
25
+ else if (arg === '--node-dir') out.nodeDir = argv[++i]
26
+ }
27
+ return out
28
+ }
29
+
30
+ /** 打印 CLI 用法。 */
31
+ function printHelp() {
32
+ console.log(`Usage: fount-p2p [--node-dir PATH] [--use-local-reputation] [--quiet]
33
+
34
+ Public-good infra relay node (overlay + mailbox).
35
+ Connectivity debug logs on by default (Nostr/LAN/mesh/dial); --quiet silences.
36
+ Non-CLI shells: setConnectivityDebug(true).
37
+ Default data dir: Windows %LOCALAPPDATA%/fount-p2p/node, else ~/.local/share/fount-p2p/node`)
38
+ }
39
+
40
+ const args = parseArgs(process.argv.slice(2))
41
+ if (args.help) {
42
+ printHelp()
43
+ process.exit(0)
44
+ }
45
+
46
+ const nodeDir = resolveNodeDir(args.nodeDir)
47
+ initNode({ nodeDir })
48
+ if (args.quiet) {
49
+ setNodeLogger(null)
50
+ setConnectivityDebug(false)
51
+ }
52
+ else
53
+ setConnectivityDebug(true)
54
+ ensureNodeDefaults()
55
+ const nodeHash = getNodeHash()
56
+ await getLinkRegistry().ensureRuntime()
57
+ if (args.useLocalReputation) setInfraPriority({ useLocalReputation: true })
58
+ await startInfra({ logger: args.quiet ? null : console })
59
+
60
+ console.log(`p2p infra running (nodeDir=${nodeDir} nodeHash=${nodeHash})`)
61
+
62
+ on_shutdown(stopInfra)
@@ -0,0 +1,56 @@
1
+ import { getLinkRegistry } from '../transport/link_registry.mjs'
2
+
3
+ /** @type {Array<() => void>} */
4
+ const cleanups = []
5
+
6
+ /**
7
+ * 挂载 registry link/overlay/node scope 调试日志。
8
+ * @param {{ info?: Function, warn?: Function, error?: Function, log?: Function } | null} logger - 日志输出目标,null 表示静默
9
+ * @returns {() => void} 取消 debug 监听的 dispose
10
+ */
11
+ export function attachInfraDebugLog(logger) {
12
+ detachInfraDebugLog()
13
+ if (!logger) return () => { }
14
+ const registry = getLinkRegistry()
15
+ /**
16
+ * @param {'info' | 'warn' | 'error' | 'log'} level - 日志级别
17
+ * @param {string} message - 日志消息
18
+ * @param {object} [extra] - 附加字段
19
+ */
20
+ const log = (level, message, extra) => {
21
+ logger?.[level]?.(message, extra)
22
+ }
23
+ cleanups.push(registry.onLinkUp((nodeHash, link) => {
24
+ log('info', 'p2p:infra link up', {
25
+ nodeHash,
26
+ providerId: link?.providerId,
27
+ level: link?.level,
28
+ })
29
+ }))
30
+ cleanups.push(registry.onLinkDown((nodeHash, reason) => {
31
+ log('info', 'p2p:infra link down', { nodeHash, reason })
32
+ }))
33
+ cleanups.push(registry.subscribeScope('overlay', (from, envelope) => {
34
+ log('info', 'p2p:infra overlay', {
35
+ from,
36
+ action: envelope?.action,
37
+ path: envelope?.payload?.path,
38
+ })
39
+ }))
40
+ cleanups.push(registry.subscribeScope('node', (from, envelope) => {
41
+ log('info', 'p2p:infra node', {
42
+ from,
43
+ action: envelope?.action,
44
+ })
45
+ }))
46
+ return detachInfraDebugLog
47
+ }
48
+
49
+ /**
50
+ * 卸掉 infra debug 监听。
51
+ * @returns {void}
52
+ */
53
+ export function detachInfraDebugLog() {
54
+ for (const cleanup of cleanups.splice(0))
55
+ try { cleanup() } catch { /* ignore */ }
56
+ }
@@ -0,0 +1,22 @@
1
+ import os from 'node:os'
2
+ import path from 'node:path'
3
+
4
+ /**
5
+ * @returns {string} 平台默认 node 数据目录
6
+ */
7
+ export function defaultNodeDir() {
8
+ if (process.platform === 'win32') {
9
+ const base = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local')
10
+ return path.join(base, 'fount-p2p', 'node')
11
+ }
12
+ return path.join(os.homedir(), '.local', 'share', 'fount-p2p', 'node')
13
+ }
14
+
15
+ /**
16
+ * @param {string | undefined} override - CLI 或调用方覆盖
17
+ * @returns {string} 解析后的绝对 node 目录
18
+ */
19
+ export function resolveNodeDir(override) {
20
+ const trimmed = String(override || '').trim()
21
+ return trimmed ? path.resolve(trimmed) : defaultNodeDir()
22
+ }
@@ -0,0 +1,56 @@
1
+ import { normalizeHex64 } from '../core/hexIds.mjs'
2
+ import { getReputationTable } from '../node/reputation_sync.mjs'
3
+ import { pickNodeScoreFromReputation } from '../reputation/pick_score.mjs'
4
+ import { getLinkRegistry } from '../transport/link_registry.mjs'
5
+
6
+
7
+ /** @type {{ useLocalReputation: boolean }} */
8
+ let priorityConfig = { useLocalReputation: false }
9
+
10
+ const PRIORITY_BOOST = 1000
11
+
12
+ /**
13
+ * 配置 infra 路由加权(是否用本地 reputation)。
14
+ * @param {{ useLocalReputation?: boolean }} config - 是否用本地 reputation 加权路由
15
+ * @returns {void}
16
+ */
17
+ export function setInfraPriority(config = {}) {
18
+ priorityConfig = {
19
+ useLocalReputation: Boolean(config.useLocalReputation),
20
+ }
21
+ applyPriorityToRegistry()
22
+ }
23
+
24
+ /**
25
+ * @returns {{ useLocalReputation: boolean }} 当前 priority 配置副本
26
+ */
27
+ export function getInfraPriority() {
28
+ return { ...priorityConfig }
29
+ }
30
+
31
+ /**
32
+ * @returns {void}
33
+ */
34
+ export function applyPriorityToRegistry() {
35
+ const registry = getLinkRegistry()
36
+ if (!priorityConfig.useLocalReputation) {
37
+ registry.setPriorityWeightFunction(null)
38
+ return
39
+ }
40
+ registry.setPriorityWeightFunction(nodeHash => {
41
+ const score = pickNodeScoreFromReputation(
42
+ getReputationTable(),
43
+ normalizeHex64(nodeHash) || nodeHash,
44
+ )
45
+ return Math.floor(score * PRIORITY_BOOST)
46
+ })
47
+ }
48
+
49
+ /**
50
+ * stopInfra:卸 weight,并重置 priority 配置,避免再次 startInfra 幽灵恢复加权。
51
+ * @returns {void}
52
+ */
53
+ export function clearInfraPriorityFromRegistry() {
54
+ priorityConfig = { useLocalReputation: false }
55
+ getLinkRegistry().setPriorityWeightFunction(null)
56
+ }