@steve02081504/fount-p2p 0.0.23 → 0.0.25

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 (67) hide show
  1. package/README.md +8 -6
  2. package/core/object.mjs +9 -0
  3. package/core/partpath.mjs +10 -0
  4. package/dag/storage.mjs +0 -5
  5. package/dag/strip_extensions.mjs +1 -3
  6. package/{wire/part_query_cache.mjs → federation/part_query/cache.mjs} +7 -7
  7. package/{wire/part_query.mjs → federation/part_query/runtime.mjs} +36 -77
  8. package/files/assemble.mjs +5 -5
  9. package/files/assemble_stream.mjs +2 -2
  10. package/files/chunk/fetch.mjs +101 -0
  11. package/files/chunk/pending.mjs +59 -0
  12. package/files/{chunk_provider_registry.mjs → chunk/provider_registry.mjs} +4 -5
  13. package/files/chunk/responder.mjs +104 -0
  14. package/files/{chunk_store.mjs → chunk/store.mjs} +2 -2
  15. package/files/{chunk_fetch_verify.mjs → chunk/verify.mjs} +2 -2
  16. package/files/evfs.mjs +18 -19
  17. package/files/fed/fetch_shared.mjs +39 -0
  18. package/files/fed/responder.mjs +47 -0
  19. package/files/{acl.mjs → manifest/acl.mjs} +4 -4
  20. package/files/{manifest_acl_registry.mjs → manifest/acl_registry.mjs} +14 -17
  21. package/files/{manifest_fetch.mjs → manifest/fetch.mjs} +47 -52
  22. package/files/{manifest.mjs → manifest/normalize.mjs} +2 -2
  23. package/files/manifest/pending.mjs +53 -0
  24. package/files/{public_manifest.mjs → manifest/public.mjs} +12 -12
  25. package/files/transfer_key.mjs +2 -2
  26. package/files/transfer_key_registry.mjs +7 -7
  27. package/index.mjs +4 -4
  28. package/infra/service.mjs +1 -1
  29. package/link/handshake.mjs +0 -3
  30. package/mailbox/deliver_or_store.mjs +0 -2
  31. package/mailbox/parse.mjs +1 -1
  32. package/mailbox/store.mjs +0 -9
  33. package/mailbox/wire.mjs +24 -16
  34. package/node/reputation_sync.mjs +33 -29
  35. package/package.json +10 -10
  36. package/registries/inbound.mjs +19 -9
  37. package/registries/part_path.mjs +3 -8
  38. package/schemas/discovery.mjs +1 -1
  39. package/schemas/federation_pull.mjs +1 -1
  40. package/schemas/part_query.mjs +6 -5
  41. package/transport/group_link_set.mjs +2 -2
  42. package/transport/link_registry.mjs +2 -2
  43. package/transport/node_scope/features.mjs +166 -0
  44. package/transport/node_scope/wire.mjs +143 -0
  45. package/transport/remote_user_room.mjs +0 -1
  46. package/{rooms → transport}/scoped_link.mjs +1 -1
  47. package/transport/user_room.mjs +15 -28
  48. package/utils/fetch_wait.mjs +98 -0
  49. package/wire/adapter.mjs +13 -0
  50. package/wire/ingress.mjs +1 -8
  51. package/wire/part/fanout.mjs +99 -0
  52. package/wire/part/group.mjs +45 -0
  53. package/wire/{part_ingress.mjs → part/ingress.mjs} +58 -67
  54. package/wire/{part_invoke.mjs → part/invoke.mjs} +1 -22
  55. package/wire/part/pending.mjs +32 -0
  56. package/wire/part/query.mjs +91 -0
  57. package/wire/subscribe.mjs +13 -0
  58. package/federation/chunk_fetch_pending.mjs +0 -124
  59. package/federation/manifest_fetch_pending.mjs +0 -88
  60. package/files/chunk_fetch.mjs +0 -99
  61. package/files/chunk_responder.mjs +0 -142
  62. package/transport/advert_ingest.mjs +0 -17
  63. package/transport/node_scope.mjs +0 -289
  64. package/wire/group_part.mjs +0 -43
  65. package/wire/part_common.mjs +0 -55
  66. package/wire/part_fanout.mjs +0 -52
  67. /package/{wire → schemas}/part_query.tunables.json +0 -0
