@steve02081504/fount-p2p 0.0.3 → 0.0.4
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 +1 -1
- package/{entity → core}/logical_entity.mjs +2 -1
- package/{entity/files → files}/acl.mjs +3 -3
- package/{entity/files → files}/evfs.mjs +21 -22
- package/{entity/files → files}/evfs_ref.mjs +3 -5
- package/{entity/files → files}/manifest_acl_registry.mjs +4 -4
- package/files/manifest_fetch.mjs +2 -2
- package/files/public_manifest.mjs +1 -1
- package/node/identity.mjs +33 -2
- package/node/personal_block.mjs +1 -1
- package/package.json +1 -3
- package/entity/files/replica_host_cache.mjs +0 -46
- package/entity/hosting_registry.mjs +0 -47
- package/entity/logical_entity_id_registry.mjs +0 -40
- package/entity/node_hash.mjs +0 -15
- package/entity/replica.mjs +0 -20
- package/entity/session_snapshot_registry.mjs +0 -38
- package/registries/p2p_viewer.mjs +0 -35
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ Subpath exports mirror source directories, e.g. `@steve02081504/fount-p2p/dag`,
|
|
|
34
34
|
| L1 | `crypto/`, `wire/`, `schemas/` | Cryptography, wire protocol, canonical validation |
|
|
35
35
|
| L2 | `node/` | Node runtime: `identity`, `entity_store`, `denylist`, `reputation_store` |
|
|
36
36
|
| L3 | `discovery/`, `link/`, `transport/`, `rooms/` | Discovery, RTC links, rooms |
|
|
37
|
-
| L4 | `trust_graph/`, `mailbox/`, `dag/`, `federation/`, `files
|
|
37
|
+
| L4 | `trust_graph/`, `mailbox/`, `dag/`, `federation/`, `files/` | Federation, store-and-forward, DAG, EVFS |
|
|
38
38
|
| — | `registries/` | Pluggable registries (event type, part path, room provider, …) |
|
|
39
39
|
|
|
40
40
|
Facade entry: `index.mjs` (`startNode`, `createGroupLinkSet`, `registerDiscoveryProvider`, …).
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { encodeEntityHash, parseEntityHash } from '../core/entity_id.mjs'
|
|
2
1
|
import { sha256TextHex } from '../crypto/crypto.mjs'
|
|
3
2
|
|
|
3
|
+
import { encodeEntityHash, parseEntityHash } from './entity_id.mjs'
|
|
4
|
+
|
|
4
5
|
/** @type {string} 逻辑实体 sentinel nodeHash(非物理节点绑定) */
|
|
5
6
|
export const LOGICAL_ENTITY_SENTINEL_NODE_HASH = '0'.repeat(64)
|
|
6
7
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { isLogicalEntityHash } from '../logical_entity.mjs'
|
|
2
|
-
import { isWritableLocalEntity } from '../
|
|
1
|
+
import { isLogicalEntityHash } from '../core/logical_entity.mjs'
|
|
2
|
+
import { isWritableLocalEntity } from '../node/identity.mjs'
|
|
3
3
|
|
|
4
4
|
import { checkManifestAcl, resolveManifestAclType } from './manifest_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('./manifest.mjs').FileManifest} manifest 清单
|
|
10
10
|
* @returns {Promise<boolean>} 是否允许读
|
|
11
11
|
*/
|
|
12
12
|
export async function canReadManifest(replicaUsername, ownerEntityHash, manifest) {
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer'
|
|
2
2
|
import { Readable } from 'node:stream'
|
|
3
3
|
|
|
4
|
-
import { FEDERATION_CHUNK_MAX_BYTES } from '
|
|
5
|
-
import {
|
|
6
|
-
import { encryptReadableToParts } from '../../files/assemble_stream.mjs'
|
|
7
|
-
import { fetchChunk } from '../../files/chunk_fetch.mjs'
|
|
8
|
-
import { getChunk, hasChunk, putChunk } from '../../files/chunk_store.mjs'
|
|
9
|
-
import { normalizeFileManifest, publicTransferKeyDescriptor } from '../../files/manifest.mjs'
|
|
10
|
-
import { assembleManifestPlaintext } from '../../files/transfer_key.mjs'
|
|
11
|
-
import { readDagManifestPlaintext, resolveTransferKeyDeps } from '../../files/transfer_key_registry.mjs'
|
|
12
|
-
import { getEntityStore } from '../../node/instance.mjs'
|
|
4
|
+
import { FEDERATION_CHUNK_MAX_BYTES } from '../core/constants.mjs'
|
|
5
|
+
import { getEntityStore } from '../node/instance.mjs'
|
|
13
6
|
|
|
7
|
+
import { buildFileManifestFromEnc, encryptPlaintextToParts, encryptPlaintextToMultiPartsAsync } from './assemble.mjs'
|
|
8
|
+
import { encryptReadableToParts } from './assemble_stream.mjs'
|
|
9
|
+
import { fetchChunk } from './chunk_fetch.mjs'
|
|
10
|
+
import { getChunk, hasChunk, putChunk } from './chunk_store.mjs'
|
|
11
|
+
import { normalizeFileManifest, publicTransferKeyDescriptor } from './manifest.mjs'
|
|
12
|
+
import { assembleManifestPlaintext } from './transfer_key.mjs'
|
|
13
|
+
import { readDagManifestPlaintext, resolveTransferKeyDeps } from './transfer_key_registry.mjs'
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* @param {string} ownerEntityHash 所有者
|
|
17
17
|
* @param {string} logicalPath 路径
|
|
18
|
-
* @returns {Promise<import('
|
|
18
|
+
* @returns {Promise<import('./manifest.mjs').FileManifest | null>} 归一化 manifest
|
|
19
19
|
*/
|
|
20
20
|
export async function loadFileManifest(ownerEntityHash, logicalPath) {
|
|
21
21
|
const manifest = await getEntityStore().readManifest(ownerEntityHash, logicalPath)
|
|
@@ -23,7 +23,7 @@ export async function loadFileManifest(ownerEntityHash, logicalPath) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* @param {import('
|
|
26
|
+
* @param {import('./manifest.mjs').FileManifest} manifest 清单
|
|
27
27
|
* @returns {Promise<void>}
|
|
28
28
|
*/
|
|
29
29
|
export async function saveFileManifest(manifest) {
|
|
@@ -31,7 +31,7 @@ export async function saveFileManifest(manifest) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* @param {import('
|
|
34
|
+
* @param {import('./manifest.mjs').FileManifest} manifest 清单
|
|
35
35
|
* @param {Array<Buffer | Uint8Array>} partBytes 密文块
|
|
36
36
|
* @returns {Promise<void>}
|
|
37
37
|
*/
|
|
@@ -42,7 +42,7 @@ export async function storeManifestParts(manifest, partBytes) {
|
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* @param {string} replicaUsername 副本用户名
|
|
45
|
-
* @param {import('
|
|
45
|
+
* @param {import('./manifest.mjs').FileManifest} manifest 清单
|
|
46
46
|
* @param {{ username?: string, fetchChunk?: Function }} [opts] miss 拉取
|
|
47
47
|
* @returns {Promise<Buffer | null>} 明文内容
|
|
48
48
|
*/
|
|
@@ -90,7 +90,7 @@ export async function readManifestPlaintext(replicaUsername, manifest, opts = {}
|
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
92
|
* @param {string} replicaUsername 副本用户名
|
|
93
|
-
* @param {import('
|
|
93
|
+
* @param {import('./manifest.mjs').FileManifest} manifest 清单
|
|
94
94
|
* @param {{ username?: string, fetchChunk?: Function }} [opts] miss 拉取
|
|
95
95
|
* @returns {Promise<import('node:stream').Readable | null>} 明文流
|
|
96
96
|
*/
|
|
@@ -107,13 +107,12 @@ export async function readManifestPlaintextStream(replicaUsername, manifest, opt
|
|
|
107
107
|
* @param {Buffer | Uint8Array} params.plaintext 明文
|
|
108
108
|
* @param {string} [params.name] 文件名
|
|
109
109
|
* @param {string} [params.mimeType] MIME
|
|
110
|
-
* @param {import('
|
|
111
|
-
* @param {import('
|
|
110
|
+
* @param {import('./manifest.mjs').CeMode} [params.ceMode] 模式
|
|
111
|
+
* @param {import('./manifest.mjs').TransferKeyDescriptor} [params.transferKeyDescriptor] 传递密钥
|
|
112
112
|
* @param {object} [params.meta] meta
|
|
113
|
-
* @returns {Promise<import('
|
|
113
|
+
* @returns {Promise<import('./manifest.mjs').FileManifest>} 写入后的 manifest
|
|
114
114
|
*/
|
|
115
115
|
export async function putFileManifest(params) {
|
|
116
|
-
const { encryptPlaintextToParts, encryptPlaintextToMultiPartsAsync } = await import('../../files/assemble.mjs')
|
|
117
116
|
const {
|
|
118
117
|
ownerEntityHash,
|
|
119
118
|
logicalPath,
|
|
@@ -152,10 +151,10 @@ export async function putFileManifest(params) {
|
|
|
152
151
|
* @param {number} params.plainSize 明文字节数
|
|
153
152
|
* @param {string} [params.name] 文件名
|
|
154
153
|
* @param {string} [params.mimeType] MIME
|
|
155
|
-
* @param {import('
|
|
156
|
-
* @param {import('
|
|
154
|
+
* @param {import('./manifest.mjs').CeMode} [params.ceMode] 模式
|
|
155
|
+
* @param {import('./manifest.mjs').TransferKeyDescriptor} [params.transferKeyDescriptor] 传递密钥
|
|
157
156
|
* @param {object} [params.meta] meta
|
|
158
|
-
* @returns {Promise<import('
|
|
157
|
+
* @returns {Promise<import('./manifest.mjs').FileManifest>} 写入后的 manifest
|
|
159
158
|
*/
|
|
160
159
|
export async function putFileManifestFromStream(params) {
|
|
161
160
|
const {
|
|
@@ -197,7 +196,7 @@ export async function putFileManifestFromStream(params) {
|
|
|
197
196
|
* @returns {Promise<Buffer | null>} 明文或 null
|
|
198
197
|
*/
|
|
199
198
|
export async function readPublicFile(replicaUsername, entityHash, logicalPath, opts = {}) {
|
|
200
|
-
const { fetchPublicManifest } = await import('
|
|
199
|
+
const { fetchPublicManifest } = await import('./manifest_fetch.mjs')
|
|
201
200
|
const manifest = await fetchPublicManifest({
|
|
202
201
|
username: opts.username || replicaUsername,
|
|
203
202
|
ownerEntityHash: entityHash,
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { isEntityHash128 } from '
|
|
2
|
-
import { assertSafeEvfsLogicalPath } from '
|
|
1
|
+
import { isEntityHash128 } from '../core/entity_id.mjs'
|
|
2
|
+
import { assertSafeEvfsLogicalPath } from '../core/evfs_logical_path.mjs'
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
*
|
|
6
|
-
*/
|
|
4
|
+
/** @type {string} evfs URI scheme */
|
|
7
5
|
export const EVFS_SCHEME = 'evfs:'
|
|
8
6
|
|
|
9
7
|
/**
|
|
@@ -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('./manifest.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('./manifest.mjs').FileManifest,
|
|
16
16
|
* }} ManifestAclContext
|
|
17
17
|
*/
|
|
18
18
|
|
|
@@ -46,7 +46,7 @@ export function unregisterManifestAcl(type, ownerId) {
|
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* @param {string} ownerId 注册方
|
|
49
|
-
* @param {(manifest: import('
|
|
49
|
+
* @param {(manifest: import('./manifest.mjs').FileManifest | null | undefined, ownerEntityHash: string) => string | null} match ACL 类型匹配
|
|
50
50
|
* @returns {void}
|
|
51
51
|
*/
|
|
52
52
|
export function registerManifestAclMatcher(ownerId, match) {
|
|
@@ -68,7 +68,7 @@ export function clearManifestAclRegistry() {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
|
-
* @param {import('
|
|
71
|
+
* @param {import('./manifest.mjs').FileManifest | null | undefined} manifest manifest
|
|
72
72
|
* @param {string} ownerEntityHash 所有者
|
|
73
73
|
* @returns {string | null} ACL 类型
|
|
74
74
|
*/
|
package/files/manifest_fetch.mjs
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto'
|
|
2
2
|
|
|
3
|
-
import { loadFileManifest, saveFileManifest } from '../entity/files/evfs.mjs'
|
|
4
|
-
import { isWritableLocalEntity } from '../entity/replica.mjs'
|
|
5
3
|
import {
|
|
6
4
|
manifestFetchExpectedKey,
|
|
7
5
|
MAX_PENDING_MANIFEST_FETCHES,
|
|
8
6
|
pendingManifestFetches,
|
|
9
7
|
registerManifestFetchWait,
|
|
10
8
|
} from '../federation/manifest_fetch_pending.mjs'
|
|
9
|
+
import { isWritableLocalEntity } from '../node/identity.mjs'
|
|
11
10
|
import { getEntityStore } from '../node/instance.mjs'
|
|
12
11
|
|
|
13
12
|
import { resolveNodeHash } from './chunk_provider_registry.mjs'
|
|
13
|
+
import { loadFileManifest, saveFileManifest } from './evfs.mjs'
|
|
14
14
|
import { fanoutFedFetch } from './fetch_fanout.mjs'
|
|
15
15
|
import { normalizeFileManifest } from './manifest.mjs'
|
|
16
16
|
import { shouldPreferIncomingPublicManifest } from './public_manifest.mjs'
|
|
@@ -6,6 +6,7 @@ import { assertSafeEvfsLogicalPath } from '../core/evfs_logical_path.mjs'
|
|
|
6
6
|
import { isHex64, normalizeHex64 } from '../core/hexIds.mjs'
|
|
7
7
|
import { sign, verify } from '../crypto/crypto.mjs'
|
|
8
8
|
|
|
9
|
+
import { putFileManifest, saveFileManifest } from './evfs.mjs'
|
|
9
10
|
import { normalizeFileManifest, publicTransferKeyDescriptor } from './manifest.mjs'
|
|
10
11
|
|
|
11
12
|
/** 实体公开 manifest 签名域 */
|
|
@@ -137,7 +138,6 @@ export async function publishPublicFile(params) {
|
|
|
137
138
|
entityPubKeyHex,
|
|
138
139
|
publishedAt = Date.now(),
|
|
139
140
|
} = params
|
|
140
|
-
const { putFileManifest, saveFileManifest } = await import('../entity/files/evfs.mjs')
|
|
141
141
|
const base = await putFileManifest({
|
|
142
142
|
ownerEntityHash,
|
|
143
143
|
logicalPath,
|
package/node/identity.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer'
|
|
1
2
|
import { randomBytes } from 'node:crypto'
|
|
2
3
|
|
|
3
|
-
import { entityHashFromRecoveryPubKeyHex } from '../core/entity_id.mjs'
|
|
4
|
+
import { entityHashFromRecoveryPubKeyHex, parseEntityHash } from '../core/entity_id.mjs'
|
|
4
5
|
import { isHex64 } from '../core/hexIds.mjs'
|
|
5
|
-
import {
|
|
6
|
+
import { keyPairFromSeed, pubKeyHash } from '../crypto/crypto.mjs'
|
|
6
7
|
import { normalizeMailboxSettings } from '../mailbox/settings.mjs'
|
|
7
8
|
|
|
8
9
|
import { emitNodeChange } from './instance.mjs'
|
|
@@ -11,6 +12,18 @@ import { readNodeJsonSync, writeNodeJsonSync } from './storage.mjs'
|
|
|
11
12
|
const NODE_SEED_HEX_RE = /^[\da-f]{64}$/iu
|
|
12
13
|
const NODE_JSON = 'node'
|
|
13
14
|
|
|
15
|
+
/**
|
|
16
|
+
* 由持久化 nodeSeed 派生 nodeHash(64 hex)。
|
|
17
|
+
* @param {string} seedHex 32 字节 hex
|
|
18
|
+
* @returns {string} 节点哈希
|
|
19
|
+
*/
|
|
20
|
+
export function nodeHashFromSeed(seedHex) {
|
|
21
|
+
const seed = Buffer.from(String(seedHex).trim(), 'hex')
|
|
22
|
+
if (seed.length !== 32) throw new Error('invalid node seed')
|
|
23
|
+
const { publicKey } = keyPairFromSeed(seed)
|
|
24
|
+
return pubKeyHash(publicKey)
|
|
25
|
+
}
|
|
26
|
+
|
|
14
27
|
/**
|
|
15
28
|
* @returns {object} 节点配置磁盘对象
|
|
16
29
|
*/
|
|
@@ -97,3 +110,21 @@ export function entityHashFromKeys(nodeHash, recoveryPubKeyHex) {
|
|
|
97
110
|
if (!isHex64(nodeHash) || !isHex64(pub)) return null
|
|
98
111
|
return entityHashFromRecoveryPubKeyHex(nodeHash, pub)
|
|
99
112
|
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @param {string} recoveryPubKeyHex 64 位十六进制 recovery 公钥
|
|
116
|
+
* @returns {string | null} 本节点 entityHash
|
|
117
|
+
*/
|
|
118
|
+
export function resolveLocalEntityHashFromRecoveryPubKeyHex(recoveryPubKeyHex) {
|
|
119
|
+
return entityHashFromKeys(getNodeHash(), recoveryPubKeyHex)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @param {string} entityHash 目标 entityHash
|
|
124
|
+
* @returns {boolean} 是否为本节点可写实体
|
|
125
|
+
*/
|
|
126
|
+
export function isWritableLocalEntity(entityHash) {
|
|
127
|
+
const parsed = parseEntityHash(entityHash)
|
|
128
|
+
if (!parsed) return false
|
|
129
|
+
return parsed.nodeHash === getNodeHash()
|
|
130
|
+
}
|
package/node/personal_block.mjs
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { parseEntityHash } from '../core/entity_id.mjs'
|
|
7
7
|
import { isHex64, normalizeHex64 } from '../core/hexIds.mjs'
|
|
8
|
-
import { isWritableLocalEntity } from '../entity/replica.mjs'
|
|
9
8
|
|
|
9
|
+
import { isWritableLocalEntity } from './identity.mjs'
|
|
10
10
|
import { isNodeInitialized, getEntityStore } from './instance.mjs'
|
|
11
11
|
|
|
12
12
|
/** @typedef {'subject' | 'entity'} PersonalListScope */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steve02081504/fount-p2p",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "fount federation P2P layer — link, trust graph, mailbox, DAG, EVFS.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"network",
|
|
@@ -45,8 +45,6 @@
|
|
|
45
45
|
"./rooms/*": "./rooms/*.mjs",
|
|
46
46
|
"./overlay": "./overlay/index.mjs",
|
|
47
47
|
"./overlay/*": "./overlay/*.mjs",
|
|
48
|
-
"./entity/*": "./entity/*.mjs",
|
|
49
|
-
"./entity/files/*": "./entity/files/*.mjs",
|
|
50
48
|
"./trust_graph/*": "./trust_graph/*.mjs",
|
|
51
49
|
"./mailbox/*": "./mailbox/*.mjs",
|
|
52
50
|
"./dag": "./dag/index.mjs",
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { getEntityStore } from '../../node/instance.mjs'
|
|
2
|
-
|
|
3
|
-
/** @type {Map<string, { replica: string | null, at: number }>} */
|
|
4
|
-
const replicaHostCache = new Map()
|
|
5
|
-
|
|
6
|
-
const REPLICA_HOST_CACHE_MS = 60_000
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @param {string | string[]} [entityHash] owner(单个或批量)
|
|
10
|
-
* @returns {void}
|
|
11
|
-
*/
|
|
12
|
-
export function invalidateReplicaHostCache(entityHash) {
|
|
13
|
-
if (Array.isArray(entityHash)) {
|
|
14
|
-
for (const eh of entityHash)
|
|
15
|
-
replicaHostCache.delete(String(eh).trim().toLowerCase())
|
|
16
|
-
return
|
|
17
|
-
}
|
|
18
|
-
if (entityHash) {
|
|
19
|
-
replicaHostCache.delete(String(entityHash).trim().toLowerCase())
|
|
20
|
-
return
|
|
21
|
-
}
|
|
22
|
-
replicaHostCache.clear()
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* @param {string} ownerEntityHash 所有者
|
|
27
|
-
* @returns {Promise<string | null>} 托管 manifest 的 replica
|
|
28
|
-
*/
|
|
29
|
-
export async function findReplicaHostingEntityFiles(ownerEntityHash) {
|
|
30
|
-
const eh = String(ownerEntityHash).trim().toLowerCase()
|
|
31
|
-
const cached = replicaHostCache.get(eh)
|
|
32
|
-
if (cached && Date.now() - cached.at < REPLICA_HOST_CACHE_MS)
|
|
33
|
-
return cached.replica
|
|
34
|
-
|
|
35
|
-
const store = getEntityStore()
|
|
36
|
-
if (typeof store.findHostingUser === 'function') {
|
|
37
|
-
const replica = await store.findHostingUser(eh)
|
|
38
|
-
replicaHostCache.set(eh, { replica, at: Date.now() })
|
|
39
|
-
return replica
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const files = await store.listEntityFiles(eh)
|
|
43
|
-
const replica = files.length ? '__node__' : null
|
|
44
|
-
replicaHostCache.set(eh, { replica, at: Date.now() })
|
|
45
|
-
return replica
|
|
46
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/** @type {((username: string, entityHash: string) => string | null) | null} */
|
|
2
|
-
let resolveAgentCharPartName = null
|
|
3
|
-
|
|
4
|
-
/** @type {((username: string) => { entityHash: string, charPartName: string }[]) | null} */
|
|
5
|
-
let listLocalAgents = null
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Chat Load 时注册:解析本地 agent 的 chars 目录名。
|
|
9
|
-
* @param {(username: string, entityHash: string) => string | null} resolver 解析函数
|
|
10
|
-
* @returns {void}
|
|
11
|
-
*/
|
|
12
|
-
export function registerAgentCharResolver(resolver) {
|
|
13
|
-
resolveAgentCharPartName = resolver
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Chat Load 时注册:枚举 replica 下本地 agent 实体。
|
|
18
|
-
* @param {(username: string) => { entityHash: string, charPartName: string }[]} provider 枚举函数
|
|
19
|
-
* @returns {void}
|
|
20
|
-
*/
|
|
21
|
-
export function registerListLocalAgentsProvider(provider) {
|
|
22
|
-
listLocalAgents = provider
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** @returns {void} */
|
|
26
|
-
export function unregisterAgentCharResolver() {
|
|
27
|
-
resolveAgentCharPartName = null
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** @returns {void} */
|
|
31
|
-
export function unregisterListLocalAgentsProvider() {
|
|
32
|
-
listLocalAgents = null
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* @returns {((username: string, entityHash: string) => string | null) | null} 已注册解析器
|
|
37
|
-
*/
|
|
38
|
-
export function getAgentCharResolver() {
|
|
39
|
-
return resolveAgentCharPartName
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* @returns {((username: string) => { entityHash: string, charPartName: string }[]) | null} 已注册枚举器
|
|
44
|
-
*/
|
|
45
|
-
export function getListLocalAgentsProvider() {
|
|
46
|
-
return listLocalAgents
|
|
47
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/** @type {Map<string, (username: string, entityHash: string) => Promise<string | null>>} */
|
|
2
|
-
const resolversByOwner = new Map()
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @param {string} ownerId 注册方
|
|
6
|
-
* @param {(username: string, entityHash: string) => Promise<string | null>} resolver logical entity id 反查
|
|
7
|
-
* @returns {void}
|
|
8
|
-
*/
|
|
9
|
-
export function registerLogicalEntityIdResolver(ownerId, resolver) {
|
|
10
|
-
resolversByOwner.set(String(ownerId), resolver)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* @param {string} ownerId 注册方
|
|
15
|
-
* @returns {void}
|
|
16
|
-
*/
|
|
17
|
-
export function unregisterLogicalEntityIdResolver(ownerId) {
|
|
18
|
-
resolversByOwner.delete(String(ownerId))
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** @returns {void} */
|
|
22
|
-
export function clearLogicalEntityIdRegistry() {
|
|
23
|
-
resolversByOwner.clear()
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* @param {string} username 副本用户名
|
|
28
|
-
* @param {string} entityHash 128 位十六进制 logical entity
|
|
29
|
-
* @returns {Promise<string | null>} 逻辑实体 id
|
|
30
|
-
*/
|
|
31
|
-
export async function resolveLogicalEntityId(username, entityHash) {
|
|
32
|
-
for (const resolver of resolversByOwner.values())
|
|
33
|
-
try {
|
|
34
|
-
const id = await resolver(username, entityHash)
|
|
35
|
-
if (id) return id
|
|
36
|
-
}
|
|
37
|
-
catch { /* next */ }
|
|
38
|
-
|
|
39
|
-
return null
|
|
40
|
-
}
|
package/entity/node_hash.mjs
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { Buffer } from 'node:buffer'
|
|
2
|
-
|
|
3
|
-
import { keyPairFromSeed, pubKeyHash } from '../crypto/crypto.mjs'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* 由持久化 nodeSeed 派生 nodeHash(64 hex)。
|
|
7
|
-
* @param {string} seedHex 32 字节 hex
|
|
8
|
-
* @returns {string} 节点哈希
|
|
9
|
-
*/
|
|
10
|
-
export function nodeHashFromSeed(seedHex) {
|
|
11
|
-
const seed = Buffer.from(String(seedHex).trim(), 'hex')
|
|
12
|
-
if (seed.length !== 32) throw new Error('invalid node seed')
|
|
13
|
-
const { publicKey } = keyPairFromSeed(seed)
|
|
14
|
-
return pubKeyHash(publicKey)
|
|
15
|
-
}
|
package/entity/replica.mjs
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { parseEntityHash } from '../core/entity_id.mjs'
|
|
2
|
-
import { entityHashFromKeys, getNodeHash } from '../node/identity.mjs'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @param {string} recoveryPubKeyHex 64 位十六进制 recovery 公钥
|
|
6
|
-
* @returns {string | null} 本节点 entityHash
|
|
7
|
-
*/
|
|
8
|
-
export function resolveLocalEntityHashFromRecoveryPubKeyHex(recoveryPubKeyHex) {
|
|
9
|
-
return entityHashFromKeys(getNodeHash(), recoveryPubKeyHex)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* @param {string} entityHash 目标 entityHash
|
|
14
|
-
* @returns {boolean} 是否为本节点可写实体
|
|
15
|
-
*/
|
|
16
|
-
export function isWritableLocalEntity(entityHash) {
|
|
17
|
-
const parsed = parseEntityHash(entityHash)
|
|
18
|
-
if (!parsed) return false
|
|
19
|
-
return parsed.nodeHash === getNodeHash()
|
|
20
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/** @type {Map<string, (replicaUsername: string, groupId: string) => Promise<object | null>>} */
|
|
2
|
-
const providersByOwner = new Map()
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @param {string} ownerId 注册方
|
|
6
|
-
* @param {(replicaUsername: string, groupId: string) => Promise<object | null>} provider 物化会话
|
|
7
|
-
* @returns {void}
|
|
8
|
-
*/
|
|
9
|
-
export function registerMaterializedSessionProvider(ownerId, provider) {
|
|
10
|
-
providersByOwner.set(String(ownerId), provider)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* @param {string} ownerId 注册方
|
|
15
|
-
* @returns {void}
|
|
16
|
-
*/
|
|
17
|
-
export function unregisterMaterializedSessionProvider(ownerId) {
|
|
18
|
-
providersByOwner.delete(String(ownerId))
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* @param {string} replicaUsername 副本用户名
|
|
23
|
-
* @param {string} groupId 群 ID
|
|
24
|
-
* @param {string} [ownerId] 注册方 id;省略时遍历所有 provider
|
|
25
|
-
* @returns {Promise<object | null>} 物化会话
|
|
26
|
-
*/
|
|
27
|
-
export async function getMaterializedSession(replicaUsername, groupId, ownerId) {
|
|
28
|
-
if (ownerId != null) {
|
|
29
|
-
const provider = providersByOwner.get(String(ownerId))
|
|
30
|
-
if (!provider) return null
|
|
31
|
-
return provider(replicaUsername, groupId)
|
|
32
|
-
}
|
|
33
|
-
for (const provider of providersByOwner.values()) {
|
|
34
|
-
const session = await provider(replicaUsername, groupId)
|
|
35
|
-
if (session) return session
|
|
36
|
-
}
|
|
37
|
-
return null
|
|
38
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/** @type {Map<string, (replicaUsername: string, groupId: string) => Promise<string>>} */
|
|
2
|
-
const groupMemberEntityByOwner = new Map()
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @param {string} ownerId 注册方
|
|
6
|
-
* @param {(replicaUsername: string, groupId: string) => Promise<string>} resolver 群成员 entityHash
|
|
7
|
-
* @returns {void}
|
|
8
|
-
*/
|
|
9
|
-
export function registerGroupMemberEntityResolver(ownerId, resolver) {
|
|
10
|
-
groupMemberEntityByOwner.set(String(ownerId), resolver)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* @param {string} ownerId 注册方
|
|
15
|
-
* @returns {void}
|
|
16
|
-
*/
|
|
17
|
-
export function unregisterGroupMemberEntityResolver(ownerId) {
|
|
18
|
-
groupMemberEntityByOwner.delete(String(ownerId))
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* @param {string} replicaUsername 副本用户名
|
|
23
|
-
* @param {string} groupId 群 ID
|
|
24
|
-
* @returns {Promise<string | null>} entityHash;无注册方时 null
|
|
25
|
-
*/
|
|
26
|
-
export async function resolveGroupMemberEntityHash(replicaUsername, groupId) {
|
|
27
|
-
for (const resolver of groupMemberEntityByOwner.values())
|
|
28
|
-
try {
|
|
29
|
-
const eh = await resolver(replicaUsername, groupId)
|
|
30
|
-
if (eh) return eh
|
|
31
|
-
}
|
|
32
|
-
catch { /* next */ }
|
|
33
|
-
|
|
34
|
-
return null
|
|
35
|
-
}
|