@steve02081504/fount-p2p 0.0.5 → 0.0.7
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/tcp_port.mjs +11 -0
- package/dag/canonicalize_row.mjs +5 -5
- package/discovery/advert_peer_hints.mjs +21 -0
- package/discovery/{bt.mjs → bt/index.mjs} +151 -55
- package/discovery/bt/peer_hints.mjs +41 -0
- package/discovery/bt/runtime.mjs +46 -0
- package/discovery/lan_peer_hints.mjs +43 -0
- package/discovery/mdns.mjs +10 -6
- 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/index.mjs +3 -2
- package/link/channel_mux.mjs +7 -7
- package/link/frame.mjs +5 -5
- package/link/handshake.mjs +63 -36
- package/link/pipe.mjs +404 -0
- package/link/providers/ble_gatt.mjs +339 -0
- package/link/providers/index.mjs +90 -0
- package/link/providers/lan_tcp.mjs +342 -0
- package/link/providers/levels.mjs +9 -0
- package/link/providers/webrtc.mjs +385 -0
- 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 +3 -2
- package/registries/inbound.mjs +8 -8
- package/rooms/scoped_link.mjs +23 -21
- package/timeline/append_core.mjs +3 -3
- package/transport/group_link_set.mjs +35 -33
- package/transport/link_registry.mjs +250 -63
- package/transport/peer_pool.mjs +4 -4
- package/transport/user_room.mjs +12 -14
- package/utils/ttl_map.mjs +41 -0
- 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 +0 -617
package/rooms/scoped_link.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { noteAdvertPeerHints } from '../discovery/advert_peer_hints.mjs'
|
|
1
2
|
import { advertiseTopic, subscribeTopic } from '../discovery/index.mjs'
|
|
2
|
-
import {
|
|
3
|
+
import { verifySignedAdvert } from '../link/handshake.mjs'
|
|
3
4
|
import {
|
|
4
5
|
groupRendezvousTopic,
|
|
5
6
|
decryptSignalPacket,
|
|
@@ -9,17 +10,17 @@ import {
|
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* 在指定 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: (
|
|
13
|
+
* @param {object} options 房间选项
|
|
14
|
+
* @param {string} options.scope link registry scope(如 `group:{id}`)
|
|
15
|
+
* @param {string} options.roomSecret 房间 rendezvous 密钥
|
|
16
|
+
* @param {(nodeHash: string) => boolean} [options.allowNode] 是否允许与某 nodeHash 通信
|
|
17
|
+
* @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
18
|
*/
|
|
18
|
-
export function createScopedLinkRoom(
|
|
19
|
+
export function createScopedLinkRoom(options) {
|
|
19
20
|
const registry = getLinkRegistry()
|
|
20
|
-
const scope = String(
|
|
21
|
-
const topic = groupRendezvousTopic(
|
|
22
|
-
const allowNode = typeof
|
|
21
|
+
const scope = String(options.scope)
|
|
22
|
+
const topic = groupRendezvousTopic(options.roomSecret)
|
|
23
|
+
const allowNode = typeof options.allowNode === 'function' ? options.allowNode : () => true
|
|
23
24
|
/** @type {Set<string>} */
|
|
24
25
|
const discoveredPeers = new Set()
|
|
25
26
|
/** @type {Set<string>} */
|
|
@@ -102,18 +103,19 @@ export function createScopedLinkRoom(opts) {
|
|
|
102
103
|
if (!discoveredPeers.has(nodeHash)) return
|
|
103
104
|
notePeerLeave(nodeHash)
|
|
104
105
|
}))
|
|
105
|
-
cleanups.add(await subscribeTopic(topic, async bytes => {
|
|
106
|
+
cleanups.add(await subscribeTopic(topic, async (bytes, meta) => {
|
|
106
107
|
const packet = decryptSignalPacket(topic, bytes)
|
|
107
108
|
if (packet?.type !== 'advert' || !packet.body) return
|
|
108
109
|
const verifiedNodeHash = await verifySignedAdvert(topic, packet.body)
|
|
109
110
|
if (!verifiedNodeHash || !allowNode(verifiedNodeHash)) return
|
|
111
|
+
noteAdvertPeerHints(verifiedNodeHash, packet.body, meta)
|
|
110
112
|
discoveredPeers.add(verifiedNodeHash)
|
|
111
113
|
await registry.ensureLinkToNode(verifiedNodeHash).catch(() => null)
|
|
112
114
|
if (registry.getLink(verifiedNodeHash)) notePeerJoin(verifiedNodeHash)
|
|
113
115
|
}))
|
|
114
116
|
cleanups.add(await advertiseTopic(topic, encryptSignalPacket(topic, {
|
|
115
117
|
type: 'advert',
|
|
116
|
-
body: await
|
|
118
|
+
body: await registry.buildLocalAdvert(topic),
|
|
117
119
|
})))
|
|
118
120
|
for (const peerId of activePeerIds())
|
|
119
121
|
notePeerJoin(peerId)
|
|
@@ -156,24 +158,24 @@ export function createScopedLinkRoom(opts) {
|
|
|
156
158
|
]
|
|
157
159
|
},
|
|
158
160
|
/**
|
|
159
|
-
* @param {(peerId: string) => void}
|
|
161
|
+
* @param {(peerId: string) => void} callback 新 peer 上线回调
|
|
160
162
|
* @returns {() => void} 取消订阅
|
|
161
163
|
*/
|
|
162
|
-
onPeerJoin(
|
|
163
|
-
joinListeners.add(
|
|
164
|
+
onPeerJoin(callback) {
|
|
165
|
+
joinListeners.add(callback)
|
|
164
166
|
for (const peerId of activePeerIds())
|
|
165
167
|
announcedPeers.add(peerId)
|
|
166
168
|
for (const peerId of announcedPeers)
|
|
167
|
-
try {
|
|
168
|
-
return () => joinListeners.delete(
|
|
169
|
+
try { callback(peerId) } catch { /* ignore */ }
|
|
170
|
+
return () => joinListeners.delete(callback)
|
|
169
171
|
},
|
|
170
172
|
/**
|
|
171
|
-
* @param {(peerId: string) => void}
|
|
173
|
+
* @param {(peerId: string) => void} callback peer 离线回调
|
|
172
174
|
* @returns {() => void} 取消订阅
|
|
173
175
|
*/
|
|
174
|
-
onPeerLeave(
|
|
175
|
-
leaveListeners.add(
|
|
176
|
-
return () => leaveListeners.delete(
|
|
176
|
+
onPeerLeave(callback) {
|
|
177
|
+
leaveListeners.add(callback)
|
|
178
|
+
return () => leaveListeners.delete(callback)
|
|
177
179
|
},
|
|
178
180
|
/**
|
|
179
181
|
* @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)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { noteAdvertPeerHints } from '../discovery/advert_peer_hints.mjs'
|
|
1
2
|
import { advertiseTopic, subscribeTopic } from '../discovery/index.mjs'
|
|
2
|
-
import {
|
|
3
|
+
import { verifySignedAdvert } from '../link/handshake.mjs'
|
|
3
4
|
import { loadPeerPoolView } from '../node/network.mjs'
|
|
4
5
|
import { loadReputation } from '../node/reputation_store.mjs'
|
|
5
6
|
|
|
@@ -14,22 +15,22 @@ import { resolveFederationPoolLimits, selectLinkTargetsFromMembers } from './pee
|
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* 创建基于 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: (
|
|
18
|
+
* @param {object} options 选项
|
|
19
|
+
* @param {string} options.groupId 群组 id
|
|
20
|
+
* @param {string} options.roomSecret 房间密钥(用于 rendezvous topic)
|
|
21
|
+
* @param {string[]} options.members 初始成员 nodeHash 列表
|
|
22
|
+
* @param {object} [options.groupSettings] 群设置(连接预算:trustedPeerSlots/explorePeerSlots/maxPeers 等)
|
|
23
|
+
* @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
24
|
*/
|
|
24
|
-
export function createGroupLinkSet(
|
|
25
|
-
const registry =
|
|
26
|
-
const autoconnect =
|
|
27
|
-
const groupId = String(
|
|
25
|
+
export function createGroupLinkSet(options) {
|
|
26
|
+
const registry = options.registry ?? getLinkRegistry()
|
|
27
|
+
const autoconnect = options.autoconnect !== false
|
|
28
|
+
const groupId = String(options.groupId)
|
|
28
29
|
const scope = `group:${groupId}`
|
|
29
|
-
const topic = groupRendezvousTopic(
|
|
30
|
-
const members = new Set((Array.isArray(
|
|
30
|
+
const topic = groupRendezvousTopic(options.roomSecret)
|
|
31
|
+
const members = new Set((Array.isArray(options.members) ? options.members : []).map(String))
|
|
31
32
|
const selfNodeHash = registry.localIdentity.nodeHash
|
|
32
|
-
const groupSettings =
|
|
33
|
+
const groupSettings = options.groupSettings ?? {}
|
|
33
34
|
// 初始成员是调用方明确知道的引导集合(如 introducer/creator/seed),作为必连锚点保证引导期连通。
|
|
34
35
|
const initialAnchors = new Set(members)
|
|
35
36
|
/** @type {ReturnType<typeof setTimeout> | null} */
|
|
@@ -50,12 +51,12 @@ export function createGroupLinkSet(opts) {
|
|
|
50
51
|
|
|
51
52
|
/**
|
|
52
53
|
* 注册 leave 时执行的清理回调。
|
|
53
|
-
* @param {() => void}
|
|
54
|
+
* @param {() => void} cleanup 清理函数
|
|
54
55
|
* @returns {void}
|
|
55
56
|
*/
|
|
56
|
-
function registerCleanup(
|
|
57
|
-
if (typeof
|
|
58
|
-
cleanups.add(
|
|
57
|
+
function registerCleanup(cleanup) {
|
|
58
|
+
if (typeof cleanup !== 'function') return
|
|
59
|
+
cleanups.add(cleanup)
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
/**
|
|
@@ -186,17 +187,18 @@ export function createGroupLinkSet(opts) {
|
|
|
186
187
|
if (!members.has(nodeHash) || nodeHash === selfNodeHash) return
|
|
187
188
|
notePeerLeave(nodeHash)
|
|
188
189
|
}))
|
|
189
|
-
registerCleanup(await subscribeTopic(topic, async bytes => {
|
|
190
|
+
registerCleanup(await subscribeTopic(topic, async (bytes, meta) => {
|
|
190
191
|
const packet = decryptSignalPacket(topic, bytes)
|
|
191
192
|
if (packet?.type !== 'advert' || !packet.body) return
|
|
192
193
|
const verifiedNodeHash = await verifySignedAdvert(topic, packet.body)
|
|
193
194
|
if (!verifiedNodeHash || verifiedNodeHash === selfNodeHash) return
|
|
195
|
+
noteAdvertPeerHints(verifiedNodeHash, packet.body, meta)
|
|
194
196
|
// 发现新成员:并入成员集并去抖重算稀疏建链目标(notePeerCandidate 内部会 scheduleDial)。
|
|
195
197
|
notePeerCandidate(verifiedNodeHash)
|
|
196
198
|
}))
|
|
197
199
|
registerCleanup(await advertiseTopic(topic, encryptSignalPacket(topic, {
|
|
198
200
|
type: 'advert',
|
|
199
|
-
body: await
|
|
201
|
+
body: await registry.buildLocalAdvert(topic),
|
|
200
202
|
})))
|
|
201
203
|
if (autoconnect) selectAndDial()
|
|
202
204
|
for (const { peerId } of activeRoster())
|
|
@@ -255,34 +257,34 @@ export function createGroupLinkSet(opts) {
|
|
|
255
257
|
},
|
|
256
258
|
/**
|
|
257
259
|
* 订阅入站 envelope。
|
|
258
|
-
* @param {(senderNodeHash: string, envelope: object) => void}
|
|
260
|
+
* @param {(senderNodeHash: string, envelope: object) => void} callback 回调
|
|
259
261
|
* @returns {() => void} 取消订阅函数
|
|
260
262
|
*/
|
|
261
|
-
onEnvelope(
|
|
262
|
-
envelopeListeners.add(
|
|
263
|
-
return () => envelopeListeners.delete(
|
|
263
|
+
onEnvelope(callback) {
|
|
264
|
+
envelopeListeners.add(callback)
|
|
265
|
+
return () => envelopeListeners.delete(callback)
|
|
264
266
|
},
|
|
265
267
|
/**
|
|
266
268
|
* 订阅 peer 加入事件(含当前已在线 peer 的即时回调)。
|
|
267
|
-
* @param {(peerId: string) => void}
|
|
269
|
+
* @param {(peerId: string) => void} callback 回调
|
|
268
270
|
* @returns {() => void} 取消订阅函数
|
|
269
271
|
*/
|
|
270
|
-
onPeerJoin(
|
|
271
|
-
peerJoinListeners.add(
|
|
272
|
+
onPeerJoin(callback) {
|
|
273
|
+
peerJoinListeners.add(callback)
|
|
272
274
|
for (const { peerId } of activeRoster())
|
|
273
275
|
if (peerId) announcedPeers.add(peerId)
|
|
274
276
|
for (const peerId of announcedPeers)
|
|
275
|
-
try {
|
|
276
|
-
return () => peerJoinListeners.delete(
|
|
277
|
+
try { callback(peerId) } catch { /* ignore */ }
|
|
278
|
+
return () => peerJoinListeners.delete(callback)
|
|
277
279
|
},
|
|
278
280
|
/**
|
|
279
281
|
* 订阅 peer 离开事件。
|
|
280
|
-
* @param {(peerId: string) => void}
|
|
282
|
+
* @param {(peerId: string) => void} callback 回调
|
|
281
283
|
* @returns {() => void} 取消订阅函数
|
|
282
284
|
*/
|
|
283
|
-
onPeerLeave(
|
|
284
|
-
peerLeaveListeners.add(
|
|
285
|
-
return () => peerLeaveListeners.delete(
|
|
285
|
+
onPeerLeave(callback) {
|
|
286
|
+
peerLeaveListeners.add(callback)
|
|
287
|
+
return () => peerLeaveListeners.delete(callback)
|
|
286
288
|
},
|
|
287
289
|
/**
|
|
288
290
|
* 返回当前在线 peer 的 Record 映射。
|