@@ -0,0 +1,45 @@
1
+ import { isPlainObject } from '../../core/object.mjs'
2
+
3
+ import { attachPartWire } from './ingress.mjs'
4
+
5
+ /** @typedef {import('../../wire/adapter.mjs').WireAdapter} WireAdapter */
6
+ /** @typedef {import('../../wire/adapter.mjs').WireContext} WireContext */
7
+
8
+ /**
9
+ * @param {unknown} data part 载荷
10
+ * @param {string} groupId 群 ID
11
+ * @returns {object | null} 校验通过后的载荷
12
+ */
13
+ function parseGroupContext(data, groupId) {
14
+ if (!isPlainObject(data)) return null
15
+ if (data.groupId !== groupId) return null
16
+ return data
17
+ }
18
+
19
+ /**
20
+ * @param {WireAdapter} wire 底层适配器
21
+ * @param {string} groupId 群 ID
22
+ * @returns {WireAdapter['on']} 注入 groupId 的 on 包装
23
+ */
24
+ function wrapWireOn(wire, groupId) {
25
+ return (name, handler) => wire.on(name, (data, peerId) => {
26
+ const payload = parseGroupContext(data, groupId)
27
+ if (!payload) return
28
+ handler(payload, peerId)
29
+ })
30
+ }
31
+
32
+ /**
33
+ * 群联邦房间挂载 part_wire(要求线载荷带 `groupId`)。
34
+ * @param {WireContext} wireContext 入站上下文
35
+ * @param {string} groupId 群 ID
36
+ * @param {WireAdapter} wire action 表
37
+ * @param {{ allowPartInvoke?: (payload: object) => boolean }} [options] 入站过滤
38
+ * @returns {() => void} 取消挂载的 dispose
39
+ */
40
+ export function attachGroupPartWire(wireContext, groupId, wire, options = {}) {
41
+ return attachPartWire(wireContext, {
42
+ send: wire.send.bind(wire),
43
+ on: wrapWireOn(wire, groupId),
44
+ }, options)
45
+ }
@@ -1,34 +1,23 @@
1
+ import { isPlainObject } from '../../core/object.mjs'
2
+ import { parsePartpath } from '../../core/partpath.mjs'
3
+ import { dispatchDeliveryInbound, dispatchRpcInbound } from '../../registries/inbound.mjs'
4
+ import { subscribeWire } from '../subscribe.mjs'
1
5
 
2
- import { dispatchDeliveryInbound, dispatchRpcInbound } from '../registries/inbound.mjs'
3
-
4
- import { isPlainObject } from './ingress.mjs'
5
- import { buildPartInvokePayload } from './part_common.mjs'
6
6
  import {
7
7
  isPartInvoke,
8
8
  isPartInvokeResponse,
9
- normalizePartpath,
10
9
  unwrapPartInvokeResult,
11
- } from './part_invoke.mjs'
12
-
13
- /** @typedef {import('./part_invoke.mjs').PartInvokeResponse} PartInvokeResponse */
14
-
15
- /** @type {Map<string, { responses: PartInvokeResponse[], finish: () => void, maxResponses: number, respondedPeers: Set<string> }>} */
16
- export const pendingPartInvoke = new Map()
17
-
18
- /**
19
- * @typedef {{
20
- * send: (name: string, payload: unknown, peerId: string | null) => void
21
- * on: (name: string, handler: (payload: unknown, peerId: string) => void) => (() => void) | void
22
- * }} PartWireAdapter
23
- */
10
+ } from './invoke.mjs'
11
+ import { handleIncomingPartInvokeResponse } from './pending.mjs'
24
12
 
25
- /**
26
- * @typedef {{ replicaUsername?: string }} PartWireContext
27
- */
13
+ /** @typedef {import('../../wire/adapter.mjs').WireAdapter} WireAdapter */
14
+ /** @typedef {import('../../wire/adapter.mjs').WireContext} PartWireContext */
15
+ /** @typedef {import('./invoke.mjs').PartInvokeResponse} PartInvokeResponse */
16
+ /** @typedef {import('./invoke.mjs').PartInvoke} PartInvoke */
28
17
 
