@steve02081504/fount-p2p 0.0.0

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 (154) hide show
  1. package/README.md +62 -0
  2. package/core/bytes_codec.mjs +51 -0
  3. package/core/canonical_json.mjs +21 -0
  4. package/core/composite_key.mjs +91 -0
  5. package/core/constants.mjs +53 -0
  6. package/core/entity_id.mjs +53 -0
  7. package/core/entity_id_parse.mjs +40 -0
  8. package/core/evfs_logical_path.mjs +15 -0
  9. package/core/hexIds.mjs +73 -0
  10. package/core/hlc.mjs +114 -0
  11. package/core/random_id.mjs +9 -0
  12. package/crypto/channel.mjs +101 -0
  13. package/crypto/checkpoint_sign.mjs +67 -0
  14. package/crypto/crypto.mjs +158 -0
  15. package/crypto/key.mjs +431 -0
  16. package/dag/canonicalize_row.mjs +65 -0
  17. package/dag/event_query.mjs +15 -0
  18. package/dag/index.mjs +335 -0
  19. package/dag/storage.mjs +333 -0
  20. package/dag/strip_extensions.mjs +18 -0
  21. package/dag/tunables.json +6 -0
  22. package/discovery/bt.mjs +260 -0
  23. package/discovery/index.mjs +134 -0
  24. package/discovery/mdns.mjs +138 -0
  25. package/discovery/nostr.mjs +273 -0
  26. package/entity/files/acl.mjs +38 -0
  27. package/entity/files/evfs.mjs +189 -0
  28. package/entity/files/evfs_ref.mjs +37 -0
  29. package/entity/files/manifest_acl_registry.mjs +93 -0
  30. package/entity/files/replica_host_cache.mjs +46 -0
  31. package/entity/files/url.mjs +19 -0
  32. package/entity/hosting_registry.mjs +47 -0
  33. package/entity/localized_core.mjs +141 -0
  34. package/entity/logical_entity.mjs +25 -0
  35. package/entity/logical_entity_id_registry.mjs +40 -0
  36. package/entity/node_hash.mjs +15 -0
  37. package/entity/presentation_registry.mjs +44 -0
  38. package/entity/profile.mjs +256 -0
  39. package/entity/replica.mjs +20 -0
  40. package/entity/session_snapshot_registry.mjs +38 -0
  41. package/federation/chunk_fetch_pending.mjs +128 -0
  42. package/federation/chunk_fetch_scheduler.mjs +97 -0
  43. package/federation/dag_order_cache.mjs +124 -0
  44. package/federation/dedupe_slot.mjs +33 -0
  45. package/federation/message_rate_limit.mjs +23 -0
  46. package/federation/operator_key_chain.mjs +200 -0
  47. package/federation/topo_order_memo.mjs +44 -0
  48. package/federation/volatile_streams.mjs +52 -0
  49. package/federation/want_ids.mjs +158 -0
  50. package/files/assemble.mjs +220 -0
  51. package/files/assemble_stream.mjs +129 -0
  52. package/files/chunk_fetch.mjs +113 -0
  53. package/files/chunk_fetch_verify.mjs +23 -0
  54. package/files/chunk_provider_registry.mjs +70 -0
  55. package/files/chunk_responder.mjs +79 -0
  56. package/files/chunk_store.mjs +87 -0
  57. package/files/manifest.mjs +99 -0
  58. package/files/transfer_key.mjs +87 -0
  59. package/files/transfer_key_registry.mjs +108 -0
  60. package/governance/branch.mjs +230 -0
  61. package/governance/join_pow.mjs +165 -0
  62. package/governance/owner_succession_ballot.mjs +57 -0
  63. package/governance/tunables.json +7 -0
  64. package/index.mjs +39 -0
  65. package/link/channel_mux.mjs +222 -0
  66. package/link/frame.mjs +246 -0
  67. package/link/handshake.mjs +175 -0
  68. package/link/link.mjs +617 -0
  69. package/link/rtc.mjs +109 -0
  70. package/link/sdp_fingerprint.mjs +25 -0
  71. package/mailbox/consumer_registry.mjs +71 -0
  72. package/mailbox/deliver_or_store.mjs +190 -0
  73. package/mailbox/importance.mjs +72 -0
  74. package/mailbox/parse.mjs +73 -0
  75. package/mailbox/prune.mjs +106 -0
  76. package/mailbox/rate.mjs +86 -0
  77. package/mailbox/settings.mjs +53 -0
  78. package/mailbox/store.mjs +256 -0
  79. package/mailbox/tunables.json +12 -0
  80. package/mailbox/wire.mjs +40 -0
  81. package/node/denylist.mjs +309 -0
  82. package/node/entity_store.mjs +214 -0
  83. package/node/identity.mjs +99 -0
  84. package/node/instance.mjs +115 -0
  85. package/node/network.mjs +306 -0
  86. package/node/personal_block.mjs +286 -0
  87. package/node/reputation_store.mjs +326 -0
  88. package/node/retention_policy.mjs +82 -0
  89. package/node/signaling_config.mjs +32 -0
  90. package/node/storage.mjs +30 -0
  91. package/node/storage_plugins.mjs +71 -0
  92. package/node/user_paths.mjs +10 -0
  93. package/overlay/index.mjs +191 -0
  94. package/package.json +73 -0
  95. package/permissions/bitmask.mjs +35 -0
  96. package/permissions/evaluator.mjs +90 -0
  97. package/permissions/index.mjs +12 -0
  98. package/permissions/layered.mjs +39 -0
  99. package/registries/action.mjs +94 -0
  100. package/registries/event_type.mjs +75 -0
  101. package/registries/inbound.mjs +90 -0
  102. package/registries/p2p_viewer.mjs +35 -0
  103. package/registries/part_path.mjs +35 -0
  104. package/registries/room_provider.mjs +47 -0
  105. package/reputation/engine.mjs +458 -0
  106. package/reputation/math.mjs +73 -0
  107. package/reputation/pick_score.mjs +11 -0
  108. package/reputation/relay_dedupe.mjs +22 -0
  109. package/reputation/tunables.json +36 -0
  110. package/rooms/scoped_link.mjs +185 -0
  111. package/schemas/discovery.mjs +110 -0
  112. package/schemas/federation_pull.mjs +102 -0
  113. package/schemas/mailbox.mjs +37 -0
  114. package/schemas/remote_event.mjs +17 -0
  115. package/timeline/append_core.mjs +54 -0
  116. package/timeline/materialize_runner.mjs +26 -0
  117. package/timeline/prune.mjs +39 -0
  118. package/timeline/retention.mjs +54 -0
  119. package/timeline/retention_runner.mjs +35 -0
  120. package/timeline/verify_remote.mjs +25 -0
  121. package/transport/group_link_set.mjs +324 -0
  122. package/transport/ice_servers.mjs +72 -0
  123. package/transport/link_registry.mjs +749 -0
  124. package/transport/peer_identity_maps.mjs +94 -0
  125. package/transport/peer_pool.mjs +355 -0
  126. package/transport/remote_user_room.mjs +109 -0
  127. package/transport/room_scopes.mjs +2 -0
  128. package/transport/room_wire_action.mjs +19 -0
  129. package/transport/rtc_connection_budget.mjs +180 -0
  130. package/transport/rtc_mdns_filter.mjs +118 -0
  131. package/transport/stale_peer_log.mjs +50 -0
  132. package/transport/user_room.mjs +263 -0
  133. package/trust_graph/build.mjs +99 -0
  134. package/trust_graph/cache.mjs +44 -0
  135. package/trust_graph/engine.mjs +184 -0
  136. package/trust_graph/registry.mjs +38 -0
  137. package/trust_graph/resolve.mjs +105 -0
  138. package/trust_graph/send.mjs +88 -0
  139. package/trust_graph/tunables.json +13 -0
  140. package/utils/async_mutex.mjs +69 -0
  141. package/utils/debug_log.mjs +16 -0
  142. package/utils/duration.mjs +28 -0
  143. package/utils/json_io.mjs +57 -0
  144. package/utils/lru.mjs +22 -0
  145. package/utils/map_pool.mjs +27 -0
  146. package/wire/group_part.mjs +43 -0
  147. package/wire/ingress.mjs +58 -0
  148. package/wire/part_common.mjs +55 -0
  149. package/wire/part_fanout.mjs +71 -0
  150. package/wire/part_ingress.mjs +172 -0
  151. package/wire/part_invoke.mjs +81 -0
  152. package/wire/rate_bucket.mjs +49 -0
  153. package/wire/volatile_signature.mjs +76 -0
  154. package/wire/wait.mjs +127 -0
