@steve02081504/fount-p2p 0.0.12 → 0.0.13
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 +41 -10
- package/core/composite_key.mjs +5 -5
- package/crypto/crypto.mjs +3 -3
- package/crypto/key.mjs +12 -12
- package/dag/storage.mjs +36 -36
- package/discovery/advert_peer_hints.mjs +1 -1
- package/discovery/adverts.mjs +109 -0
- package/discovery/bt/index.mjs +153 -112
- package/discovery/bt/probe_child.mjs +2 -1
- package/discovery/bt/runtime.mjs +2 -9
- package/discovery/index.mjs +267 -62
- package/discovery/internal/signal_crypto.mjs +109 -0
- package/discovery/lan.mjs +228 -0
- package/discovery/nostr.mjs +394 -59
- package/federation/chunk_fetch_pending.mjs +10 -13
- package/federation/manifest_fetch_pending.mjs +1 -3
- package/files/assemble.mjs +14 -14
- package/files/assemble_stream.mjs +6 -6
- package/files/chunk_responder.mjs +29 -20
- package/files/evfs.mjs +29 -28
- package/files/manifest_fetch.mjs +16 -3
- package/files/public_manifest.mjs +11 -11
- package/files/transfer_key_registry.mjs +2 -2
- package/index.mjs +86 -11
- package/infra/cli.mjs +62 -0
- package/infra/debug_log.mjs +56 -0
- package/infra/default_node_dir.mjs +22 -0
- package/infra/priority.mjs +56 -0
- package/infra/service.mjs +140 -0
- package/infra/tunables.json +5 -0
- package/link/frame.mjs +9 -16
- package/link/handshake.mjs +14 -15
- package/link/pipe.mjs +8 -8
- package/link/providers/ble_gatt.mjs +6 -6
- package/link/rtc.mjs +3 -3
- package/mailbox/consumer_registry.mjs +2 -2
- package/mailbox/deliver_or_store.mjs +5 -5
- package/mailbox/wire.mjs +28 -24
- package/node/entity_store.mjs +6 -6
- package/node/instance.mjs +46 -27
- package/node/local_data_revision.mjs +26 -0
- package/node/log.mjs +56 -0
- package/node/network.mjs +37 -5
- package/node/reputation_store.mjs +4 -4
- package/node/reputation_sync.mjs +318 -0
- package/node/routing_profile.mjs +21 -0
- package/node/signaling_config.mjs +30 -11
- package/overlay/index.mjs +22 -1
- package/package.json +13 -2
- package/rooms/scoped_link.mjs +17 -166
- package/transport/advert_ingest.mjs +11 -14
- package/transport/group_link_set.mjs +149 -99
- package/transport/link_registry.mjs +211 -60
- package/transport/mesh_keepalive.mjs +217 -0
- package/transport/node_scope.mjs +289 -0
- package/transport/offer_answer.mjs +45 -48
- package/transport/peer_pool.mjs +116 -14
- package/transport/remote_user_room.mjs +6 -9
- package/transport/{rtc_mdns_filter.mjs → rtc_ice_local_hostname.mjs} +13 -13
- package/transport/runtime_bootstrap.mjs +170 -108
- package/transport/tunables.json +11 -0
- package/transport/tunables.mjs +18 -0
- package/transport/user_room.mjs +83 -158
- package/trust_graph/build.mjs +0 -2
- package/trust_graph/cache.mjs +12 -3
- package/trust_graph/registry.mjs +6 -6
- package/trust_graph/send.mjs +0 -3
- package/utils/async_mutex.mjs +4 -4
- package/utils/emit_safe.mjs +3 -3
- package/utils/json_io.mjs +12 -12
- package/utils/map_pool.mjs +5 -5
- package/wire/part_ingress.mjs +32 -28
- package/wire/part_query.mjs +26 -20
- package/discovery/mdns.mjs +0 -197
- package/transport/signal_crypto.mjs +0 -104
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { bumpLocalDataRevision } from '../node/local_data_revision.mjs'
|
|
1
2
|
import { registerFederationRoomProvider } from '../registries/room_provider.mjs'
|
|
2
|
-
import { invalidateTrustGraphCache } from '../trust_graph/cache.mjs'
|
|
3
3
|
|
|
4
4
|
import { closeLink, ensureLinkToNode, getLink } from './link_registry.mjs'
|
|
5
5
|
import { USER_ROOM_SCOPE } from './room_scopes.mjs'
|
|
@@ -14,7 +14,7 @@ import { USER_ROOM_SCOPE } from './room_scopes.mjs'
|
|
|
14
14
|
|
|
15
15
|
/** @type {Map<string, RemoteUserRoomSlot>} nodeHash → slot */
|
|
16
16
|
const slots = new Map()
|
|
17
|
-
/** @type {Map<string, Promise<RemoteUserRoomSlot
|
|
17
|
+
/** @type {Map<string, Promise<RemoteUserRoomSlot>>} nodeHash → 进行中的 promise */
|
|
18
18
|
const inflights = new Map()
|
|
19
19
|
|
|
20
20
|
registerFederationRoomProvider('remote-user-room', () => {
|
|
@@ -25,7 +25,7 @@ registerFederationRoomProvider('remote-user-room', () => {
|
|
|
25
25
|
* 加入目标节点的用户房间(幂等)。
|
|
26
26
|
* @param {string} username 本地 replica 用户名
|
|
27
27
|
* @param {string} targetNodeHash 目标节点 64 hex
|
|
28
|
-
* @returns {Promise<RemoteUserRoomSlot
|
|
28
|
+
* @returns {Promise<RemoteUserRoomSlot>} 房间槽
|
|
29
29
|
*/
|
|
30
30
|
export async function ensureRemoteUserRoom(username, targetNodeHash) {
|
|
31
31
|
void username
|
|
@@ -37,7 +37,8 @@ export async function ensureRemoteUserRoom(username, targetNodeHash) {
|
|
|
37
37
|
|
|
38
38
|
const task = (async () => {
|
|
39
39
|
try {
|
|
40
|
-
if (!await ensureLinkToNode(key))
|
|
40
|
+
if (!await ensureLinkToNode(key))
|
|
41
|
+
throw new Error(`p2p: ensureRemoteUserRoom link failed for ${key}`)
|
|
41
42
|
|
|
42
43
|
/** @type {import('../registries/room_provider.mjs').FederationRoomSlot} */
|
|
43
44
|
const roomSlot = {
|
|
@@ -78,13 +79,9 @@ export async function ensureRemoteUserRoom(username, targetNodeHash) {
|
|
|
78
79
|
},
|
|
79
80
|
}
|
|
80
81
|
slots.set(key, slot)
|
|
81
|
-
|
|
82
|
+
bumpLocalDataRevision()
|
|
82
83
|
return slot
|
|
83
84
|
}
|
|
84
|
-
catch (error) {
|
|
85
|
-
console.error('p2p: failed to join remote user room', key, error)
|
|
86
|
-
return null
|
|
87
|
-
}
|
|
88
85
|
finally {
|
|
89
86
|
inflights.delete(key)
|
|
90
87
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 服务端 WebRTC polyfill
|
|
3
|
-
*
|
|
2
|
+
* 服务端 WebRTC polyfill 在 Windows 等环境常产出 `.local` host candidate,
|
|
3
|
+
* 远端无法解析。按 iceLocalHostnamePolicy 改写为 loopback 或丢弃。
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
/** @typedef {'none' | 'rewrite-loopback' | 'drop'}
|
|
6
|
+
/** @typedef {'none' | 'rewrite-loopback' | 'drop'} IceLocalHostnamePolicy */
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @param {string | null | undefined} candidateSdp ICE candidate SDP 行
|
|
10
|
-
* @param {
|
|
10
|
+
* @param {IceLocalHostnamePolicy} policy 处理策略
|
|
11
11
|
* @returns {string | null} 处理后的 SDP;drop 策略下不可用时返回 null
|
|
12
12
|
*/
|
|
13
|
-
export function
|
|
13
|
+
export function applyIceLocalHostnamePolicy(candidateSdp, policy) {
|
|
14
14
|
const sdp = String(candidateSdp || '').trim()
|
|
15
15
|
if (!sdp || !/\.local/i.test(sdp)) return sdp || null
|
|
16
16
|
if (!/\btyp host\b/i.test(sdp)) return sdp
|
|
@@ -25,15 +25,15 @@ export function applyMdnsHostCandidatePolicy(candidateSdp, policy) {
|
|
|
25
25
|
/**
|
|
26
26
|
* @param {RTCIceCandidate | { candidate?: string } | null | undefined} candidate ICE candidate
|
|
27
27
|
* @param {typeof RTCIceCandidate} RTCIceCandidateCtor 构造函数
|
|
28
|
-
* @param {
|
|
28
|
+
* @param {IceLocalHostnamePolicy} policy 处理策略
|
|
29
29
|
* @returns {RTCIceCandidate | { candidate?: string } | null | undefined} 过滤/改写后的 candidate
|
|
30
30
|
*/
|
|
31
|
-
export function
|
|
31
|
+
export function filterIceLocalHostnameCandidate(candidate, RTCIceCandidateCtor, policy) {
|
|
32
32
|
if (!candidate || policy === 'none') return candidate
|
|
33
33
|
const raw = typeof candidate === 'string'
|
|
34
34
|
? candidate
|
|
35
35
|
: candidate.candidate ?? candidate.toJSON?.()?.candidate ?? ''
|
|
36
|
-
const rewritten =
|
|
36
|
+
const rewritten = applyIceLocalHostnamePolicy(raw, policy)
|
|
37
37
|
if (!rewritten) return null
|
|
38
38
|
if (rewritten === raw) return candidate
|
|
39
39
|
try {
|
|
@@ -50,10 +50,10 @@ export function filterMdnsIceCandidate(candidate, RTCIceCandidateCtor, policy) {
|
|
|
50
50
|
/**
|
|
51
51
|
* @param {typeof RTCPeerConnection} BaseRTC 原始 polyfill 类
|
|
52
52
|
* @param {typeof RTCIceCandidate} [RTCIceCandidate] ICE candidate 构造函数
|
|
53
|
-
* @param {
|
|
53
|
+
* @param {IceLocalHostnamePolicy} [policy='drop'] 策略
|
|
54
54
|
* @returns {typeof RTCPeerConnection} 包装后的 RTCPeerConnection 类(none 时原样返回)
|
|
55
55
|
*/
|
|
56
|
-
export function
|
|
56
|
+
export function wrapRtcPeerConnectionForIceLocalHostname(BaseRTC, RTCIceCandidate = globalThis.RTCIceCandidate, policy = 'drop') {
|
|
57
57
|
if (policy === 'none') return BaseRTC
|
|
58
58
|
|
|
59
59
|
/**
|
|
@@ -66,7 +66,7 @@ export function wrapRtcPeerConnectionForMdns(BaseRTC, RTCIceCandidate = globalTh
|
|
|
66
66
|
handler(event)
|
|
67
67
|
return
|
|
68
68
|
}
|
|
69
|
-
const filtered =
|
|
69
|
+
const filtered = filterIceLocalHostnameCandidate(event.candidate, RTCIceCandidate, policy)
|
|
70
70
|
if (!filtered) return
|
|
71
71
|
if (filtered === event.candidate) {
|
|
72
72
|
handler(event)
|
|
@@ -75,7 +75,7 @@ export function wrapRtcPeerConnectionForMdns(BaseRTC, RTCIceCandidate = globalTh
|
|
|
75
75
|
handler({ ...event, candidate: filtered })
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
return class
|
|
78
|
+
return class IceLocalHostnameFilteredRTCPeerConnection extends BaseRTC {
|
|
79
79
|
/** @type {((event: RTCPeerConnectionIceEvent) => void) | null} */
|
|
80
80
|
#userIceHandler = null
|
|
81
81
|
|
|
@@ -98,7 +98,7 @@ export function wrapRtcPeerConnectionForMdns(BaseRTC, RTCIceCandidate = globalTh
|
|
|
98
98
|
this.#userIceHandler({ candidate: null })
|
|
99
99
|
return
|
|
100
100
|
}
|
|
101
|
-
const filtered =
|
|
101
|
+
const filtered = filterIceLocalHostnameCandidate(candidate, RTCIceCandidate, policy)
|
|
102
102
|
if (filtered)
|
|
103
103
|
this.#userIceHandler({ candidate: filtered })
|
|
104
104
|
})
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import { normalizeTcpPort } from '../core/tcp_port.mjs'
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
buildSignedAdvertForScope,
|
|
4
|
+
clearDiscoveryProviders,
|
|
5
|
+
encryptAdvertForScope,
|
|
6
|
+
listDiscoveryProviders,
|
|
7
|
+
listenNodeSignals,
|
|
8
|
+
registerDiscoveryProvider,
|
|
9
|
+
startDiscoveryPresence,
|
|
10
|
+
unregisterDiscoveryProvider,
|
|
11
|
+
} from '../discovery/index.mjs'
|
|
12
|
+
import { createLanDiscoveryProvider } from '../discovery/lan.mjs'
|
|
4
13
|
import { mergeSignalingRelayUrls, createNostrDiscoveryProvider } from '../discovery/nostr.mjs'
|
|
5
|
-
import { buildSignedAdvert } from '../link/handshake.mjs'
|
|
6
14
|
import { createBleGattLinkProvider } from '../link/providers/ble_gatt.mjs'
|
|
7
15
|
import {
|
|
8
16
|
listLinkProviders,
|
|
@@ -12,24 +20,20 @@ import {
|
|
|
12
20
|
import { createLanTcpLinkProvider } from '../link/providers/lan_tcp.mjs'
|
|
13
21
|
import { createWebRtcLinkProvider } from '../link/providers/webrtc.mjs'
|
|
14
22
|
import { getNodeTransportSettings } from '../node/identity.mjs'
|
|
15
|
-
import { getSignalingRuntimeConfig } from '../node/instance.mjs'
|
|
16
|
-
|
|
17
|
-
import { encryptSignalPacket } from './signal_crypto.mjs'
|
|
23
|
+
import { getSignalingRuntimeConfig, onNodeChange } from '../node/instance.mjs'
|
|
24
|
+
import { isConnectivityDebug, nodeDebug, shortHash } from '../node/log.mjs'
|
|
18
25
|
|
|
19
26
|
/**
|
|
20
|
-
* Provider 是否会在 isAvailable() 调用时触发 native/异步探测(不得在 ensureRuntime 快路径调用)。
|
|
21
27
|
* @param {import('../link/providers/index.mjs').LinkProvider} provider 链路提供者
|
|
22
|
-
* @returns {boolean}
|
|
28
|
+
* @returns {boolean} 是否使用原生 probe 路径
|
|
23
29
|
*/
|
|
24
30
|
export function providerHasNativeProbe(provider) {
|
|
25
31
|
return provider.caps?.probe === 'native'
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
/**
|
|
29
|
-
* 收集快路径应 listen 的 provider(仅 owned lan_tcp + 同步可用者)。
|
|
30
|
-
* 切勿调用 caps.probe==='native' 的 isAvailable(会触发 ble/webrtc 探测)。
|
|
31
35
|
* @param {import('../link/providers/index.mjs').LinkProvider | null} ownedLanTcp 本 registry 持有的 lan_tcp
|
|
32
|
-
* @returns {import('../link/providers/index.mjs').LinkProvider[]}
|
|
36
|
+
* @returns {import('../link/providers/index.mjs').LinkProvider[]} 可快速启动监听的 provider 列表
|
|
33
37
|
*/
|
|
34
38
|
export function collectFastListenProviders(ownedLanTcp) {
|
|
35
39
|
/** @type {import('../link/providers/index.mjs').LinkProvider[]} */
|
|
@@ -43,7 +47,6 @@ export function collectFastListenProviders(ownedLanTcp) {
|
|
|
43
47
|
if (typeof provider.isAvailable === 'function')
|
|
44
48
|
try {
|
|
45
49
|
const available = provider.isAvailable()
|
|
46
|
-
// 未标 probe:native 却返回 thenable:跳过,勿 await。
|
|
47
50
|
if (available && typeof available.then === 'function') continue
|
|
48
51
|
if (!available) continue
|
|
49
52
|
}
|
|
@@ -55,29 +58,17 @@ export function collectFastListenProviders(ownedLanTcp) {
|
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
/**
|
|
58
|
-
* Discovery + link listen 暖机(ensureRuntime 立即返回;listen/公网/BT 后台渐进)。
|
|
59
61
|
* @param {object} deps 依赖注入
|
|
60
62
|
* @param {{ nodeHash: string, nodePubKey: string, secretKey: Uint8Array }} deps.localIdentity 本地身份
|
|
61
|
-
* @param {string} deps.selfTopic 本机 rendezvous topic
|
|
62
63
|
* @param {boolean} deps.autoRegisterDiscoveryProviders 是否自动注册 discovery provider
|
|
63
64
|
* @param {boolean} deps.autoRegisterLinkProviders 是否自动注册内置 link provider
|
|
64
65
|
* @param {(link: object) => void} deps.onInboundLink 入站链路回调
|
|
65
66
|
* @param {(bytes: Uint8Array) => Promise<void>} deps.handleIncomingSignal 入站加密信令处理
|
|
66
|
-
* @returns {
|
|
67
|
-
* ensureRuntime: () => Promise<void>,
|
|
68
|
-
* whenListening: () => Promise<void>,
|
|
69
|
-
* whenSignalListening: () => Promise<void>,
|
|
70
|
-
* buildLocalAdvert: (topic: string) => Promise<object>,
|
|
71
|
-
* lanTcpPort: () => number | null,
|
|
72
|
-
* ownedLanTcp: () => object | null,
|
|
73
|
-
* ownedBleGatt: () => object | null,
|
|
74
|
-
* shutdown: () => Promise<void>,
|
|
75
|
-
* }} 运行时暖机句柄
|
|
67
|
+
* @returns {object} 运行时暖机句柄
|
|
76
68
|
*/
|
|
77
69
|
export function createRuntimeBootstrap(deps) {
|
|
78
70
|
const {
|
|
79
71
|
localIdentity,
|
|
80
|
-
selfTopic,
|
|
81
72
|
autoRegisterDiscoveryProviders,
|
|
82
73
|
autoRegisterLinkProviders,
|
|
83
74
|
onInboundLink,
|
|
@@ -89,14 +80,14 @@ export function createRuntimeBootstrap(deps) {
|
|
|
89
80
|
let runtimeStart = null
|
|
90
81
|
/** @type {Promise<void> | null} */
|
|
91
82
|
let lanListenReady = null
|
|
92
|
-
/** @type {Promise<void> | null}
|
|
83
|
+
/** @type {Promise<void> | null} */
|
|
93
84
|
let signalListenReady = null
|
|
94
85
|
/** @type {Promise<void> | null} */
|
|
95
86
|
let runtimeWarm = null
|
|
96
87
|
/** @type {Promise<void> | null} */
|
|
97
88
|
let bluetoothWarm = null
|
|
98
89
|
/** @type {(() => void) | null} */
|
|
99
|
-
let
|
|
90
|
+
let stopPresence = null
|
|
100
91
|
/** @type {(() => void) | null} */
|
|
101
92
|
let stopSignalListener = null
|
|
102
93
|
/** @type {Array<() => void>} */
|
|
@@ -105,19 +96,49 @@ export function createRuntimeBootstrap(deps) {
|
|
|
105
96
|
let ownedLanTcp = null
|
|
106
97
|
/** @type {ReturnType<typeof createBleGattLinkProvider> | null} */
|
|
107
98
|
let ownedBleGatt = null
|
|
108
|
-
/** @type {number} shutdown 时递增,后台任务检查后退出 */
|
|
109
99
|
let generation = 0
|
|
100
|
+
/** @type {Promise<void> | null} */
|
|
101
|
+
let reloadInflight = null
|
|
102
|
+
/** @type {(() => void) | null} */
|
|
103
|
+
let stopSignalingWatch = null
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @returns {string[]} 当前 Nostr relay URL 列表
|
|
107
|
+
*/
|
|
108
|
+
function resolveNostrRelayUrls() {
|
|
109
|
+
return getSignalingRuntimeConfig().relayOverride
|
|
110
|
+
?? mergeSignalingRelayUrls(getNodeTransportSettings().relayUrls)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @returns {void}
|
|
115
|
+
*/
|
|
116
|
+
function registerDiscoveryDefaults() {
|
|
117
|
+
const providerIds = new Set(listDiscoveryProviders().map(provider => provider.id))
|
|
118
|
+
if (!providerIds.has('lan'))
|
|
119
|
+
registerDiscoveryProvider(createLanDiscoveryProvider())
|
|
120
|
+
if (!providerIds.has('nostr'))
|
|
121
|
+
registerNostrProvider()
|
|
122
|
+
}
|
|
110
123
|
|
|
111
124
|
/**
|
|
112
|
-
*
|
|
113
|
-
|
|
125
|
+
* @returns {void}
|
|
126
|
+
*/
|
|
127
|
+
function registerNostrProvider() {
|
|
128
|
+
unregisterDiscoveryProvider('nostr')
|
|
129
|
+
registerDiscoveryProvider(createNostrDiscoveryProvider({
|
|
130
|
+
getRelayUrls: resolveNostrRelayUrls,
|
|
131
|
+
}))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @returns {boolean} runtime 是否已启动
|
|
114
136
|
*/
|
|
115
137
|
function isLive() {
|
|
116
138
|
return runtimeStarted
|
|
117
139
|
}
|
|
118
140
|
|
|
119
141
|
/**
|
|
120
|
-
* 自动注册默认 LinkProvider(lan_tcp / webrtc / ble_gatt)。
|
|
121
142
|
* @returns {Promise<void>}
|
|
122
143
|
*/
|
|
123
144
|
async function ensureLinkProviders() {
|
|
@@ -136,8 +157,7 @@ export function createRuntimeBootstrap(deps) {
|
|
|
136
157
|
}
|
|
137
158
|
|
|
138
159
|
/**
|
|
139
|
-
*
|
|
140
|
-
* @returns {number | null} listen 端口;未 listen 为 null
|
|
160
|
+
* @returns {number | null} 本机 lan_tcp 监听端口,未就绪为 null
|
|
141
161
|
*/
|
|
142
162
|
function lanTcpPort() {
|
|
143
163
|
const endpoint = typeof ownedLanTcp?.localEndpoint === 'function' ? ownedLanTcp.localEndpoint() : null
|
|
@@ -145,7 +165,6 @@ export function createRuntimeBootstrap(deps) {
|
|
|
145
165
|
}
|
|
146
166
|
|
|
147
167
|
/**
|
|
148
|
-
* 等待本地 lan_tcp listen 落定。shell / startNode 不应调用。
|
|
149
168
|
* @returns {Promise<void>}
|
|
150
169
|
*/
|
|
151
170
|
async function whenListening() {
|
|
@@ -153,7 +172,6 @@ export function createRuntimeBootstrap(deps) {
|
|
|
153
172
|
}
|
|
154
173
|
|
|
155
174
|
/**
|
|
156
|
-
* 等待 selfTopic 信令监听挂上(needsOfferAnswer dial 前调用)。
|
|
157
175
|
* @returns {Promise<void>}
|
|
158
176
|
*/
|
|
159
177
|
async function whenSignalListening() {
|
|
@@ -161,21 +179,16 @@ export function createRuntimeBootstrap(deps) {
|
|
|
161
179
|
}
|
|
162
180
|
|
|
163
181
|
/**
|
|
164
|
-
*
|
|
165
|
-
* @
|
|
166
|
-
* @returns {Promise<object>} 签名 advert
|
|
182
|
+
* @param {import('../discovery/adverts.mjs').AdvertScope} [scope='node'] advert 域
|
|
183
|
+
* @returns {Promise<object>} 签名后的 advert body
|
|
167
184
|
*/
|
|
168
|
-
async function buildLocalAdvert(
|
|
185
|
+
async function buildLocalAdvert(scope = 'node') {
|
|
169
186
|
await whenListening()
|
|
170
187
|
const tcpPort = lanTcpPort()
|
|
171
|
-
return await
|
|
172
|
-
...localIdentity,
|
|
173
|
-
...tcpPort != null ? { tcpPort } : {},
|
|
174
|
-
})
|
|
188
|
+
return await buildSignedAdvertForScope(scope, localIdentity, tcpPort ?? undefined)
|
|
175
189
|
}
|
|
176
190
|
|
|
177
191
|
/**
|
|
178
|
-
* 对单个 link provider 调用 ensureListening,收集 stop。
|
|
179
192
|
* @param {import('../link/providers/index.mjs').LinkProvider} provider 链路提供者
|
|
180
193
|
* @returns {Promise<void>}
|
|
181
194
|
*/
|
|
@@ -188,39 +201,18 @@ export function createRuntimeBootstrap(deps) {
|
|
|
188
201
|
})
|
|
189
202
|
if (typeof stop === 'function') stopLinkListeners.push(stop)
|
|
190
203
|
}
|
|
191
|
-
catch { /* provider listen unavailable
|
|
204
|
+
catch { /* provider listen unavailable */ }
|
|
192
205
|
}
|
|
193
206
|
|
|
194
207
|
/**
|
|
195
|
-
*
|
|
196
|
-
* @param {'signal' | 'advert'} kind 停止钩子类型
|
|
197
|
-
* @param {() => void} stop 新 provider 的取消函数
|
|
198
|
-
* @returns {void}
|
|
208
|
+
* @returns {Promise<Uint8Array>} 加密后的全网 advert 字节
|
|
199
209
|
*/
|
|
200
|
-
function
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
*
|
|
205
|
-
*/
|
|
206
|
-
stopSignalListener = () => {
|
|
207
|
-
try { stop() } catch { /* ignore */ }
|
|
208
|
-
prev?.()
|
|
209
|
-
}
|
|
210
|
-
return
|
|
211
|
-
}
|
|
212
|
-
const prev = stopAdvert
|
|
213
|
-
/**
|
|
214
|
-
*
|
|
215
|
-
*/
|
|
216
|
-
stopAdvert = () => {
|
|
217
|
-
try { stop() } catch { /* ignore */ }
|
|
218
|
-
prev?.()
|
|
219
|
-
}
|
|
210
|
+
async function buildNetworkAdvertBytes() {
|
|
211
|
+
const body = await buildLocalAdvert('network')
|
|
212
|
+
return encryptAdvertForScope('network', localIdentity, body)
|
|
220
213
|
}
|
|
221
214
|
|
|
222
215
|
/**
|
|
223
|
-
* 后台探测 BT discovery / ble_gatt listen。
|
|
224
216
|
* @param {number} gen 启动世代
|
|
225
217
|
* @returns {Promise<void>}
|
|
226
218
|
*/
|
|
@@ -235,23 +227,33 @@ export function createRuntimeBootstrap(deps) {
|
|
|
235
227
|
const provider = bt.createBluetoothDiscoveryProvider()
|
|
236
228
|
registerDiscoveryProvider(provider)
|
|
237
229
|
if (generation !== gen || !isLive()) return
|
|
238
|
-
if (
|
|
230
|
+
if (typeof provider.startPresence === 'function')
|
|
239
231
|
try {
|
|
240
|
-
const stop = await provider.
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
232
|
+
const stop = await provider.startPresence(async () => ({
|
|
233
|
+
nodeHash: localIdentity.nodeHash,
|
|
234
|
+
advertBytes: await buildNetworkAdvertBytes(),
|
|
235
|
+
}))
|
|
236
|
+
if (typeof stop === 'function' && generation === gen && isLive()) {
|
|
237
|
+
const prev = stopPresence
|
|
238
|
+
/**
|
|
239
|
+
*
|
|
240
|
+
*/
|
|
241
|
+
stopPresence = () => { try { stop() } catch { /* ignore */ }; prev?.() }
|
|
242
|
+
}
|
|
245
243
|
}
|
|
246
244
|
catch { /* ignore */ }
|
|
247
|
-
if (provider.caps?.
|
|
245
|
+
if (provider.caps?.canSignal && typeof provider.listenNodeSignals === 'function')
|
|
248
246
|
try {
|
|
249
|
-
const stop = await provider.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
247
|
+
const stop = await provider.listenNodeSignals(localIdentity.nodeHash, bytes => {
|
|
248
|
+
void handleIncomingSignal(bytes).catch(() => { })
|
|
249
|
+
})
|
|
250
|
+
if (typeof stop === 'function' && generation === gen && isLive()) {
|
|
251
|
+
const prev = stopSignalListener
|
|
252
|
+
/**
|
|
253
|
+
*
|
|
254
|
+
*/
|
|
255
|
+
stopSignalListener = () => { try { stop() } catch { /* ignore */ }; prev?.() }
|
|
256
|
+
}
|
|
255
257
|
}
|
|
256
258
|
catch { /* ignore */ }
|
|
257
259
|
}
|
|
@@ -265,35 +267,82 @@ export function createRuntimeBootstrap(deps) {
|
|
|
265
267
|
}
|
|
266
268
|
|
|
267
269
|
/**
|
|
268
|
-
*
|
|
269
|
-
* @param {number} gen 启动世代(shutdown 时递增以作废后台任务)
|
|
270
|
+
* @param {number} gen 启动世代
|
|
270
271
|
* @returns {Promise<void>}
|
|
271
272
|
*/
|
|
272
273
|
function warmListenAndDiscovery(gen) {
|
|
273
274
|
const listenProviders = collectFastListenProviders(ownedLanTcp)
|
|
274
|
-
// 同步挂上 Promise,避免 ensureRuntime 返回后 buildLocalAdvert 看不到 lanListenReady。
|
|
275
275
|
lanListenReady = Promise.all(listenProviders.map(provider => startProviderListening(provider))).then(() => { })
|
|
276
276
|
signalListenReady = (async () => {
|
|
277
277
|
await lanListenReady.catch(() => { })
|
|
278
278
|
if (generation !== gen || !isLive()) return
|
|
279
279
|
if (!listDiscoveryProviders().length) return
|
|
280
|
-
stopSignalListener = await
|
|
280
|
+
stopSignalListener = await listenNodeSignals(localIdentity.nodeHash, bytes => {
|
|
281
281
|
void handleIncomingSignal(bytes).catch(() => { })
|
|
282
282
|
})
|
|
283
|
+
nodeDebug('p2p:runtime signal listening', {
|
|
284
|
+
self: shortHash(localIdentity.nodeHash),
|
|
285
|
+
providers: listDiscoveryProviders().map(provider => provider.id),
|
|
286
|
+
})
|
|
283
287
|
})()
|
|
284
288
|
return (async () => {
|
|
285
289
|
await signalListenReady.catch(() => { })
|
|
286
290
|
if (generation !== gen || !isLive()) return
|
|
287
291
|
if (!listDiscoveryProviders().length) return
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
292
|
+
stopPresence = await startDiscoveryPresence(async () => ({
|
|
293
|
+
nodeHash: localIdentity.nodeHash,
|
|
294
|
+
tcpPort: lanTcpPort() ?? undefined,
|
|
295
|
+
advertBody: await buildLocalAdvert('network'),
|
|
296
|
+
advertBytes: await buildNetworkAdvertBytes(),
|
|
291
297
|
}))
|
|
298
|
+
if (isConnectivityDebug())
|
|
299
|
+
nodeDebug('p2p:runtime presence started', {
|
|
300
|
+
self: shortHash(localIdentity.nodeHash),
|
|
301
|
+
lanTcpPort: lanTcpPort(),
|
|
302
|
+
relays: resolveNostrRelayUrls().length,
|
|
303
|
+
})
|
|
292
304
|
})()
|
|
293
305
|
}
|
|
294
306
|
|
|
295
307
|
/**
|
|
296
|
-
*
|
|
308
|
+
* @returns {Promise<void>}
|
|
309
|
+
*/
|
|
310
|
+
async function reloadDiscoveryRelays() {
|
|
311
|
+
if (!runtimeStarted || !autoRegisterDiscoveryProviders) return
|
|
312
|
+
if (reloadInflight) return await reloadInflight
|
|
313
|
+
reloadInflight = (async () => {
|
|
314
|
+
const gen = generation
|
|
315
|
+
stopPresence?.()
|
|
316
|
+
stopSignalListener?.()
|
|
317
|
+
stopPresence = null
|
|
318
|
+
stopSignalListener = null
|
|
319
|
+
registerNostrProvider()
|
|
320
|
+
if (generation !== gen || !isLive()) return
|
|
321
|
+
signalListenReady = (async () => {
|
|
322
|
+
if (generation !== gen || !isLive()) return
|
|
323
|
+
if (!listDiscoveryProviders().length) return
|
|
324
|
+
stopSignalListener = await listenNodeSignals(localIdentity.nodeHash, bytes => {
|
|
325
|
+
void handleIncomingSignal(bytes).catch(() => { })
|
|
326
|
+
})
|
|
327
|
+
})()
|
|
328
|
+
await signalListenReady.catch(() => { })
|
|
329
|
+
if (generation !== gen || !isLive()) return
|
|
330
|
+
stopPresence = await startDiscoveryPresence(async () => ({
|
|
331
|
+
nodeHash: localIdentity.nodeHash,
|
|
332
|
+
tcpPort: lanTcpPort() ?? undefined,
|
|
333
|
+
advertBody: await buildLocalAdvert('network'),
|
|
334
|
+
advertBytes: await buildNetworkAdvertBytes(),
|
|
335
|
+
}))
|
|
336
|
+
})()
|
|
337
|
+
try {
|
|
338
|
+
await reloadInflight
|
|
339
|
+
}
|
|
340
|
+
finally {
|
|
341
|
+
reloadInflight = null
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
297
346
|
* @returns {Promise<void>}
|
|
298
347
|
*/
|
|
299
348
|
async function ensureRuntime() {
|
|
@@ -303,16 +352,19 @@ export function createRuntimeBootstrap(deps) {
|
|
|
303
352
|
runtimeStarted = true
|
|
304
353
|
const gen = generation
|
|
305
354
|
await ensureLinkProviders()
|
|
306
|
-
if (autoRegisterDiscoveryProviders)
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
355
|
+
if (autoRegisterDiscoveryProviders)
|
|
356
|
+
registerDiscoveryDefaults()
|
|
357
|
+
if (isConnectivityDebug())
|
|
358
|
+
nodeDebug('p2p:runtime ensure', {
|
|
359
|
+
self: shortHash(localIdentity.nodeHash),
|
|
360
|
+
discovery: listDiscoveryProviders().map(provider => provider.id),
|
|
361
|
+
relays: resolveNostrRelayUrls(),
|
|
362
|
+
})
|
|
363
|
+
if (!stopSignalingWatch)
|
|
364
|
+
stopSignalingWatch = onNodeChange(event => {
|
|
365
|
+
if (event === 'signaling-changed')
|
|
366
|
+
void reloadDiscoveryRelays().catch(() => { })
|
|
367
|
+
})
|
|
316
368
|
bluetoothWarm = warmBluetoothTask(gen).catch(() => { })
|
|
317
369
|
runtimeWarm = warmListenAndDiscovery(gen)
|
|
318
370
|
void runtimeWarm.catch(() => { })
|
|
@@ -326,20 +378,24 @@ export function createRuntimeBootstrap(deps) {
|
|
|
326
378
|
}
|
|
327
379
|
|
|
328
380
|
/**
|
|
329
|
-
* 停止暖机与 owned providers(链路表由 registry 自行清理)。
|
|
330
|
-
* 不 await bluetoothWarm:noble/poweredOn 可能挂死;generation 已作废其后副作用。
|
|
331
381
|
* @returns {Promise<void>}
|
|
332
382
|
*/
|
|
333
383
|
async function shutdown() {
|
|
334
384
|
runtimeStarted = false
|
|
335
385
|
generation++
|
|
336
|
-
|
|
337
|
-
|
|
386
|
+
stopSignalingWatch?.()
|
|
387
|
+
stopSignalingWatch = null
|
|
388
|
+
stopPresence?.()
|
|
338
389
|
stopSignalListener?.()
|
|
339
|
-
|
|
390
|
+
stopPresence = null
|
|
340
391
|
stopSignalListener = null
|
|
392
|
+
await Promise.race([
|
|
393
|
+
runtimeWarm?.catch(() => { }) ?? Promise.resolve(),
|
|
394
|
+
new Promise(resolve => setTimeout(resolve, 500)),
|
|
395
|
+
])
|
|
341
396
|
for (const stop of stopLinkListeners.splice(0))
|
|
342
397
|
try { stop() } catch { /* ignore */ }
|
|
398
|
+
clearDiscoveryProviders()
|
|
343
399
|
if (ownedLanTcp) {
|
|
344
400
|
unregisterLinkProvider(ownedLanTcp.id)
|
|
345
401
|
ownedLanTcp = null
|
|
@@ -352,6 +408,7 @@ export function createRuntimeBootstrap(deps) {
|
|
|
352
408
|
signalListenReady = null
|
|
353
409
|
runtimeWarm = null
|
|
354
410
|
bluetoothWarm = null
|
|
411
|
+
reloadInflight = null
|
|
355
412
|
}
|
|
356
413
|
|
|
357
414
|
return {
|
|
@@ -360,10 +417,15 @@ export function createRuntimeBootstrap(deps) {
|
|
|
360
417
|
whenSignalListening,
|
|
361
418
|
buildLocalAdvert,
|
|
362
419
|
lanTcpPort,
|
|
363
|
-
/**
|
|
420
|
+
/**
|
|
421
|
+
* @returns {ReturnType<typeof createLanTcpLinkProvider> | null} 本 registry 持有的 lan_tcp provider
|
|
422
|
+
*/
|
|
364
423
|
ownedLanTcp: () => ownedLanTcp,
|
|
365
|
-
/**
|
|
424
|
+
/**
|
|
425
|
+
* @returns {ReturnType<typeof createBleGattLinkProvider> | null} 本 registry 持有的 BLE GATT provider
|
|
426
|
+
*/
|
|
366
427
|
ownedBleGatt: () => ownedBleGatt,
|
|
428
|
+
reloadDiscoveryRelays,
|
|
367
429
|
shutdown,
|
|
368
430
|
}
|
|
369
431
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { dirname, join } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
6
|
+
|
|
7
|
+
/** @type {object | null} */
|
|
8
|
+
let cache = null
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 读取 transport/tunables.json(进程内缓存)。
|
|
12
|
+
* @returns {object} transport/tunables.json
|
|
13
|
+
*/
|
|
14
|
+
export function loadTransportTunables() {
|
|
15
|
+
if (cache) return cache
|
|
16
|
+
cache = JSON.parse(readFileSync(join(__dirname, 'tunables.json'), 'utf8'))
|
|
17
|
+
return cache
|
|
18
|
+
}
|