29
18
  /**
30
19
  * @param {object} data 入站 part_timeline_put 载荷
31
- * @param {string} partpath 已规范化 part 路径
20
+ * @param {string} partpath 已校验 part 路径
32
21
  * @returns {object | null} 白名单字段
33
22
  */
34
23
  function parsePartTimelinePut(data, partpath) {
@@ -44,25 +33,40 @@ function parsePartTimelinePut(data, partpath) {
44
33
  }
45
34
  }
46
35
 
36
+ /**
37
+ * @param {string} partpath part 路径
38
+ * @param {PartInvoke} invoke 调用体
39
+ * @param {string} [nodeHash] 来源节点
40
+ * @param {string} [groupId] 群上下文
41
+ * @returns {object} part_invoke 线载荷
42
+ */
43
+ function buildPartInvokePayload(partpath, invoke, nodeHash, groupId) {
44
+ return {
45
+ partpath,
46
+ invoke,
47
+ ...nodeHash ? { nodeHash } : {},
48
+ ...groupId ? { groupId } : {},
49
+ }
50
+ }
51
+
47
52
  /**
48
53
  * @param {PartWireContext} wireContext 入站上下文
49
- * @param {object} payload part_invoke 请求
54
+ * @param {object} payload part_invoke 请求(含已验证 peerId)
50
55
  * @returns {Promise<PartInvokeResponse | null>} RPC 处理器返回值
51
56
  */