@@ -0,0 +1,326 @@
1
+ import { assertHex64, isHex64, normalizeHex64 } from '../core/hexIds.mjs'
2
+ import { recordWantIdsBackoff, wantIdsPeerKey } from '../federation/want_ids.mjs'
3
+ import {
4
+ applyDecayCollusionAfterSlashPure,
5
+ applyReputationResetToScoresPure,
6
+ applySubjectiveSlashPure,
7
+ bumpChunkStorageReputationPure,
8
+ bumpReputationOnRelayPure,
9
+ ensureReputationShape,
10
+ isQuarantinedPure,
11
+ observeBehaviorSamplePure,
12
+ penalizeArchiveServeMismatchPure,
13
+ penalizeChunkStorageFailurePure,
14
+ pruneReputationFile,
15
+ recordGossipAllUnknownWantPure,
16
+ recordMessageRateViolationPure,
17
+ resolveSlashAlertTtlMsPure,
18
+ seedMemberReputationFromIntroducerPure,
19
+ } from '../reputation/engine.mjs'
20
+ import { pickNodeScoreFromReputation } from '../reputation/pick_score.mjs'
21
+ import reputationTunables from '../reputation/tunables.json' with { type: 'json' }
22
+ import { invalidateTrustGraphCache } from '../trust_graph/cache.mjs'
23
+ import { withAsyncMutex } from '../utils/async_mutex.mjs'
24
+
25
+ import { getNodeDir, getNodeLogger, isNodeInitialized } from './instance.mjs'
26
+ import { readNodeJsonSync, writeNodeJsonSync } from './storage.mjs'
27
+
28
+ const DATA_NAME = 'reputation'
29
+
30
+ /** @type {((opts: object) => Promise<boolean>) | null} */
31
+ let blockReputationHandler = null
32
+
33
+ /**
34
+ * Shell Load 时注册:公开 block/unblock → 信誉传导。
35
+ * @param {(opts: object) => Promise<boolean>} handler block/unblock 信誉传导回调
36
+ * @returns {void}
37
+ */
38
+ export function registerBlockReputationHandler(handler) {
39
+ blockReputationHandler = handler
40
+ }
41
+
42
+ /** @returns {void} */
43
+ export function unregisterBlockReputationHandler() {
44
+ blockReputationHandler = null
45
+ }
46
+
47
+ /**
48
+ * @typedef {{
49
+ * byNodeHash: Record<string, { score: number, offenseStreak?: number, lastOffenseAt?: number, blockPenalties?: Record<string, { penalty: number, appliedAt: number, decayedRefund?: number }> }>
50
+ * wantUnknownHits: Array<{ peerNodeHash: string, t: number }>
51
+ * relayBumpSeen: Array<{ peerNodeHash: string, key: string, t: number }>
52
+ * }} ReputationFile
53
+ */
54
+
55
+ /** @type {ReputationFile | null} */
56
+ let reputationCache = null
57
+
58
+ /** @type {string | null} */
59
+ let reputationCacheNodeDir = null
60
+
61
+ /**
62
+ * @param {(data: ReputationFile) => void | Promise<void>} mutator 突变
63
+ * @returns {Promise<void>}
64
+ */
65
+ export function mutateReputation(mutator) {
66
+ return withAsyncMutex('reputation', async () => {
67
+ const data = loadReputation()
68
+ await mutator(data)
69
+ saveReputation(data)
70
+ })
71
+ }
72
+
73
+ /**
74
+ * @returns {ReputationFile} 节点级信誉表
75
+ */
76
+ export function loadReputation() {
77
+ const nodeDir = isNodeInitialized() ? getNodeDir() : ''
78
+ if (reputationCache && reputationCacheNodeDir === nodeDir) return reputationCache
79
+ reputationCacheNodeDir = nodeDir
80
+ reputationCache = ensureReputationShape(readNodeJsonSync(DATA_NAME) || {})
81
+ return reputationCache
82
+ }
83
+
84
+ /**
85
+ * @param {ReputationFile} data 信誉表
86
+ * @returns {void}
87
+ */
88
+ export function saveReputation(data) {
89
+ pruneReputationFile(data)
90
+ writeNodeJsonSync(DATA_NAME, data)
91
+ reputationCache = data
92
+ invalidateTrustGraphCache()
93
+ }
94
+
95
+ /**
96
+ * @param {string} peerNodeHash 对端节点
97
+ * @param {string} [dedupeKey] 去重键
98
+ * @returns {Promise<void>}
99
+ */
100
+ export function bumpReputationOnRelay(peerNodeHash, dedupeKey) {
101
+ return mutateReputation(data => {
102
+ bumpReputationOnRelayPure(data, peerNodeHash, dedupeKey)
103
+ })
104
+ }
105
+
106
+ /**
107
+ * @param {string} groupId 群 ID(仅用于 want 去重键)
108
+ * @param {string} peerNodeHash 请求方
109
+ * @returns {Promise<void>}
110
+ */
111
+ export function recordGossipAllUnknownWant(groupId, peerNodeHash) {
112
+ return mutateReputation(data => {
113
+ if (recordGossipAllUnknownWantPure(data, peerNodeHash))
114
+ recordWantIdsBackoff(wantIdsPeerKey(groupId, peerNodeHash))
115
+ })
116
+ }
117
+
118
+ /**
119
+ * @param {string} peerNodeHash 对端
120
+ * @param {number} [excessRatio=1] 超速超额比例 0..1
121
+ * @returns {Promise<void>}
122
+ */
123
+ export function recordMessageRateViolation(peerNodeHash, excessRatio = 1) {
124
+ const id = String(peerNodeHash || '').trim()
125
+ if (!id) return Promise.resolve()
126
+ return mutateReputation(data => {
127
+ recordMessageRateViolationPure(data, id, undefined, excessRatio)
128
+ observeBehaviorSamplePure(data, id, 1)
129
+ })
130
+ }
131
+
132
+ /**
133
+ * @param {string} peerNodeHash 对端
134
+ * @param {number} sample 行为样本强度
135
+ * @returns {Promise<boolean>} 是否触发异常隔离
136
+ */
137
+ export async function observePeerBehavior(peerNodeHash, sample) {
138
+ const id = String(peerNodeHash || '').trim()
139
+ if (!id) return false
140
+ let anomaly = false
141
+ await mutateReputation(data => {
142
+ const result = observeBehaviorSamplePure(data, id, sample)
143
+ anomaly = result.anomaly
144
+ })
145
+ return anomaly
146
+ }
147
+
148
+ /**
149
+ * @param {string} peerNodeHash 对端
150
+ * @returns {boolean} 是否处于本地隔离
151
+ */
152
+ export function isPeerQuarantined(peerNodeHash) {
153
+ return isQuarantinedPure(loadReputation(), peerNodeHash)
154
+ }
155
+
156
+ /**
157
+ * @param {string} storagePeerKey 责任方
158
+ * @returns {void}
159
+ */
160
+ export function bumpChunkStorageReputation(storagePeerKey) {
161
+ const id = String(storagePeerKey || '').trim()
162
+ if (!id) return
163
+ void mutateReputation(data => {
164
+ bumpChunkStorageReputationPure(data, id)
165
+ })
166
+ }
167
+
168
+ /**
169
+ * @param {string} blamePeerKey 责任方
170
+ * @returns {void}
171
+ */
172
+ export function penalizeChunkStorageFailure(blamePeerKey) {
173
+ const id = String(blamePeerKey || '').trim()
174
+ if (!id) return
175
+ void mutateReputation(data => {
176
+ penalizeChunkStorageFailurePure(data, id)
177
+ })
178
+ }
179
+
180
+ /**
181
+ * @param {string} peerNodeHash 对端 nodeHash
182
+ * @returns {void}
183
+ */
184
+ export function penalizeArchiveServeMismatch(peerNodeHash) {
185
+ const id = String(peerNodeHash || '').trim()
186
+ if (!id) return
187
+ void mutateReputation(data => {
188
+ penalizeArchiveServeMismatchPure(data, id)
189
+ })
190
+ }
191
+
192
+ /**
193
+ * @param {object | null | undefined} groupSettings 群设置
194
+ * @returns {number} 毫秒
195
+ */
196
+ export function resolveSlashAlertTtlMs(groupSettings) {
197
+ return resolveSlashAlertTtlMsPure(groupSettings)
198
+ }
199
+
200
+ /**
201
+ * @param {object} alert VOLATILE slash 载荷
202
+ * @returns {boolean} 是否已应用
203
+ */
204
+ export async function applyVolatileSlashAlert(alert) {
205
+ const expiresAt = Number(alert?.expiresAt)
206
+ if (Number.isFinite(expiresAt) && Date.now() > expiresAt) return false
207
+ const target = normalizeHex64(alert?.targetPubKeyHash)
208
+ const sender = normalizeHex64(alert?.sender)
209
+ if (!isHex64(target) || !isHex64(sender)) return false
210
+ const claim = Number.isFinite(Number(alert?.claim)) ? Number(alert.claim) : reputationTunables.slashDefaultClaim
211
+ await mutateReputation(data => {
212
+ applySubjectiveSlashPure(data, target, sender, claim, false)
213
+ })
214
+ return true
215
+ }
216
+
217
+ /**
218
+ * @param {string} senderPubKeyHash 签发者
219
+ * @param {{ targetPubKeyHash: string, claim?: number }} content Slash 内容
220
+ * @param {object} [groupSettings] 群设置
221
+ * @returns {object} VOLATILE 载荷
222
+ */
223
+ export function buildAndApplyUnverifiedSlashAlert(senderPubKeyHash, content, groupSettings = {}) {
224
+ const targetPubKeyHash = assertHex64(content.targetPubKeyHash, 'slash target')
225
+ const claim = Number.isFinite(Number(content.claim)) ? Number(content.claim) : reputationTunables.slashDefaultClaim
226
+ const sender = assertHex64(senderPubKeyHash, 'slash sender')
227
+ const ttl = resolveSlashAlertTtlMs(groupSettings)
228
+ return {
229
+ type: 'reputation_slash_alert',
230
+ targetPubKeyHash,
231
+ sender,
232
+ claim,
233
+ expiresAt: Date.now() + ttl,
234
+ }
235
+ }
236
+
237
+ /**
238
+ * @param {string} username 用户(readEvents 回调用)
239
+ * @param {string} groupId 群
240
+ * @param {object} event reputation_slash 事件
241
+ * @param {(username: string, groupId: string) => Promise<object[]>} readEvents 读 DAG 事件
242
+ * @returns {Promise<void>}
243
+ */
244
+ export async function applySubjectiveSlashFromEvent(username, groupId, event, readEvents) {
245
+ if (event.type !== 'reputation_slash') return
246
+ const { content } = event
247
+ const target = content.targetPubKeyHash.trim().toLowerCase()
248
+ const sender = event.sender.trim().toLowerCase()
249
+
250
+ await mutateReputation(async data => {
251
+ const verified = content.verified && await verifySlashProof(username, groupId, content, readEvents)
252
+ const claim = Number(content.claim ?? (verified ? reputationTunables.slashVerifiedDefaultClaim : reputationTunables.slashUnverifiedDefaultClaim))
253
+ applySubjectiveSlashPure(data, target, sender, claim, verified)
254
+ })
255
+ }
256
+
257
+ /**
258
+ * @param {string} username 用户
259
+ * @param {string} groupId 群
260
+ * @param {object} content slash 内容
261
+ * @param {(username: string, groupId: string) => Promise<object[]>} readEvents 读 DAG
262
+ * @returns {Promise<boolean>} 是否找到 proof 对应事件
263
+ */
264
+ async function verifySlashProof(username, groupId, content, readEvents) {
265
+ const eventId = content?.proof?.eventId?.trim().toLowerCase()
266
+ if (!eventId) return false
267
+ const events = await readEvents(username, groupId)
268
+ return events.some(event => event.id === eventId)
269
+ }
270
+
271
+ /**
272
+ * @param {string} targetPubKeyHash 目标
273
+ * @param {Array<{ from?: string, to?: string }>} inviteEdges 邀请边
274
+ * @returns {void}
275
+ */
276
+ export function applyDecayCollusionAfterSlash(targetPubKeyHash, inviteEdges) {
277
+ void mutateReputation(data => {
278
+ const applied = applyDecayCollusionAfterSlashPure(data, targetPubKeyHash, inviteEdges)
279
+ if (applied.length)
280
+ getNodeLogger().warn?.('reputation: collusion decay after slash', {
281
+ target: targetPubKeyHash.trim().toLowerCase(),
282
+ upstreamCount: applied.length,
283
+ hops: applied.map(row => row.hop),
284
+ })
285
+ })
286
+ }
287
+
288
+ /**
289
+ * @param {string} targetPubKeyHash 目标
290
+ * @returns {void}
291
+ */
292
+ export function applyReputationResetToScores(targetPubKeyHash) {
293
+ void mutateReputation(data => {
294
+ applyReputationResetToScoresPure(data, targetPubKeyHash)
295
+ })
296
+ }
297
+
298
+ /**
299
+ * @param {string} memberPubKeyHash 新成员
300
+ * @param {string} [introducerPubKeyHash] 介绍者
301
+ * @param {number} [repEdge] 边信任
302
+ * @param {number} [powBonus=0] 入群 PoW 自愿封顶加成
303
+ * @returns {void}
304
+ */
305
+ export function seedMemberReputationFromIntroducer(memberPubKeyHash, introducerPubKeyHash, repEdge, powBonus = 0) {
306
+ void mutateReputation(data => {
307
+ seedMemberReputationFromIntroducerPure(data, memberPubKeyHash, introducerPubKeyHash, repEdge, undefined, powBonus)
308
+ })
309
+ }
310
+
311
+ /**
312
+ * @param {string} nodeId 64 位十六进制
313
+ * @returns {number} 信誉分
314
+ */
315
+ export function pickNodeScore(nodeId) {
316
+ return pickNodeScoreFromReputation(loadReputation(), nodeId)
317
+ }
318
+
319
+ /**
320
+ * @param {object} opts applyFollowedBlockSignal 参数
321
+ * @returns {Promise<boolean>} 是否已应用
322
+ */
323
+ export async function applyBlockReputationSignal(opts) {
324
+ if (!blockReputationHandler) return false
325
+ return blockReputationHandler(opts)
326
+ }
@@ -0,0 +1,82 @@
1
+ import { sortedPrevEventIds } from '../dag/index.mjs'
2
+ import {
3
+ authzFoldOrderIds,
4
+ descendantClosureFromTip,
5
+ } from '../governance/branch.mjs'
6
+ import { getPermissionAnchorTypes } from '../registries/event_type.mjs'
7
+
8
+ /**
9
+ * @param {string[]} order 拓扑序 id 列表
10
+ * @param {Map<string, object>} byId id → 事件
11
+ * @returns {number} 最早须保留的事件下标
12
+ */
13
+ export function permissionAnchorStartIndex(order, byId) {
14
+ let anchor = order.length
15
+ for (let index = order.length - 1; index >= 0; index--) {
16
+ const ev = byId.get(order[index])
17
+ if (ev && getPermissionAnchorTypes().has(ev.type)) anchor = index
18
+ }
19
+ return anchor
20
+ }
21
+
22
+ /**
23
+ * 在共识分支上计算须保留的事件 id(连通子图,不用拓扑下标切片)。
24
+ * @param {string[]} order 规范拓扑序
25
+ * @param {Map<string, object>} byId id → 事件
26
+ * @param {object} opts 保留策略
27
+ * @param {number} opts.maxDepth 分支上最大事件深度
28
+ * @param {number} opts.cutoffWall 最早保留的 HLC wall
29
+ * @param {Set<string>} opts.anchorTypes 权限锚点事件类型
30
+ * @param {string | null} [opts.checkpointTipId] checkpoint 尖
31
+ * @param {string | null} [opts.branchTipId] 共识分支尖
32
+ * @returns {Set<string>} 保留 id
33
+ */
34
+ export function computeRetentionKeepIds(order, byId, opts) {
35
+ const { maxDepth, cutoffWall, anchorTypes, checkpointTipId, branchTipId } = opts
36
+ const branchOrder = authzFoldOrderIds(order, byId, branchTipId)
37
+ const branchSet = new Set(branchOrder)
38
+ if (!branchSet.size) return new Set()
39
+
40
+ /** @type {Set<string>} */
41
+ const keep = new Set()
42
+ /** @type {Set<string>} */
43
+ const ancestorSeeds = new Set()
44
+
45
+ if (checkpointTipId && branchSet.has(checkpointTipId))
46
+ for (const id of descendantClosureFromTip(checkpointTipId, byId))
47
+ if (branchSet.has(id)) keep.add(id)
48
+
49
+ for (let index = branchOrder.length - 1; index >= 0; index--) {
50
+ const ev = byId.get(branchOrder[index])
51
+ if (ev && anchorTypes.has(ev.type)) {
52
+ ancestorSeeds.add(branchOrder[index])
53
+ break
54
+ }
55
+ }
56
+
57
+ for (const id of branchOrder) {
58
+ const ev = byId.get(id)
59
+ const wall = Number(ev?.hlc?.wall ?? 0)
60
+ if (wall >= cutoffWall) ancestorSeeds.add(id)
61
+ }
62
+
63
+ if (branchOrder.length > maxDepth)
64
+ for (const id of branchOrder.slice(-maxDepth))
65
+ ancestorSeeds.add(id)
66
+
67
+ const stack = [...ancestorSeeds]
68
+ while (stack.length) {
69
+ const id = stack.pop()
70
+ if (!id || !branchSet.has(id) || keep.has(id)) continue
71
+ keep.add(id)
72
+ const event = byId.get(id)
73
+ if (!event) continue
74
+ for (const parentId of sortedPrevEventIds(event.prev_event_ids))
75
+ if (branchSet.has(parentId)) stack.push(parentId)
76
+ }
77
+
78
+ if (!keep.size)
79
+ for (const id of branchOrder) keep.add(id)
80
+
81
+ return keep
82
+ }
@@ -0,0 +1,32 @@
1
+ import process from 'node:process'
2
+
3
+ /** @typedef {'none' | 'rewrite-loopback' | 'drop'} MdnsCandidatePolicy */
4
+
5
+ /**
6
+ * @typedef {{
7
+ * relayOverride: string[] | null
8
+ * mdnsPolicy: MdnsCandidatePolicy
9
+ * trickleIceOff: boolean
10
+ * }} SignalingRuntimeConfig
11
+ */
12
+
13
+ /**
14
+ * 生产默认:win32 丢弃 .local host candidate;其它平台不过滤。
15
+ * @returns {SignalingRuntimeConfig} 生产默认信令配置
16
+ */
17
+ export function defaultSignalingRuntimeConfig() {
18
+ const mdnsPolicy = process.platform === 'win32' ? 'drop' : 'none'
19
+ return {
20
+ relayOverride: null,
21
+ mdnsPolicy,
22
+ trickleIceOff: mdnsPolicy !== 'none',
23
+ }
24
+ }
25
+
26
+ /**
27
+ * 生产默认信令配置;测试通过 initNode({ signaling }) 注入。
28
+ * @returns {SignalingRuntimeConfig} 生产默认信令配置
29
+ */
30
+ export function resolveSignalingRuntimeConfig() {
31
+ return defaultSignalingRuntimeConfig()
32
+ }
@@ -0,0 +1,30 @@
1
+ import path from 'node:path'
2
+
3
+ import { readJsonFileSync, writeJsonFileSync } from '../utils/json_io.mjs'
4
+
5
+ import { getNodeDir } from './instance.mjs'
6
+
7
+ /**
8
+ * @param {string} name 不含扩展名的配置文件名
9
+ * @returns {string} 绝对路径
10
+ */
11
+ export function nodeJsonPath(name) {
12
+ return path.join(getNodeDir(), `${name}.json`)
13
+ }
14
+
15
+ /**
16
+ * @param {string} name 配置文件名
17
+ * @returns {object | null} 解析后的 JSON 或 null
18
+ */
19
+ export function readNodeJsonSync(name) {
20
+ return readJsonFileSync(nodeJsonPath(name))
21
+ }
22
+
23
+ /**
24
+ * @param {string} name 配置文件名
25
+ * @param {unknown} data 数据
26
+ * @returns {void}
27
+ */
28
+ export function writeNodeJsonSync(name, data) {
29
+ writeJsonFileSync(nodeJsonPath(name), data)
30
+ }
@@ -0,0 +1,71 @@
1
+ import { mkdir, writeFile, readFile, unlink } from 'node:fs/promises'
2
+ import { join } from 'node:path'
3
+
4
+ /**
5
+ * 群组分块存储插件:put/get/delete + 不透明 storageLocator。
6
+ * S3、多副本等后端由 shell 注入实现,本包只提供本地参考实现。
7
+ *
8
+ * @typedef {{
9
+ * putChunk: (groupId: string, chunkHash: string, data: Uint8Array) => Promise<{ storageLocator: string }>,
10
+ * getChunk: (locator: string) => Promise<Uint8Array>,
11
+ * deleteChunk: (locator: string) => Promise<void>,
12
+ * }} GroupStoragePlugin
13
+ */
14
+
15
+ /**
16
+ * @param {unknown} error 存储删除错误
17
+ * @returns {boolean} 是否为「文件不存在」
18
+ */
19
+ function isEnoent(error) {
20
+ return /** @type {{ code?: string }} */ error?.code === 'ENOENT'
21
+ }
22
+
23
+ /**
24
+ * 默认:本地目录 {baseDir}/groups/{groupId}/chunks/(baseDir 一般为用户 shells/chat)
25
+ *
26
+ * @param {string} baseDir 绝对路径
27
+ * @returns {GroupStoragePlugin} 本地文件系统实现的 put/get/delete
28
+ */
29
+ export function createLocalStoragePlugin(baseDir) {
30
+ return {
31
+ storagePeerId: 'local',
32
+ /**
33
+ * @param {string} groupId 群组 id
34
+ * @param {string} chunkHash 分块内容哈希(文件名)
35
+ * @param {Uint8Array} data 原始字节
36
+ * @returns {Promise<{ storageLocator: string }>} `local:` 前缀的定位符
37
+ */
38
+ async putChunk(groupId, chunkHash, data) {
39
+ const dir = join(baseDir, 'groups', groupId, 'chunks')
40
+ await mkdir(dir, { recursive: true })
41
+ const name = `${chunkHash}.bin`
42
+ await writeFile(join(dir, name), data)
43
+ return { storageLocator: `local:${groupId}/chunks/${name}` }
44
+ },
45
+ /**
46
+ * @param {string} locator `local:...` 格式
47
+ * @returns {Promise<Uint8Array>} 文件内容
48
+ */
49
+ async getChunk(locator) {
50
+ const localLocatorMatch = String(locator).match(/^local:([^/]+)\/chunks\/(.+)$/)
51
+ if (!localLocatorMatch) throw new Error('Invalid local locator')
52
+ const chunkPath = join(baseDir, 'groups', localLocatorMatch[1], 'chunks', localLocatorMatch[2])
53
+ return new Uint8Array(await readFile(chunkPath))
54
+ },
55
+ /**
56
+ * @param {string} locator `local:...` 格式
57
+ * @returns {Promise<void>}
58
+ */
59
+ async deleteChunk(locator) {
60
+ const localLocatorMatch = String(locator).match(/^local:([^/]+)\/chunks\/(.+)$/)
61
+ if (!localLocatorMatch) return
62
+ const chunkPath = join(baseDir, 'groups', localLocatorMatch[1], 'chunks', localLocatorMatch[2])
63
+ try {
64
+ await unlink(chunkPath)
65
+ }
66
+ catch (error) {
67
+ if (!isEnoent(error)) throw error
68
+ }
69
+ },
70
+ }
71
+ }
@@ -0,0 +1,10 @@
1
+ import path from 'node:path'
2
+
3
+ import { getNodeDir } from './instance.mjs'
4
+
5
+ /**
6
+ * @returns {string} P2P 邮箱存储转发 JSONL 路径
7
+ */
8
+ export function mailboxStorePath() {
9
+ return path.join(getNodeDir(), 'mailbox', 'store.jsonl')
10
+ }