@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
package/registries/inbound.mjs
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {{ message: string, code: string }} PartInvokeError
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {object} PartInvokeResultResponse
|
|
7
|
+
* @property {unknown} result
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {object} PartInvokeErrorResponse
|
|
12
|
+
* @property {PartInvokeError} error
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** @typedef {PartInvokeResultResponse | PartInvokeErrorResponse} PartInvokeResponse */
|
|
2
16
|
|
|
3
17
|
/**
|
|
4
18
|
* @typedef {{
|
|
@@ -29,7 +43,7 @@ const deliveryHandlers = new Map()
|
|
|
29
43
|
* @returns {void}
|
|
30
44
|
*/
|
|
31
45
|
export function registerRpcInboundHandler(type, handler) {
|
|
32
|
-
rpcHandlers.set(
|
|
46
|
+
rpcHandlers.set(type, handler)
|
|
33
47
|
}
|
|
34
48
|
|
|
35
49
|
/**
|
|
@@ -38,7 +52,7 @@ export function registerRpcInboundHandler(type, handler) {
|
|
|
38
52
|
* @returns {void}
|
|
39
53
|
*/
|
|
40
54
|
export function registerDeliveryInboundHandler(type, handler) {
|
|
41
|
-
deliveryHandlers.set(
|
|
55
|
+
deliveryHandlers.set(type, handler)
|
|
42
56
|
}
|
|
43
57
|
|
|
44
58
|
/**
|
|
@@ -47,9 +61,7 @@ export function registerDeliveryInboundHandler(type, handler) {
|
|
|
47
61
|
* @returns {Promise<PartInvokeResponse | null>} 处理器返回值
|
|
48
62
|
*/
|
|
49
63
|
export async function dispatchRpcInbound(inboundContext, message) {
|
|
50
|
-
const
|
|
51
|
-
if (!type) return null
|
|
52
|
-
const handler = rpcHandlers.get(type)
|
|
64
|
+
const handler = rpcHandlers.get(message.type)
|
|
53
65
|
if (!handler) return null
|
|
54
66
|
return handler(inboundContext, message)
|
|
55
67
|
}
|
|
@@ -60,9 +72,7 @@ export async function dispatchRpcInbound(inboundContext, message) {
|
|
|
60
72
|
* @returns {Promise<void>}
|
|
61
73
|
*/
|
|
62
74
|
export async function dispatchDeliveryInbound(inboundContext, message) {
|
|
63
|
-
const
|
|
64
|
-
if (!type) return
|
|
65
|
-
const handler = deliveryHandlers.get(type)
|
|
75
|
+
const handler = deliveryHandlers.get(message.type)
|
|
66
76
|
if (!handler) return
|
|
67
77
|
await handler(inboundContext, message)
|
|
68
78
|
}
|
package/registries/part_path.mjs
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { normalizePartpath } from '../wire/part_invoke.mjs'
|
|
2
|
-
|
|
3
1
|
/** @type {Map<string, string>} shell 逻辑名 → partpath */
|
|
4
2
|
const shellPartpaths = new Map()
|
|
5
3
|
|
|
@@ -10,10 +8,7 @@ const shellPartpaths = new Map()
|
|
|
10
8
|
* @returns {void}
|
|
11
9
|
*/
|
|
12
10
|
export function registerShellPartpath(shellKey, partpath) {
|
|
13
|
-
|
|
14
|
-
const path = normalizePartpath(partpath)
|
|
15
|
-
if (!key || !path) throw new Error('invalid_shell_partpath_registration')
|
|
16
|
-
shellPartpaths.set(key, path)
|
|
11
|
+
shellPartpaths.set(shellKey, partpath)
|
|
17
12
|
}
|
|
18
13
|
|
|
19
14
|
/**
|
|
@@ -21,7 +16,7 @@ export function registerShellPartpath(shellKey, partpath) {
|
|
|
21
16
|
* @returns {void}
|
|
22
17
|
*/
|
|
23
18
|
export function unregisterShellPartpath(shellKey) {
|
|
24
|
-
shellPartpaths.delete(
|
|
19
|
+
shellPartpaths.delete(shellKey)
|
|
25
20
|
}
|
|
26
21
|
|
|
27
22
|
/**
|
|
@@ -29,7 +24,7 @@ export function unregisterShellPartpath(shellKey) {
|
|
|
29
24
|
* @returns {string} 已注册的 partpath
|
|
30
25
|
*/
|
|
31
26
|
export function getShellPartpath(shellKey) {
|
|
32
|
-
const path = shellPartpaths.get(
|
|
27
|
+
const path = shellPartpaths.get(shellKey)
|
|
33
28
|
if (!path) throw new Error(`shell partpath not registered: ${shellKey}`)
|
|
34
29
|
return path
|
|
35
30
|
}
|
package/schemas/discovery.mjs
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* 联邦补拉 attestation / HPKE 响应 wire 解析(无 attestation/envelope 即丢弃)。
|
|
3
3
|
*/
|
|
4
4
|
import { isHex64, isSignatureHex128, normalizeHex64 } from '../core/hexIds.mjs'
|
|
5
|
+
import { isPlainObject } from '../core/object.mjs'
|
|
5
6
|
import { EVENT_ID_HEX } from '../dag/index.mjs'
|
|
6
|
-
import { isPlainObject } from '../wire/ingress.mjs'
|
|
7
7
|
|
|
8
8
|
/** @typedef {{ requesterPubKeyHash: string, groupId: string, requestId: string, timestamp: number, wantIds?: string[], signature: string }} PullAttestation */
|
|
9
9
|
|
package/schemas/part_query.mjs
CHANGED
|
@@ -2,9 +2,10 @@ import { Buffer } from 'node:buffer'
|
|
|
2
2
|
|
|
3
3
|
import { canonicalStringify } from '../core/canonical_json.mjs'
|
|
4
4
|
import { isHex64, normalizeHex64 } from '../core/hexIds.mjs'
|
|
5
|
-
import { isPlainObject } from '../
|
|
6
|
-
import {
|
|
7
|
-
|
|
5
|
+
import { isPlainObject } from '../core/object.mjs'
|
|
6
|
+
import { parsePartpath } from '../core/partpath.mjs'
|
|
7
|
+
|
|
8
|
+
import partQueryTunables from './part_query.tunables.json' with { type: 'json' }
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* @typedef {{
|
|
@@ -100,7 +101,7 @@ export function parsePartQueryReq(value, tunables = partQueryTunables) {
|
|
|
100
101
|
if (!requestId) return null
|
|
101
102
|
const originNodeHash = normalizeHex64(value.originNodeHash)
|
|
102
103
|
if (!isHex64(originNodeHash)) return null
|
|
103
|
-
const partpath =
|
|
104
|
+
const partpath = parsePartpath(value.partpath)
|
|
104
105
|
if (!partpath) return null
|
|
105
106
|
const kind = String(value.kind || '').trim()
|
|
106
107
|
if (!kind) return null
|
|
@@ -143,7 +144,7 @@ export function parsePartQueryRes(value, tunables = partQueryTunables) {
|
|
|
143
144
|
* @returns {string | null} 规范化三元组的缓存材料;非法 null
|
|
144
145
|
*/
|
|
145
146
|
export function normalizePartQueryCacheMaterial(partpath, kind, query) {
|
|
146
|
-
const path =
|
|
147
|
+
const path = parsePartpath(partpath)
|
|
147
148
|
const k = String(kind || '').trim()
|
|
148
149
|
if (!path || !k) return null
|
|
149
150
|
if (measureJsonBytes(query) > partQueryTunables.maxQueryBytes) return null
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { noteAdvertPeerHints } from '../discovery/advert_peer_hints.mjs'
|
|
1
2
|
import { listVisibleNodeHashes, startGroupPresence, watchVerifiedGroupAdverts } from '../discovery/index.mjs'
|
|
2
3
|
import { loadPeerPoolView } from '../node/network.mjs'
|
|
3
4
|
import { loadReputation } from '../node/reputation_store.mjs'
|
|
4
5
|
import { emitSafe } from '../utils/emit_safe.mjs'
|
|
5
6
|
|
|
6
|
-
import { applyAdvertPeerHints } from './advert_ingest.mjs'
|
|
7
7
|
import { getLinkRegistry } from './link_registry.mjs'
|
|
8
8
|
import { resolveFederationPoolLimits, selectLinkTargetsFromMembers } from './peer_pool.mjs'
|
|
9
9
|
import { loadTransportTunables } from './tunables.mjs'
|
|
@@ -208,7 +208,7 @@ export function createGroupLinkSet(options) {
|
|
|
208
208
|
registerCleanup(await watchVerifiedGroupAdverts(roomSecret, async (verifiedNodeHash, body, meta) => {
|
|
209
209
|
if (verifiedNodeHash === selfNodeHash) return
|
|
210
210
|
if (!allowNode(verifiedNodeHash)) return
|
|
211
|
-
|
|
211
|
+
noteAdvertPeerHints(verifiedNodeHash, body, meta)
|
|
212
212
|
notePeerCandidate(verifiedNodeHash)
|
|
213
213
|
}))
|
|
214
214
|
|
|
@@ -2,6 +2,7 @@ import { Buffer } from 'node:buffer'
|
|
|
2
2
|
|
|
3
3
|
import { compareHex64Asc, normalizeHex64 } from '../core/hexIds.mjs'
|
|
4
4
|
import { keyPairFromSeed } from '../crypto/crypto.mjs'
|
|
5
|
+
import { noteAdvertPeerHints } from '../discovery/advert_peer_hints.mjs'
|
|
5
6
|
import { watchVerifiedNodeAdvert, setDiscoveryLinkDialer, prepareConnectToNode } from '../discovery/index.mjs'
|
|
6
7
|
import { setDiscoveryPeerClueListener } from '../discovery/peer_clue.mjs'
|
|
7
8
|
import { listLinkProviders } from '../link/providers/index.mjs'
|
|
@@ -13,7 +14,6 @@ import { ms } from '../utils/duration.mjs'
|
|
|
13
14
|
import { emitSafe } from '../utils/emit_safe.mjs'
|
|
14
15
|
import { createLruMap } from '../utils/lru.mjs'
|
|
15
16
|
|
|
16
|
-
import { applyAdvertPeerHints } from './advert_ingest.mjs'
|
|
17
17
|
import { DEFAULT_ICE_SERVERS } from './ice_servers.mjs'
|
|
18
18
|
import { createMeshKeepalive } from './mesh_keepalive.mjs'
|
|
19
19
|
import { createOfferAnswerDial } from './offer_answer.mjs'
|
|
@@ -650,7 +650,7 @@ export function createLinkRegistry(options = {}) {
|
|
|
650
650
|
async watchNodeAdvert(nodeHash, onAdvert) {
|
|
651
651
|
const hash = normalizeHex64(nodeHash)
|
|
652
652
|
return await watchVerifiedNodeAdvert(hash, async (verifiedNodeHash, body, meta) => {
|
|
653
|
-
|
|
653
|
+
noteAdvertPeerHints(verifiedNodeHash, body, meta)
|
|
654
654
|
recentAdverts.touch(verifiedNodeHash, Date.now())
|
|
655
655
|
dialCooldown.delete(verifiedNodeHash)
|
|
656
656
|
await Promise.resolve(onAdvert(verifiedNodeHash, body))
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { attachNodeScopeFedResponder } from '../../files/fed/responder.mjs'
|
|
2
|
+
import { attachMailboxWire } from '../../mailbox/wire.mjs'
|
|
3
|
+
import { attachPartWire } from '../../wire/part/ingress.mjs'
|
|
4
|
+
import { attachPartQueryWire } from '../../wire/part/query.mjs'
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
clearNodeScopeSubscribe,
|
|
8
|
+
ensureNodeScope,
|
|
9
|
+
getNodeScopeContext,
|
|
10
|
+
getNodeScopeWire,
|
|
11
|
+
} from './wire.mjs'
|
|
12
|
+
|
|
13
|
+
/** @typedef {import('./wire.mjs').NodeScopeWire} NodeScopeWire */
|
|
14
|
+
/** @typedef {import('./wire.mjs').NodeScopeContext} NodeScopeContext */
|
|
15
|
+
|
|
16
|
+
/** @type {Set<() => void>} */
|
|
17
|
+
const nodeScopeFeatureDisposers = new Set()
|
|
18
|
+
|
|
19
|
+
/** @type {Map<string, { count: number, disposeCore: () => void }>} */
|
|
20
|
+
const featureAttachRefs = new Map()
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {() => void} dispose - feature 卸载函数
|
|
24
|
+
* @returns {() => void} 包装后的 dispose(同时从跟踪集移除)
|
|
25
|
+
*/
|
|
26
|
+
function trackFeatureDisposer(dispose) {
|
|
27
|
+
nodeScopeFeatureDisposers.add(dispose)
|
|
28
|
+
return () => {
|
|
29
|
+
dispose()
|
|
30
|
+
nodeScopeFeatureDisposers.delete(dispose)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 同一 feature 多次 attach 共享一份 wire;dispose 引用计数归零才卸。
|
|
36
|
+
* @param {string} key - feature 去重键
|
|
37
|
+
* @param {() => () => void} attachCore - 首次 attach 时执行,返回核心 dispose
|
|
38
|
+
* @returns {() => void} 引用计数包装的 dispose
|
|
39
|
+
*/
|
|
40
|
+
function attachFeatureRefCounted(key, attachCore) {
|
|
41
|
+
let entry = featureAttachRefs.get(key)
|
|
42
|
+
if (!entry) {
|
|
43
|
+
entry = { count: 0, disposeCore: attachCore() }
|
|
44
|
+
featureAttachRefs.set(key, entry)
|
|
45
|
+
}
|
|
46
|
+
entry.count++
|
|
47
|
+
return trackFeatureDisposer(() => {
|
|
48
|
+
const current = featureAttachRefs.get(key)
|
|
49
|
+
if (!current) return
|
|
50
|
+
current.count--
|
|
51
|
+
if (current.count > 0) return
|
|
52
|
+
current.disposeCore()
|
|
53
|
+
featureAttachRefs.delete(key)
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {string} key - feature 去重键
|
|
59
|
+
* @param {(wire: NodeScopeWire, context: NodeScopeContext) => () => void} attachCore - 首次 attach
|
|
60
|
+
* @param {{ replicaUsername?: string }} [options] - 可选副本用户名
|
|
61
|
+
* @returns {() => void} 引用计数包装的 dispose
|
|
62
|
+
*/
|
|
63
|
+
export function attachNodeScopeFeature(key, attachCore, options = {}) {
|
|
64
|
+
ensureNodeScope(options)
|
|
65
|
+
return attachFeatureRefCounted(key, () => attachCore(getNodeScopeWire(), getNodeScopeContext()))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** @type {Record<string, (wire: NodeScopeWire, context: NodeScopeContext) => () => void>} */
|
|
69
|
+
const builtinFeatures = {
|
|
70
|
+
/**
|
|
71
|
+
* @param {NodeScopeWire} wire node scope wire
|
|
72
|
+
* @param {NodeScopeContext} context 入站上下文
|
|
73
|
+
* @returns {() => void} 取消挂载的 dispose
|
|
74
|
+
*/
|
|
75
|
+
mailbox: (wire, context) => attachMailboxWire(context, wire),
|
|
76
|
+
/**
|
|
77
|
+
* @param {NodeScopeWire} wire node scope wire
|
|
78
|
+
* @param {NodeScopeContext} context 入站上下文
|
|
79
|
+
* @returns {() => void} 取消挂载的 dispose
|
|
80
|
+
*/
|
|
81
|
+
part: (wire, context) => attachPartWire(context, wire),
|
|
82
|
+
/**
|
|
83
|
+
* @param {NodeScopeWire} wire node scope wire
|
|
84
|
+
* @param {NodeScopeContext} context 入站上下文
|
|
85
|
+
* @returns {() => void} 取消挂载的 dispose
|
|
86
|
+
*/
|
|
87
|
+
partQuery: (wire, context) => attachPartQueryWire(context, wire),
|
|
88
|
+
/**
|
|
89
|
+
* @param {NodeScopeWire} wire node scope wire
|
|
90
|
+
* @returns {() => void} 取消挂载的 dispose
|
|
91
|
+
*/
|
|
92
|
+
chunks: wire => attachNodeScopeFedResponder(wire),
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @param {keyof typeof builtinFeatures} key - 内置 feature 名
|
|
97
|
+
* @param {{ replicaUsername?: string }} [options] - 可选副本用户名
|
|
98
|
+
* @returns {() => void} 引用计数包装的 dispose
|
|
99
|
+
*/
|
|
100
|
+
function attachBuiltin(key, options = {}) {
|
|
101
|
+
return attachNodeScopeFeature(key, builtinFeatures[key], options)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @param {{ replicaUsername?: string }} [options] - 可选副本用户名
|
|
106
|
+
* @returns {() => void} 取消挂载的 dispose
|
|
107
|
+
*/
|
|
108
|
+
export function attachNodeScopeMailbox(options = {}) {
|
|
109
|
+
return attachBuiltin('mailbox', options)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @param {{ replicaUsername?: string }} [options] - 可选副本用户名
|
|
114
|
+
* @returns {() => void} 取消挂载的 dispose
|
|
115
|
+
*/
|
|
116
|
+
export function attachNodeScopePart(options = {}) {
|
|
117
|
+
return attachBuiltin('part', options)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @param {{ replicaUsername?: string }} [options] - 可选副本用户名
|
|
122
|
+
* @returns {() => void} 取消挂载的 dispose
|
|
123
|
+
*/
|
|
124
|
+
export function attachNodeScopePartQuery(options = {}) {
|
|
125
|
+
return attachBuiltin('partQuery', options)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @param {{ replicaUsername?: string }} [options] - 可选副本用户名
|
|
130
|
+
* @returns {() => void} 取消挂载的 dispose
|
|
131
|
+
*/
|
|
132
|
+
export function attachNodeScopeChunks(options = {}) {
|
|
133
|
+
return attachBuiltin('chunks', options)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 全业务 preset(part / partQuery / mailbox / chunks)。
|
|
138
|
+
* @param {{ replicaUsername?: string }} [options] - 可选副本用户名
|
|
139
|
+
* @returns {() => void} 取消全部 preset 挂载的 dispose
|
|
140
|
+
*/
|
|
141
|
+
export function attachNodeScopeDefaultFeatures(options = {}) {
|
|
142
|
+
const disposers = [
|
|
143
|
+
attachNodeScopePart(options),
|
|
144
|
+
attachNodeScopePartQuery(options),
|
|
145
|
+
attachNodeScopeMailbox(options),
|
|
146
|
+
attachNodeScopeChunks(options),
|
|
147
|
+
]
|
|
148
|
+
return () => {
|
|
149
|
+
for (const dispose of disposers) dispose()
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 卸掉 feature 挂载;可选保留 node scope 订阅。
|
|
155
|
+
* @param {{ keepSubscribe?: boolean }} [options] - true 时保留 node scope 订阅
|
|
156
|
+
* @returns {void}
|
|
157
|
+
*/
|
|
158
|
+
export function stopNodeScopeRuntime(options = {}) {
|
|
159
|
+
for (const dispose of [...nodeScopeFeatureDisposers]) dispose()
|
|
160
|
+
nodeScopeFeatureDisposers.clear()
|
|
161
|
+
for (const entry of featureAttachRefs.values())
|
|
162
|
+
try { entry.disposeCore() } catch { /* ignore */ }
|
|
163
|
+
featureAttachRefs.clear()
|
|
164
|
+
if (options.keepSubscribe) return
|
|
165
|
+
clearNodeScopeSubscribe()
|
|
166
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { subscribeScope, sendToNodeLink } from '../link_registry.mjs'
|
|
2
|
+
|
|
3
|
+
/** @typedef {import('../../wire/adapter.mjs').WireAdapter} NodeScopeWire */
|
|
4
|
+
/** @typedef {{ replicaUsername: string }} NodeScopeContext */
|
|
5
|
+
|
|
6
|
+
/** @type {Map<string, Set<(payload: unknown, peerId: string) => void>>} */
|
|
7
|
+
const nodeActionHandlers = new Map()
|
|
8
|
+
|
|
9
|
+
/** @type {NodeScopeContext} */
|
|
10
|
+
const nodeScopeContext = { replicaUsername: '' }
|
|
11
|
+
|
|
12
|
+
/** @type {NodeScopeWire | null} */
|
|
13
|
+
let nodeScopeWire = null
|
|
14
|
+
|
|
15
|
+
/** @type {(() => void) | null} */
|
|
16
|
+
let nodeScopeSubscribeCleanup = null
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @returns {NodeScopeWire} node scope 的 wire 适配器
|
|
20
|
+
*/
|
|
21
|
+
function createNodeScopeWire() {
|
|
22
|
+
return {
|
|
23
|
+
/**
|
|
24
|
+
* @param {string} name action 名
|
|
25
|
+
* @param {(payload: unknown, peerId: string) => void} handler 回调
|
|
26
|
+
* @returns {() => void} 取消订阅
|
|
27
|
+
*/
|
|
28
|
+
on(name, handler) {
|
|
29
|
+
if (!nodeActionHandlers.has(name)) nodeActionHandlers.set(name, new Set())
|
|
30
|
+
nodeActionHandlers.get(name).add(handler)
|
|
31
|
+
return () => {
|
|
32
|
+
const set = nodeActionHandlers.get(name)
|
|
33
|
+
if (!set) return
|
|
34
|
+
set.delete(handler)
|
|
35
|
+
if (!set.size) nodeActionHandlers.delete(name)
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
/**
|
|
39
|
+
* @param {string} name action 名
|
|
40
|
+
* @param {unknown} payload 载荷
|
|
41
|
+
* @param {string} peerId 目标 peer
|
|
42
|
+
* @returns {void}
|
|
43
|
+
*/
|
|
44
|
+
send(name, payload, peerId) {
|
|
45
|
+
if (!peerId) return
|
|
46
|
+
void sendToNodeLink(peerId, { scope: 'node', action: name, payload }).catch(() => { })
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {string} action - action 名
|
|
53
|
+
* @returns {boolean} 是否已挂载处理器
|
|
54
|
+
*/
|
|
55
|
+
export function hasNodeScopeAction(action) {
|
|
56
|
+
return (nodeActionHandlers.get(action)?.size ?? 0) > 0
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @param {string} action - action 名
|
|
61
|
+
* @returns {number} 已注册的处理器数量
|
|
62
|
+
*/
|
|
63
|
+
export function countNodeScopeActionHandlers(action) {
|
|
64
|
+
return nodeActionHandlers.get(action)?.size ?? 0
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 测试/调试:直接派发已挂载的 node action。
|
|
69
|
+
* @param {string} action - action 名
|
|
70
|
+
* @param {unknown} payload - 载荷
|
|
71
|
+
* @param {string} peerId - 发送方 nodeHash
|
|
72
|
+
* @returns {boolean} 是否有处理器被调用
|
|
73
|
+
*/
|
|
74
|
+
export function dispatchNodeScopeAction(action, payload, peerId) {
|
|
75
|
+
const handlers = nodeActionHandlers.get(action)
|
|
76
|
+
if (!handlers?.size) return false
|
|
77
|
+
for (const handler of handlers)
|
|
78
|
+
try { handler(payload, peerId) } catch { /* ignore */ }
|
|
79
|
+
return true
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @returns {NodeScopeWire | null} 当前 wire,未 ensure 时为 null
|
|
84
|
+
*/
|
|
85
|
+
export function getNodeScopeWire() {
|
|
86
|
+
return nodeScopeWire
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @returns {NodeScopeContext} 可变 node scope 上下文
|
|
91
|
+
*/
|
|
92
|
+
export function getNodeScopeContext() {
|
|
93
|
+
return nodeScopeContext
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 只订阅 node scope 派发,不挂任何 feature。
|
|
98
|
+
* @param {{ replicaUsername?: string }} [options] - 可选副本用户名
|
|
99
|
+
* @returns {() => void} 取消订阅的 dispose
|
|
100
|
+
*/
|
|
101
|
+
export function ensureNodeScope(options = {}) {
|
|
102
|
+
if (options.replicaUsername != null)
|
|
103
|
+
nodeScopeContext.replicaUsername = String(options.replicaUsername)
|
|
104
|
+
if (nodeScopeSubscribeCleanup) return nodeScopeSubscribeCleanup
|
|
105
|
+
nodeScopeSubscribeCleanup = subscribeScope('node', (senderNodeHash, envelope) => {
|
|
106
|
+
const handlers = nodeActionHandlers.get(envelope.action)
|
|
107
|
+
if (!handlers?.size) return
|
|
108
|
+
for (const handler of handlers)
|
|
109
|
+
try { handler(envelope.payload, senderNodeHash) } catch { /* ignore */ }
|
|
110
|
+
})
|
|
111
|
+
nodeScopeWire ||= createNodeScopeWire()
|
|
112
|
+
return nodeScopeSubscribeCleanup
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 卸掉 scope 订阅与 wire(feature 须先卸)。
|
|
117
|
+
* @returns {void}
|
|
118
|
+
*/
|
|
119
|
+
export function clearNodeScopeSubscribe() {
|
|
120
|
+
nodeScopeSubscribeCleanup?.()
|
|
121
|
+
nodeScopeSubscribeCleanup = null
|
|
122
|
+
nodeScopeWire = null
|
|
123
|
+
nodeActionHandlers.clear()
|
|
124
|
+
}
|
|
@@ -28,7 +28,6 @@ registerFederationRoomProvider('remote-user-room', () => {
|
|
|
28
28
|
* @returns {Promise<RemoteUserRoomSlot>} 房间槽
|
|
29
29
|
*/
|
|
30
30
|
export async function ensureRemoteUserRoom(username, targetNodeHash) {
|
|
31
|
-
void username
|
|
32
31
|
const key = targetNodeHash.toLowerCase()
|
|
33
32
|
const existing = slots.get(key)
|
|
34
33
|
if (existing) return existing
|
package/transport/user_room.mjs
CHANGED
|
@@ -5,21 +5,12 @@ import { registerFederationRoomProvider } from '../registries/room_provider.mjs'
|
|
|
5
5
|
import { shuffleInPlace } from '../utils/shuffle.mjs'
|
|
6
6
|
|
|
7
7
|
import { getLinkRegistry, listLinks, sendToNodeLink } from './link_registry.mjs'
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
ensureNodeScope,
|
|
11
|
-
} from './node_scope.mjs'
|
|
8
|
+
import { attachNodeScopeDefaultFeatures } from './node_scope/features.mjs'
|
|
9
|
+
import { ensureNodeScope } from './node_scope/wire.mjs'
|
|
12
10
|
import { USER_ROOM_SCOPE } from './room_scopes.mjs'
|
|
13
11
|
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
* @param {string} peerId 对端 nodeHash
|
|
17
|
-
* @param {string} action 动作名
|
|
18
|
-
* @param {unknown} payload 载荷
|
|
19
|
-
* @returns {Promise<boolean>} 是否发出
|
|
20
|
-
*/
|
|
21
|
-
const sendNodeAction = (peerId, action, payload) =>
|
|
22
|
-
sendToNodeLink(peerId, { scope: 'node', action, payload })
|
|
12
|
+
/** User Room 随机 peer 转发默认上限 */
|
|
13
|
+
const USER_ROOM_PEER_FANOUT_DEFAULT = 6
|
|
23
14
|
|
|
24
15
|
/** @type {Promise<UserRoomSlot> | null} */
|
|
25
16
|
let userRoomInflight = null
|
|
@@ -50,7 +41,7 @@ export function activeLinkRoster() {
|
|
|
50
41
|
* roomId: string
|
|
51
42
|
* roomSecret: string
|
|
52
43
|
* room: object | null
|
|
53
|
-
* sendToPeer: (peerId: string, actionName: string, payload: unknown) =>
|
|
44
|
+
* sendToPeer: (peerId: string, actionName: string, payload: unknown) => Promise<boolean>
|
|
54
45
|
* getRoster: () => Array<{ peerId: string, remoteNodeHash: string | undefined }>
|
|
55
46
|
* getPeerIdByNodeHash: (nodeHash: string) => string | null
|
|
56
47
|
* }} UserRoomSlot
|
|
@@ -74,7 +65,7 @@ registerFederationRoomProvider('user-room', () => {
|
|
|
74
65
|
* @param {string} peerId - 目标 peer
|
|
75
66
|
* @param {string} actionName - node scope action 名
|
|
76
67
|
* @param {unknown} payload - 载荷
|
|
77
|
-
* @returns {
|
|
68
|
+
* @returns {Promise<boolean>} 是否发出
|
|
78
69
|
*/
|
|
79
70
|
sendToPeer: (peerId, actionName, payload) => slot.sendToPeer(peerId, actionName, payload),
|
|
80
71
|
}]
|
|
@@ -95,7 +86,7 @@ export function resolveUserRoomCredentials() {
|
|
|
95
86
|
}
|
|
96
87
|
|
|
97
88
|
/**
|
|
98
|
-
* 用户房间槽 + runtime;默认不挂业务 wire(用 `
|
|
89
|
+
* 用户房间槽 + runtime;默认不挂业务 wire(用 `attachNodeScopeDefaultFeatures` / `attachDefaultWires: true`)。
|
|
99
90
|
* @param {{ replicaUsername?: string, attachDefaultWires?: boolean }} [options] - 副本用户名与是否挂载默认 wire
|
|
100
91
|
* @returns {Promise<UserRoomSlot>} 用户房间槽
|
|
101
92
|
*/
|
|
@@ -105,7 +96,7 @@ export async function ensureUserRoom(options = {}) {
|
|
|
105
96
|
ensureNodeScope({ replicaUsername: options.replicaUsername })
|
|
106
97
|
if (userRoomSlot) {
|
|
107
98
|
if (attachDefaultWires && !userRoomDefaultWiresDispose)
|
|
108
|
-
userRoomDefaultWiresDispose =
|
|
99
|
+
userRoomDefaultWiresDispose = attachNodeScopeDefaultFeatures({ replicaUsername: options.replicaUsername })
|
|
109
100
|
return userRoomSlot
|
|
110
101
|
}
|
|
111
102
|
if (userRoomInflight) return await userRoomInflight
|
|
@@ -115,7 +106,7 @@ export async function ensureUserRoom(options = {}) {
|
|
|
115
106
|
await getLinkRegistry().ensureRuntime()
|
|
116
107
|
ensureNodeScope({ replicaUsername: options.replicaUsername })
|
|
117
108
|
if (attachDefaultWires && !userRoomDefaultWiresDispose)
|
|
118
|
-
userRoomDefaultWiresDispose =
|
|
109
|
+
userRoomDefaultWiresDispose = attachNodeScopeDefaultFeatures({ replicaUsername: options.replicaUsername })
|
|
119
110
|
const creds = resolveUserRoomCredentials()
|
|
120
111
|
userRoomSlot = {
|
|
121
112
|
roomId: creds.roomId,
|
|
@@ -125,10 +116,10 @@ export async function ensureUserRoom(options = {}) {
|
|
|
125
116
|
* @param {string} peerId - 目标 peer
|
|
126
117
|
* @param {string} actionName - node scope action 名
|
|
127
118
|
* @param {unknown} payload - 载荷
|
|
128
|
-
* @returns {
|
|
119
|
+
* @returns {Promise<boolean>} 是否发出
|
|
129
120
|
*/
|
|
130
121
|
sendToPeer(peerId, actionName, payload) {
|
|
131
|
-
|
|
122
|
+
return sendToNodeLink(peerId, { scope: 'node', action: actionName, payload }).catch(() => false)
|
|
132
123
|
},
|
|
133
124
|
/**
|
|
134
125
|
* @returns {Array<{ peerId: string, remoteNodeHash: string }>} 当前活跃链路 roster
|
|
@@ -162,16 +153,15 @@ export async function ensureUserRoom(options = {}) {
|
|
|
162
153
|
* @returns {Promise<number>} 成功转发的 peer 数
|
|
163
154
|
*/
|
|
164
155
|
export async function deliverToUserRoomPeers(username, actionName, payload, exceptPeerId = null, limit) {
|
|
165
|
-
void username
|
|
166
|
-
const { USER_ROOM_PEER_FANOUT_DEFAULT } = await import('../wire/part_common.mjs')
|
|
167
156
|
const fanoutLimit = limit ?? USER_ROOM_PEER_FANOUT_DEFAULT
|
|
168
|
-
|
|
157
|
+
if (fanoutLimit <= 0) return 0
|
|
158
|
+
const slot = await ensureUserRoom({ replicaUsername: username })
|
|
169
159
|
let sent = 0
|
|
170
|
-
const peers = shuffleInPlace(
|
|
160
|
+
const peers = shuffleInPlace(slot.getRoster()
|
|
171
161
|
.filter(({ peerId }) => peerId && peerId !== exceptPeerId))
|
|
172
162
|
for (const { peerId } of peers)
|
|
173
163
|
try {
|
|
174
|
-
if (await
|
|
164
|
+
if (await slot.sendToPeer(peerId, actionName, { ...payload, nodeHash: getNodeHash() }))
|
|
175
165
|
sent++
|
|
176
166
|
if (sent >= fanoutLimit) break
|
|
177
167
|
}
|
|
@@ -179,6 +169,3 @@ export async function deliverToUserRoomPeers(username, actionName, payload, exce
|
|
|
179
169
|
|
|
180
170
|
return sent
|
|
181
171
|
}
|
|
182
|
-
|
|
183
|
-
/** 再导出:node-scope 订阅与默认 wires(见 `node_scope.mjs`)。 */
|
|
184
|
-
export { attachUserRoomDefaultWires, ensureNodeScope } from './node_scope.mjs'
|