52
57
  async function dispatchPartInvoke(wireContext, payload) {
53
- const partpath = normalizePartpath(payload?.partpath)
58
+ const partpath = parsePartpath(payload?.partpath)
54
59
  const invoke = payload?.invoke
55
60
  if (!partpath || !isPlainObject(invoke)) return null
56
61
  return dispatchRpcInbound({
57
62
  replicaUsername: wireContext.replicaUsername,
58
- requesterNodeHash: payload.nodeHash ? String(payload.nodeHash).trim() : null,
63
+ requesterNodeHash: payload.peerId ? String(payload.peerId).trim() : null,
59
64
  groupId: payload.groupId ? String(payload.groupId).trim() : undefined,
60
65
  peerId: payload.peerId,
61
66
  }, {
62
67
  type: 'part_invoke',
63
68
  partpath,
64
69
  invoke,
65
- nodeHash: payload.nodeHash,
66
70
  groupId: payload.groupId,
67
71
  requestId: payload.requestId,
68
72
  })
@@ -71,24 +75,32 @@ async function dispatchPartInvoke(wireContext, payload) {
71
75
  /**
72
76
  * 挂载 part_timeline_put / part_invoke / part_invoke_response。
73
77
  * @param {PartWireContext} wireContext 入站上下文
74
- * @param {PartWireAdapter} wire Trystero 适配器
78
+ * @param {WireAdapter} wire action 表
75
79
  * @param {{ allowPartInvoke?: (payload: object) => boolean }} [options] 入站过滤
76
80
  * @returns {() => void} 取消挂载的 dispose
77
81
  */
78
82
  export function attachPartWire(wireContext, wire, options = {}) {
79
- const offs = [
80
- wire.on('part_timeline_put', data => {
83
+ return subscribeWire(wire, {
84
+ /**
85
+ * @param {unknown} data part_timeline_put 载荷
86
+ * @param {string} peerId 对端 nodeHash
87
+ */
88
+ part_timeline_put(data, peerId) {
81
89
  if (!isPlainObject(data)) return
82
- const partpath = normalizePartpath(data.partpath)
90
+ const partpath = parsePartpath(data.partpath)
83
91
  if (!partpath) return
84
92
  const message = parsePartTimelinePut(data, partpath)
85
93
  if (!message) return
86
94
  void dispatchDeliveryInbound({
87
95
  replicaUsername: wireContext.replicaUsername,
88
- requesterNodeHash: data.nodeHash ? String(data.nodeHash).trim() : null,
96
+ requesterNodeHash: peerId ? String(peerId).trim() : null,
89
97
  }, message)
90
- }),
91
- wire.on('part_invoke', (data, peerId) => {
98
+ },
99
+ /**
100
+ * @param {unknown} data part_invoke 载荷
101
+ * @param {string} peerId 对端 nodeHash
102
+ */
103
+ part_invoke(data, peerId) {
92
104
  if (!isPlainObject(data)) return
93
105
  if (options.allowPartInvoke?.(data) === false) return
94
106
  const payload = { ...data, peerId }
@@ -96,27 +108,27 @@ export function attachPartWire(wireContext, wire, options = {}) {
96
108
  void handleIncomingPartInvokeRequest(wireContext, payload, wire, peerId)
97
109
  else
98
110
  void handleIncomingPartInvokeFireAndForget(wireContext, payload, wire, peerId)
99
- }),
100
- wire.on('part_invoke_response', (data, peerId) => {
111
+ },
112
+ /**
113
+ * @param {unknown} data part_invoke_response 载荷
114
+ * @param {string} peerId 对端 nodeHash
115
+ */
116
+ part_invoke_response(data, peerId) {
101
117
  if (!isPlainObject(data)) return
102
118
  handleIncomingPartInvokeResponse(data, peerId)
103
- }),
104
- ]
105
- return () => {
106
- for (const off of offs)
107
- try { off?.() } catch { /* ignore */ }
108
- }
119
+ },
120
+ })
109
121
  }
110
122
 
111
123
  /**
112
124
  * @param {PartWireContext} wireContext 入站上下文
113
125
  * @param {object} payload part_invoke 请求(含 requestId)
114
- * @param {PartWireAdapter} wire 发送适配器
126
+ * @param {WireAdapter} wire 发送适配器
115
127
  * @param {string} peerId 对端
116
128
  * @returns {Promise<void>}
117
129
  */
118
130
  export async function handleIncomingPartInvokeRequest(wireContext, payload, wire, peerId) {
119
- const partpath = normalizePartpath(payload?.partpath)
131
+ const partpath = parsePartpath(payload?.partpath)
120
132
  if (!partpath || !payload.requestId) return
121
133
 
122
134
  const response = await dispatchPartInvoke(wireContext, { ...payload, peerId })
@@ -135,40 +147,19 @@ export async function handleIncomingPartInvokeRequest(wireContext, payload, wire
135
147
  /**
136
148
  * @param {PartWireContext} wireContext 入站上下文
137
149
  * @param {object} payload part_invoke 请求(无 requestId)
138
- * @param {PartWireAdapter} wire 发送适配器
150
+ * @param {WireAdapter} wire 发送适配器
139
151
  * @param {string} peerId 对端
140
152
  * @returns {Promise<void>}
141
153
  */
142
154
  export async function handleIncomingPartInvokeFireAndForget(wireContext, payload, wire, peerId) {
143
- const partpath = normalizePartpath(payload?.partpath)
155
+ const partpath = parsePartpath(payload?.partpath)
144
156
  if (!partpath) return
145
157
 
146
158
  const response = await dispatchPartInvoke(wireContext, { ...payload, peerId })
147
159
  const followUp = unwrapPartInvokeResult(response)
148
160
  if (!isPartInvoke(followUp)) return
149
161
  try {
150
- wire.send('part_invoke', buildPartInvokePayload({
151
- partpath,
152
- invoke: followUp,
153
- nodeHash: payload.nodeHash,
154
- groupId: payload.groupId,
155
- }), peerId)
162
+ wire.send('part_invoke', buildPartInvokePayload(partpath, followUp, peerId, payload.groupId), peerId)
156
163
  }
157
164
  catch { /* disconnected */ }
158
165
  }
159
-
160
- /**
161
- * @param {object} payload 响应
162
- * @param {string} [peerId] Trystero 对端 id,用于同 peer 去重
163
- * @returns {void}
164
- */
165
- export function handleIncomingPartInvokeResponse(payload, peerId = '') {
166
- const pending = pendingPartInvoke.get(payload.requestId)
167
- if (!pending || !isPartInvokeResponse(payload.response)) return
168
- if (peerId) {
169
- if (pending.respondedPeers.has(peerId)) return
170
- pending.respondedPeers.add(peerId)
171
- }
172
- pending.responses.push(payload.response)
173
- if (pending.responses.length >= pending.maxResponses) pending.finish()
174
- }
@@ -1,6 +1,4 @@
1
- import { isPlainObject } from './ingress.mjs'
2
-
3
- const PARTPATH_RE = /^[\w-]+(?:\/[\w-]+)*$/
1
+ import { isPlainObject } from '../../core/object.mjs'
4
2
 
5
3
  /** @typedef {string} PartInvokeKind */
6
4
 
@@ -22,16 +20,6 @@ const PARTPATH_RE = /^[\w-]+(?:\/[\w-]+)*$/
22
20
 
23
21
  /** @typedef {PartInvokeResultResponse | PartInvokeErrorResponse} PartInvokeResponse */
24
22
 
25
- /**
26
- * @typedef {object} SocialRpcPartInvokeResponse
27
- * @property {object} result RPC 响应体
28
- */
29
-
30
- /**
31
- * @typedef {object} TimelinePutPartInvokeResponse
32
- * @property {{ ok: boolean }} result timeline ingest 结果
33
- */
34
-
35
23
  /**
36
24
  * @param {unknown} value 候选响应
37
25
  * @returns {value is PartInvokeResponse} 是否为 part_invoke 响应体
@@ -61,15 +49,6 @@ export function unwrapPartInvokeResult(response) {
61
49
  return response.result ?? null
62
50
  }
63
51
 
64
- /**
65
- * @param {unknown} value partpath 字符串
66
- * @returns {string | null} 规范化 partpath
67
- */
68
- export function normalizePartpath(value) {
69
- const path = String(value || '').trim().replace(/^\/+|\/+$/g, '')
70
- return PARTPATH_RE.test(path) ? path : null
71
- }
72
-
73
52
  /**
74
53
  * @param {unknown} value invoke 体
75
54
  * @returns {value is PartInvoke} 是否含已知 kind
@@ -0,0 +1,32 @@
1
+ import { isPartInvokeResponse } from './invoke.mjs'
2
+
3
+ /** @typedef {import('./invoke.mjs').PartInvokeResponse} PartInvokeResponse */
4
+
5
+ /**
6
+ * @typedef {{
7
+ * responses: PartInvokeResponse[]
8
+ * finish: () => void
9
+ * maxResponses: number
10
+ * respondedPeers: Set<string>
11
+ * }} PendingPartInvoke
12
+ */
13
+
14
+ /** @type {Map<string, PendingPartInvoke>} */
15
+ export const pendingPartInvoke = new Map()
16
+
17
+ /**
18
+ * 入站 part_invoke_response:写入对应 pending 槽。
19
+ * @param {object} payload 响应
20
+ * @param {string} [peerId] 对端 id,用于同 peer 去重
21
+ * @returns {void}
22
+ */
23
+ export function handleIncomingPartInvokeResponse(payload, peerId = '') {
24
+ const pending = pendingPartInvoke.get(payload.requestId)
25
+ if (!pending || !isPartInvokeResponse(payload.response)) return
26
+ if (peerId) {
27
+ if (pending.respondedPeers.has(peerId)) return
28
+ pending.respondedPeers.add(peerId)
29
+ }
30
+ pending.responses.push(payload.response)
31
+ if (pending.responses.length >= pending.maxResponses) pending.finish()
32
+ }
@@ -0,0 +1,91 @@
1
+ import {
2
+ handleIncomingPartQueryResponse,
3
+ processIncomingPartQueryRequest,
4
+ queryNetwork as queryNetworkCore,
5
+ resolvePartQueryState,
6
+ } from '../../federation/part_query/runtime.mjs'
7
+ import {
8
+ parsePartQueryReq,
9
+ parsePartQueryRes,
10
+ } from '../../schemas/part_query.mjs'
11
+ import partQueryTunables from '../../schemas/part_query.tunables.json' with { type: 'json' }
12
+ import { sendToNodeLink } from '../../transport/link_registry.mjs'
13
+ import { consumeWireRateBucket } from '../rate_bucket.mjs'
14
+ import { subscribeWire } from '../subscribe.mjs'
15
+
16
+ /** @typedef {import('../adapter.mjs').WireAdapter} WireAdapter */
17
+ /** @typedef {import('../adapter.mjs').WireContext} WireContext */
18
+ /** @typedef {import('../../federation/part_query/runtime.mjs').PartQueryDependencies} PartQueryDependencies */
19
+
20
+ /**
21
+ * @param {PartQueryDependencies} [dependencies] 可注入依赖
22
+ * @returns {PartQueryDependencies} 补上默认 deliver 后的依赖
23
+ */
24
+ function withDefaultDeliver(dependencies = {}) {
25
+ return {
26
+ ...dependencies,
27
+ deliver: dependencies.deliver || ((nodeHash, action, payload) =>
28
+ sendToNodeLink(nodeHash, { scope: 'node', action, payload })),
29
+ }
30
+ }
31
+
32
+ /**
33
+ * 挂载 part_query_req / part_query_res(入站 parse / dedupe / rate;运行时在 federation)。
34
+ * @param {WireContext} wireContext 入站上下文
35
+ * @param {WireAdapter} wire wire
36
+ * @param {PartQueryDependencies} [dependencies] 可注入依赖(含 per-node state)
37
+ * @returns {() => void} 取消挂载的 dispose
38
+ */
39
+ export function attachPartQueryWire(wireContext, wire, dependencies = {}) {
40
+ const deps = withDefaultDeliver(dependencies)
41
+ const state = resolvePartQueryState(deps)
42
+ return subscribeWire(wire, {
43
+ /**
44
+ * @param {unknown} data part_query_req 载荷
45
+ * @param {string} peerId 对端 nodeHash
46
+ */
47
+ part_query_req(data, peerId) {
48
+ const request = parsePartQueryReq(data)
49
+ if (!request) return
50
+ if (!state.takeDedupe(request.requestId)) return
51
+ const source = String(peerId || request.originNodeHash || '').trim().toLowerCase()
52
+ if (source && !consumeWireRateBucket(`part_query:${source}`, {
53
+ maxCount: partQueryTunables.ratePerSourcePerMin,
54
+ })) return
55
+ void processIncomingPartQueryRequest(wireContext, wire, request, String(peerId || ''), deps)
56
+ },
57
+ /**
58
+ * @param {unknown} data part_query_res 载荷
59
+ * @param {string} peerId 对端 nodeHash
60
+ */
61
+ part_query_res(data, peerId) {
62
+ const response = parsePartQueryRes(data)
63
+ if (!response) return
64
+ handleIncomingPartQueryResponse(response, String(peerId || ''), deps)
65
+ },
66
+ })
67
+ }
68
+
69
+ /**
70
+ * 多跳查询(默认经 link_registry 投递;可注入 deliver)。
71
+ * @param {string} username trust graph 上下文
72
+ * @param {string} partpath part 路径
73
+ * @param {string} kind 查询标签
74
+ * @param {unknown} query 不透明查询
75
+ * @param {Parameters<typeof queryNetworkCore>[4]} [options] 选项
76
+ * @returns {Promise<unknown[]>} 合并后的 rows
77
+ */
78
+ export function queryNetwork(username, partpath, kind, query, options = {}) {
79
+ return queryNetworkCore(username, partpath, kind, query, withDefaultDeliver(options))
80
+ }
81
+
82
+ /**
83
+ * Part query 运行时(state、入站 handler、hop timeout、测试重置)。
84
+ */
85
+ export {
86
+ createPartQueryNodeState,
87
+ registerQueryInboundHandler,
88
+ resetPartQueryStateForTests,
89
+ resolvePartQueryHopTimeoutMs,
90
+ resolvePartQueryState,
91
+ } from '../../federation/part_query/runtime.mjs'
@@ -0,0 +1,13 @@
1
+ /**
2
+ * 批量注册 wire action,返回统一 dispose。
3
+ * @param {import('./adapter.mjs').WireAdapter} wire action 表
4
+ * @param {Record<string, (payload: unknown, peerId: string) => void>} handlers action → handler
5
+ * @returns {() => void} 取消全部注册
6
+ */
7
+ export function subscribeWire(wire, handlers) {
8
+ const disposers = Object.entries(handlers).map(([name, handler]) => wire.on(name, handler))
9
+ return () => {
10
+ for (const dispose of disposers)
11
+ dispose()
12
+ }
13
+ }
@@ -1,124 +0,0 @@
1
- import { base64ToBytes } from '../core/bytes_codec.mjs'
2
- import { verifiedChunkBytes } from '../files/chunk_fetch_verify.mjs'
3
-
4
- /** @type {Map<string, { expectedHash: string, timer: ReturnType<typeof setTimeout>, resolve: (v: Uint8Array | null) => void, reject?: (e: Error) => void }>} */
5
- export const pendingChunkFetches = new Map()
6
-
7
- /** 并发 pending chunk fetch 上限。 */
8
- export const MAX_PENDING_CHUNK_FETCHES = 2048
9
-
10
- /**
11
- * 注册 chunk 拉取等待槽(requestId 或 compositeKey)。
12
- * @param {string} key 唯一等待键
13
- * @param {string} expectedHash 期望 64 hex 密文哈希
14
- * @param {number} timeoutMs 超时毫秒
15
- * @param {{ rejectOnTimeout?: boolean }} [options] rejectOnTimeout 时 Promise 以 Error 拒绝
16
- * @returns {{ done: Promise<Uint8Array | null>, cancel: () => void }} 等待 Promise 与取消函数
17
- */
18
- export function registerChunkFetchWait(key, expectedHash, timeoutMs, options = {}) {
19
- if (!key || pendingChunkFetches.size >= MAX_PENDING_CHUNK_FETCHES)
20
- return {
21
- done: Promise.resolve(null),
22
- /**
23
- * 槽位已满时的空操作取消。
24
- */
25
- cancel: () => { },
26
- }
27
-
28
- /** @type {(value: Uint8Array | null | Error) => void} */
29
- let settle
30
- const done = new Promise((resolve, reject) => {
31
- /**
32
- * @param {Uint8Array | null | Error} value 块数据或错误
33
- * @returns {void}
34
- */
35
- function settleWait(value) {
36
- if (value instanceof Error) reject(value)
37
- else resolve(value)
38
- }
39
- settle = settleWait
40
- })
41
- const timer = setTimeout(() => {
42
- pendingChunkFetches.delete(key)
43
- if (options.rejectOnTimeout)
44
- settle(new Error('chunk fetch timeout'))
45
- else
46
- settle(null)
47
- }, timeoutMs)
48
-
49
- /**
50
- * @param {Uint8Array | null} data 块数据
51
- * @returns {void}
52
- */
53
- function finish(data) {
54
- clearTimeout(timer)
55
- pendingChunkFetches.delete(key)
56
- settle(data)
57
- }
58
-
59
- /**
60
- * @param {Error | unknown} error 拒绝原因
61
- * @returns {void}
62
- */
63
- function rejectWait(error) {
64
- finish(error instanceof Error ? error : new Error(String(error)))
65
- }
66
-
67
- pendingChunkFetches.set(key, {
68
- expectedHash,
69
- timer,
70
- resolve: finish,
71
- reject: rejectWait,
72
- })
73
-
74
- return {
75
- done,
76
- /** 取消等待并以 null settle。 */
77
- cancel: () => {
78
- clearTimeout(timer)
79
- pendingChunkFetches.delete(key)
80
- settle(null)
81
- },
82
- }
83
- }
84
-
85
- /**
86
- * 按等待键解析入站 chunk 响应(校验哈希后 resolve)。
87
- * @param {string} key 等待键
88
- * @param {string} expectedHash 期望哈希
89
- * @param {Uint8Array | null} bytes 密文块
90
- * @returns {boolean} 是否命中并完成等待
91
- */
92
- export function resolveChunkFetchWait(key, expectedHash, bytes) {
93
- const entry = pendingChunkFetches.get(key)
94
- if (!entry || entry.expectedHash !== expectedHash) return false
95
- const verified = bytes ? verifiedChunkBytes(expectedHash, bytes) : null
96
- if (bytes && !verified) return false
97
- clearTimeout(entry.timer)
98
- pendingChunkFetches.delete(key)
99
- entry.resolve(verified)
100
- return true
101
- }
102
-
103
- /**
104
- * 处理 fed_chunk_data / 带 requestId 的响应载荷。
105
- * @param {object} payload 入站载荷
106
- * @returns {boolean} 是否命中 pending
107
- */
108
- export function resolvePendingChunkFetch(payload) {
109
- const requestId = typeof payload?.requestId === 'string' ? payload.requestId : ''
110
- if (!requestId) return false
111
- const entry = pendingChunkFetches.get(requestId)
112
- if (!entry) return false
113
- if (typeof payload?.dataBase64 === 'string') {
114
- try {
115
- return resolveChunkFetchWait(requestId, entry.expectedHash, base64ToBytes(payload.dataBase64))
116
- }
117
- catch { /* keep waiting */ }
118
- return false
119
- }
120
- clearTimeout(entry.timer)
121
- pendingChunkFetches.delete(requestId)
122
- entry.resolve(null)
123
- return true
124
- }
@@ -1,88 +0,0 @@
1
- import { verifySignedPublicManifest } from '../files/public_manifest.mjs'
2
-
3
- /** @type {Map<string, { expectedKey: string, timer: ReturnType<typeof setTimeout>, resolve: (v: object | null) => void }>} */
4
- export const pendingManifestFetches = new Map()
5
-
6
- /** 并发 pending manifest fetch 上限。 */
7
- export const MAX_PENDING_MANIFEST_FETCHES = 512
8
-
9
- /**
10
- * @param {string} ownerEntityHash owner
11
- * @param {string} logicalPath 路径
12
- * @returns {string} 期望键
13
- */
14
- export function manifestFetchExpectedKey(ownerEntityHash, logicalPath) {
15
- return `${String(ownerEntityHash || '').trim().toLowerCase()}\0${String(logicalPath || '').trim().replace(/^\/+/, '')}`
16
- }
17
-
18
- /**
19
- * @param {string} key requestId
20
- * @param {string} expectedKey owner+path 复合键
21
- * @param {number} timeoutMs 超时毫秒
22
- * @returns {{ done: Promise<object | null>, cancel: () => void }} 等待 Promise 与取消
23
- */
24
- export function registerManifestFetchWait(key, expectedKey, timeoutMs) {
25
- if (!key || pendingManifestFetches.size >= MAX_PENDING_MANIFEST_FETCHES)
26
- return {
27
- done: Promise.resolve(null),
28
- /**
29
- * 槽位已满时的空操作取消。
30
- */
31
- cancel: () => { },
32
- }
33
-
34
- /** @type {(value: object | null) => void} */
35
- let settle
36
- const done = new Promise(resolve => {
37
- settle = resolve
38
- })
39
- const timer = setTimeout(() => {
40
- pendingManifestFetches.delete(key)
41
- settle(null)
42
- }, timeoutMs)
43
-
44
- pendingManifestFetches.set(key, {
45
- expectedKey,
46
- timer,
47
- /**
48
- * @param {object | null} value 验签后的 manifest,超时/取消为 null
49
- */
50
- resolve: value => {
51
- clearTimeout(timer)
52
- pendingManifestFetches.delete(key)
53
- settle(value)
54
- },
55
- })
56
-
57
- return {
58
- done,
59
- /**
60
- * 取消等待并以 null 结算。
61
- */
62
- cancel: () => {
63
- clearTimeout(timer)
64
- pendingManifestFetches.delete(key)
65
- settle(null)
66
- },
67
- }
68
- }
69
-
70
- /**
71
- * 处理 fed_manifest_data:验签通过后 resolve pending。
72
- * @param {object} payload 入站载荷
73
- * @returns {Promise<boolean>} 是否命中并完成等待
74
- */
75
- export async function resolvePendingManifestFetch(payload) {
76
- const requestId = String(payload?.requestId || '')
77
- if (!requestId) return false
78
- const entry = pendingManifestFetches.get(requestId)
79
- if (!entry) return false
80
-
81
- const verified = await verifySignedPublicManifest(payload?.manifest)
82
- if (!verified) return false
83
- const key = manifestFetchExpectedKey(verified.ownerEntityHash, verified.logicalPath)
84
- if (key !== entry.expectedKey) return false
85
-
86
- entry.resolve(verified)
87
- return true
88
- }