@steve02081504/fount-p2p 0.0.5 → 0.0.6
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/dag/canonicalize_row.mjs +5 -5
- package/discovery/mdns.mjs +4 -4
- package/discovery/nostr.mjs +3 -3
- package/federation/chunk_fetch_pending.mjs +3 -3
- package/federation/chunk_fetch_scheduler.mjs +3 -3
- package/federation/dag_order_cache.mjs +3 -3
- package/federation/topo_order_memo.mjs +3 -3
- package/files/chunk_responder.mjs +1 -1
- package/files/evfs.mjs +26 -26
- package/files/transfer_key.mjs +9 -9
- package/files/transfer_key_registry.mjs +9 -9
- package/governance/join_pow.mjs +17 -17
- package/link/channel_mux.mjs +7 -7
- package/link/frame.mjs +5 -5
- package/link/handshake.mjs +17 -17
- package/link/link.mjs +33 -33
- package/mailbox/deliver_or_store.mjs +13 -13
- package/mailbox/wire.mjs +4 -4
- package/node/reputation_store.mjs +5 -5
- package/node/retention_policy.mjs +8 -8
- package/overlay/index.mjs +6 -6
- package/package.json +1 -1
- package/registries/inbound.mjs +8 -8
- package/rooms/scoped_link.mjs +18 -18
- package/timeline/append_core.mjs +3 -3
- package/transport/group_link_set.mjs +30 -30
- package/transport/link_registry.mjs +13 -13
- package/transport/peer_pool.mjs +4 -4
- package/transport/user_room.mjs +12 -14
- package/wire/group_part.mjs +3 -3
- package/wire/part_ingress.mjs +14 -14
- package/wire/part_query.mjs +81 -82
- package/wire/part_query_cache.mjs +7 -0
package/link/link.mjs
CHANGED
|
@@ -73,27 +73,27 @@ function formatErrorReason(error) {
|
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
75
|
* 建立 P2P link:WebRTC 双通道、握手、分帧收发与心跳。
|
|
76
|
-
* @param {object}
|
|
77
|
-
* @param {string | null} [
|
|
78
|
-
* @param {boolean}
|
|
79
|
-
* @param {{ send: (message: unknown) => void | Promise<void>, onRemote: (handler: (message: unknown) => void) => (() => void) | void }}
|
|
80
|
-
* @param {RTCConfiguration['iceServers']} [
|
|
81
|
-
* @param {number} [
|
|
82
|
-
* @param {number} [
|
|
83
|
-
* @param {number} [
|
|
84
|
-
* @param {{ RTCPeerConnection: typeof RTCPeerConnection } | null} [
|
|
85
|
-
* @param {{ nodeHash?: string, nodePubKey?: string, secretKey?: Uint8Array, nonce?: string } | null} [
|
|
86
|
-
* @returns {Promise<{ ready: Promise<void>, get nodeHash(): string | null, send: (envelope: { scope: string, action: string, payload: unknown }) => Promise<boolean>, onEnvelope: (
|
|
76
|
+
* @param {object} options link 配置
|
|
77
|
+
* @param {string | null} [options.nodeHash] 期望的对端 nodeHash,省略则不校验
|
|
78
|
+
* @param {boolean} options.initiator 是否为连接发起方
|
|
79
|
+
* @param {{ send: (message: unknown) => void | Promise<void>, onRemote: (handler: (message: unknown) => void) => (() => void) | void }} options.signal 信令收发接口
|
|
80
|
+
* @param {RTCConfiguration['iceServers']} [options.iceServers] ICE 服务器列表
|
|
81
|
+
* @param {number} [options.heartbeatMs] 心跳间隔(毫秒)
|
|
82
|
+
* @param {number} [options.idleTimeoutMs] 无入站流量超时(毫秒)
|
|
83
|
+
* @param {number} [options.handshakeTimeoutMs] 握手超时(毫秒)
|
|
84
|
+
* @param {{ RTCPeerConnection: typeof RTCPeerConnection } | null} [options.rtc] RTC 构造器,省略则加载 polyfill
|
|
85
|
+
* @param {{ nodeHash?: string, nodePubKey?: string, secretKey?: Uint8Array, nonce?: string } | null} [options.localIdentity] 本地握手身份
|
|
86
|
+
* @returns {Promise<{ ready: Promise<void>, get nodeHash(): string | null, send: (envelope: { scope: string, action: string, payload: unknown }) => Promise<boolean>, onEnvelope: (callback: (envelope: { scope: string, action: string, payload: unknown }, remoteNodeHash: string) => void) => () => void, onDown: (callback: (reason: string) => void) => () => void, close: (reason?: string) => Promise<void>, stats: () => object }>} link 句柄
|
|
87
87
|
*/
|
|
88
|
-
export async function createLink(
|
|
89
|
-
const heartbeatMs = Number(
|
|
90
|
-
const idleTimeoutMs = Number(
|
|
91
|
-
const handshakeTimeoutMs = Number(
|
|
88
|
+
export async function createLink(options) {
|
|
89
|
+
const heartbeatMs = Number(options.heartbeatMs) || ms('15s')
|
|
90
|
+
const idleTimeoutMs = Number(options.idleTimeoutMs) || ms('45s')
|
|
91
|
+
const handshakeTimeoutMs = Number(options.handshakeTimeoutMs) || ms('10s')
|
|
92
92
|
const channelOpenTimeoutMs = Math.max(handshakeTimeoutMs, ms('30s'))
|
|
93
93
|
const trickleIceOff = getSignalingRuntimeConfig().trickleIceOff === true
|
|
94
|
-
const rtc =
|
|
95
|
-
const pc = new rtc.RTCPeerConnection(
|
|
96
|
-
const targetNodeHash = normalizeHex64(
|
|
94
|
+
const rtc = options.rtc ?? await loadNodeRtcPolyfill()
|
|
95
|
+
const pc = new rtc.RTCPeerConnection(options.iceServers?.length ? { iceServers: options.iceServers } : undefined)
|
|
96
|
+
const targetNodeHash = normalizeHex64(options.nodeHash || '')
|
|
97
97
|
const remoteSignalQueue = []
|
|
98
98
|
const seenRemoteSignals = createLruMap(1024)
|
|
99
99
|
let remoteDescriptionSet = false
|
|
@@ -142,7 +142,7 @@ export async function createLink(opts) {
|
|
|
142
142
|
* @returns {Promise<void>}
|
|
143
143
|
*/
|
|
144
144
|
async function sendSignal(message) {
|
|
145
|
-
await Promise.resolve(
|
|
145
|
+
await Promise.resolve(options.signal.send(message))
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
/**
|
|
@@ -185,7 +185,7 @@ export async function createLink(opts) {
|
|
|
185
185
|
const fingerprint = localFingerprint()
|
|
186
186
|
if (!fingerprint) return
|
|
187
187
|
authSent = true
|
|
188
|
-
await sendRawControl(await buildAuth(remoteHello.nonce, fingerprint,
|
|
188
|
+
await sendRawControl(await buildAuth(remoteHello.nonce, fingerprint, options.localIdentity ?? {}))
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
/**
|
|
@@ -446,7 +446,7 @@ export async function createLink(opts) {
|
|
|
446
446
|
if (!helloSent) {
|
|
447
447
|
handshakeTimer = setTimeout(() => { void close('handshake-timeout') }, handshakeTimeoutMs)
|
|
448
448
|
helloSent = true
|
|
449
|
-
localHello = buildHello(
|
|
449
|
+
localHello = buildHello(options.localIdentity ?? {})
|
|
450
450
|
await sendRawControl(localHello)
|
|
451
451
|
}
|
|
452
452
|
await maybeSendAuth()
|
|
@@ -507,7 +507,7 @@ export async function createLink(opts) {
|
|
|
507
507
|
emitListeners(downListeners, reason)
|
|
508
508
|
}
|
|
509
509
|
|
|
510
|
-
unlistenRemote =
|
|
510
|
+
unlistenRemote = options.signal.onRemote(message => {
|
|
511
511
|
void handleRemoteSignal(message).catch(error => close(`signal-error:${formatErrorReason(error)}`))
|
|
512
512
|
}) ?? null
|
|
513
513
|
|
|
@@ -535,7 +535,7 @@ export async function createLink(opts) {
|
|
|
535
535
|
}
|
|
536
536
|
}
|
|
537
537
|
|
|
538
|
-
if (
|
|
538
|
+
if (options.initiator) {
|
|
539
539
|
await attachChannel(pc.createDataChannel(CHANNEL_CONTROL))
|
|
540
540
|
await attachChannel(pc.createDataChannel(CHANNEL_BULK))
|
|
541
541
|
const offer = await pc.createOffer()
|
|
@@ -559,25 +559,25 @@ export async function createLink(opts) {
|
|
|
559
559
|
* 本端是否为连接发起方。
|
|
560
560
|
* @returns {boolean} 发起方标志
|
|
561
561
|
*/
|
|
562
|
-
get initiator() { return !!
|
|
562
|
+
get initiator() { return !!options.initiator },
|
|
563
563
|
send,
|
|
564
564
|
/**
|
|
565
565
|
* 订阅业务 envelope 入站。
|
|
566
|
-
* @param {(envelope: { scope: string, action: string, payload: unknown }, remoteNodeHash: string) => void}
|
|
566
|
+
* @param {(envelope: { scope: string, action: string, payload: unknown }, remoteNodeHash: string) => void} callback 回调
|
|
567
567
|
* @returns {() => void} 取消订阅函数
|
|
568
568
|
*/
|
|
569
|
-
onEnvelope(
|
|
570
|
-
envelopeListeners.add(
|
|
571
|
-
return () => envelopeListeners.delete(
|
|
569
|
+
onEnvelope(callback) {
|
|
570
|
+
envelopeListeners.add(callback)
|
|
571
|
+
return () => envelopeListeners.delete(callback)
|
|
572
572
|
},
|
|
573
573
|
/**
|
|
574
574
|
* 订阅 link 断开事件。
|
|
575
|
-
* @param {(reason: string) => void}
|
|
575
|
+
* @param {(reason: string) => void} callback 回调
|
|
576
576
|
* @returns {() => void} 取消订阅函数
|
|
577
577
|
*/
|
|
578
|
-
onDown(
|
|
579
|
-
downListeners.add(
|
|
580
|
-
return () => downListeners.delete(
|
|
578
|
+
onDown(callback) {
|
|
579
|
+
downListeners.add(callback)
|
|
580
|
+
return () => downListeners.delete(callback)
|
|
581
581
|
},
|
|
582
582
|
close,
|
|
583
583
|
/**
|
|
@@ -597,7 +597,7 @@ export async function createLink(opts) {
|
|
|
597
597
|
ready,
|
|
598
598
|
nodeHash: remoteNodeHash,
|
|
599
599
|
targetNodeHash: targetNodeHash || null,
|
|
600
|
-
initiator: !!
|
|
600
|
+
initiator: !!options.initiator,
|
|
601
601
|
connectionState: pc.connectionState,
|
|
602
602
|
iceConnectionState: pc.iceConnectionState,
|
|
603
603
|
lastInboundAt,
|
|
@@ -59,26 +59,26 @@ async function resolveRouting(username) {
|
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
61
|
* @param {string} username 副本用户名(trust graph 投递上下文)
|
|
62
|
-
* @param {object}
|
|
62
|
+
* @param {object} options 投递选项
|
|
63
63
|
* @returns {Promise<{ stored: boolean, delivered: boolean, relayed: number }>} 存转结果
|
|
64
64
|
*/
|
|
65
|
-
export async function deliverOrStoreMailboxPut(username,
|
|
65
|
+
export async function deliverOrStoreMailboxPut(username, options) {
|
|
66
66
|
const routing = await resolveRouting(username)
|
|
67
|
-
const toPubKeyHash = normalizeHex64(
|
|
67
|
+
const toPubKeyHash = normalizeHex64(options.toPubKeyHash)
|
|
68
68
|
if (!toPubKeyHash) return { stored: false, delivered: false, relayed: 0 }
|
|
69
|
-
const hop = normalizeMailboxHop(
|
|
69
|
+
const hop = normalizeMailboxHop(options.hop)
|
|
70
70
|
if (hop >= routing.maxHop) return { stored: false, delivered: false, relayed: 0 }
|
|
71
71
|
const tier = mailboxTierFromHop(hop)
|
|
72
72
|
const nodeHash = getNodeHash()
|
|
73
73
|
const record = {
|
|
74
|
-
...
|
|
74
|
+
...options.record,
|
|
75
75
|
toPubKeyHash,
|
|
76
76
|
hop,
|
|
77
77
|
tier,
|
|
78
|
-
fromNodeHash:
|
|
78
|
+
fromNodeHash: options.record?.fromNodeHash || nodeHash,
|
|
79
79
|
}
|
|
80
80
|
const stored = await storeMailboxRecord(record)
|
|
81
|
-
const toNodeHash =
|
|
81
|
+
const toNodeHash = options.toNodeHash?.trim().toLowerCase()
|
|
82
82
|
const delivered = toNodeHash && isMailboxRecordWithinSizeLimit(record)
|
|
83
83
|
? await requireTrustGraphProvider(DEFAULT_TRUST_GRAPH_OWNER).sendToNode(username, toNodeHash, 'mailbox_put', { nodeHash, record })
|
|
84
84
|
: false
|
|
@@ -108,17 +108,17 @@ export async function publishMailboxRecord(username, toPubKeyHash, record, toNod
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
/**
|
|
111
|
-
* @param {{ replicaUsername?: string }}
|
|
111
|
+
* @param {{ replicaUsername?: string }} wireContext 入站上下文
|
|
112
112
|
* @param {object} put 入站 mailbox_put
|
|
113
113
|
* @param {string} [peerId] Trystero 对端 id(有则校验 nodeHash 绑定)
|
|
114
114
|
* @returns {Promise<void>}
|
|
115
115
|
*/
|
|
116
|
-
export async function ingestMailboxPut(
|
|
116
|
+
export async function ingestMailboxPut(wireContext, put, peerId = '') {
|
|
117
117
|
const { record } = put
|
|
118
118
|
if (!record?.envelope || !record?.toPubKeyHash) return
|
|
119
119
|
const fromNode = normalizeHex64(put.nodeHash)
|
|
120
120
|
if (!fromNode || !takeIncomingMailboxPutSlot(fromNode)) return
|
|
121
|
-
const username = String(
|
|
121
|
+
const username = String(wireContext.replicaUsername || '').trim()
|
|
122
122
|
if (!username) return
|
|
123
123
|
if (peerId) {
|
|
124
124
|
const remote = await resolveRemoteNodeHashForPeer(username, peerId)
|
|
@@ -157,14 +157,14 @@ export async function respondMailboxWant(want, sendGive, peerId) {
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
/**
|
|
160
|
-
* @param {{ replicaUsername?: string }}
|
|
160
|
+
* @param {{ replicaUsername?: string }} wireContext 入站上下文
|
|
161
161
|
* @param {object} give mailbox_give 载荷
|
|
162
162
|
* @returns {Promise<number>} 投递给消费者的记录数
|
|
163
163
|
*/
|
|
164
|
-
export async function ingestMailboxGive(
|
|
164
|
+
export async function ingestMailboxGive(wireContext, give) {
|
|
165
165
|
const records = (give.records || []).filter(isDeliverableMailboxRecord)
|
|
166
166
|
if (!records.length) return 0
|
|
167
|
-
const username = String(
|
|
167
|
+
const username = String(wireContext.replicaUsername || '').trim()
|
|
168
168
|
if (!username) return 0
|
|
169
169
|
const { dispatchMailboxRecordsToConsumers } = await import('./consumer_registry.mjs')
|
|
170
170
|
const { deleteMailboxRecords } = await import('./store.mjs')
|
package/mailbox/wire.mjs
CHANGED
|
@@ -10,15 +10,15 @@ import { parseMailboxGive, parseMailboxPut, parseMailboxWant } from './parse.mjs
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* @param {MailboxWireContext}
|
|
13
|
+
* @param {MailboxWireContext} wireContext 入站上下文
|
|
14
14
|
* @param {{ on: (name: string, handler: (payload: unknown, peerId: string) => void) => void, send: (name: string, payload: unknown, peerId: string | null) => void }} wire Trystero 适配器
|
|
15
15
|
* @returns {void}
|
|
16
16
|
*/
|
|
17
|
-
export function attachMailboxWire(
|
|
17
|
+
export function attachMailboxWire(wireContext, wire) {
|
|
18
18
|
wire.on('mailbox_put', (payload, peerId) => {
|
|
19
19
|
const put = parseMailboxPut(payload)
|
|
20
20
|
if (!put.ok) return
|
|
21
|
-
void ingestMailboxPut(
|
|
21
|
+
void ingestMailboxPut(wireContext, put.value, peerId).catch(err => console.error('mailbox: put ingest failed', err))
|
|
22
22
|
})
|
|
23
23
|
|
|
24
24
|
wire.on('mailbox_want', (payload, peerId) => {
|
|
@@ -35,6 +35,6 @@ export function attachMailboxWire(ctx, wire) {
|
|
|
35
35
|
wire.on('mailbox_give', payload => {
|
|
36
36
|
const give = parseMailboxGive(payload)
|
|
37
37
|
if (!give.ok) return
|
|
38
|
-
void ingestMailboxGive(
|
|
38
|
+
void ingestMailboxGive(wireContext, give.value).catch(err => console.error('mailbox: give ingest failed', err))
|
|
39
39
|
})
|
|
40
40
|
}
|
|
@@ -26,12 +26,12 @@ import { readNodeJsonSync, writeNodeJsonSync } from './storage.mjs'
|
|
|
26
26
|
|
|
27
27
|
const DATA_NAME = 'reputation'
|
|
28
28
|
|
|
29
|
-
/** @type {((
|
|
29
|
+
/** @type {((options: object) => Promise<boolean>) | null} */
|
|
30
30
|
let blockReputationHandler = null
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Shell Load 时注册:公开 block/unblock → 信誉传导。
|
|
34
|
-
* @param {(
|
|
34
|
+
* @param {(options: object) => Promise<boolean>} handler block/unblock 信誉传导回调
|
|
35
35
|
* @returns {void}
|
|
36
36
|
*/
|
|
37
37
|
export function registerBlockReputationHandler(handler) {
|
|
@@ -308,10 +308,10 @@ export function pickNodeScore(nodeId) {
|
|
|
308
308
|
}
|
|
309
309
|
|
|
310
310
|
/**
|
|
311
|
-
* @param {object}
|
|
311
|
+
* @param {object} options applyFollowedBlockSignal 参数
|
|
312
312
|
* @returns {Promise<boolean>} 是否已应用
|
|
313
313
|
*/
|
|
314
|
-
export async function applyBlockReputationSignal(
|
|
314
|
+
export async function applyBlockReputationSignal(options) {
|
|
315
315
|
if (!blockReputationHandler) return false
|
|
316
|
-
return blockReputationHandler(
|
|
316
|
+
return blockReputationHandler(options)
|
|
317
317
|
}
|
|
@@ -8,16 +8,16 @@ import {
|
|
|
8
8
|
* 在共识分支上计算须保留的事件 id(连通子图,不用拓扑下标切片)。
|
|
9
9
|
* @param {string[]} order 规范拓扑序
|
|
10
10
|
* @param {Map<string, object>} byId id → 事件
|
|
11
|
-
* @param {object}
|
|
12
|
-
* @param {number}
|
|
13
|
-
* @param {number}
|
|
14
|
-
* @param {Set<string>}
|
|
15
|
-
* @param {string | null} [
|
|
16
|
-
* @param {string | null} [
|
|
11
|
+
* @param {object} options 保留策略
|
|
12
|
+
* @param {number} options.maxDepth 分支上最大事件深度
|
|
13
|
+
* @param {number} options.cutoffWall 最早保留的 HLC wall
|
|
14
|
+
* @param {Set<string>} options.anchorTypes 权限锚点事件类型
|
|
15
|
+
* @param {string | null} [options.checkpointTipId] checkpoint 尖
|
|
16
|
+
* @param {string | null} [options.branchTipId] 共识分支尖
|
|
17
17
|
* @returns {Set<string>} 保留 id
|
|
18
18
|
*/
|
|
19
|
-
export function computeRetentionKeepIds(order, byId,
|
|
20
|
-
const { maxDepth, cutoffWall, anchorTypes, checkpointTipId, branchTipId } =
|
|
19
|
+
export function computeRetentionKeepIds(order, byId, options) {
|
|
20
|
+
const { maxDepth, cutoffWall, anchorTypes, checkpointTipId, branchTipId } = options
|
|
21
21
|
const branchOrder = authzFoldOrderIds(order, byId, branchTipId)
|
|
22
22
|
const branchSet = new Set(branchOrder)
|
|
23
23
|
if (!branchSet.size) return new Set()
|
package/overlay/index.mjs
CHANGED
|
@@ -131,15 +131,15 @@ export function createOverlayRouter(registry, ttl = 3) {
|
|
|
131
131
|
/**
|
|
132
132
|
* 发现到目标节点的签名路由路径。
|
|
133
133
|
* @param {string} targetNodeHash 目标节点 64 hex
|
|
134
|
-
* @param {object} [
|
|
135
|
-
* @param {number} [
|
|
136
|
-
* @param {number} [
|
|
134
|
+
* @param {object} [options] 选项
|
|
135
|
+
* @param {number} [options.ttl] 路由 TTL
|
|
136
|
+
* @param {number} [options.timeoutMs] 超时毫秒
|
|
137
137
|
* @returns {Promise<string[]>} 从本节点到目标的 nodeHash 路径
|
|
138
138
|
*/
|
|
139
|
-
async discoverRoute(targetNodeHash,
|
|
139
|
+
async discoverRoute(targetNodeHash, options = {}) {
|
|
140
140
|
const reqId = randomMsgIdHex()
|
|
141
|
-
const maxTtl = Number(
|
|
142
|
-
const timeoutMs = Number(
|
|
141
|
+
const maxTtl = Number(options.ttl) || ttl
|
|
142
|
+
const timeoutMs = Number(options.timeoutMs) || 10_000
|
|
143
143
|
const promise = new Promise((resolve, reject) => {
|
|
144
144
|
const timer = setTimeout(() => {
|
|
145
145
|
pendingRoutes.delete(reqId)
|
package/package.json
CHANGED
package/registries/inbound.mjs
CHANGED
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* @typedef {(
|
|
13
|
+
* @typedef {(inboundContext: InboundContext, message: object) => Promise<PartInvokeResponse | null>} RpcInboundHandler
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* @typedef {(
|
|
17
|
+
* @typedef {(inboundContext: InboundContext, message: object) => Promise<void>} DeliveryInboundHandler
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
/** @type {Map<string, RpcInboundHandler>} */
|
|
@@ -42,27 +42,27 @@ export function registerDeliveryInboundHandler(type, handler) {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
* @param {InboundContext}
|
|
45
|
+
* @param {InboundContext} inboundContext 入站上下文
|
|
46
46
|
* @param {object} message 已校验的线载荷(含 type)
|
|
47
47
|
* @returns {Promise<PartInvokeResponse | null>} 处理器返回值
|
|
48
48
|
*/
|
|
49
|
-
export async function dispatchRpcInbound(
|
|
49
|
+
export async function dispatchRpcInbound(inboundContext, message) {
|
|
50
50
|
const type = String(message?.type || '').trim()
|
|
51
51
|
if (!type) return null
|
|
52
52
|
const handler = rpcHandlers.get(type)
|
|
53
53
|
if (!handler) return null
|
|
54
|
-
return handler(
|
|
54
|
+
return handler(inboundContext, message)
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
|
-
* @param {InboundContext}
|
|
58
|
+
* @param {InboundContext} inboundContext 入站上下文
|
|
59
59
|
* @param {object} message 已校验的线载荷(含 type)
|
|
60
60
|
* @returns {Promise<void>}
|
|
61
61
|
*/
|
|
62
|
-
export async function dispatchDeliveryInbound(
|
|
62
|
+
export async function dispatchDeliveryInbound(inboundContext, message) {
|
|
63
63
|
const type = String(message?.type || '').trim()
|
|
64
64
|
if (!type) return
|
|
65
65
|
const handler = deliveryHandlers.get(type)
|
|
66
66
|
if (!handler) return
|
|
67
|
-
await handler(
|
|
67
|
+
await handler(inboundContext, message)
|
|
68
68
|
}
|
package/rooms/scoped_link.mjs
CHANGED
|
@@ -9,17 +9,17 @@ import {
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* 在指定 scope 与 roomSecret 下创建 link 层房间(discovery + registry 转发)。
|
|
12
|
-
* @param {object}
|
|
13
|
-
* @param {string}
|
|
14
|
-
* @param {string}
|
|
15
|
-
* @param {(nodeHash: string) => boolean} [
|
|
16
|
-
* @returns {{ start: () => Promise<void>, leave: () => Promise<void>, makeAction: (name: string) => [(payload: unknown, peerId?: string | string[] | null) => Promise<void>, (handler: (payload: unknown, peerId: string) => void) => void], onPeerJoin: (
|
|
12
|
+
* @param {object} options 房间选项
|
|
13
|
+
* @param {string} options.scope link registry scope(如 `group:{id}`)
|
|
14
|
+
* @param {string} options.roomSecret 房间 rendezvous 密钥
|
|
15
|
+
* @param {(nodeHash: string) => boolean} [options.allowNode] 是否允许与某 nodeHash 通信
|
|
16
|
+
* @returns {{ start: () => Promise<void>, leave: () => Promise<void>, makeAction: (name: string) => [(payload: unknown, peerId?: string | string[] | null) => Promise<void>, (handler: (payload: unknown, peerId: string) => void) => void], onPeerJoin: (callback: (peerId: string) => void) => () => void, onPeerLeave: (callback: (peerId: string) => void) => () => void, getPeers: () => Record<string, true> }} 房间句柄
|
|
17
17
|
*/
|
|
18
|
-
export function createScopedLinkRoom(
|
|
18
|
+
export function createScopedLinkRoom(options) {
|
|
19
19
|
const registry = getLinkRegistry()
|
|
20
|
-
const scope = String(
|
|
21
|
-
const topic = groupRendezvousTopic(
|
|
22
|
-
const allowNode = typeof
|
|
20
|
+
const scope = String(options.scope)
|
|
21
|
+
const topic = groupRendezvousTopic(options.roomSecret)
|
|
22
|
+
const allowNode = typeof options.allowNode === 'function' ? options.allowNode : () => true
|
|
23
23
|
/** @type {Set<string>} */
|
|
24
24
|
const discoveredPeers = new Set()
|
|
25
25
|
/** @type {Set<string>} */
|
|
@@ -156,24 +156,24 @@ export function createScopedLinkRoom(opts) {
|
|
|
156
156
|
]
|
|
157
157
|
},
|
|
158
158
|
/**
|
|
159
|
-
* @param {(peerId: string) => void}
|
|
159
|
+
* @param {(peerId: string) => void} callback 新 peer 上线回调
|
|
160
160
|
* @returns {() => void} 取消订阅
|
|
161
161
|
*/
|
|
162
|
-
onPeerJoin(
|
|
163
|
-
joinListeners.add(
|
|
162
|
+
onPeerJoin(callback) {
|
|
163
|
+
joinListeners.add(callback)
|
|
164
164
|
for (const peerId of activePeerIds())
|
|
165
165
|
announcedPeers.add(peerId)
|
|
166
166
|
for (const peerId of announcedPeers)
|
|
167
|
-
try {
|
|
168
|
-
return () => joinListeners.delete(
|
|
167
|
+
try { callback(peerId) } catch { /* ignore */ }
|
|
168
|
+
return () => joinListeners.delete(callback)
|
|
169
169
|
},
|
|
170
170
|
/**
|
|
171
|
-
* @param {(peerId: string) => void}
|
|
171
|
+
* @param {(peerId: string) => void} callback peer 离线回调
|
|
172
172
|
* @returns {() => void} 取消订阅
|
|
173
173
|
*/
|
|
174
|
-
onPeerLeave(
|
|
175
|
-
leaveListeners.add(
|
|
176
|
-
return () => leaveListeners.delete(
|
|
174
|
+
onPeerLeave(callback) {
|
|
175
|
+
leaveListeners.add(callback)
|
|
176
|
+
return () => leaveListeners.delete(callback)
|
|
177
177
|
},
|
|
178
178
|
/**
|
|
179
179
|
* @returns {Record<string, true>} 当前活跃 peer 的 nodeHash 集合
|
package/timeline/append_core.mjs
CHANGED
|
@@ -14,17 +14,17 @@ import { computeDagTipIdsFromEvents } from '../governance/branch.mjs'
|
|
|
14
14
|
* 为追加事件计算 HLC 与 DAG 前驱(chat 群与 social 时间线共用)。
|
|
15
15
|
* @param {object[]} previous 已有事件
|
|
16
16
|
* @param {object} event 待追加事件
|
|
17
|
-
* @param {{ multiTip?: boolean }} [
|
|
17
|
+
* @param {{ multiTip?: boolean }} [options] chat 在多 tip 时连接全部 tip
|
|
18
18
|
* @returns {{ hlc: object, prev_event_ids: string[], last: object | undefined }} HLC、前驱与末条事件
|
|
19
19
|
*/
|
|
20
|
-
export function computeAppendHlcAndPrev(previous, event,
|
|
20
|
+
export function computeAppendHlcAndPrev(previous, event, options = {}) {
|
|
21
21
|
const last = previous[previous.length - 1]
|
|
22
22
|
const hlc = nextHlc(last?.hlc, event.timestamp ?? Date.now())
|
|
23
23
|
const tips = computeDagTipIdsFromEvents(previous)
|
|
24
24
|
let prev_event_ids
|
|
25
25
|
if (event.prev_event_ids?.length)
|
|
26
26
|
prev_event_ids = sortedPrevEventIds(event.prev_event_ids)
|
|
27
|
-
else if (
|
|
27
|
+
else if (options.multiTip && tips.length > 1)
|
|
28
28
|
prev_event_ids = sortedPrevEventIds(tips)
|
|
29
29
|
else if (tips.length)
|
|
30
30
|
prev_event_ids = sortedPrevEventIds(tips)
|
|
@@ -14,22 +14,22 @@ import { resolveFederationPoolLimits, selectLinkTargetsFromMembers } from './pee
|
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* 创建基于 link registry 的群组联邦房间。
|
|
17
|
-
* @param {object}
|
|
18
|
-
* @param {string}
|
|
19
|
-
* @param {string}
|
|
20
|
-
* @param {string[]}
|
|
21
|
-
* @param {object} [
|
|
22
|
-
* @returns {{ groupId: string, scope: string, start: () => Promise<void>, leave: () => Promise<void>, getRoster: () => Array<{ peerId: string, remoteNodeHash: string }>, getPeerIdByNodeHash: (nodeHash: string) => string | null, sendToPeer: (peerId: string, actionName: string, payload: unknown) => Promise<boolean>, send: (actionName: string, payload: unknown, peerId?: string | null) => Promise<number>, onEnvelope: (
|
|
17
|
+
* @param {object} options 选项
|
|
18
|
+
* @param {string} options.groupId 群组 id
|
|
19
|
+
* @param {string} options.roomSecret 房间密钥(用于 rendezvous topic)
|
|
20
|
+
* @param {string[]} options.members 初始成员 nodeHash 列表
|
|
21
|
+
* @param {object} [options.groupSettings] 群设置(连接预算:trustedPeerSlots/explorePeerSlots/maxPeers 等)
|
|
22
|
+
* @returns {{ groupId: string, scope: string, start: () => Promise<void>, leave: () => Promise<void>, getRoster: () => Array<{ peerId: string, remoteNodeHash: string }>, getPeerIdByNodeHash: (nodeHash: string) => string | null, sendToPeer: (peerId: string, actionName: string, payload: unknown) => Promise<boolean>, send: (actionName: string, payload: unknown, peerId?: string | null) => Promise<number>, onEnvelope: (callback: (senderNodeHash: string, envelope: object) => void) => () => void, onPeerJoin: (callback: (peerId: string) => void) => () => void, onPeerLeave: (callback: (peerId: string) => void) => () => void, getPeers: () => Record<string, true>, makeAction: (name: string) => [(payload: unknown, peerId?: string | string[] | null) => Promise<void>, (handler: (payload: unknown, peerId: string) => void) => void], registerCleanup: (cleanup: () => void) => void, isActive: () => boolean }} 群组 link set 接口
|
|
23
23
|
*/
|
|
24
|
-
export function createGroupLinkSet(
|
|
25
|
-
const registry =
|
|
26
|
-
const autoconnect =
|
|
27
|
-
const groupId = String(
|
|
24
|
+
export function createGroupLinkSet(options) {
|
|
25
|
+
const registry = options.registry ?? getLinkRegistry()
|
|
26
|
+
const autoconnect = options.autoconnect !== false
|
|
27
|
+
const groupId = String(options.groupId)
|
|
28
28
|
const scope = `group:${groupId}`
|
|
29
|
-
const topic = groupRendezvousTopic(
|
|
30
|
-
const members = new Set((Array.isArray(
|
|
29
|
+
const topic = groupRendezvousTopic(options.roomSecret)
|
|
30
|
+
const members = new Set((Array.isArray(options.members) ? options.members : []).map(String))
|
|
31
31
|
const selfNodeHash = registry.localIdentity.nodeHash
|
|
32
|
-
const groupSettings =
|
|
32
|
+
const groupSettings = options.groupSettings ?? {}
|
|
33
33
|
// 初始成员是调用方明确知道的引导集合(如 introducer/creator/seed),作为必连锚点保证引导期连通。
|
|
34
34
|
const initialAnchors = new Set(members)
|
|
35
35
|
/** @type {ReturnType<typeof setTimeout> | null} */
|
|
@@ -50,12 +50,12 @@ export function createGroupLinkSet(opts) {
|
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* 注册 leave 时执行的清理回调。
|
|
53
|
-
* @param {() => void}
|
|
53
|
+
* @param {() => void} cleanup 清理函数
|
|
54
54
|
* @returns {void}
|
|
55
55
|
*/
|
|
56
|
-
function registerCleanup(
|
|
57
|
-
if (typeof
|
|
58
|
-
cleanups.add(
|
|
56
|
+
function registerCleanup(cleanup) {
|
|
57
|
+
if (typeof cleanup !== 'function') return
|
|
58
|
+
cleanups.add(cleanup)
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
@@ -255,34 +255,34 @@ export function createGroupLinkSet(opts) {
|
|
|
255
255
|
},
|
|
256
256
|
/**
|
|
257
257
|
* 订阅入站 envelope。
|
|
258
|
-
* @param {(senderNodeHash: string, envelope: object) => void}
|
|
258
|
+
* @param {(senderNodeHash: string, envelope: object) => void} callback 回调
|
|
259
259
|
* @returns {() => void} 取消订阅函数
|
|
260
260
|
*/
|
|
261
|
-
onEnvelope(
|
|
262
|
-
envelopeListeners.add(
|
|
263
|
-
return () => envelopeListeners.delete(
|
|
261
|
+
onEnvelope(callback) {
|
|
262
|
+
envelopeListeners.add(callback)
|
|
263
|
+
return () => envelopeListeners.delete(callback)
|
|
264
264
|
},
|
|
265
265
|
/**
|
|
266
266
|
* 订阅 peer 加入事件(含当前已在线 peer 的即时回调)。
|
|
267
|
-
* @param {(peerId: string) => void}
|
|
267
|
+
* @param {(peerId: string) => void} callback 回调
|
|
268
268
|
* @returns {() => void} 取消订阅函数
|
|
269
269
|
*/
|
|
270
|
-
onPeerJoin(
|
|
271
|
-
peerJoinListeners.add(
|
|
270
|
+
onPeerJoin(callback) {
|
|
271
|
+
peerJoinListeners.add(callback)
|
|
272
272
|
for (const { peerId } of activeRoster())
|
|
273
273
|
if (peerId) announcedPeers.add(peerId)
|
|
274
274
|
for (const peerId of announcedPeers)
|
|
275
|
-
try {
|
|
276
|
-
return () => peerJoinListeners.delete(
|
|
275
|
+
try { callback(peerId) } catch { /* ignore */ }
|
|
276
|
+
return () => peerJoinListeners.delete(callback)
|
|
277
277
|
},
|
|
278
278
|
/**
|
|
279
279
|
* 订阅 peer 离开事件。
|
|
280
|
-
* @param {(peerId: string) => void}
|
|
280
|
+
* @param {(peerId: string) => void} callback 回调
|
|
281
281
|
* @returns {() => void} 取消订阅函数
|
|
282
282
|
*/
|
|
283
|
-
onPeerLeave(
|
|
284
|
-
peerLeaveListeners.add(
|
|
285
|
-
return () => peerLeaveListeners.delete(
|
|
283
|
+
onPeerLeave(callback) {
|
|
284
|
+
peerLeaveListeners.add(callback)
|
|
285
|
+
return () => peerLeaveListeners.delete(callback)
|
|
286
286
|
},
|
|
287
287
|
/**
|
|
288
288
|
* 返回当前在线 peer 的 Record 映射。
|
|
@@ -187,20 +187,20 @@ function subscribeBucket(buckets, key, listener) {
|
|
|
187
187
|
|
|
188
188
|
/**
|
|
189
189
|
* 创建 P2P 链路注册表(discovery、信令、直连与 overlay relay)。
|
|
190
|
-
* @param {object} [
|
|
191
|
-
* @param {{ nodeHash?: string, nodePubKey?: string, secretKey?: Uint8Array }} [
|
|
192
|
-
* @param {typeof createLink} [
|
|
193
|
-
* @param {RTCConfiguration['iceServers']} [
|
|
194
|
-
* @param {number} [
|
|
195
|
-
* @param {boolean} [
|
|
190
|
+
* @param {object} [options] 选项
|
|
191
|
+
* @param {{ nodeHash?: string, nodePubKey?: string, secretKey?: Uint8Array }} [options.localIdentity] 本地身份
|
|
192
|
+
* @param {typeof createLink} [options.createLink] 链路工厂(可注入 mock)
|
|
193
|
+
* @param {RTCConfiguration['iceServers']} [options.iceServers] ICE 服务器列表
|
|
194
|
+
* @param {number} [options.maxActive] 最大并发活跃链路数
|
|
195
|
+
* @param {boolean} [options.autoRegisterDiscoveryProviders] 是否自动注册 discovery provider
|
|
196
196
|
* @returns {object} link registry 接口
|
|
197
197
|
*/
|
|
198
|
-
export function createLinkRegistry(
|
|
199
|
-
const localIdentity = resolveLocalIdentity(
|
|
200
|
-
const createLinkImpl =
|
|
201
|
-
const iceServers =
|
|
202
|
-
const maxActive = Math.max(4, Number(
|
|
203
|
-
const autoRegisterDiscoveryProviders =
|
|
198
|
+
export function createLinkRegistry(options = {}) {
|
|
199
|
+
const localIdentity = resolveLocalIdentity(options.localIdentity)
|
|
200
|
+
const createLinkImpl = options.createLink ?? createLink
|
|
201
|
+
const iceServers = options.iceServers?.length ? options.iceServers : DEFAULT_ICE_SERVERS
|
|
202
|
+
const maxActive = Math.max(4, Number(options.maxActive) || 32)
|
|
203
|
+
const autoRegisterDiscoveryProviders = options.autoRegisterDiscoveryProviders !== false
|
|
204
204
|
const selfTopic = nodeRendezvousTopic(localIdentity.nodeHash)
|
|
205
205
|
/** @type {Map<string, Awaited<ReturnType<typeof createLinkImpl>>>} */
|
|
206
206
|
const links = new Map()
|
|
@@ -348,7 +348,7 @@ export function createLinkRegistry(opts = {}) {
|
|
|
348
348
|
/**
|
|
349
349
|
* 为一条 connId 会话建 PC 并走 glare 择一:建 link → 绑 onDown 清 session → ready → registerResolvedLink。
|
|
350
350
|
* initiator 侧建链前先 trimToBudget 腾预算。出错则清理该 connId 会话。
|
|
351
|
-
* @param {{ remoteNodeHash: string, connId: string, session: ReturnType<typeof createBufferedSignalSession>, initiator: boolean }}
|
|
351
|
+
* @param {{ remoteNodeHash: string, connId: string, session: ReturnType<typeof createBufferedSignalSession>, initiator: boolean }} options 建链参数
|
|
352
352
|
* @returns {Promise<Awaited<ReturnType<typeof createLinkImpl>> | null>} 当前规范链(本条可能因 glare 被关,取 links 现值);失败 null
|
|
353
353
|
*/
|
|
354
354
|
async function buildConnLink({ remoteNodeHash, connId, session, initiator }) {
|
package/transport/peer_pool.mjs
CHANGED
|
@@ -141,7 +141,7 @@ export function selectExploreWithSourceQuota(exploreIds, exploreSources, k, maxP
|
|
|
141
141
|
* selfNodeHash: string,
|
|
142
142
|
* inRoomNodeHashes?: Set<string> | string[] 群内在线 node_id;有则优先,仅全不可达时用 explore 中非房内节点
|
|
143
143
|
* hintSources?: Map<string, string> explore 节点来源(用于配额)
|
|
144
|
-
* }}
|
|
144
|
+
* }} options 选取参数(roster、peers、rep、limits、selfNodeHash)
|
|
145
145
|
* @returns {string[]} 目标 Trystero peerId 列表(去重,长度 ≤ maxPeers)
|
|
146
146
|
*/
|
|
147
147
|
export function selectPeerIdsFromPool({ roster, peers, rep, limits, selfNodeHash, inRoomNodeHashes, hintSources }) {
|
|
@@ -212,7 +212,7 @@ export function selectPeerIdsFromPool({ roster, peers, rep, limits, selfNodeHash
|
|
|
212
212
|
* peers: { trustedPeers: string[], explorePeers: string[], blockedPeers: string[], hintSources?: Map<string, string> },
|
|
213
213
|
* limits: ReturnType<typeof resolveFederationPoolLimits>,
|
|
214
214
|
* anchors?: Iterable<string>,
|
|
215
|
-
* }}
|
|
215
|
+
* }} options 选取参数(members、selfNodeHash、rep、peers、limits、anchors)
|
|
216
216
|
* @returns {string[]} 应建链的 nodeHash 列表(去重)
|
|
217
217
|
*/
|
|
218
218
|
export function selectLinkTargetsFromMembers({ members, selfNodeHash, rep, peers, limits, anchors = [] }) {
|
|
@@ -245,7 +245,7 @@ export function selectLinkTargetsFromMembers({ members, selfNodeHash, rep, peers
|
|
|
245
245
|
* rep: { byNodeHash?: Record<string, { score?: number }> },
|
|
246
246
|
* hints: string[],
|
|
247
247
|
* limits: ReturnType<typeof resolveFederationPoolLimits>,
|
|
248
|
-
* }}
|
|
248
|
+
* }} options 合并参数(peers、rep、hints、limits)
|
|
249
249
|
* @returns {{ trustedPeers: string[], explorePeers: string[] }} 更新后的 trusted/explore 列表
|
|
250
250
|
*/
|
|
251
251
|
export function applyPexHints({ peers, rep, hints, limits }) {
|
|
@@ -275,7 +275,7 @@ export function applyPexHints({ peers, rep, hints, limits }) {
|
|
|
275
275
|
* rep: { byNodeHash?: Record<string, { score?: number }> },
|
|
276
276
|
* roster: Array<{ remoteNodeHash?: string }>,
|
|
277
277
|
* limits: ReturnType<typeof resolveFederationPoolLimits>,
|
|
278
|
-
* }}
|
|
278
|
+
* }} options roster 更新参数(peers、rep、roster、limits)
|
|
279
279
|
* @returns {{ trustedPeers: string[], explorePeers: string[] }} 更新后的 trusted/explore 列表
|
|
280
280
|
*/
|
|
281
281
|
export function applyRosterToPeerPool({ peers, rep, roster, limits }) {
|