@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,104 @@
|
|
|
1
|
+
import { handleIncomingManifestGet } from '../manifest/fetch.mjs'
|
|
2
|
+
import { resolvePendingManifestFetch } from '../manifest/pending.mjs'
|
|
3
|
+
|
|
4
|
+
import { handleIncomingChunkGet } from './fetch.mjs'
|
|
5
|
+
import { resolvePendingChunkFetch } from './pending.mjs'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 群联邦 / TrustGraph 路径:响应带 requestId 的 fed_chunk_get。
|
|
9
|
+
* @param {object} data 入站 fed_chunk_get
|
|
10
|
+
* @param {string} peerId 对端 id
|
|
11
|
+
* @param {(resp: object, peerId: string) => void} sendChunkData 发送 fed_chunk_data
|
|
12
|
+
* @returns {Promise<void>}
|
|
13
|
+
*/
|
|
14
|
+
export async function handleFedChunkGetIngress(data, peerId, sendChunkData) {
|
|
15
|
+
await handleIncomingChunkGet(data, sendChunkData, peerId)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {object} data 入站 fed_chunk_data(含 requestId 时 resolve pending fetch)
|
|
20
|
+
* @returns {boolean} 是否命中 pending
|
|
21
|
+
*/
|
|
22
|
+
export function handleFedChunkDataIngress(data) {
|
|
23
|
+
return resolvePendingChunkFetch(data)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {object} data 入站 fed_manifest_get
|
|
28
|
+
* @param {string} peerId 对端 id
|
|
29
|
+
* @param {(resp: object, peerId: string) => void} sendManifestData 发送 fed_manifest_data
|
|
30
|
+
* @returns {Promise<void>}
|
|
31
|
+
*/
|
|
32
|
+
export async function handleFedManifestGetIngress(data, peerId, sendManifestData) {
|
|
33
|
+
await handleIncomingManifestGet(data, sendManifestData, peerId)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {object} data 入站 fed_manifest_data
|
|
38
|
+
* @returns {Promise<boolean>} 是否命中 pending
|
|
39
|
+
*/
|
|
40
|
+
export function handleFedManifestDataIngress(data) {
|
|
41
|
+
return resolvePendingManifestFetch(data)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {(resp: object, peerId: string) => void} sendData 发送
|
|
46
|
+
* @param {{ enqueue: (prio: number, cleanup: () => void) => void }} [fedOut] 出站限速队列
|
|
47
|
+
* @returns {(resp: object, peerId: string) => void} 可入队的发送
|
|
48
|
+
*/
|
|
49
|
+
function wrapSend(sendData, fedOut) {
|
|
50
|
+
if (!fedOut) return sendData
|
|
51
|
+
return (resp, peerId) => {
|
|
52
|
+
fedOut.enqueue(6, () => {
|
|
53
|
+
try { sendData(resp, peerId) }
|
|
54
|
+
catch { /* peer gone */ }
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Trystero room:注册带 requestId 的 fed_chunk_* + fed_manifest_*(TrustGraph / 群全局 miss)。
|
|
61
|
+
* @param {object} room Trystero room
|
|
62
|
+
* @param {{ enqueue: (prio: number, cleanup: () => void) => void }} [fedOut] 出站队列
|
|
63
|
+
* @param {(roomKey: string, action: string, rtcLimits: object) => boolean} [guardGet] RTC 负载守卫
|
|
64
|
+
* @param {object} [rtcLimits] RTC 限额
|
|
65
|
+
* @param {string} [roomKey] 房间键
|
|
66
|
+
* @returns {void}
|
|
67
|
+
*/
|
|
68
|
+
export function attachTrustGraphFedChunkResponder(room, fedOut, guardGet, rtcLimits = {}, roomKey = '') {
|
|
69
|
+
const [sendChunkData, getChunkData] = room.makeAction('fed_chunk_data')
|
|
70
|
+
const [, getChunkGet] = room.makeAction('fed_chunk_get')
|
|
71
|
+
const [sendManifestData, getManifestData] = room.makeAction('fed_manifest_data')
|
|
72
|
+
const [, getManifestGet] = room.makeAction('fed_manifest_get')
|
|
73
|
+
|
|
74
|
+
const sendChunk = wrapSend((resp, peerId) => {
|
|
75
|
+
try { sendChunkData(resp, peerId) }
|
|
76
|
+
catch { /* peer gone */ }
|
|
77
|
+
}, fedOut)
|
|
78
|
+
const sendManifest = wrapSend((resp, peerId) => {
|
|
79
|
+
try { sendManifestData(resp, peerId) }
|
|
80
|
+
catch { /* peer gone */ }
|
|
81
|
+
}, fedOut)
|
|
82
|
+
|
|
83
|
+
getChunkGet((data, peerId) => {
|
|
84
|
+
if (guardGet && !guardGet(roomKey, 'fed_chunk_get', rtcLimits)) return
|
|
85
|
+
if (!data?.requestId) return
|
|
86
|
+
void handleFedChunkGetIngress(data, peerId, sendChunk)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
getChunkData(data => {
|
|
90
|
+
if (!data?.requestId) return
|
|
91
|
+
handleFedChunkDataIngress(data)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
getManifestGet((data, peerId) => {
|
|
95
|
+
if (guardGet && !guardGet(roomKey, 'fed_manifest_get', rtcLimits)) return
|
|
96
|
+
if (!data?.requestId) return
|
|
97
|
+
void handleFedManifestGetIngress(data, peerId, sendManifest)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
getManifestData(data => {
|
|
101
|
+
if (!data?.requestId) return
|
|
102
|
+
handleFedManifestDataIngress(data)
|
|
103
|
+
})
|
|
104
|
+
}
|
|
@@ -4,8 +4,8 @@ import fsp from 'node:fs/promises'
|
|
|
4
4
|
import { dirname, join } from 'node:path'
|
|
5
5
|
import { pipeline } from 'node:stream/promises'
|
|
6
6
|
|
|
7
|
-
import { isHex64 } from '
|
|
8
|
-
import { getNodeDir } from '
|
|
7
|
+
import { isHex64 } from '../../core/hexIds.mjs'
|
|
8
|
+
import { getNodeDir } from '../../node/instance.mjs'
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* @returns {string} `{nodeDir}/chunks`
|
package/files/evfs.mjs
CHANGED
|
@@ -6,15 +6,15 @@ import { getEntityStore } from '../node/instance.mjs'
|
|
|
6
6
|
|
|
7
7
|
import { buildFileManifestFromEnc, encryptPlaintextToMultiPartsAsync, encryptPlaintextToParts, manifestPartsForPersist } from './assemble.mjs'
|
|
8
8
|
import { createManifestPlaintextStream, encryptReadableToParts } from './assemble_stream.mjs'
|
|
9
|
-
import { fetchChunk } from './
|
|
10
|
-
import { createChunkReadStream, getChunk, hasChunk, putChunk } from './
|
|
11
|
-
import { normalizeFileManifest, publicTransferKeyDescriptor } from './manifest.mjs'
|
|
9
|
+
import { fetchChunk } from './chunk/fetch.mjs'
|
|
10
|
+
import { createChunkReadStream, getChunk, hasChunk, putChunk } from './chunk/store.mjs'
|
|
11
|
+
import { normalizeFileManifest, publicTransferKeyDescriptor } from './manifest/normalize.mjs'
|
|
12
12
|
import { assembleManifestPlaintext, resolveContentKey } from './transfer_key.mjs'
|
|
13
13
|
import { readDagManifestPlaintext, resolveTransferKeyDependencies } from './transfer_key_registry.mjs'
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* @param {string} replicaUsername 副本用户名
|
|
17
|
-
* @param {import('./manifest.mjs').FileManifest} manifest 清单
|
|
17
|
+
* @param {import('./manifest/normalize.mjs').FileManifest} manifest 清单
|
|
18
18
|
* @returns {{ getGroupFileMasterKey?: Function, getVaultMasterKey?: Function }} 密钥依赖
|
|
19
19
|
*/
|
|
20
20
|
function transferKeyDependenciesForReplica(replicaUsername, manifest) {
|
|
@@ -31,7 +31,7 @@ function transferKeyDependenciesForReplica(replicaUsername, manifest) {
|
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* @param {string} username 拉取身份
|
|
34
|
-
* @param {import('./manifest.mjs').FileManifest} manifest 清单
|
|
34
|
+
* @param {import('./manifest/normalize.mjs').FileManifest} manifest 清单
|
|
35
35
|
* @param {{ fetchChunk?: Function }} [options] miss 拉取
|
|
36
36
|
* @returns {Promise<boolean>} 全部 part 是否已就位
|
|
37
37
|
*/
|
|
@@ -53,15 +53,14 @@ async function ensureManifestPartsLocal(username, manifest, options = {}) {
|
|
|
53
53
|
/**
|
|
54
54
|
* @param {string} ownerEntityHash 所有者
|
|
55
55
|
* @param {string} logicalPath 路径
|
|
56
|
-
* @returns {Promise<import('./manifest.mjs').FileManifest | null>}
|
|
56
|
+
* @returns {Promise<import('./manifest/normalize.mjs').FileManifest | null>} 本机已写盘的 manifest
|
|
57
57
|
*/
|
|
58
58
|
export async function loadFileManifest(ownerEntityHash, logicalPath) {
|
|
59
|
-
|
|
60
|
-
return manifest ? normalizeFileManifest(manifest) : null
|
|
59
|
+
return getEntityStore().readManifest(ownerEntityHash, logicalPath)
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
/**
|
|
64
|
-
* @param {import('./manifest.mjs').FileManifest} manifest 清单
|
|
63
|
+
* @param {import('./manifest/normalize.mjs').FileManifest} manifest 清单
|
|
65
64
|
* @returns {Promise<void>}
|
|
66
65
|
*/
|
|
67
66
|
export async function saveFileManifest(manifest) {
|
|
@@ -69,7 +68,7 @@ export async function saveFileManifest(manifest) {
|
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
/**
|
|
72
|
-
* @param {import('./manifest.mjs').FileManifest} manifest 清单
|
|
71
|
+
* @param {import('./manifest/normalize.mjs').FileManifest} manifest 清单
|
|
73
72
|
* @param {Array<Buffer | Uint8Array>} partBytes 密文块
|
|
74
73
|
* @returns {Promise<void>}
|
|
75
74
|
*/
|
|
@@ -80,7 +79,7 @@ export async function storeManifestParts(manifest, partBytes) {
|
|
|
80
79
|
|
|
81
80
|
/**
|
|
82
81
|
* @param {string} replicaUsername 副本用户名
|
|
83
|
-
* @param {import('./manifest.mjs').FileManifest} manifest 清单
|
|
82
|
+
* @param {import('./manifest/normalize.mjs').FileManifest} manifest 清单
|
|
84
83
|
* @param {{ username?: string, fetchChunk?: Function }} [options] miss 拉取
|
|
85
84
|
* @returns {Promise<Buffer | null>} 明文内容
|
|
86
85
|
*/
|
|
@@ -104,7 +103,7 @@ export async function readManifestPlaintext(replicaUsername, manifest, options =
|
|
|
104
103
|
|
|
105
104
|
/**
|
|
106
105
|
* @param {string} replicaUsername 副本用户名
|
|
107
|
-
* @param {import('./manifest.mjs').FileManifest} manifest 清单
|
|
106
|
+
* @param {import('./manifest/normalize.mjs').FileManifest} manifest 清单
|
|
108
107
|
* @param {{ username?: string, fetchChunk?: Function }} [options] miss 拉取
|
|
109
108
|
* @returns {Promise<import('node:stream').Readable | null>} 明文流
|
|
110
109
|
*/
|
|
@@ -134,10 +133,10 @@ export async function readManifestPlaintextStream(replicaUsername, manifest, opt
|
|
|
134
133
|
* @param {Buffer | Uint8Array} parameters.plaintext 明文
|
|
135
134
|
* @param {string} [parameters.name] 文件名
|
|
136
135
|
* @param {string} [parameters.mimeType] MIME
|
|
137
|
-
* @param {import('./manifest.mjs').CeMode} [parameters.ceMode] 模式
|
|
138
|
-
* @param {import('./manifest.mjs').TransferKeyDescriptor} [parameters.transferKeyDescriptor] 传递密钥
|
|
136
|
+
* @param {import('./manifest/normalize.mjs').CeMode} [parameters.ceMode] 模式
|
|
137
|
+
* @param {import('./manifest/normalize.mjs').TransferKeyDescriptor} [parameters.transferKeyDescriptor] 传递密钥
|
|
139
138
|
* @param {object} [parameters.meta] meta
|
|
140
|
-
* @returns {Promise<import('./manifest.mjs').FileManifest>} 写入后的 manifest
|
|
139
|
+
* @returns {Promise<import('./manifest/normalize.mjs').FileManifest>} 写入后的 manifest
|
|
141
140
|
*/
|
|
142
141
|
export async function putFileManifest(parameters) {
|
|
143
142
|
const {
|
|
@@ -178,10 +177,10 @@ export async function putFileManifest(parameters) {
|
|
|
178
177
|
* @param {number} parameters.plainSize 明文字节数
|
|
179
178
|
* @param {string} [parameters.name] 文件名
|
|
180
179
|
* @param {string} [parameters.mimeType] MIME
|
|
181
|
-
* @param {import('./manifest.mjs').CeMode} [parameters.ceMode] 模式
|
|
182
|
-
* @param {import('./manifest.mjs').TransferKeyDescriptor} [parameters.transferKeyDescriptor] 传递密钥
|
|
180
|
+
* @param {import('./manifest/normalize.mjs').CeMode} [parameters.ceMode] 模式
|
|
181
|
+
* @param {import('./manifest/normalize.mjs').TransferKeyDescriptor} [parameters.transferKeyDescriptor] 传递密钥
|
|
183
182
|
* @param {object} [parameters.meta] meta
|
|
184
|
-
* @returns {Promise<import('./manifest.mjs').FileManifest>} 写入后的 manifest
|
|
183
|
+
* @returns {Promise<import('./manifest/normalize.mjs').FileManifest>} 写入后的 manifest
|
|
185
184
|
*/
|
|
186
185
|
export async function putFileManifestFromStream(parameters) {
|
|
187
186
|
const {
|
|
@@ -223,7 +222,7 @@ export async function putFileManifestFromStream(parameters) {
|
|
|
223
222
|
* @returns {Promise<Buffer | null>} 明文或 null
|
|
224
223
|
*/
|
|
225
224
|
export async function readPublicFile(replicaUsername, entityHash, logicalPath, options = {}) {
|
|
226
|
-
const { fetchPublicManifest } = await import('./
|
|
225
|
+
const { fetchPublicManifest } = await import('./manifest/fetch.mjs')
|
|
227
226
|
const manifest = await fetchPublicManifest({
|
|
228
227
|
username: options.username || replicaUsername,
|
|
229
228
|
ownerEntityHash: entityHash,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
import { resolveNodeHash } from '../chunk/provider_registry.mjs'
|
|
4
|
+
import { fanoutFedFetch } from '../fetch_fanout.mjs'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* inflight + pending wait + fanout 共用骨架。
|
|
8
|
+
* @template T
|
|
9
|
+
* @param {{
|
|
10
|
+
* inflight: { acquire: (key: string, start: () => { done: Promise<T | null>, cancel: () => void }) => Promise<T | null> | null },
|
|
11
|
+
* inflightKey: string,
|
|
12
|
+
* username: string,
|
|
13
|
+
* action: string,
|
|
14
|
+
* registerWait: (requestId: string) => { done: Promise<T | null>, cancel: () => void },
|
|
15
|
+
* buildPayload: (requestId: string, nodeHash: string) => object,
|
|
16
|
+
* }} options 选项
|
|
17
|
+
* @returns {Promise<T | null> | null} 共享 Promise;队满为 null
|
|
18
|
+
*/
|
|
19
|
+
export function beginFedFanoutFetch({
|
|
20
|
+
inflight,
|
|
21
|
+
inflightKey,
|
|
22
|
+
username,
|
|
23
|
+
action,
|
|
24
|
+
registerWait,
|
|
25
|
+
buildPayload,
|
|
26
|
+
}) {
|
|
27
|
+
return inflight.acquire(inflightKey, () => {
|
|
28
|
+
const requestId = randomUUID()
|
|
29
|
+
const wait = registerWait(requestId)
|
|
30
|
+
void (async () => {
|
|
31
|
+
try {
|
|
32
|
+
const { nodeHash } = await resolveNodeHash(username)
|
|
33
|
+
await fanoutFedFetch(username, action, buildPayload(requestId, nodeHash))
|
|
34
|
+
}
|
|
35
|
+
catch { /* pending wait 超时/cancel 负责 settle */ }
|
|
36
|
+
})()
|
|
37
|
+
return { done: wait.done, cancel: wait.cancel }
|
|
38
|
+
})
|
|
39
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { subscribeWire } from '../../wire/subscribe.mjs'
|
|
2
|
+
import { handleIncomingChunkGet } from '../chunk/fetch.mjs'
|
|
3
|
+
import { resolvePendingChunkFetch } from '../chunk/pending.mjs'
|
|
4
|
+
import { handleIncomingManifestGet } from '../manifest/fetch.mjs'
|
|
5
|
+
import { resolvePendingManifestFetch } from '../manifest/pending.mjs'
|
|
6
|
+
|
|
7
|
+
/** @typedef {import('../../wire/adapter.mjs').WireAdapter} WireAdapter */
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* node scope wire:注册 fed_chunk_* + fed_manifest_*。
|
|
11
|
+
* @param {WireAdapter} wire action 表
|
|
12
|
+
* @returns {() => void} 取消挂载的 dispose
|
|
13
|
+
*/
|
|
14
|
+
export function attachNodeScopeFedResponder(wire) {
|
|
15
|
+
return subscribeWire(wire, {
|
|
16
|
+
/**
|
|
17
|
+
* @param {unknown} data chunk 请求载荷
|
|
18
|
+
* @param {string} peerId 对端 nodeHash
|
|
19
|
+
*/
|
|
20
|
+
fed_chunk_get(data, peerId) {
|
|
21
|
+
void handleIncomingChunkGet(data, (resp, pid) => {
|
|
22
|
+
try { wire.send('fed_chunk_data', resp, pid) }
|
|
23
|
+
catch { /* disconnected */ }
|
|
24
|
+
}, peerId)
|
|
25
|
+
},
|
|
26
|
+
/**
|
|
27
|
+
* @param {unknown} data chunk 响应载荷
|
|
28
|
+
*/
|
|
29
|
+
fed_chunk_data(data) { resolvePendingChunkFetch(data) },
|
|
30
|
+
/**
|
|
31
|
+
* @param {unknown} data manifest 请求载荷
|
|
32
|
+
* @param {string} peerId 对端 nodeHash
|
|
33
|
+
*/
|
|
34
|
+
fed_manifest_get(data, peerId) {
|
|
35
|
+
void handleIncomingManifestGet(data, (resp, pid) => {
|
|
36
|
+
try { wire.send('fed_manifest_data', resp, pid) }
|
|
37
|
+
catch { /* disconnected */ }
|
|
38
|
+
}, peerId)
|
|
39
|
+
},
|
|
40
|
+
/**
|
|
41
|
+
* @param {unknown} data manifest 响应载荷
|
|
42
|
+
*/
|
|
43
|
+
fed_manifest_data(data) {
|
|
44
|
+
void resolvePendingManifestFetch(data)
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { isLogicalEntityHash } from '
|
|
2
|
-
import { isWritableLocalEntity } from '
|
|
1
|
+
import { isLogicalEntityHash } from '../../core/logical_entity.mjs'
|
|
2
|
+
import { isWritableLocalEntity } from '../../node/identity.mjs'
|
|
3
3
|
|
|
4
|
-
import { checkManifestAcl, resolveManifestAclType } from './
|
|
4
|
+
import { checkManifestAcl, resolveManifestAclType } from './acl_registry.mjs'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @param {string} replicaUsername 请求 replica
|
|
8
8
|
* @param {string} ownerEntityHash 文件 owner
|
|
9
|
-
* @param {import('./
|
|
9
|
+
* @param {import('./normalize.mjs').FileManifest} manifest 清单
|
|
10
10
|
* @returns {Promise<boolean>} 是否允许读
|
|
11
11
|
*/
|
|
12
12
|
export async function canReadManifest(replicaUsername, ownerEntityHash, manifest) {
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
/** @type {Map<string, { ownerId: string, handler: (context: ManifestAclContext, logicalPath?: string) => Promise<boolean> }>} */
|
|
6
6
|
const handlersByType = new Map()
|
|
7
7
|
|
|
8
|
-
/** @type {Map<string, { ownerId: string, match: (manifest: import('./
|
|
8
|
+
/** @type {Map<string, { ownerId: string, match: (manifest: import('./normalize.mjs').FileManifest | null | undefined, ownerEntityHash: string) => string | null }>} */
|
|
9
9
|
const matchersByOwner = new Map()
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* @typedef {{
|
|
13
13
|
* replicaUsername: string,
|
|
14
14
|
* ownerEntityHash: string,
|
|
15
|
-
* manifest: import('./
|
|
15
|
+
* manifest: import('./normalize.mjs').FileManifest,
|
|
16
16
|
* }} ManifestAclContext
|
|
17
17
|
*/
|
|
18
18
|
|
|
@@ -23,12 +23,10 @@ const matchersByOwner = new Map()
|
|
|
23
23
|
* @returns {void}
|
|
24
24
|
*/
|
|
25
25
|
export function registerManifestAcl(type, ownerId, handler) {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
throw new Error(`manifest acl handler for '${key}' already registered by '${existing.ownerId}'`)
|
|
31
|
-
handlersByType.set(key, { ownerId: owner, handler })
|
|
26
|
+
const existing = handlersByType.get(type)
|
|
27
|
+
if (existing && existing.ownerId !== ownerId)
|
|
28
|
+
throw new Error(`manifest acl handler for '${type}' already registered by '${existing.ownerId}'`)
|
|
29
|
+
handlersByType.set(type, { ownerId, handler })
|
|
32
30
|
}
|
|
33
31
|
|
|
34
32
|
/**
|
|
@@ -37,20 +35,19 @@ export function registerManifestAcl(type, ownerId, handler) {
|
|
|
37
35
|
* @returns {void}
|
|
38
36
|
*/
|
|
39
37
|
export function unregisterManifestAcl(type, ownerId) {
|
|
40
|
-
const
|
|
41
|
-
const existing = handlersByType.get(key)
|
|
38
|
+
const existing = handlersByType.get(type)
|
|
42
39
|
if (!existing) return
|
|
43
|
-
if (existing.ownerId ===
|
|
44
|
-
handlersByType.delete(
|
|
40
|
+
if (existing.ownerId === ownerId)
|
|
41
|
+
handlersByType.delete(type)
|
|
45
42
|
}
|
|
46
43
|
|
|
47
44
|
/**
|
|
48
45
|
* @param {string} ownerId 注册方
|
|
49
|
-
* @param {(manifest: import('./
|
|
46
|
+
* @param {(manifest: import('./normalize.mjs').FileManifest | null | undefined, ownerEntityHash: string) => string | null} match ACL 类型匹配
|
|
50
47
|
* @returns {void}
|
|
51
48
|
*/
|
|
52
49
|
export function registerManifestAclMatcher(ownerId, match) {
|
|
53
|
-
matchersByOwner.set(
|
|
50
|
+
matchersByOwner.set(ownerId, { ownerId, match })
|
|
54
51
|
}
|
|
55
52
|
|
|
56
53
|
/**
|
|
@@ -58,11 +55,11 @@ export function registerManifestAclMatcher(ownerId, match) {
|
|
|
58
55
|
* @returns {void}
|
|
59
56
|
*/
|
|
60
57
|
export function unregisterManifestAclMatcher(ownerId) {
|
|
61
|
-
matchersByOwner.delete(
|
|
58
|
+
matchersByOwner.delete(ownerId)
|
|
62
59
|
}
|
|
63
60
|
|
|
64
61
|
/**
|
|
65
|
-
* @param {import('./
|
|
62
|
+
* @param {import('./normalize.mjs').FileManifest | null | undefined} manifest manifest
|
|
66
63
|
* @param {string} ownerEntityHash 所有者
|
|
67
64
|
* @returns {string | null} ACL 类型
|
|
68
65
|
*/
|
|
@@ -81,7 +78,7 @@ export function resolveManifestAclType(manifest, ownerEntityHash) {
|
|
|
81
78
|
* @returns {Promise<boolean>} 是否允许(未注册为 false)
|
|
82
79
|
*/
|
|
83
80
|
export async function checkManifestAcl(type, context, logicalPath) {
|
|
84
|
-
const row = handlersByType.get(
|
|
81
|
+
const row = handlersByType.get(type)
|
|
85
82
|
if (!row?.handler) return false
|
|
86
83
|
return row.handler(context, logicalPath)
|
|
87
84
|
}
|
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseEntityHash } from '../../core/entity_id.mjs'
|
|
2
|
+
import { assertSafeEvfsLogicalPath } from '../../core/evfs_logical_path.mjs'
|
|
3
|
+
import { isWritableLocalEntity } from '../../node/identity.mjs'
|
|
4
|
+
import { getEntityStore } from '../../node/instance.mjs'
|
|
5
|
+
import { ms } from '../../utils/duration.mjs'
|
|
6
|
+
import { createInflightTable } from '../../utils/inflight_table.mjs'
|
|
7
|
+
import { loadFileManifest, saveFileManifest } from '../evfs.mjs'
|
|
8
|
+
import { beginFedFanoutFetch } from '../fed/fetch_shared.mjs'
|
|
2
9
|
|
|
10
|
+
import { normalizeFileManifest } from './normalize.mjs'
|
|
3
11
|
import {
|
|
4
12
|
manifestFetchExpectedKey,
|
|
5
13
|
MAX_PENDING_MANIFEST_FETCHES,
|
|
6
14
|
registerManifestFetchWait,
|
|
7
|
-
} from '
|
|
8
|
-
import {
|
|
9
|
-
import { getEntityStore } from '../node/instance.mjs'
|
|
10
|
-
import { ms } from '../utils/duration.mjs'
|
|
11
|
-
import { createInflightTable } from '../utils/inflight_table.mjs'
|
|
12
|
-
|
|
13
|
-
import { resolveNodeHash } from './chunk_provider_registry.mjs'
|
|
14
|
-
import { loadFileManifest, saveFileManifest } from './evfs.mjs'
|
|
15
|
-
import { fanoutFedFetch } from './fetch_fanout.mjs'
|
|
16
|
-
import { normalizeFileManifest } from './manifest.mjs'
|
|
17
|
-
import { shouldPreferIncomingPublicManifest } from './public_manifest.mjs'
|
|
15
|
+
} from './pending.mjs'
|
|
16
|
+
import { shouldPreferIncomingPublicManifest } from './public.mjs'
|
|
18
17
|
|
|
19
18
|
const DEFAULT_MANIFEST_FETCH_TIMEOUT_MS = ms('8s')
|
|
20
19
|
|
|
@@ -29,7 +28,7 @@ const manifestInflight = createInflightTable({
|
|
|
29
28
|
* 本地已有 publicSig 时立即返回,并后台 fanout;`cache: true` 时按 publishedAt 择新写盘。
|
|
30
29
|
* 冷 miss 仍等 fanout。同 key in-flight 去重;调用方外层超时不 abort,后台继续填缓存。
|
|
31
30
|
* @param {{ username: string, ownerEntityHash: string, logicalPath: string, cache?: boolean, timeoutMs?: number }} context - 拉取上下文
|
|
32
|
-
* @returns {Promise<import('./
|
|
31
|
+
* @returns {Promise<import('./normalize.mjs').FileManifest | null>} 验签后的 manifest,失败为 null
|
|
33
32
|
*/
|
|
34
33
|
export async function fetchPublicManifest(context) {
|
|
35
34
|
const ownerEntityHash = String(context.ownerEntityHash || '').trim().toLowerCase()
|
|
@@ -41,27 +40,31 @@ export async function fetchPublicManifest(context) {
|
|
|
41
40
|
? Number(context.timeoutMs)
|
|
42
41
|
: DEFAULT_MANIFEST_FETCH_TIMEOUT_MS
|
|
43
42
|
const expectedKey = manifestFetchExpectedKey(ownerEntityHash, logicalPath)
|
|
44
|
-
const inflightKey = `${username}\0${expectedKey}`
|
|
45
43
|
const wantCache = context.cache === true
|
|
46
44
|
|
|
47
45
|
// 先挂 in-flight(同步),再读本地 — 避免并发调用在 await 间隙各自 start
|
|
48
46
|
const localPromise = loadFileManifest(ownerEntityHash, logicalPath)
|
|
49
|
-
const shared =
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
47
|
+
const shared = beginFedFanoutFetch({
|
|
48
|
+
inflight: manifestInflight,
|
|
49
|
+
inflightKey: `${username}\0${expectedKey}`,
|
|
50
|
+
username,
|
|
51
|
+
action: 'fed_manifest_get',
|
|
52
|
+
/**
|
|
53
|
+
* @param {string} requestId 请求 id
|
|
54
|
+
* @returns {{ done: Promise<import('./normalize.mjs').FileManifest | null>, cancel: () => void }} 等待句柄
|
|
55
|
+
*/
|
|
56
|
+
registerWait: requestId => registerManifestFetchWait(requestId, expectedKey, timeoutMs),
|
|
57
|
+
/**
|
|
58
|
+
* @param {string} requestId 请求 id
|
|
59
|
+
* @param {string} nodeHash 目标节点 hash
|
|
60
|
+
* @returns {object} fanout 载荷
|
|
61
|
+
*/
|
|
62
|
+
buildPayload: (requestId, nodeHash) => ({
|
|
63
|
+
requestId,
|
|
64
|
+
nodeHash,
|
|
65
|
+
ownerEntityHash,
|
|
66
|
+
logicalPath,
|
|
67
|
+
}),
|
|
65
68
|
})
|
|
66
69
|
|
|
67
70
|
const local = await localPromise
|
|
@@ -69,13 +72,12 @@ export async function fetchPublicManifest(context) {
|
|
|
69
72
|
if (!shared) return hasLocalPublic ? local : null
|
|
70
73
|
|
|
71
74
|
if (hasLocalPublic) {
|
|
72
|
-
|
|
73
|
-
if (wantCache) {
|
|
75
|
+
if (wantCache)
|
|
74
76
|
void shared.then(async result => {
|
|
75
77
|
if (result && shouldPreferIncomingPublicManifest(local, result))
|
|
76
78
|
await cachePublicManifest(ownerEntityHash, logicalPath, result)
|
|
77
79
|
})
|
|
78
|
-
|
|
80
|
+
|
|
79
81
|
return local
|
|
80
82
|
}
|
|
81
83
|
|
|
@@ -89,41 +91,34 @@ export async function fetchPublicManifest(context) {
|
|
|
89
91
|
* 将已验签公开 manifest 写入本地缓存(显式 apply;`fetchPublicManifest` 默认不调用)。
|
|
90
92
|
* @param {string} ownerEntityHash owner
|
|
91
93
|
* @param {string} logicalPath 路径
|
|
92
|
-
* @param {import('./
|
|
94
|
+
* @param {import('./normalize.mjs').FileManifest} incoming 已验签入站清单
|
|
93
95
|
* @returns {Promise<void>}
|
|
94
96
|
*/
|
|
95
97
|
export async function cachePublicManifest(ownerEntityHash, logicalPath, incoming) {
|
|
96
|
-
await maybeCacheIncomingPublicManifest(ownerEntityHash, logicalPath, incoming)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* @param {string} ownerEntityHash owner
|
|
101
|
-
* @param {string} logicalPath 路径
|
|
102
|
-
* @param {import('./manifest.mjs').FileManifest} incoming 已验签入站清单
|
|
103
|
-
* @returns {Promise<void>}
|
|
104
|
-
*/
|
|
105
|
-
async function maybeCacheIncomingPublicManifest(ownerEntityHash, logicalPath, incoming) {
|
|
106
98
|
if (isWritableLocalEntity(ownerEntityHash)) return
|
|
107
|
-
const
|
|
108
|
-
const existing = await store.readManifest(ownerEntityHash, logicalPath)
|
|
99
|
+
const existing = await getEntityStore().readManifest(ownerEntityHash, logicalPath)
|
|
109
100
|
if (existing && !shouldPreferIncomingPublicManifest(existing, incoming)) return
|
|
110
101
|
await saveFileManifest(incoming)
|
|
111
102
|
}
|
|
112
103
|
|
|
113
104
|
/**
|
|
114
105
|
* 若本机有已签名公开 manifest 则响应 fed_manifest_get。
|
|
115
|
-
* @param {string} username 用户
|
|
116
106
|
* @param {object} payload 请求
|
|
117
107
|
* @param {(response: object, peerId: string) => void} sendResponse 发送
|
|
118
108
|
* @param {string} peerId 对端
|
|
119
109
|
* @returns {Promise<void>}
|
|
120
110
|
*/
|
|
121
|
-
export async function handleIncomingManifestGet(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
111
|
+
export async function handleIncomingManifestGet(payload, sendResponse, peerId) {
|
|
112
|
+
const parsedOwner = parseEntityHash(payload?.ownerEntityHash)
|
|
113
|
+
if (!parsedOwner) return
|
|
114
|
+
let logicalPath
|
|
115
|
+
try {
|
|
116
|
+
logicalPath = assertSafeEvfsLogicalPath(payload?.logicalPath)
|
|
117
|
+
}
|
|
118
|
+
catch { return }
|
|
125
119
|
const requestId = String(payload?.requestId || '')
|
|
126
|
-
if (!
|
|
120
|
+
if (!requestId) return
|
|
121
|
+
const { entityHash: ownerEntityHash } = parsedOwner
|
|
127
122
|
|
|
128
123
|
const raw = await getEntityStore().readManifest(ownerEntityHash, logicalPath)
|
|
129
124
|
const manifest = normalizeFileManifest(raw)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { isEntityHash128 } from '
|
|
2
|
-
import { isHex64 } from '
|
|
1
|
+
import { isEntityHash128 } from '../../core/entity_id.mjs'
|
|
2
|
+
import { isHex64 } from '../../core/hexIds.mjs'
|
|
3
3
|
|
|
4
4
|
/** @typedef {'plain' | 'convergent' | 'random'} CeMode */
|
|
5
5
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { compositeKey } from '../../core/composite_key.mjs'
|
|
2
|
+
import { createFetchWaitTable } from '../../utils/fetch_wait.mjs'
|
|
3
|
+
|
|
4
|
+
import { verifySignedPublicManifest } from './public.mjs'
|
|
5
|
+
|
|
6
|
+
/** 并发 pending manifest fetch 上限。 */
|
|
7
|
+
export const MAX_PENDING_MANIFEST_FETCHES = 512
|
|
8
|
+
|
|
9
|
+
const table = createFetchWaitTable({ maxSize: MAX_PENDING_MANIFEST_FETCHES })
|
|
10
|
+
|
|
11
|
+
/** @type {typeof table.pending} */
|
|
12
|
+
export const pendingManifestFetches = table.pending
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} ownerEntityHash owner
|
|
16
|
+
* @param {string} logicalPath 路径
|
|
17
|
+
* @returns {string} 期望键
|
|
18
|
+
*/
|
|
19
|
+
export function manifestFetchExpectedKey(ownerEntityHash, logicalPath) {
|
|
20
|
+
return compositeKey(
|
|
21
|
+
String(ownerEntityHash || '').trim().toLowerCase(),
|
|
22
|
+
String(logicalPath || '').trim().replace(/^\/+/, ''),
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {string} key requestId
|
|
28
|
+
* @param {string} expectedKey owner+path 复合键
|
|
29
|
+
* @param {number} timeoutMs 超时毫秒
|
|
30
|
+
* @returns {{ done: Promise<object | null>, cancel: () => void }} 等待 Promise 与取消
|
|
31
|
+
*/
|
|
32
|
+
export function registerManifestFetchWait(key, expectedKey, timeoutMs) {
|
|
33
|
+
return table.register(key, expectedKey, timeoutMs)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 处理 fed_manifest_data:验签通过后 resolve pending。
|
|
38
|
+
* @param {object} payload 入站载荷
|
|
39
|
+
* @returns {Promise<boolean>} 是否命中并完成等待
|
|
40
|
+
*/
|
|
41
|
+
export async function resolvePendingManifestFetch(payload) {
|
|
42
|
+
const requestId = String(payload?.requestId || '')
|
|
43
|
+
if (!requestId) return false
|
|
44
|
+
const entry = table.peek(requestId)
|
|
45
|
+
if (!entry) return false
|
|
46
|
+
|
|
47
|
+
const verified = await verifySignedPublicManifest(payload?.manifest)
|
|
48
|
+
if (!verified) return false
|
|
49
|
+
const key = manifestFetchExpectedKey(verified.ownerEntityHash, verified.logicalPath)
|
|
50
|
+
if (key !== entry.expectedKey) return false
|
|
51
|
+
|
|
52
|
+
return table.settle(requestId, verified)
|
|
53
|
+
}
|