@steve02081504/fount-p2p 0.0.4 → 0.0.5
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/core/bytes_codec.mjs +0 -27
- package/core/constants.mjs +0 -31
- package/core/hexIds.mjs +0 -13
- package/crypto/key.mjs +14 -15
- package/dag/index.mjs +0 -73
- package/dag/storage.mjs +0 -22
- package/federation/entity_key_chain.mjs +0 -9
- package/federation/topo_order_memo.mjs +0 -15
- package/files/assemble.mjs +55 -39
- package/files/assemble_stream.mjs +96 -44
- package/files/chunk_provider_registry.mjs +0 -6
- package/files/chunk_store.mjs +1 -11
- package/files/evfs.mjs +64 -37
- package/files/manifest.mjs +11 -2
- package/files/manifest_acl_registry.mjs +0 -6
- package/files/transfer_key.mjs +12 -6
- package/files/transfer_key_registry.mjs +0 -7
- package/governance/branch.mjs +0 -11
- package/link/channel_mux.mjs +2 -0
- package/mailbox/importance.mjs +0 -33
- package/mailbox/settings.mjs +0 -9
- package/node/denylist.mjs +0 -24
- package/node/network.mjs +0 -51
- package/node/personal_block.mjs +0 -35
- package/node/reputation_store.mjs +0 -9
- package/node/retention_policy.mjs +0 -15
- package/package.json +1 -1
- package/registries/event_type.mjs +0 -12
- package/registries/inbound.mjs +0 -22
- package/reputation/engine.mjs +0 -11
- package/schemas/part_query.mjs +156 -0
- package/transport/ice_servers.mjs +0 -8
- package/transport/peer_identity_maps.mjs +0 -58
- package/transport/remote_user_room.mjs +0 -12
- package/transport/rtc_connection_budget.mjs +2 -0
- package/transport/user_room.mjs +2 -11
- package/trust_graph/cache.mjs +0 -13
- package/trust_graph/engine.mjs +0 -24
- package/utils/async_mutex.mjs +0 -9
- package/wire/part_fanout.mjs +0 -19
- package/wire/part_query.mjs +487 -0
- package/wire/part_query.tunables.json +14 -0
- package/wire/part_query_cache.mjs +94 -0
package/node/personal_block.mjs
CHANGED
|
@@ -104,41 +104,6 @@ export async function loadPersonalBlockEntries(viewerEntityHash) {
|
|
|
104
104
|
return normalizePersonalListEntries(data?.blocked || [])
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
/**
|
|
108
|
-
* @param {string} viewerEntityHash 观看者实体
|
|
109
|
-
* @param {object} subject 待检主体
|
|
110
|
-
* @returns {Promise<boolean>} 是否被隐藏
|
|
111
|
-
*/
|
|
112
|
-
export async function isHiddenBy(viewerEntityHash, subject) {
|
|
113
|
-
return matchesPersonalListEntries(await loadPersonalHideEntries(viewerEntityHash), subject)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* @param {string} viewerEntityHash 观看者实体
|
|
118
|
-
* @param {object} subject 待检主体
|
|
119
|
-
* @returns {Promise<boolean>} 是否被拉黑
|
|
120
|
-
*/
|
|
121
|
-
export async function isBlockedBy(viewerEntityHash, subject) {
|
|
122
|
-
return matchesPersonalListEntries(await loadPersonalBlockEntries(viewerEntityHash), subject)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* @param {string} viewerEntityHash 观看者实体
|
|
127
|
-
* @param {object} subject 待检主体
|
|
128
|
-
* @returns {Promise<boolean>} 是否应过滤
|
|
129
|
-
*/
|
|
130
|
-
export async function isFilteredByPersonalLists(viewerEntityHash, subject) {
|
|
131
|
-
if (!viewerEntityHash) return false
|
|
132
|
-
const [blocked, hidden, muted] = await Promise.all([
|
|
133
|
-
loadPersonalBlockEntries(viewerEntityHash),
|
|
134
|
-
loadPersonalHideEntries(viewerEntityHash),
|
|
135
|
-
loadPersonalMuteEntries(viewerEntityHash),
|
|
136
|
-
])
|
|
137
|
-
return matchesPersonalListEntries(blocked, subject)
|
|
138
|
-
|| matchesPersonalListEntries(hidden, subject)
|
|
139
|
-
|| matchesPersonalListEntries(muted, subject)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
107
|
/**
|
|
143
108
|
* @param {string} viewerEntityHash 本地可写实体
|
|
144
109
|
* @param {string} targetEntityHash 目标
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
bumpChunkStorageReputationPure,
|
|
8
8
|
bumpReputationOnRelayPure,
|
|
9
9
|
ensureReputationShape,
|
|
10
|
-
isQuarantinedPure,
|
|
11
10
|
observeBehaviorSamplePure,
|
|
12
11
|
penalizeArchiveServeMismatchPure,
|
|
13
12
|
penalizeChunkStorageFailurePure,
|
|
@@ -145,14 +144,6 @@ export async function observePeerBehavior(peerNodeHash, sample) {
|
|
|
145
144
|
return anomaly
|
|
146
145
|
}
|
|
147
146
|
|
|
148
|
-
/**
|
|
149
|
-
* @param {string} peerNodeHash 对端
|
|
150
|
-
* @returns {boolean} 是否处于本地隔离
|
|
151
|
-
*/
|
|
152
|
-
export function isPeerQuarantined(peerNodeHash) {
|
|
153
|
-
return isQuarantinedPure(loadReputation(), peerNodeHash)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
147
|
/**
|
|
157
148
|
* @param {string} storagePeerKey 责任方
|
|
158
149
|
* @returns {void}
|
|
@@ -3,21 +3,6 @@ import {
|
|
|
3
3
|
authzFoldOrderIds,
|
|
4
4
|
descendantClosureFromTip,
|
|
5
5
|
} from '../governance/branch.mjs'
|
|
6
|
-
import { getPermissionAnchorTypes } from '../registries/event_type.mjs'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @param {string[]} order 拓扑序 id 列表
|
|
10
|
-
* @param {Map<string, object>} byId id → 事件
|
|
11
|
-
* @returns {number} 最早须保留的事件下标
|
|
12
|
-
*/
|
|
13
|
-
export function permissionAnchorStartIndex(order, byId) {
|
|
14
|
-
let anchor = order.length
|
|
15
|
-
for (let index = order.length - 1; index >= 0; index--) {
|
|
16
|
-
const ev = byId.get(order[index])
|
|
17
|
-
if (ev && getPermissionAnchorTypes().has(ev.type)) anchor = index
|
|
18
|
-
}
|
|
19
|
-
return anchor
|
|
20
|
-
}
|
|
21
6
|
|
|
22
7
|
/**
|
|
23
8
|
* 在共识分支上计算须保留的事件 id(连通子图,不用拓扑下标切片)。
|
package/package.json
CHANGED
|
@@ -56,18 +56,6 @@ export function getGovernanceAuthzTypes() {
|
|
|
56
56
|
return typesWithFlag('governance')
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
/** 联邦入站/中继前须物化 ACL 门控的类型。 */
|
|
60
|
-
/** @returns {Set<string>} ACL 门控事件 type 集合 */
|
|
61
|
-
export function getFederationAclGatedEventTypes() {
|
|
62
|
-
return typesWithFlag('aclGated')
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** §6.2 频道 GC 沉寂计时排除的类型。 */
|
|
66
|
-
/** @returns {Set<string>} GC 排除事件 type 集合 */
|
|
67
|
-
export function getChannelGcExcludedEventTypes() {
|
|
68
|
-
return typesWithFlag('gcExclude')
|
|
69
|
-
}
|
|
70
|
-
|
|
71
59
|
/** 裁剪时不得早于最早一条权限锚点事件(§7.1)。 */
|
|
72
60
|
/** @returns {Set<string>} 权限锚点事件 type 集合 */
|
|
73
61
|
export function getPermissionAnchorTypes() {
|
package/registries/inbound.mjs
CHANGED
|
@@ -41,22 +41,6 @@ export function registerDeliveryInboundHandler(type, handler) {
|
|
|
41
41
|
deliveryHandlers.set(String(type || '').trim(), handler)
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
/**
|
|
45
|
-
* @param {string} type 入站类型
|
|
46
|
-
* @returns {void}
|
|
47
|
-
*/
|
|
48
|
-
export function unregisterRpcInboundHandler(type) {
|
|
49
|
-
rpcHandlers.delete(String(type || '').trim())
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* @param {string} type 入站类型
|
|
54
|
-
* @returns {void}
|
|
55
|
-
*/
|
|
56
|
-
export function unregisterDeliveryInboundHandler(type) {
|
|
57
|
-
deliveryHandlers.delete(String(type || '').trim())
|
|
58
|
-
}
|
|
59
|
-
|
|
60
44
|
/**
|
|
61
45
|
* @param {InboundContext} ctx 入站上下文
|
|
62
46
|
* @param {object} message 已校验的线载荷(含 type)
|
|
@@ -82,9 +66,3 @@ export async function dispatchDeliveryInbound(ctx, message) {
|
|
|
82
66
|
if (!handler) return
|
|
83
67
|
await handler(ctx, message)
|
|
84
68
|
}
|
|
85
|
-
|
|
86
|
-
/** @returns {void} 测试用 */
|
|
87
|
-
export function clearInboundHandlers() {
|
|
88
|
-
rpcHandlers.clear()
|
|
89
|
-
deliveryHandlers.clear()
|
|
90
|
-
}
|
package/reputation/engine.mjs
CHANGED
|
@@ -445,14 +445,3 @@ export function isQuarantinedPure(data, peerNodeHash, now = Date.now()) {
|
|
|
445
445
|
const until = Number(data.byNodeHash?.[id]?.quarantinedUntil ?? 0)
|
|
446
446
|
return Number.isFinite(until) && until > now
|
|
447
447
|
}
|
|
448
|
-
|
|
449
|
-
/**
|
|
450
|
-
* @param {ReputationFile} data 信誉表
|
|
451
|
-
* @param {string} peerNodeHash 对端
|
|
452
|
-
* @param {number} [now] 当前时间
|
|
453
|
-
* @param {typeof reputationTunables} [tunables] tunables
|
|
454
|
-
* @returns {boolean} 是否检测到异常并触发隔离
|
|
455
|
-
*/
|
|
456
|
-
export function detectAnomalyPure(data, peerNodeHash, now = Date.now(), tunables = reputationTunables) {
|
|
457
|
-
return isQuarantinedPure(data, peerNodeHash, now)
|
|
458
|
-
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer'
|
|
2
|
+
|
|
3
|
+
import { canonicalStringify } from '../core/canonical_json.mjs'
|
|
4
|
+
import { isHex64, normalizeHex64 } from '../core/hexIds.mjs'
|
|
5
|
+
import { isPlainObject } from '../wire/ingress.mjs'
|
|
6
|
+
import { normalizePartpath } from '../wire/part_invoke.mjs'
|
|
7
|
+
import partQueryTunables from '../wire/part_query.tunables.json' with { type: 'json' }
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {{
|
|
11
|
+
* requestId: string
|
|
12
|
+
* originNodeHash: string
|
|
13
|
+
* partpath: string
|
|
14
|
+
* kind: string
|
|
15
|
+
* query: unknown
|
|
16
|
+
* ttl: number
|
|
17
|
+
* budget: { maxHits: number }
|
|
18
|
+
* }} PartQueryReq
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {{
|
|
23
|
+
* requestId: string
|
|
24
|
+
* fromNodeHash: string
|
|
25
|
+
* rows: unknown[]
|
|
26
|
+
* }} PartQueryRes
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {unknown} value 任意 JSON
|
|
31
|
+
* @returns {number} UTF-8 序列化字节数;不可序列化时 Infinity
|
|
32
|
+
*/
|
|
33
|
+
export function measureJsonBytes(value) {
|
|
34
|
+
try {
|
|
35
|
+
return Buffer.byteLength(JSON.stringify(value), 'utf8')
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return Number.POSITIVE_INFINITY
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {unknown} budget 请求预算
|
|
44
|
+
* @param {number} [maxHits=partQueryTunables.maxHits] 上限
|
|
45
|
+
* @returns {{ maxHits: number }} 钳制后的预算
|
|
46
|
+
*/
|
|
47
|
+
export function clampPartQueryBudget(budget, maxHits = partQueryTunables.maxHits) {
|
|
48
|
+
const cap = Math.max(1, Math.floor(Number(maxHits) || partQueryTunables.maxHits))
|
|
49
|
+
const raw = isPlainObject(budget) ? Number(budget.maxHits) : NaN
|
|
50
|
+
const hits = Number.isFinite(raw) ? Math.floor(raw) : cap
|
|
51
|
+
return { maxHits: Math.max(1, Math.min(cap, hits)) }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {unknown} ttl 跳数预算
|
|
56
|
+
* @param {number} [maxTtl=partQueryTunables.maxTtl] 上限
|
|
57
|
+
* @returns {number | null} 钳制后的 ttl;非法为 null
|
|
58
|
+
*/
|
|
59
|
+
export function clampPartQueryTtl(ttl, maxTtl = partQueryTunables.maxTtl) {
|
|
60
|
+
const cap = Math.max(1, Math.floor(Number(maxTtl) || partQueryTunables.maxTtl))
|
|
61
|
+
const n = Math.floor(Number(ttl))
|
|
62
|
+
if (!Number.isFinite(n) || n < 1) return null
|
|
63
|
+
return Math.min(cap, n)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {unknown} rows 行数组
|
|
68
|
+
* @param {number} maxHits 条数上限
|
|
69
|
+
* @param {number} [maxRowsBytes=partQueryTunables.maxRowsBytes] 总尺寸上限
|
|
70
|
+
* @returns {unknown[] | null} 通过校验的 rows;失败 null
|
|
71
|
+
*/
|
|
72
|
+
export function clampPartQueryRows(rows, maxHits, maxRowsBytes = partQueryTunables.maxRowsBytes) {
|
|
73
|
+
if (!Array.isArray(rows)) return null
|
|
74
|
+
const limited = rows.slice(0, Math.max(0, Math.floor(Number(maxHits) || 0)))
|
|
75
|
+
if (measureJsonBytes(limited) > maxRowsBytes) return null
|
|
76
|
+
return limited
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** requestId 上限(randomUUID 36 字符;防超长键灌爆 dedupe/pending 表) */
|
|
80
|
+
const REQUEST_ID_MAX_LENGTH = 128
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @param {unknown} value 原始 requestId
|
|
84
|
+
* @returns {string | null} 修剪后的 requestId;非法 null
|
|
85
|
+
*/
|
|
86
|
+
function normalizeRequestId(value) {
|
|
87
|
+
const requestId = String(value || '').trim()
|
|
88
|
+
if (!requestId || requestId.length > REQUEST_ID_MAX_LENGTH) return null
|
|
89
|
+
return requestId
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {unknown} value 入站 req
|
|
94
|
+
* @param {typeof partQueryTunables} [tunables] 可调参数
|
|
95
|
+
* @returns {PartQueryReq | null} 校验通过的 req
|
|
96
|
+
*/
|
|
97
|
+
export function parsePartQueryReq(value, tunables = partQueryTunables) {
|
|
98
|
+
if (!isPlainObject(value)) return null
|
|
99
|
+
const requestId = normalizeRequestId(value.requestId)
|
|
100
|
+
if (!requestId) return null
|
|
101
|
+
const originNodeHash = normalizeHex64(value.originNodeHash)
|
|
102
|
+
if (!isHex64(originNodeHash)) return null
|
|
103
|
+
const partpath = normalizePartpath(value.partpath)
|
|
104
|
+
if (!partpath) return null
|
|
105
|
+
const kind = String(value.kind || '').trim()
|
|
106
|
+
if (!kind) return null
|
|
107
|
+
if (!Object.prototype.hasOwnProperty.call(value, 'query')) return null
|
|
108
|
+
if (measureJsonBytes(value.query) > (tunables.maxQueryBytes ?? 2048)) return null
|
|
109
|
+
const ttl = clampPartQueryTtl(value.ttl, tunables.maxTtl)
|
|
110
|
+
if (ttl == null) return null
|
|
111
|
+
const budget = clampPartQueryBudget(value.budget, tunables.maxHits)
|
|
112
|
+
return {
|
|
113
|
+
requestId,
|
|
114
|
+
originNodeHash,
|
|
115
|
+
partpath,
|
|
116
|
+
kind,
|
|
117
|
+
query: value.query,
|
|
118
|
+
ttl,
|
|
119
|
+
budget,
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @param {unknown} value 入站 res
|
|
125
|
+
* @param {typeof partQueryTunables} [tunables] 可调参数
|
|
126
|
+
* @returns {PartQueryRes | null} 校验通过的 res
|
|
127
|
+
*/
|
|
128
|
+
export function parsePartQueryRes(value, tunables = partQueryTunables) {
|
|
129
|
+
if (!isPlainObject(value)) return null
|
|
130
|
+
const requestId = normalizeRequestId(value.requestId)
|
|
131
|
+
if (!requestId) return null
|
|
132
|
+
const fromNodeHash = normalizeHex64(value.fromNodeHash)
|
|
133
|
+
if (!isHex64(fromNodeHash)) return null
|
|
134
|
+
const rows = clampPartQueryRows(value.rows, tunables.maxHits, tunables.maxRowsBytes)
|
|
135
|
+
if (!rows) return null
|
|
136
|
+
return { requestId, fromNodeHash, rows }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @param {unknown} partpath part 路径
|
|
141
|
+
* @param {unknown} kind 查询标签
|
|
142
|
+
* @param {unknown} query 不透明查询
|
|
143
|
+
* @returns {string | null} 规范化三元组的缓存材料;非法 null
|
|
144
|
+
*/
|
|
145
|
+
export function normalizePartQueryCacheMaterial(partpath, kind, query) {
|
|
146
|
+
const path = normalizePartpath(partpath)
|
|
147
|
+
const k = String(kind || '').trim()
|
|
148
|
+
if (!path || !k) return null
|
|
149
|
+
if (measureJsonBytes(query) > partQueryTunables.maxQueryBytes) return null
|
|
150
|
+
try {
|
|
151
|
+
return canonicalStringify({ partpath: path, kind: k, query })
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return null
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -47,14 +47,6 @@ export function resolveIceServers(groupSettings) {
|
|
|
47
47
|
return out.length ? out : [...DEFAULT_ICE_SERVERS]
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
/**
|
|
51
|
-
* @param {unknown} groupSettings 物化群设置
|
|
52
|
-
* @returns {{ iceServers: ReturnType<typeof resolveIceServers> }} Trystero rtcConfig 片段
|
|
53
|
-
*/
|
|
54
|
-
export function resolveIceServersForTrystero(groupSettings) {
|
|
55
|
-
return { iceServers: resolveIceServers(groupSettings) }
|
|
56
|
-
}
|
|
57
|
-
|
|
58
50
|
/**
|
|
59
51
|
* 校验并规范化待写入 DAG 的 iceServers 数组。
|
|
60
52
|
* @param {unknown} raw 请求体字段
|
|
@@ -34,61 +34,3 @@ export function pruneStaleRosterEntries(peerToNode, nodeToPeer, livePeerIds) {
|
|
|
34
34
|
}
|
|
35
35
|
return stale
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* 创建 peer id 与 nodeHash 双向映射及 roster 查询辅助。
|
|
40
|
-
* @param {object} [opts] 选项
|
|
41
|
-
* @param {() => Iterable<string>} [opts.getLivePeerIds] 获取当前在线 peer id 的回调
|
|
42
|
-
* @param {(stale: Array<{ peerId: string, remoteNodeHash?: string }>) => void} [opts.onStalePruned] stale 条目被清理时的回调
|
|
43
|
-
* @returns {{ peerToNode: Map<string, string>, nodeToPeer: Map<string, string>, getRoster: () => Array<{ peerId: string, remoteNodeHash: string | undefined }>, getPeerIdByNodeHash: (nodeHash: string) => string | null, onPeerLeave: (peerId: string) => void }} 映射与查询接口
|
|
44
|
-
*/
|
|
45
|
-
export function createPeerIdentityMaps(opts = {}) {
|
|
46
|
-
/** @type {Map<string, string>} */
|
|
47
|
-
const peerToNode = new Map()
|
|
48
|
-
/** @type {Map<string, string>} */
|
|
49
|
-
const nodeToPeer = new Map()
|
|
50
|
-
const getLivePeerIds = typeof opts.getLivePeerIds === 'function' ? opts.getLivePeerIds : null
|
|
51
|
-
const onStalePruned = typeof opts.onStalePruned === 'function' ? opts.onStalePruned : null
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* 根据 live peer 集合清理 stale 映射。
|
|
55
|
-
* @returns {void}
|
|
56
|
-
*/
|
|
57
|
-
const reconcile = () => {
|
|
58
|
-
if (!getLivePeerIds) return
|
|
59
|
-
const stale = pruneStaleRosterEntries(peerToNode, nodeToPeer, getLivePeerIds())
|
|
60
|
-
if (stale.length && onStalePruned) onStalePruned(stale)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
peerToNode,
|
|
65
|
-
nodeToPeer,
|
|
66
|
-
/**
|
|
67
|
-
* 返回当前 roster(会先 reconcile stale 条目)。
|
|
68
|
-
* @returns {Array<{ peerId: string, remoteNodeHash: string | undefined }>} roster 列表
|
|
69
|
-
*/
|
|
70
|
-
getRoster() {
|
|
71
|
-
reconcile()
|
|
72
|
-
return [...peerToNode.entries()].map(([peerId, remoteNodeHash]) => ({ peerId, remoteNodeHash }))
|
|
73
|
-
},
|
|
74
|
-
/**
|
|
75
|
-
* 按 nodeHash 查找对应 peer id。
|
|
76
|
-
* @param {string} targetNodeHash 目标节点 64 hex
|
|
77
|
-
* @returns {string | null} 对端 id;无映射时为 null
|
|
78
|
-
*/
|
|
79
|
-
getPeerIdByNodeHash(targetNodeHash) {
|
|
80
|
-
reconcile()
|
|
81
|
-
return nodeToPeer.get(String(targetNodeHash).trim().toLowerCase()) || null
|
|
82
|
-
},
|
|
83
|
-
/**
|
|
84
|
-
* peer 离线时从映射中移除。
|
|
85
|
-
* @param {string} peerId 离线的 peer id
|
|
86
|
-
* @returns {void}
|
|
87
|
-
*/
|
|
88
|
-
onPeerLeave(peerId) {
|
|
89
|
-
const remote = peerToNode.get(peerId)
|
|
90
|
-
if (remote) nodeToPeer.delete(remote)
|
|
91
|
-
peerToNode.delete(peerId)
|
|
92
|
-
},
|
|
93
|
-
}
|
|
94
|
-
}
|
|
@@ -95,15 +95,3 @@ export async function ensureRemoteUserRoom(username, targetNodeHash) {
|
|
|
95
95
|
inflights.set(key, task)
|
|
96
96
|
return await task
|
|
97
97
|
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* 释放目标节点的远端用户房间连接。
|
|
101
|
-
* @param {string} targetNodeHash 目标节点 64 hex
|
|
102
|
-
* @returns {void}
|
|
103
|
-
*/
|
|
104
|
-
export function releaseRemoteUserRoom(targetNodeHash) {
|
|
105
|
-
const key = targetNodeHash.toLowerCase()
|
|
106
|
-
const slot = slots.get(key)
|
|
107
|
-
if (slot) void Promise.resolve(slot.leave()).catch(() => { })
|
|
108
|
-
slots.delete(key)
|
|
109
|
-
}
|
package/transport/user_room.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { attachMailboxWire } from '../mailbox/wire.mjs'
|
|
|
4
4
|
import { ensureNodeDefaults, getNodeHash } from '../node/identity.mjs'
|
|
5
5
|
import { registerFederationRoomProvider } from '../registries/room_provider.mjs'
|
|
6
6
|
import { attachPartWire } from '../wire/part_ingress.mjs'
|
|
7
|
+
import { attachPartQueryWire } from '../wire/part_query.mjs'
|
|
7
8
|
|
|
8
9
|
import { listLinks, sendToNodeLink, subscribeScope, getLinkRegistry } from './link_registry.mjs'
|
|
9
10
|
import { USER_ROOM_SCOPE } from './room_scopes.mjs'
|
|
@@ -105,6 +106,7 @@ async function ensureNodeScopeRuntime(ctx) {
|
|
|
105
106
|
const wire = createNodeScopeWire()
|
|
106
107
|
nodeScopeWire = wire
|
|
107
108
|
attachPartWire({ replicaUsername: ctx.replicaUsername }, wire)
|
|
109
|
+
attachPartQueryWire({ replicaUsername: ctx.replicaUsername }, wire)
|
|
108
110
|
attachMailboxWire({ replicaUsername: ctx.replicaUsername }, wire)
|
|
109
111
|
attachUserRoomChunkHandlers(ctx.replicaUsername || '', wire)
|
|
110
112
|
for (const hook of nodeScopeWireHooks)
|
|
@@ -217,17 +219,6 @@ export async function ensureUserRoom(ctx = {}) {
|
|
|
217
219
|
return await userRoomInflight
|
|
218
220
|
}
|
|
219
221
|
|
|
220
|
-
/**
|
|
221
|
-
* @returns {void}
|
|
222
|
-
*/
|
|
223
|
-
export function invalidateUserRoom() {
|
|
224
|
-
userRoomSlot = null
|
|
225
|
-
userRoomInflight = null
|
|
226
|
-
nodeScopeWire = null
|
|
227
|
-
nodeScopeReplicaUsername = ''
|
|
228
|
-
nodeScopeCleanup = null
|
|
229
|
-
}
|
|
230
|
-
|
|
231
222
|
/**
|
|
232
223
|
* @param {string} username 副本用户名
|
|
233
224
|
* @param {string} actionName 节点 scope action
|
package/trust_graph/cache.mjs
CHANGED
|
@@ -4,13 +4,6 @@ let revision = 0
|
|
|
4
4
|
|
|
5
5
|
const DEFAULT_TTL_MS = 30_000
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
* @returns {number} 当前 revision
|
|
9
|
-
*/
|
|
10
|
-
export function getTrustGraphRevision() {
|
|
11
|
-
return revision
|
|
12
|
-
}
|
|
13
|
-
|
|
14
7
|
/**
|
|
15
8
|
* @returns {void}
|
|
16
9
|
*/
|
|
@@ -36,9 +29,3 @@ export async function getCachedTrustGraph(username, build, ttlMs = DEFAULT_TTL_M
|
|
|
36
29
|
cacheByUsername.set(key, { graph, builtAt: now, revision })
|
|
37
30
|
return graph
|
|
38
31
|
}
|
|
39
|
-
|
|
40
|
-
/** @returns {void} 测试用 */
|
|
41
|
-
export function clearTrustGraphCache() {
|
|
42
|
-
cacheByUsername.clear()
|
|
43
|
-
revision = 0
|
|
44
|
-
}
|
package/trust_graph/engine.mjs
CHANGED
|
@@ -17,30 +17,6 @@ import trustGraphTunables from './tunables.json' with { type: 'json' }
|
|
|
17
17
|
* }} TrustGraphInputs
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
/**
|
|
21
|
-
* @returns {typeof trustGraphTunables} 默认 tunables
|
|
22
|
-
*/
|
|
23
|
-
export function defaultTrustGraphTunables() {
|
|
24
|
-
return trustGraphTunables
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* @param {Map<string, TrustNode>} byNode 累积图
|
|
29
|
-
* @param {string} scopeId scope 标识
|
|
30
|
-
* @param {string} nodeHash 64 位十六进制
|
|
31
|
-
* @param {number} score 信誉分
|
|
32
|
-
*/
|
|
33
|
-
export function mergeTrustNode(byNode, scopeId, nodeHash, score) {
|
|
34
|
-
const previous = byNode.get(nodeHash)
|
|
35
|
-
if (previous) {
|
|
36
|
-
const seenCount = previous.scopeIds.length
|
|
37
|
-
previous.score = (previous.score * seenCount + score) / (seenCount + 1)
|
|
38
|
-
if (!previous.scopeIds.includes(scopeId)) previous.scopeIds.push(scopeId)
|
|
39
|
-
return
|
|
40
|
-
}
|
|
41
|
-
byNode.set(nodeHash, { nodeHash, score, scopeIds: [scopeId] })
|
|
42
|
-
}
|
|
43
|
-
|
|
44
20
|
/**
|
|
45
21
|
* @param {TrustGraphInputs} inputs 图输入
|
|
46
22
|
* @param {typeof trustGraphTunables} [tunables] tunables
|
package/utils/async_mutex.mjs
CHANGED
|
@@ -58,12 +58,3 @@ export async function withAsyncMutex(key, fn) {
|
|
|
58
58
|
}
|
|
59
59
|
})
|
|
60
60
|
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* @param {string} keyPrefix 前缀
|
|
64
|
-
* @returns {(key: string, fn: () => Promise<T>) => Promise<T>} 带前缀的 mutex 函数
|
|
65
|
-
* @template T
|
|
66
|
-
*/
|
|
67
|
-
export function asyncMutexForPrefix(keyPrefix) {
|
|
68
|
-
return (key, fn) => withAsyncMutex(`${keyPrefix}:${key}`, fn)
|
|
69
|
-
}
|
package/wire/part_fanout.mjs
CHANGED
|
@@ -50,22 +50,3 @@ export async function collectPartInvokeResponses(username, partpath, invoke, tim
|
|
|
50
50
|
|
|
51
51
|
return waitForResponses
|
|
52
52
|
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* 单向 part_invoke fanout(mailbox put 等)。
|
|
56
|
-
* @param {string} username 用户
|
|
57
|
-
* @param {string} partpath part 路径
|
|
58
|
-
* @param {import('./part_invoke.mjs').PartInvoke} invoke 调用体
|
|
59
|
-
* @param {number} limit fanout 上限
|
|
60
|
-
* @param {string} [nodeHash] 来源节点
|
|
61
|
-
* @param {string} [groupId] 群上下文
|
|
62
|
-
* @returns {Promise<number>} 发送次数
|
|
63
|
-
*/
|
|
64
|
-
export async function fanoutPartInvoke(username, partpath, invoke, limit, nodeHash, groupId) {
|
|
65
|
-
return requireTrustGraphProvider(DEFAULT_TRUST_GRAPH_OWNER).fanoutToTopNodes(username, 'part_invoke', buildPartInvokePayload({
|
|
66
|
-
partpath,
|
|
67
|
-
invoke,
|
|
68
|
-
nodeHash,
|
|
69
|
-
groupId,
|
|
70
|
-
}), limit)
|
|
71
|
-
}
|