@steve02081504/fount-p2p 0.0.22 → 0.0.24
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/entity_id.mjs +5 -5
- package/core/logical_entity.mjs +1 -1
- package/core/object.mjs +9 -0
- package/core/partpath.mjs +10 -0
- package/core/random_id.mjs +4 -2
- package/crypto/crypto.mjs +26 -93
- 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 +9 -8
- 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 +124 -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
|
@@ -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
|
+
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer'
|
|
2
2
|
|
|
3
|
-
import { canonicalStringify } from '
|
|
4
|
-
import { hashFromPubKeyHex, parseEntityHash } from '
|
|
5
|
-
import { assertSafeEvfsLogicalPath } from '
|
|
6
|
-
import { isHex64, normalizeHex64 } from '
|
|
7
|
-
import { sign, verify } from '
|
|
3
|
+
import { canonicalStringify } from '../../core/canonical_json.mjs'
|
|
4
|
+
import { hashFromPubKeyHex, parseEntityHash } from '../../core/entity_id.mjs'
|
|
5
|
+
import { assertSafeEvfsLogicalPath } from '../../core/evfs_logical_path.mjs'
|
|
6
|
+
import { isHex64, normalizeHex64 } from '../../core/hexIds.mjs'
|
|
7
|
+
import { sign, verify } from '../../crypto/crypto.mjs'
|
|
8
|
+
import { putFileManifest, saveFileManifest } from '../evfs.mjs'
|
|
8
9
|
|
|
9
|
-
import {
|
|
10
|
-
import { normalizeFileManifest, publicTransferKeyDescriptor } from './manifest.mjs'
|
|
10
|
+
import { normalizeFileManifest, publicTransferKeyDescriptor } from './normalize.mjs'
|
|
11
11
|
|
|
12
12
|
/** 实体公开 manifest 签名域 */
|
|
13
13
|
export const ENTITY_PUBLIC_MANIFEST_DOMAIN = 'fount-entity-public-manifest'
|
|
@@ -32,11 +32,11 @@ export function publicManifestSignBytes(fields) {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* @param {import('./
|
|
35
|
+
* @param {import('./normalize.mjs').FileManifest} manifest 清单
|
|
36
36
|
* @param {number} publishedAt 发布时间
|
|
37
37
|
* @param {Uint8Array | Buffer} entitySecretKey recovery 私钥种子
|
|
38
38
|
* @param {string} entityPubKeyHex recovery 公钥 hex
|
|
39
|
-
* @returns {Promise<import('./
|
|
39
|
+
* @returns {Promise<import('./normalize.mjs').FileManifest>} 带 publicSig 的清单
|
|
40
40
|
*/
|
|
41
41
|
export async function attachPublicManifestSig(manifest, publishedAt, entitySecretKey, entityPubKeyHex) {
|
|
42
42
|
const pubKeyHex = normalizeHex64(entityPubKeyHex)
|
|
@@ -63,7 +63,7 @@ export async function attachPublicManifestSig(manifest, publishedAt, entitySecre
|
|
|
63
63
|
|
|
64
64
|
/**
|
|
65
65
|
* @param {unknown} input 原始 manifest
|
|
66
|
-
* @returns {Promise<import('./
|
|
66
|
+
* @returns {Promise<import('./normalize.mjs').FileManifest | null>} 验签通过的清单;非法为 null
|
|
67
67
|
*/
|
|
68
68
|
export async function verifySignedPublicManifest(input) {
|
|
69
69
|
const manifest = normalizeFileManifest(input)
|
|
@@ -105,7 +105,7 @@ export async function verifySignedPublicManifest(input) {
|
|
|
105
105
|
|
|
106
106
|
/**
|
|
107
107
|
* @param {object | null | undefined} localManifest 本地已有清单
|
|
108
|
-
* @param {import('./
|
|
108
|
+
* @param {import('./normalize.mjs').FileManifest} incoming 入站已验签清单
|
|
109
109
|
* @returns {boolean} 是否应以 incoming 覆盖本地缓存
|
|
110
110
|
*/
|
|
111
111
|
export function shouldPreferIncomingPublicManifest(localManifest, incoming) {
|
|
@@ -125,7 +125,7 @@ export function shouldPreferIncomingPublicManifest(localManifest, incoming) {
|
|
|
125
125
|
* @param {Uint8Array | Buffer} parameters.entitySecretKey recovery 私钥种子
|
|
126
126
|
* @param {string} parameters.entityPubKeyHex recovery 公钥 hex
|
|
127
127
|
* @param {number} [parameters.publishedAt] 发布时间(默认 Date.now)
|
|
128
|
-
* @returns {Promise<import('./
|
|
128
|
+
* @returns {Promise<import('./normalize.mjs').FileManifest>} 已签名并落盘的公开清单
|
|
129
129
|
*/
|
|
130
130
|
export async function publishPublicFile(parameters) {
|
|
131
131
|
const {
|
package/files/transfer_key.mjs
CHANGED
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
} from '../crypto/key.mjs'
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* @typedef {import('./manifest.mjs').FileManifest} FileManifest
|
|
12
|
-
* @typedef {import('./manifest.mjs').TransferKeyDescriptor} TransferKeyDescriptor
|
|
11
|
+
* @typedef {import('./manifest/normalize.mjs').FileManifest} FileManifest
|
|
12
|
+
* @typedef {import('./manifest/normalize.mjs').TransferKeyDescriptor} TransferKeyDescriptor
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** @type {Map<string, { getGroupFileMasterKey?: (replicaUsername: string, groupId: string, keyGeneration?: number) => Promise<Buffer | string | null>, getVaultMasterKey?: (replicaUsername: string, entityHash: string) => Promise<Buffer | string | null> }>} */
|
|
2
2
|
const transferDependenciesByOwner = new Map()
|
|
3
3
|
|
|
4
|
-
/** @type {Map<string, (replicaUsername: string, manifest: import('./manifest.mjs').FileManifest) => Promise<Buffer | null>>} */
|
|
4
|
+
/** @type {Map<string, (replicaUsername: string, manifest: import('./manifest/normalize.mjs').FileManifest) => Promise<Buffer | null>>} */
|
|
5
5
|
const dagPlaintextReadersByOwner = new Map()
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -17,19 +17,19 @@ export function registerTransferKeyDependencies(ownerId, dependencies) {
|
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* @param {string} ownerId 注册方
|
|
20
|
-
* @param {(replicaUsername: string, manifest: import('./manifest.mjs').FileManifest) => Promise<Buffer | null>} reader dagParts 明文读取
|
|
20
|
+
* @param {(replicaUsername: string, manifest: import('./manifest/normalize.mjs').FileManifest) => Promise<Buffer | null>} reader dagParts 明文读取
|
|
21
21
|
* @returns {void}
|
|
22
22
|
*/
|
|
23
23
|
export function registerDagManifestPlaintextReader(ownerId, reader) {
|
|
24
24
|
dagPlaintextReadersByOwner.set(String(ownerId), reader)
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
/** @type {Array<{ ownerId: string, match: (manifest: import('./manifest.mjs').FileManifest) => boolean }>} */
|
|
27
|
+
/** @type {Array<{ ownerId: string, match: (manifest: import('./manifest/normalize.mjs').FileManifest) => boolean }>} */
|
|
28
28
|
const manifestOwnerMatchers = []
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* @param {string} ownerId 注册方
|
|
32
|
-
* @param {(manifest: import('./manifest.mjs').FileManifest) => boolean} match manifest 匹配谓词
|
|
32
|
+
* @param {(manifest: import('./manifest/normalize.mjs').FileManifest) => boolean} match manifest 匹配谓词
|
|
33
33
|
* @returns {void}
|
|
34
34
|
*/
|
|
35
35
|
export function registerManifestOwnerMatcher(ownerId, match) {
|
|
@@ -59,7 +59,7 @@ export function unregisterTransferKeyDependencies(ownerId) {
|
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
61
|
* 从 manifest 推断 transfer key 注册方 id(按 registerManifestOwnerMatcher 注册顺序匹配)。
|
|
62
|
-
* @param {import('./manifest.mjs').FileManifest} manifest 清单
|
|
62
|
+
* @param {import('./manifest/normalize.mjs').FileManifest} manifest 清单
|
|
63
63
|
* @returns {string | null} 所有者 id
|
|
64
64
|
*/
|
|
65
65
|
export function resolveManifestTransferOwnerId(manifest) {
|
|
@@ -70,7 +70,7 @@ export function resolveManifestTransferOwnerId(manifest) {
|
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
72
|
* @param {string} [ownerId] 显式注册方;省略时按 manifest 推断
|
|
73
|
-
* @param {import('./manifest.mjs').FileManifest} [manifest] 用于推断 ownerId
|
|
73
|
+
* @param {import('./manifest/normalize.mjs').FileManifest} [manifest] 用于推断 ownerId
|
|
74
74
|
* @returns {{ getGroupFileMasterKey?: (replicaUsername: string, groupId: string, keyGeneration?: number) => Promise<Buffer | string | null>, getVaultMasterKey?: (replicaUsername: string, entityHash: string) => Promise<Buffer | string | null> }} 依赖
|
|
75
75
|
*/
|
|
76
76
|
export function resolveTransferKeyDependencies(ownerId, manifest) {
|
|
@@ -81,7 +81,7 @@ export function resolveTransferKeyDependencies(ownerId, manifest) {
|
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
83
|
* @param {string} replicaUsername 副本用户名
|
|
84
|
-
* @param {import('./manifest.mjs').FileManifest} manifest 清单
|
|
84
|
+
* @param {import('./manifest/normalize.mjs').FileManifest} manifest 清单
|
|
85
85
|
* @returns {Promise<Buffer | null>} 明文;无 reader 或未命中为 null
|
|
86
86
|
*/
|
|
87
87
|
export async function readDagManifestPlaintext(replicaUsername, manifest) {
|
package/index.mjs
CHANGED
|
@@ -32,7 +32,6 @@ import {
|
|
|
32
32
|
unlockReputationMax,
|
|
33
33
|
} from './node/reputation_sync.mjs'
|
|
34
34
|
import { getRoutingProfile, setRoutingProfile } from './node/routing_profile.mjs'
|
|
35
|
-
import { createScopedLinkRoom } from './rooms/scoped_link.mjs'
|
|
36
35
|
import { createGroupLinkSet } from './transport/group_link_set.mjs'
|
|
37
36
|
import {
|
|
38
37
|
configureLinkRegistry,
|
|
@@ -42,9 +41,10 @@ import {
|
|
|
42
41
|
reloadDiscoveryRelays,
|
|
43
42
|
sendToNodeLink,
|
|
44
43
|
} from './transport/link_registry.mjs'
|
|
44
|
+
import { attachNodeScopeDefaultFeatures } from './transport/node_scope/features.mjs'
|
|
45
|
+
import { ensureNodeScope } from './transport/node_scope/wire.mjs'
|
|
46
|
+
import { createScopedLinkRoom } from './transport/scoped_link.mjs'
|
|
45
47
|
import {
|
|
46
|
-
attachUserRoomDefaultWires,
|
|
47
|
-
ensureNodeScope,
|
|
48
48
|
ensureUserRoom,
|
|
49
49
|
getUserRoomSlot,
|
|
50
50
|
} from './transport/user_room.mjs'
|
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
*/
|
|
55
55
|
export {
|
|
56
56
|
attachReputationSyncWire,
|
|
57
|
-
|
|
57
|
+
attachNodeScopeDefaultFeatures,
|
|
58
58
|
configureLinkRegistry,
|
|
59
59
|
createGroupLinkSet,
|
|
60
60
|
createScopedLinkRoom,
|
package/infra/service.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { isNodeInitialized } from '../node/instance.mjs'
|
|
3
3
|
import { setOverlayRateGate, clearOverlayRateGate } from '../overlay/index.mjs'
|
|
4
4
|
import { getLinkRegistry } from '../transport/link_registry.mjs'
|
|
5
|
-
import { attachNodeScopeMailbox } from '../transport/node_scope.mjs'
|
|
5
|
+
import { attachNodeScopeMailbox } from '../transport/node_scope/features.mjs'
|
|
6
6
|
|
|
7
7
|
import { attachInfraDebugLog, detachInfraDebugLog } from './debug_log.mjs'
|
|
8
8
|
import {
|
package/link/handshake.mjs
CHANGED
|
@@ -9,9 +9,6 @@ import { ensureNodeSeed, getNodeHash } from '../node/identity.mjs'
|
|
|
9
9
|
|
|
10
10
|
import { normalizeDtlsFingerprint } from './sdp_fingerprint.mjs'
|
|
11
11
|
|
|
12
|
-
/** advert / peer-hint 端口规范化(re-export,调用方可直接从 core/tcp_port 导入) */
|
|
13
|
-
export const normalizeAdvertTcpPort = normalizeTcpPort
|
|
14
|
-
|
|
15
12
|
/**
|
|
16
13
|
* Link 握手签名域标识符。
|
|
17
14
|
*/
|
|
@@ -23,7 +23,6 @@ import {
|
|
|
23
23
|
* @returns {Promise<string | null>} 已验证的 remote nodeHash
|
|
24
24
|
*/
|
|
25
25
|
async function resolveRemoteNodeHashForPeer(username, peerId) {
|
|
26
|
-
void username
|
|
27
26
|
if (!peerId) return null
|
|
28
27
|
const entry = activeLinkRoster().find(row => row.peerId === peerId)
|
|
29
28
|
const remote = entry?.remoteNodeHash?.trim().toLowerCase()
|
|
@@ -51,7 +50,6 @@ async function resolveRelayHopForIngress(record) {
|
|
|
51
50
|
* @returns {Promise<{ maxHop: number, relayFanoutTrusted: number, relayFanoutNormal: number, wantFanout: number, batterySaver: boolean }>} 按在线 peer 数缩放的路由
|
|
52
51
|
*/
|
|
53
52
|
async function resolveRouting(username) {
|
|
54
|
-
void username
|
|
55
53
|
const { batterySaver, mailbox } = getNodeTransportSettings()
|
|
56
54
|
const peerCount = activeLinkRoster().length
|
|
57
55
|
return resolveMailboxRoutingForPeerCount(peerCount, mailbox, batterySaver)
|
package/mailbox/parse.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { assertHex64 } from '../core/hexIds.mjs'
|
|
2
|
+
import { isPlainObject } from '../core/object.mjs'
|
|
2
3
|
import {
|
|
3
4
|
assertMailboxPubKeyHash,
|
|
4
5
|
assertMailboxRecordShape,
|
|
5
6
|
} from '../schemas/mailbox.mjs'
|
|
6
|
-
import { isPlainObject } from '../wire/ingress.mjs'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @typedef {import('../schemas/mailbox.mjs').MailboxWireErr} MailboxParseErr
|
package/mailbox/store.mjs
CHANGED
|
@@ -11,19 +11,10 @@ import {
|
|
|
11
11
|
sortMailboxForRetention,
|
|
12
12
|
} from './importance.mjs'
|
|
13
13
|
import {
|
|
14
|
-
MAX_BUCKET_BYTES,
|
|
15
|
-
MAX_BUCKET_ENTRIES,
|
|
16
|
-
mailboxBucketKey,
|
|
17
|
-
mailboxRecordBytes,
|
|
18
14
|
pruneMailboxBuckets,
|
|
19
15
|
pruneMailboxGlobalFair,
|
|
20
16
|
} from './prune.mjs'
|
|
21
17
|
|
|
22
|
-
/**
|
|
23
|
-
* mailbox 桶裁剪与计量辅助(re-export)。
|
|
24
|
-
*/
|
|
25
|
-
export { MAX_BUCKET_BYTES, MAX_BUCKET_ENTRIES, mailboxBucketKey, mailboxRecordBytes }
|
|
26
|
-
|
|
27
18
|
/** 单条 mailbox envelope JSON 字节上限 */
|
|
28
19
|
export const MAX_ENTRY_BYTES = 256 * 1024
|
|
29
20
|
|
package/mailbox/wire.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { subscribeWire } from '../wire/subscribe.mjs'
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
ingestMailboxGive,
|
|
3
5
|
ingestMailboxPut,
|
|
@@ -5,23 +7,30 @@ import {
|
|
|
5
7
|
} from './deliver_or_store.mjs'
|
|
6
8
|
import { parseMailboxGive, parseMailboxPut, parseMailboxWant } from './parse.mjs'
|
|
7
9
|
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
*/
|
|
10
|
+
/** @typedef {import('../wire/adapter.mjs').WireContext} MailboxWireContext */
|
|
11
|
+
/** @typedef {import('../wire/adapter.mjs').WireAdapter} WireAdapter */
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* @param {MailboxWireContext} wireContext 入站上下文
|
|
14
|
-
* @param {
|
|
15
|
+
* @param {WireAdapter} wire action 表
|
|
15
16
|
* @returns {() => void} 取消挂载的 dispose
|
|
16
17
|
*/
|
|
17
18
|
export function attachMailboxWire(wireContext, wire) {
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
return subscribeWire(wire, {
|
|
20
|
+
/**
|
|
21
|
+
* @param {unknown} payload mailbox_put 载荷
|
|
22
|
+
* @param {string} peerId 对端 nodeHash
|
|
23
|
+
*/
|
|
24
|
+
mailbox_put(payload, peerId) {
|
|
20
25
|
const put = parseMailboxPut(payload)
|
|
21
26
|
if (!put.ok) return
|
|
22
27
|
void ingestMailboxPut(wireContext, put.value, peerId).catch(error => console.error('mailbox: put ingest failed', error))
|
|
23
|
-
}
|
|
24
|
-
|
|
28
|
+
},
|
|
29
|
+
/**
|
|
30
|
+
* @param {unknown} payload mailbox_want 载荷
|
|
31
|
+
* @param {string} peerId 对端 nodeHash
|
|
32
|
+
*/
|
|
33
|
+
mailbox_want(payload, peerId) {
|
|
25
34
|
const want = parseMailboxWant(payload)
|
|
26
35
|
if (!want.ok) return
|
|
27
36
|
void respondMailboxWant(want.value, (giveWire, targetPeerId) => {
|
|
@@ -30,15 +39,14 @@ export function attachMailboxWire(wireContext, wire) {
|
|
|
30
39
|
}
|
|
31
40
|
catch { /* disconnected */ }
|
|
32
41
|
}, peerId).catch(error => console.error('mailbox: want failed', error))
|
|
33
|
-
}
|
|
34
|
-
|
|
42
|
+
},
|
|
43
|
+
/**
|
|
44
|
+
* @param {unknown} payload mailbox_give 载荷
|
|
45
|
+
*/
|
|
46
|
+
mailbox_give(payload) {
|
|
35
47
|
const give = parseMailboxGive(payload)
|
|
36
48
|
if (!give.ok) return
|
|
37
49
|
void ingestMailboxGive(wireContext, give.value).catch(error => console.error('mailbox: give ingest failed', error))
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return () => {
|
|
41
|
-
for (const off of offs)
|
|
42
|
-
try { off?.() } catch { /* ignore */ }
|
|
43
|
-
}
|
|
50
|
+
},
|
|
51
|
+
})
|
|
44
52
|
}
|
package/node/reputation_sync.mjs
CHANGED
|
@@ -2,7 +2,9 @@ import { randomUUID } from 'node:crypto'
|
|
|
2
2
|
|
|
3
3
|
import { isHex64, normalizeHex64 } from '../core/hexIds.mjs'
|
|
4
4
|
import { sendToNodeLink } from '../transport/link_registry.mjs'
|
|
5
|
-
import { attachNodeScopeFeature
|
|
5
|
+
import { attachNodeScopeFeature } from '../transport/node_scope/features.mjs'
|
|
6
|
+
import { ensureNodeScope, getNodeScopeWire } from '../transport/node_scope/wire.mjs'
|
|
7
|
+
import { subscribeWire } from '../wire/subscribe.mjs'
|
|
6
8
|
|
|
7
9
|
import {
|
|
8
10
|
loadReputation,
|
|
@@ -226,34 +228,36 @@ function exportScoreTable() {
|
|
|
226
228
|
export function attachReputationSyncWire() {
|
|
227
229
|
ensureNodeScope()
|
|
228
230
|
if (!getNodeScopeWire()) throw new Error('p2p: attachReputationSyncWire requires node scope wire')
|
|
229
|
-
const dispose = attachNodeScopeFeature('rep_sync', wire => {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
231
|
+
const dispose = attachNodeScopeFeature('rep_sync', wire => subscribeWire(wire, {
|
|
232
|
+
/**
|
|
233
|
+
* @param {unknown} payload rep_sync_req 载荷
|
|
234
|
+
* @param {string} peerId 对端 nodeHash
|
|
235
|
+
*/
|
|
236
|
+
rep_sync_req(payload, peerId) {
|
|
237
|
+
const requester = normalizeHex64(peerId)
|
|
238
|
+
if (!requester || !getReputationExportAllowlist().includes(requester)) return
|
|
239
|
+
try {
|
|
240
|
+
wire.send('rep_sync_res', {
|
|
241
|
+
requestId: payload?.requestId,
|
|
242
|
+
...exportScoreTable(),
|
|
243
|
+
}, peerId)
|
|
244
|
+
}
|
|
245
|
+
catch { /* disconnected */ }
|
|
246
|
+
},
|
|
247
|
+
/**
|
|
248
|
+
* @param {unknown} payload rep_sync_res 载荷
|
|
249
|
+
* @param {string} peerId 对端 nodeHash
|
|
250
|
+
*/
|
|
251
|
+
rep_sync_res(payload, peerId) {
|
|
252
|
+
const requestId = String(payload?.requestId || '')
|
|
253
|
+
const pending = pendingPulls.get(requestId)
|
|
254
|
+
if (!pending) return
|
|
255
|
+
if (normalizeHex64(peerId) !== pending.donor) return
|
|
256
|
+
clearTimeout(pending.timer)
|
|
257
|
+
pendingPulls.delete(requestId)
|
|
258
|
+
pending.resolve(payload)
|
|
259
|
+
},
|
|
260
|
+
}))
|
|
257
261
|
syncWireDisposers.add(dispose)
|
|
258
262
|
return () => {
|
|
259
263
|
if (!syncWireDisposers.delete(dispose)) return
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steve02081504/fount-p2p",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.24",
|
|
4
4
|
"description": "fount federation P2P layer — link, trust graph, mailbox, DAG, EVFS.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"network",
|
|
@@ -41,19 +41,15 @@
|
|
|
41
41
|
"./crypto": "./crypto/crypto.mjs",
|
|
42
42
|
"./crypto/*": "./crypto/*.mjs",
|
|
43
43
|
"./wire/*": "./wire/*.mjs",
|
|
44
|
+
"./wire/part/*": "./wire/part/*.mjs",
|
|
44
45
|
"./schemas/*": "./schemas/*.mjs",
|
|
45
46
|
"./node/*": "./node/*.mjs",
|
|
46
47
|
"./discovery": "./discovery/index.mjs",
|
|
47
48
|
"./discovery/bt": "./discovery/bt/index.mjs",
|
|
48
49
|
"./discovery/bt/*": "./discovery/bt/*.mjs",
|
|
49
50
|
"./discovery/*": "./discovery/*.mjs",
|
|
50
|
-
"./transport/
|
|
51
|
-
"./transport
|
|
52
|
-
"./transport/group_link_set": "./transport/group_link_set.mjs",
|
|
53
|
-
"./transport/node_scope": "./transport/node_scope.mjs",
|
|
54
|
-
"./transport/room_scopes": "./transport/room_scopes.mjs",
|
|
55
|
-
"./transport/remote_user_room": "./transport/remote_user_room.mjs",
|
|
56
|
-
"./rooms/*": "./rooms/*.mjs",
|
|
51
|
+
"./transport/node_scope/*": "./transport/node_scope/*.mjs",
|
|
52
|
+
"./transport/*": "./transport/*.mjs",
|
|
57
53
|
"./overlay": "./overlay/index.mjs",
|
|
58
54
|
"./overlay/*": "./overlay/*.mjs",
|
|
59
55
|
"./trust_graph/*": "./trust_graph/*.mjs",
|
|
@@ -62,7 +58,11 @@
|
|
|
62
58
|
"./dag/*": "./dag/*.mjs",
|
|
63
59
|
"./timeline/*": "./timeline/*.mjs",
|
|
64
60
|
"./files/*": "./files/*.mjs",
|
|
61
|
+
"./files/chunk/*": "./files/chunk/*.mjs",
|
|
62
|
+
"./files/manifest/*": "./files/manifest/*.mjs",
|
|
63
|
+
"./files/fed/*": "./files/fed/*.mjs",
|
|
65
64
|
"./federation/*": "./federation/*.mjs",
|
|
65
|
+
"./federation/part_query/*": "./federation/part_query/*.mjs",
|
|
66
66
|
"./governance/*": "./governance/*.mjs",
|
|
67
67
|
"./registries/*": "./registries/*.mjs",
|
|
68
68
|
"./permissions": "./permissions/index.mjs",
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
73
|
"@noble/curves": "latest",
|
|
74
|
+
"@noble/hashes": "latest",
|
|
74
75
|
"node-rtc-connection": "latest",
|
|
75
76
|
"on-shutdown": "latest",
|
|
76
77
|
"ws": "latest"
|