@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.
- package/README.md +8 -6
- package/core/object.mjs +9 -0
- package/core/partpath.mjs +10 -0
- package/dag/storage.mjs +0 -5
- package/dag/strip_extensions.mjs +1 -3
- package/{wire/part_query_cache.mjs → federation/part_query/cache.mjs} +7 -7
- package/{wire/part_query.mjs → federation/part_query/runtime.mjs} +36 -77
- package/files/assemble.mjs +5 -5
- package/files/assemble_stream.mjs +2 -2
- package/files/chunk/fetch.mjs +101 -0
- package/files/chunk/pending.mjs +59 -0
- package/files/{chunk_provider_registry.mjs → chunk/provider_registry.mjs} +4 -5
- package/files/chunk/responder.mjs +104 -0
- package/files/{chunk_store.mjs → chunk/store.mjs} +2 -2
- package/files/{chunk_fetch_verify.mjs → chunk/verify.mjs} +2 -2
- package/files/evfs.mjs +18 -19
- package/files/fed/fetch_shared.mjs +39 -0
- package/files/fed/responder.mjs +47 -0
- package/files/{acl.mjs → manifest/acl.mjs} +4 -4
- package/files/{manifest_acl_registry.mjs → manifest/acl_registry.mjs} +14 -17
- package/files/{manifest_fetch.mjs → manifest/fetch.mjs} +47 -52
- package/files/{manifest.mjs → manifest/normalize.mjs} +2 -2
- package/files/manifest/pending.mjs +53 -0
- package/files/{public_manifest.mjs → manifest/public.mjs} +12 -12
- package/files/transfer_key.mjs +2 -2
- package/files/transfer_key_registry.mjs +7 -7
- package/index.mjs +4 -4
- package/infra/service.mjs +1 -1
- package/link/handshake.mjs +0 -3
- package/mailbox/deliver_or_store.mjs +0 -2
- package/mailbox/parse.mjs +1 -1
- package/mailbox/store.mjs +0 -9
- package/mailbox/wire.mjs +24 -16
- package/node/reputation_sync.mjs +33 -29
- package/package.json +10 -10
- package/registries/inbound.mjs +19 -9
- package/registries/part_path.mjs +3 -8
- package/schemas/discovery.mjs +1 -1
- package/schemas/federation_pull.mjs +1 -1
- package/schemas/part_query.mjs +6 -5
- package/transport/group_link_set.mjs +2 -2
- package/transport/link_registry.mjs +2 -2
- package/transport/node_scope/features.mjs +166 -0
- package/transport/node_scope/wire.mjs +143 -0
- package/transport/remote_user_room.mjs +0 -1
- package/{rooms → transport}/scoped_link.mjs +1 -1
- package/transport/user_room.mjs +15 -28
- package/utils/fetch_wait.mjs +98 -0
- package/wire/adapter.mjs +13 -0
- package/wire/ingress.mjs +1 -8
- package/wire/part/fanout.mjs +99 -0
- package/wire/part/group.mjs +45 -0
- package/wire/{part_ingress.mjs → part/ingress.mjs} +58 -67
- package/wire/{part_invoke.mjs → part/invoke.mjs} +1 -22
- package/wire/part/pending.mjs +32 -0
- package/wire/part/query.mjs +91 -0
- package/wire/subscribe.mjs +13 -0
- package/federation/chunk_fetch_pending.mjs +0 -124
- package/federation/manifest_fetch_pending.mjs +0 -88
- package/files/chunk_fetch.mjs +0 -99
- package/files/chunk_responder.mjs +0 -142
- package/transport/advert_ingest.mjs +0 -17
- package/transport/node_scope.mjs +0 -289
- package/wire/group_part.mjs +0 -43
- package/wire/part_common.mjs +0 -55
- package/wire/part_fanout.mjs +0 -52
- /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 './
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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.
|
|
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 {
|
|
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
|
-
|
|
80
|
-
|
|
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 =
|
|
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:
|
|
96
|
+
requesterNodeHash: peerId ? String(peerId).trim() : null,
|
|
89
97
|
}, message)
|
|
90
|
-
}
|
|
91
|
-
|
|
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
|
-
|
|
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 {
|
|
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 =
|
|
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 {
|
|
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 =
|
|
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 '
|
|
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
|
-
}
|