@steve02081504/fount-p2p 0.0.48 → 0.0.49
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/AGENTS.md +1 -0
- package/link/providers/webrtc.mjs +35 -9
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -54,6 +54,7 @@ Deno / native / BT: [runtime.md](docs/runtime.md).
|
|
|
54
54
|
- Assertions: `test/helpers/assert.mjs`
|
|
55
55
|
- Fixed-seed identity: `test/helpers/identity.mjs`
|
|
56
56
|
- Mock discovery: `test/helpers/mock_discovery.mjs`
|
|
57
|
+
- Transport fakes must mirror the backend's failure signalling: a fake `RTCPeerConnection` whose `close()` stays silent (no `connectionstatechange`) hides re-entrancy bugs where a replaced connection closes the live one (fount-p2p#39, `link/providers/webrtc.mjs`).
|
|
57
58
|
|
|
58
59
|
## Static checks (`scripts/checks/`, ported from fount's check standard)
|
|
59
60
|
|
|
@@ -135,7 +135,7 @@ export async function createWebRtcLink(options) {
|
|
|
135
135
|
const rungs = iceLocalHostnameLadder(icePolicy)
|
|
136
136
|
const remoteSignalQueue = []
|
|
137
137
|
const seenRemoteSignals = createLruMap(1024)
|
|
138
|
-
/** @type {InstanceType<import('../rtc/polyfill.mjs').LoadedRtcPolyfill['RTCPeerConnection']> & {
|
|
138
|
+
/** @type {InstanceType<import('../rtc/polyfill.mjs').LoadedRtcPolyfill['RTCPeerConnection']> & { ondatachannel?: unknown, onconnectionstatechange?: unknown, candidateCountHandler?: unknown }} */
|
|
139
139
|
let peerConnection = null
|
|
140
140
|
let remoteDescriptionSet = false
|
|
141
141
|
let controlChannel = null
|
|
@@ -269,8 +269,12 @@ export async function createWebRtcLink(options) {
|
|
|
269
269
|
remoteSignalQueue.length = 0
|
|
270
270
|
localCandidateCount = 0
|
|
271
271
|
attachPeerConnection(peerConnection)
|
|
272
|
-
if (previous)
|
|
272
|
+
if (previous) {
|
|
273
|
+
// 先摘回调再关:旧连接 close() 会经 connectionstatechange 触发 pipe.close,而
|
|
274
|
+
// closeTransport() 关的是 peerConnection 这个变量(此刻已指向新连接),于是刚建好的一级被自己关掉(fount-p2p#39)。
|
|
275
|
+
detachPeerConnection(previous)
|
|
273
276
|
try { await previous.close() } catch { /* ignore */ }
|
|
277
|
+
}
|
|
274
278
|
nodeDebug('p2p:webrtc ice rung', {
|
|
275
279
|
rung: rungIndex,
|
|
276
280
|
policy: rungs[rungIndex],
|
|
@@ -288,22 +292,29 @@ export async function createWebRtcLink(options) {
|
|
|
288
292
|
|
|
289
293
|
/**
|
|
290
294
|
* 给 peer connection 挂上事件与回调(换级重建后需重新挂)。
|
|
295
|
+
*
|
|
296
|
+
* 回调一律用传入的这个连接,不读 `peerConnection` 闭包变量:换级后旧连接仍可能派发事件,
|
|
297
|
+
* 读变量就会把事件算到新连接头上。
|
|
291
298
|
* @param {RTCPeerConnection} connection peer connection
|
|
292
299
|
* @returns {void}
|
|
293
300
|
*/
|
|
294
301
|
function attachPeerConnection(connection) {
|
|
295
302
|
// 候选随 description 一次性带出(对端要先有 remoteDescription 才吃候选),故这里只计数、不外发。
|
|
296
|
-
// 事件 API 由后端提供(W3C 是 addEventListener);缺失时退化为 onicecandidate
|
|
303
|
+
// 事件 API 由后端提供(W3C 是 addEventListener);缺失时退化为 onicecandidate,摘的时候看 candidateCountHandler 在不在。
|
|
297
304
|
const countCandidate = event => {
|
|
298
305
|
if (event?.candidate) localCandidateCount++
|
|
299
306
|
}
|
|
300
|
-
if (connection.addEventListener)
|
|
307
|
+
if (connection.addEventListener) {
|
|
308
|
+
connection.candidateCountHandler = countCandidate
|
|
309
|
+
connection.addEventListener('icecandidate', countCandidate)
|
|
310
|
+
}
|
|
301
311
|
else {
|
|
302
312
|
const previousHandler = connection.onicecandidate
|
|
303
|
-
connection.
|
|
313
|
+
connection.candidateCountHandler = event => {
|
|
304
314
|
countCandidate(event)
|
|
305
315
|
previousHandler?.(event)
|
|
306
316
|
}
|
|
317
|
+
connection.onicecandidate = connection.candidateCountHandler
|
|
307
318
|
}
|
|
308
319
|
connection.ondatachannel = event => {
|
|
309
320
|
attachChannel(event.channel)
|
|
@@ -317,6 +328,19 @@ export async function createWebRtcLink(options) {
|
|
|
317
328
|
}
|
|
318
329
|
}
|
|
319
330
|
|
|
331
|
+
/**
|
|
332
|
+
* 摘掉某一级 peer connection 上挂的事件与回调(重建前必须调用,见 [buildPeerConnection])。
|
|
333
|
+
* @param {RTCPeerConnection} connection 待摘的连接
|
|
334
|
+
* @returns {void}
|
|
335
|
+
*/
|
|
336
|
+
function detachPeerConnection(connection) {
|
|
337
|
+
if (connection.candidateCountHandler) connection.removeEventListener?.('icecandidate', connection.candidateCountHandler)
|
|
338
|
+
else connection.onicecandidate = null
|
|
339
|
+
delete connection.candidateCountHandler
|
|
340
|
+
connection.ondatachannel = null
|
|
341
|
+
connection.onconnectionstatechange = null
|
|
342
|
+
}
|
|
343
|
+
|
|
320
344
|
/**
|
|
321
345
|
* 等本地候选收齐(停滞判定见 [collectIceGathering]),并回报本次是否产出了候选。
|
|
322
346
|
* @returns {Promise<boolean>} 是否收到过候选
|
|
@@ -469,7 +493,7 @@ export async function createWebRtcLink(options) {
|
|
|
469
493
|
void handleRemoteSignal(message).catch(error => pipe.close(`signal-error:${formatErrorReason(error)}`))
|
|
470
494
|
}) ?? null
|
|
471
495
|
|
|
472
|
-
//
|
|
496
|
+
// 信令随时可能到,故先为当前一级(第 0 级)建好 pc;发起方阶梯的首次迭代直接用它,不重建。
|
|
473
497
|
await ensurePeerConnection()
|
|
474
498
|
|
|
475
499
|
if (options.initiator) {
|
|
@@ -478,7 +502,8 @@ export async function createWebRtcLink(options) {
|
|
|
478
502
|
const ladderDeadline = Date.now() + handshakeTimeoutMs
|
|
479
503
|
let sentOffer = false
|
|
480
504
|
for (let index = 0; index < rungs.length; index++) {
|
|
481
|
-
|
|
505
|
+
// 同一级不重建:换 pc 会牵动并发信令与旧连接的关闭,只在真的换级时才需要(fount-p2p#39)。
|
|
506
|
+
if (rungIndex !== index) await buildPeerConnection(index)
|
|
482
507
|
attachChannel(peerConnection.createDataChannel(CHANNEL_CONTROL))
|
|
483
508
|
attachChannel(peerConnection.createDataChannel(CHANNEL_BULK))
|
|
484
509
|
const offer = await peerConnection.createOffer()
|
|
@@ -491,7 +516,8 @@ export async function createWebRtcLink(options) {
|
|
|
491
516
|
})
|
|
492
517
|
// 候选集为空说明这一级策略把本机候选全滤掉了(例如只产出 mDNS 候选),换更宽松的一级重建。
|
|
493
518
|
// 只在预算内、且还有更宽松的一级时升级;`rewrite-loopback` 起点不会走到这里(阶梯只有它自己)。
|
|
494
|
-
|
|
519
|
+
const escalatable = index + 1 < rungs.length
|
|
520
|
+
if (!hasCandidates && escalatable && Date.now() < ladderDeadline) {
|
|
495
521
|
nodeDebug('p2p:webrtc ice rung escalated', {
|
|
496
522
|
from: rungs[index],
|
|
497
523
|
to: rungs[index + 1],
|
|
@@ -509,7 +535,7 @@ export async function createWebRtcLink(options) {
|
|
|
509
535
|
nodeDebug('p2p:webrtc offer has no usable ice candidates', {
|
|
510
536
|
rung: index,
|
|
511
537
|
policy: rungs[index],
|
|
512
|
-
escalatable
|
|
538
|
+
escalatable,
|
|
513
539
|
iceServers: options.iceServers?.length || 0,
|
|
514
540
|
})
|
|
515
541
|
break
|