@steve02081504/fount-p2p 0.0.13 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/deno.json +4 -1
- package/discovery/advert_peer_hints.mjs +8 -2
- package/discovery/adverts.mjs +5 -0
- package/discovery/bt/index.mjs +4 -1
- package/discovery/lan.mjs +73 -14
- package/discovery/lan_interfaces.mjs +78 -0
- package/discovery/lan_peer_hints.mjs +22 -5
- package/discovery/nostr.mjs +384 -136
- package/discovery/peer_clue.mjs +23 -0
- package/link/handshake.mjs +15 -7
- package/link/providers/lan_tcp.mjs +62 -33
- package/link/providers/webrtc.mjs +15 -1
- package/link/rtc.mjs +14 -5
- package/package.json +1 -1
- package/transport/link_registry.mjs +37 -1
- package/transport/runtime_bootstrap.mjs +4 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { normalizeHex64 } from '../core/hexIds.mjs'
|
|
2
|
+
|
|
3
|
+
/** @type {((nodeHash: string) => void) | null} */
|
|
4
|
+
let peerClueListener = null
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 由 link registry 注入:peer 首次可见时清 dial 冷却。
|
|
8
|
+
* @param {((nodeHash: string) => void) | null} listener 回调
|
|
9
|
+
* @returns {void}
|
|
10
|
+
*/
|
|
11
|
+
export function setDiscoveryPeerClueListener(listener) {
|
|
12
|
+
peerClueListener = listener
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* discovery accept 路径:peer 首次进入可见池时通知 registry。
|
|
17
|
+
* @param {string} nodeHash 对端
|
|
18
|
+
* @returns {void}
|
|
19
|
+
*/
|
|
20
|
+
export function noteDiscoveryPeerClue(nodeHash) {
|
|
21
|
+
const hash = normalizeHex64(nodeHash)
|
|
22
|
+
if (hash) peerClueListener?.(hash)
|
|
23
|
+
}
|
package/link/handshake.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { randomBytes } from 'node:crypto'
|
|
|
4
4
|
import { isHex64, normalizeHex64 } from '../core/hexIds.mjs'
|
|
5
5
|
import { normalizeTcpPort } from '../core/tcp_port.mjs'
|
|
6
6
|
import { keyPairFromSeed, pubKeyHash, sign, verify } from '../crypto/crypto.mjs'
|
|
7
|
+
import { normalizeLanHosts } from '../discovery/lan_interfaces.mjs'
|
|
7
8
|
import { ensureNodeSeed, getNodeHash } from '../node/identity.mjs'
|
|
8
9
|
|
|
9
10
|
import { normalizeDtlsFingerprint } from './sdp_fingerprint.mjs'
|
|
@@ -139,20 +140,24 @@ export async function verifyAuth(hello, auth, expectedNonce, remoteBinding) {
|
|
|
139
140
|
* @param {number} ts 时间戳(毫秒)
|
|
140
141
|
* @param {string} nodeHash 节点 nodeHash
|
|
141
142
|
* @param {number | null} [tcpPort=null] 可选 LAN TCP 监听端口(签入消息)
|
|
143
|
+
* @param {unknown} [lanHosts=null] 可选 LAN IPv4 列表(签入消息)
|
|
142
144
|
* @returns {Uint8Array} 待签名消息字节
|
|
143
145
|
*/
|
|
144
|
-
export function buildAdvertMessage(rendezvousKey, ts, nodeHash, tcpPort = null) {
|
|
146
|
+
export function buildAdvertMessage(rendezvousKey, ts, nodeHash, tcpPort = null, lanHosts = null) {
|
|
145
147
|
const base = `fount-advert\0${String(rendezvousKey)}\0${String(ts)}\0${normalizeHex64(nodeHash)}`
|
|
146
148
|
const port = normalizeTcpPort(tcpPort)
|
|
147
|
-
|
|
149
|
+
let message = port ? `${base}\0${port}` : base
|
|
150
|
+
const hosts = normalizeLanHosts(lanHosts)
|
|
151
|
+
if (hosts.length) message += `\0${hosts.join(',')}`
|
|
152
|
+
return Buffer.from(message, 'utf8')
|
|
148
153
|
}
|
|
149
154
|
|
|
150
155
|
/**
|
|
151
|
-
* 构造带签名的 discovery advert
|
|
156
|
+
* 构造带签名的 discovery advert(tcpPort / lanHosts 一并签入消息)。
|
|
152
157
|
* @param {string} rendezvousKey discovery 内部汇合键
|
|
153
158
|
* @param {number} [ts=Date.now()] 时间戳(毫秒)
|
|
154
|
-
* @param {{ secretKey?: Uint8Array, nodeHash?: string, nodePubKey?: string, tcpPort?: number } | null} [options]
|
|
155
|
-
* @returns {Promise<{ nodeHash: string, nodePubKey: string, ts: number, sig: string, tcpPort?: number }>} 签名 advert
|
|
159
|
+
* @param {{ secretKey?: Uint8Array, nodeHash?: string, nodePubKey?: string, tcpPort?: number, lanHosts?: unknown } | null} [options] 签名身份;可选 LAN 监听端口与本机 IPv4 列表
|
|
160
|
+
* @returns {Promise<{ nodeHash: string, nodePubKey: string, ts: number, sig: string, tcpPort?: number, lanHosts?: string[] }>} 签名 advert
|
|
156
161
|
*/
|
|
157
162
|
export async function buildSignedAdvert(rendezvousKey, ts = Date.now(), options = null) {
|
|
158
163
|
const seed = options?.secretKey
|
|
@@ -166,7 +171,8 @@ export async function buildSignedAdvert(rendezvousKey, ts = Date.now(), options
|
|
|
166
171
|
const tcpPort = normalizeTcpPort(options?.tcpPort)
|
|
167
172
|
if (options?.tcpPort && !tcpPort)
|
|
168
173
|
throw new Error('p2p: advert tcpPort invalid')
|
|
169
|
-
const
|
|
174
|
+
const lanHosts = normalizeLanHosts(options?.lanHosts)
|
|
175
|
+
const message = buildAdvertMessage(rendezvousKey, ts, nodeHash, tcpPort, lanHosts)
|
|
170
176
|
const sig = await sign(message, secretKey)
|
|
171
177
|
const advert = {
|
|
172
178
|
nodeHash,
|
|
@@ -175,6 +181,7 @@ export async function buildSignedAdvert(rendezvousKey, ts = Date.now(), options
|
|
|
175
181
|
sig: Buffer.from(sig).toString('hex'),
|
|
176
182
|
}
|
|
177
183
|
if (tcpPort) advert.tcpPort = tcpPort
|
|
184
|
+
if (lanHosts.length) advert.lanHosts = lanHosts
|
|
178
185
|
return advert
|
|
179
186
|
}
|
|
180
187
|
|
|
@@ -195,7 +202,8 @@ export async function verifySignedAdvert(rendezvousKey, advert, now = Date.now()
|
|
|
195
202
|
const hasTcpPortField = !!advert?.tcpPort
|
|
196
203
|
const tcpPort = normalizeTcpPort(advert?.tcpPort)
|
|
197
204
|
if (hasTcpPortField && !tcpPort) return null
|
|
198
|
-
const
|
|
205
|
+
const lanHosts = normalizeLanHosts(advert?.lanHosts)
|
|
206
|
+
const message = buildAdvertMessage(rendezvousKey, ts, parsedHello.nodeHash, tcpPort, lanHosts)
|
|
199
207
|
const ok = await verify(Buffer.from(sig, 'hex'), message, Buffer.from(parsedHello.nodePubKey, 'hex'))
|
|
200
208
|
return ok ? parsedHello.nodeHash : null
|
|
201
209
|
}
|
|
@@ -3,13 +3,50 @@ import { randomBytes } from 'node:crypto'
|
|
|
3
3
|
import net from 'node:net'
|
|
4
4
|
|
|
5
5
|
import { normalizeHex64 } from '../../core/hexIds.mjs'
|
|
6
|
-
import { getLanPeerHint } from '../../discovery/lan_peer_hints.mjs'
|
|
6
|
+
import { getLanPeerHint, listLanPeerHints } from '../../discovery/lan_peer_hints.mjs'
|
|
7
7
|
import { asLinkHandle } from '../pipe.mjs'
|
|
8
8
|
|
|
9
9
|
import { LINK_LEVEL_LAN_TCP } from './levels.mjs'
|
|
10
10
|
import { buildLinkOpen, createLinkIdBoundPipe, parseLinkOpen } from './link_id_pipe.mjs'
|
|
11
11
|
|
|
12
12
|
const MAX_FRAME_BYTES = 1 << 20
|
|
13
|
+
/** LAN TCP 单 endpoint 连接超时。 */
|
|
14
|
+
const LAN_TCP_CONNECT_TIMEOUT_MS = 3_000
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} host 目标 host
|
|
18
|
+
* @param {number} port 目标 port
|
|
19
|
+
* @param {number} [timeoutMs=LAN_TCP_CONNECT_TIMEOUT_MS] 超时
|
|
20
|
+
* @returns {Promise<import('node:net').Socket>} 已连接 socket
|
|
21
|
+
*/
|
|
22
|
+
function connectTcp(host, port, timeoutMs = LAN_TCP_CONNECT_TIMEOUT_MS) {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
const conn = net.createConnection({ host, port })
|
|
25
|
+
const timer = setTimeout(() => {
|
|
26
|
+
conn.destroy()
|
|
27
|
+
reject(new Error(`p2p: lan_tcp connect timeout ${host}:${port}`))
|
|
28
|
+
}, timeoutMs)
|
|
29
|
+
/**
|
|
30
|
+
* @param {Error} error 连接错误
|
|
31
|
+
* @returns {void}
|
|
32
|
+
*/
|
|
33
|
+
const onError = error => {
|
|
34
|
+
clearTimeout(timer)
|
|
35
|
+
conn.off('connect', onConnect)
|
|
36
|
+
reject(error)
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* @returns {void}
|
|
40
|
+
*/
|
|
41
|
+
function onConnect() {
|
|
42
|
+
clearTimeout(timer)
|
|
43
|
+
conn.off('error', onError)
|
|
44
|
+
resolve(conn)
|
|
45
|
+
}
|
|
46
|
+
conn.once('error', onError)
|
|
47
|
+
conn.once('connect', onConnect)
|
|
48
|
+
})
|
|
49
|
+
}
|
|
13
50
|
|
|
14
51
|
/**
|
|
15
52
|
* 在 socket 上挂 length-prefix 编解码(u32be + payload)。
|
|
@@ -168,41 +205,33 @@ async function openTcpPipe(options) {
|
|
|
168
205
|
*/
|
|
169
206
|
async function dialLanTcp(options) {
|
|
170
207
|
const remoteNodeHash = normalizeHex64(options.nodeHash)
|
|
171
|
-
const
|
|
172
|
-
if (!
|
|
208
|
+
const hints = listLanPeerHints(remoteNodeHash)
|
|
209
|
+
if (!hints.length) throw new Error('p2p: lan_tcp no peer hint')
|
|
173
210
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
211
|
+
let lastError = null
|
|
212
|
+
for (const hint of hints) {
|
|
213
|
+
/** @type {import('node:net').Socket | null} */
|
|
214
|
+
let socket = null
|
|
215
|
+
try {
|
|
216
|
+
socket = await connectTcp(hint.host, hint.port)
|
|
217
|
+
const link = await openTcpPipe({
|
|
218
|
+
initiator: true,
|
|
219
|
+
linkId: randomBytes(32).toString('hex'),
|
|
220
|
+
nodeHash: remoteNodeHash,
|
|
221
|
+
localIdentity: options.localIdentity,
|
|
222
|
+
socket,
|
|
223
|
+
host: hint.host,
|
|
224
|
+
port: hint.port,
|
|
225
|
+
})
|
|
226
|
+
await link.ready
|
|
227
|
+
return link
|
|
183
228
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
function onConnect() {
|
|
188
|
-
conn.off('error', onError)
|
|
189
|
-
resolve(conn)
|
|
229
|
+
catch (error) {
|
|
230
|
+
lastError = error
|
|
231
|
+
try { socket?.destroy() } catch { /* ignore */ }
|
|
190
232
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
const link = await openTcpPipe({
|
|
196
|
-
initiator: true,
|
|
197
|
-
linkId: randomBytes(32).toString('hex'),
|
|
198
|
-
nodeHash: remoteNodeHash,
|
|
199
|
-
localIdentity: options.localIdentity,
|
|
200
|
-
socket,
|
|
201
|
-
host: hint.host,
|
|
202
|
-
port: hint.port,
|
|
203
|
-
})
|
|
204
|
-
await link.ready
|
|
205
|
-
return link
|
|
233
|
+
}
|
|
234
|
+
throw lastError || new Error('p2p: lan_tcp dial failed')
|
|
206
235
|
}
|
|
207
236
|
|
|
208
237
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getSignalingRuntimeConfig } from '../../node/instance.mjs'
|
|
2
|
+
import { nodeDebug } from '../../node/log.mjs'
|
|
2
3
|
import { ms } from '../../utils/duration.mjs'
|
|
3
4
|
import { createLruMap } from '../../utils/lru.mjs'
|
|
4
5
|
import {
|
|
@@ -33,6 +34,13 @@ function formatErrorReason(error) {
|
|
|
33
34
|
|
|
34
35
|
let cachedAvailable = null
|
|
35
36
|
|
|
37
|
+
/**
|
|
38
|
+
* @returns {boolean} 是否跑在 Deno
|
|
39
|
+
*/
|
|
40
|
+
function isDenoRuntime() {
|
|
41
|
+
return typeof globalThis.Deno !== 'undefined'
|
|
42
|
+
}
|
|
43
|
+
|
|
36
44
|
/**
|
|
37
45
|
* 探测 WebRTC(node-datachannel)是否可用。
|
|
38
46
|
* @returns {Promise<boolean>} 可用为 true
|
|
@@ -43,8 +51,14 @@ export async function canUseWebRtcLink() {
|
|
|
43
51
|
await loadNodeRtcPolyfill()
|
|
44
52
|
cachedAvailable = true
|
|
45
53
|
}
|
|
46
|
-
catch {
|
|
54
|
+
catch (error) {
|
|
47
55
|
cachedAvailable = false
|
|
56
|
+
const reason = formatErrorReason(error)
|
|
57
|
+
nodeDebug('p2p:webrtc unavailable', {
|
|
58
|
+
err: isDenoRuntime()
|
|
59
|
+
? `${reason} | deno: see docs/runtime.md (node-datachannel scripts only)`
|
|
60
|
+
: reason,
|
|
61
|
+
})
|
|
48
62
|
}
|
|
49
63
|
return cachedAvailable
|
|
50
64
|
}
|
package/link/rtc.mjs
CHANGED
|
@@ -4,15 +4,23 @@ import { toBytes } from '../core/bytes_codec.mjs'
|
|
|
4
4
|
import { getSignalingRuntimeConfig } from '../node/instance.mjs'
|
|
5
5
|
import { wrapRtcPeerConnectionForIceLocalHostname } from '../transport/rtc_ice_local_hostname.mjs'
|
|
6
6
|
|
|
7
|
+
/** @type {boolean} */
|
|
8
|
+
let exitCleanupHooked = false
|
|
9
|
+
|
|
7
10
|
/**
|
|
8
|
-
* 注册进程退出时销毁 libdatachannel
|
|
11
|
+
* 注册进程退出时销毁 libdatachannel 原生资源(首次成功加载后挂一次)。
|
|
9
12
|
* libdatachannel 的原生线程在 pc.close() 后仍需时间回收;进程退出时若原生资源未同步销毁,
|
|
10
13
|
* Windows 上会触发堆损坏(退出码 0xC0000374)。
|
|
14
|
+
* @returns {Promise<void>}
|
|
11
15
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
})
|
|
16
|
+
async function ensureNodeDatachannelExitCleanup() {
|
|
17
|
+
if (exitCleanupHooked) return
|
|
18
|
+
exitCleanupHooked = true
|
|
19
|
+
const { cleanup = undefined } = await import('node-datachannel').catch(() => ({}))
|
|
20
|
+
process.on('exit', () => {
|
|
21
|
+
try { cleanup?.() } catch { /* already torn down */ }
|
|
22
|
+
})
|
|
23
|
+
}
|
|
16
24
|
|
|
17
25
|
/**
|
|
18
26
|
* 加载 node-datachannel polyfill,并按配置包装 RTCPeerConnection。
|
|
@@ -20,6 +28,7 @@ process.on('exit', () => {
|
|
|
20
28
|
*/
|
|
21
29
|
export async function loadNodeRtcPolyfill() {
|
|
22
30
|
const mod = await import('node-datachannel/polyfill')
|
|
31
|
+
await ensureNodeDatachannelExitCleanup()
|
|
23
32
|
const { iceLocalHostnamePolicy } = getSignalingRuntimeConfig()
|
|
24
33
|
return {
|
|
25
34
|
RTCPeerConnection: wrapRtcPeerConnectionForIceLocalHostname(mod.RTCPeerConnection, mod.RTCIceCandidate, iceLocalHostnamePolicy),
|
package/package.json
CHANGED
|
@@ -3,11 +3,13 @@ import { Buffer } from 'node:buffer'
|
|
|
3
3
|
import { compareHex64Asc, normalizeHex64 } from '../core/hexIds.mjs'
|
|
4
4
|
import { keyPairFromSeed } from '../crypto/crypto.mjs'
|
|
5
5
|
import { watchVerifiedNodeAdvert, setDiscoveryLinkDialer, prepareConnectToNode } from '../discovery/index.mjs'
|
|
6
|
+
import { setDiscoveryPeerClueListener } from '../discovery/peer_clue.mjs'
|
|
6
7
|
import { listLinkProviders } from '../link/providers/index.mjs'
|
|
7
8
|
import { ensureNodeSeed, getNodeHash } from '../node/identity.mjs'
|
|
8
9
|
import { nodeDebug, shortHash } from '../node/log.mjs'
|
|
9
10
|
import { loadPeerPoolView } from '../node/network.mjs'
|
|
10
11
|
import { createOverlayRouter } from '../overlay/index.mjs'
|
|
12
|
+
import { ms } from '../utils/duration.mjs'
|
|
11
13
|
import { emitSafe } from '../utils/emit_safe.mjs'
|
|
12
14
|
import { createLruMap } from '../utils/lru.mjs'
|
|
13
15
|
|
|
@@ -18,6 +20,11 @@ import { createOfferAnswerDial } from './offer_answer.mjs'
|
|
|
18
20
|
import { pickMeshEvictionVictim } from './peer_pool.mjs'
|
|
19
21
|
import { createRuntimeBootstrap } from './runtime_bootstrap.mjs'
|
|
20
22
|
|
|
23
|
+
/** dial exhausted 后的初始冷却。 */
|
|
24
|
+
const DIAL_COOLDOWN_BASE_MS = ms('30s')
|
|
25
|
+
/** dial 冷却上限。 */
|
|
26
|
+
const DIAL_COOLDOWN_MAX_MS = ms('10m')
|
|
27
|
+
|
|
21
28
|
/**
|
|
22
29
|
* 解析或从节点种子推导本地身份。
|
|
23
30
|
* @param {{ nodeHash?: string, nodePubKey?: string, secretKey?: Uint8Array } | undefined} localIdentity 可选的预置身份
|
|
@@ -84,6 +91,8 @@ export function createLinkRegistry(options = {}) {
|
|
|
84
91
|
const links = new Map()
|
|
85
92
|
/** @type {Map<string, Promise<object | null>>} */
|
|
86
93
|
const inflights = new Map()
|
|
94
|
+
/** @type {Map<string, { until: number, failures: number }> & { touch: (key: string, value: { until: number, failures: number }) => void }} */
|
|
95
|
+
const dialCooldown = createLruMap(1024)
|
|
87
96
|
/** @type {Map<string, ReturnType<typeof import('./offer_answer.mjs').createBufferedSignalSession>>} */
|
|
88
97
|
const signalSessions = new Map()
|
|
89
98
|
/** @type {Map<string, Set<string>>} */
|
|
@@ -265,6 +274,19 @@ export function createLinkRegistry(options = {}) {
|
|
|
265
274
|
if (!normalized || normalized === localIdentity.nodeHash) return null
|
|
266
275
|
if (links.has(normalized)) return links.get(normalized)
|
|
267
276
|
if (inflights.has(normalized)) return await inflights.get(normalized)
|
|
277
|
+
const cool = dialCooldown.get(normalized)
|
|
278
|
+
const now = Date.now()
|
|
279
|
+
if (cool) {
|
|
280
|
+
if (now < cool.until) {
|
|
281
|
+
nodeDebug('p2p:dial cooldown', {
|
|
282
|
+
peer: shortHash(normalized),
|
|
283
|
+
failures: cool.failures,
|
|
284
|
+
retryInMs: cool.until - now,
|
|
285
|
+
})
|
|
286
|
+
return null
|
|
287
|
+
}
|
|
288
|
+
dialCooldown.delete(normalized)
|
|
289
|
+
}
|
|
268
290
|
const task = (async () => {
|
|
269
291
|
nodeDebug('p2p:dial start', { peer: shortHash(normalized) })
|
|
270
292
|
await prepareConnectToNode(normalized)
|
|
@@ -289,6 +311,7 @@ export function createLinkRegistry(options = {}) {
|
|
|
289
311
|
if (provider.caps?.needsOfferAnswer) {
|
|
290
312
|
const link = await dialOfferAnswer(provider, normalized)
|
|
291
313
|
if (link) {
|
|
314
|
+
dialCooldown.delete(normalized)
|
|
292
315
|
nodeDebug('p2p:dial ok', { peer: shortHash(normalized), provider: provider.id })
|
|
293
316
|
return link
|
|
294
317
|
}
|
|
@@ -297,6 +320,7 @@ export function createLinkRegistry(options = {}) {
|
|
|
297
320
|
}
|
|
298
321
|
const link = await dialProvider(provider, normalized)
|
|
299
322
|
if (link) {
|
|
323
|
+
dialCooldown.delete(normalized)
|
|
300
324
|
nodeDebug('p2p:dial ok', { peer: shortHash(normalized), provider: provider.id })
|
|
301
325
|
return link
|
|
302
326
|
}
|
|
@@ -310,7 +334,10 @@ export function createLinkRegistry(options = {}) {
|
|
|
310
334
|
})
|
|
311
335
|
}
|
|
312
336
|
|
|
313
|
-
|
|
337
|
+
const failures = (cool?.failures || 0) + 1
|
|
338
|
+
const delay = Math.min(DIAL_COOLDOWN_MAX_MS, DIAL_COOLDOWN_BASE_MS * (2 ** Math.min(failures - 1, 5)))
|
|
339
|
+
dialCooldown.touch(normalized, { until: Date.now() + delay, failures })
|
|
340
|
+
nodeDebug('p2p:dial exhausted', { peer: shortHash(normalized), cooldownMs: delay, failures })
|
|
314
341
|
return null
|
|
315
342
|
})().finally(() => inflights.delete(normalized))
|
|
316
343
|
inflights.set(normalized, task)
|
|
@@ -351,6 +378,12 @@ export function createLinkRegistry(options = {}) {
|
|
|
351
378
|
})
|
|
352
379
|
exploreLinkHashes = meshKeepalive.exploreLinkHashes
|
|
353
380
|
setDiscoveryLinkDialer(ensureDirectLinkToNode)
|
|
381
|
+
setDiscoveryPeerClueListener(nodeHash => {
|
|
382
|
+
const hash = normalizeHex64(nodeHash)
|
|
383
|
+
if (!hash) return
|
|
384
|
+
dialCooldown.delete(hash)
|
|
385
|
+
recentAdverts.touch(hash, Date.now())
|
|
386
|
+
})
|
|
354
387
|
|
|
355
388
|
/**
|
|
356
389
|
*
|
|
@@ -619,6 +652,7 @@ export function createLinkRegistry(options = {}) {
|
|
|
619
652
|
return await watchVerifiedNodeAdvert(hash, async (verifiedNodeHash, body, meta) => {
|
|
620
653
|
applyAdvertPeerHints(verifiedNodeHash, body, meta)
|
|
621
654
|
recentAdverts.touch(verifiedNodeHash, Date.now())
|
|
655
|
+
dialCooldown.delete(verifiedNodeHash)
|
|
622
656
|
await Promise.resolve(onAdvert(verifiedNodeHash, body))
|
|
623
657
|
})
|
|
624
658
|
},
|
|
@@ -631,6 +665,7 @@ export function createLinkRegistry(options = {}) {
|
|
|
631
665
|
async shutdown() {
|
|
632
666
|
await meshKeepalive?.stop()
|
|
633
667
|
setDiscoveryLinkDialer(null)
|
|
668
|
+
setDiscoveryPeerClueListener(null)
|
|
634
669
|
await bootstrap.shutdown()
|
|
635
670
|
overlayRouter?.close()
|
|
636
671
|
overlayRouter = null
|
|
@@ -638,6 +673,7 @@ export function createLinkRegistry(options = {}) {
|
|
|
638
673
|
await link.close('registry-shutdown')
|
|
639
674
|
links.clear()
|
|
640
675
|
inflights.clear()
|
|
676
|
+
dialCooldown.clear()
|
|
641
677
|
for (const session of signalSessions.values()) session.clear()
|
|
642
678
|
signalSessions.clear()
|
|
643
679
|
},
|
|
@@ -116,7 +116,9 @@ export function createRuntimeBootstrap(deps) {
|
|
|
116
116
|
function registerDiscoveryDefaults() {
|
|
117
117
|
const providerIds = new Set(listDiscoveryProviders().map(provider => provider.id))
|
|
118
118
|
if (!providerIds.has('lan'))
|
|
119
|
-
registerDiscoveryProvider(createLanDiscoveryProvider(
|
|
119
|
+
registerDiscoveryProvider(createLanDiscoveryProvider({
|
|
120
|
+
localNodeHash: localIdentity.nodeHash,
|
|
121
|
+
}))
|
|
120
122
|
if (!providerIds.has('nostr'))
|
|
121
123
|
registerNostrProvider()
|
|
122
124
|
}
|
|
@@ -128,6 +130,7 @@ export function createRuntimeBootstrap(deps) {
|
|
|
128
130
|
unregisterDiscoveryProvider('nostr')
|
|
129
131
|
registerDiscoveryProvider(createNostrDiscoveryProvider({
|
|
130
132
|
getRelayUrls: resolveNostrRelayUrls,
|
|
133
|
+
localNodeHash: localIdentity.nodeHash,
|
|
131
134
|
}))
|
|
132
135
|
}
|
|
133
136
|
|