@steve02081504/fount-p2p 0.0.30 → 0.0.31

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.
@@ -68,8 +68,7 @@ export async function createWebRtcLink(options) {
68
68
  const handshakeTimeoutMs = Number(options.handshakeTimeoutMs) || ms('10s')
69
69
  const channelOpenTimeoutMs = Math.max(handshakeTimeoutMs, ms('30s'))
70
70
  const rtc = options.rtc ?? await loadNodeRtcPolyfill()
71
- // JS 后端只做 trickle(SDP 不含 candidate);强制开启 trickle。
72
- const trickleIceOff = !rtc.forcesTrickleIce && getSignalingRuntimeConfig().trickleIceOff === true
71
+ const trickleIceOff = getSignalingRuntimeConfig().trickleIceOff === true
73
72
  const peerConnection = new rtc.RTCPeerConnection(options.iceServers?.length ? { iceServers: options.iceServers } : undefined)
74
73
  const remoteSignalQueue = []
75
74
  const seenRemoteSignals = createLruMap(1024)
@@ -183,6 +182,10 @@ export async function createWebRtcLink(options) {
183
182
  const deadline = Date.now() + handshakeTimeoutMs
184
183
  while (peerConnection.iceGatheringState !== 'complete' && Date.now() < deadline)
185
184
  await new Promise(resolve => setTimeout(resolve, 50))
185
+ if (peerConnection.iceGatheringState !== 'complete') {
186
+ await pipe.close('ice-gathering-timeout')
187
+ throw new Error(`p2p: ice gathering incomplete after ${handshakeTimeoutMs}ms`)
188
+ }
186
189
  }
187
190
 
188
191
  /**
@@ -50,18 +50,12 @@ export function filterIceLocalHostnameCandidate(candidate, RTCIceCandidateCtor,
50
50
  export function wrapRtcPeerConnectionForIceLocalHostname(BaseRTC, RTCIceCandidate, policy = 'drop') {
51
51
  if (policy === 'none') return BaseRTC
52
52
 
53
- const baseRoutesIce = !!BaseRTC.prototype.prepareIceCandidateEvent
54
-
55
53
  return class IceLocalHostnameFilteredRTCPeerConnection extends BaseRTC {
56
54
  /** @type {((event: RTCPeerConnectionIceEvent) => void) | null} */
57
55
  #userIceHandler = null
58
- /** @type {Set<(event: unknown) => void>} */
59
- #iceListeners = new Set()
60
- /** 去重:同一次 native 派发可能既走 attribute 又走 listener */
61
- #lastIceEvent = null
62
56
 
63
57
  /**
64
- * drop:不派发;rewrite:仅派发替换 candidate 后的事件。
58
+ * drop:不派发;rewrite:构造仅携带替换 candidate 的派生事件。
65
59
  * @param {RTCPeerConnectionIceEvent | { candidate?: unknown }} event 原始 ICE 事件
66
60
  * @returns {RTCPeerConnectionIceEvent | { candidate?: unknown } | null} 规范化后的事件;drop 时为 null
67
61
  */
@@ -69,7 +63,10 @@ export function wrapRtcPeerConnectionForIceLocalHostname(BaseRTC, RTCIceCandidat
69
63
  if (!event?.candidate) return event
70
64
  const filtered = filterIceLocalHostnameCandidate(event.candidate, RTCIceCandidate, policy)
71
65
  if (!filtered) return null
72
- return filtered === event.candidate ? event : { candidate: filtered }
66
+ if (filtered === event.candidate) return event
67
+ const rewritten = new event.constructor('icecandidate')
68
+ rewritten.candidate = filtered
69
+ return rewritten
73
70
  }
74
71
 
75
72
  /**
@@ -77,9 +74,6 @@ export function wrapRtcPeerConnectionForIceLocalHostname(BaseRTC, RTCIceCandidat
77
74
  */
78
75
  constructor(config) {
79
76
  super(config)
80
- if (baseRoutesIce) return
81
-
82
- // native EventTarget:在派发前规范化,自管 listener,不依赖 stopImmediatePropagation。
83
77
  Object.defineProperty(this, 'onicecandidate', {
84
78
  configurable: true,
85
79
  enumerable: true,
@@ -93,48 +87,22 @@ export function wrapRtcPeerConnectionForIceLocalHostname(BaseRTC, RTCIceCandidat
93
87
  */
94
88
  set: handler => { this.#userIceHandler = handler },
95
89
  })
96
- super.addEventListener('icecandidate', event => this.#deliverIce(event))
97
90
  }
98
91
 
99
92
  /**
100
- * @param {RTCPeerConnectionIceEvent | { candidate?: unknown }} event 原始 ICE 事件
101
- * @returns {void}
93
+ * 先完成 candidate 转换,再走标准 EventTarget 派发(保留 once/AbortSignal/capture 语义)。
94
+ * addEventListener / removeEventListener 交由基类,故 once / AbortSignal / capture 均保留。
95
+ * drop:不派发;pass-through:派发原事件;rewrite:派发替换 candidate 后的派生事件。
96
+ * @param {Event} event 待派发事件
97
+ * @returns {boolean} 事件是否未被取消
102
98
  */
103
- #deliverIce = event => {
104
- if (this.#lastIceEvent === event) return
105
- this.#lastIceEvent = event
99
+ dispatchEvent(event) {
100
+ if (event?.type !== 'icecandidate') return super.dispatchEvent(event)
106
101
  const normalized = this.prepareIceCandidateEvent(event)
107
- if (normalized == null) return
102
+ if (normalized == null) return true
108
103
  this.#userIceHandler?.(normalized)
109
- for (const listener of this.#iceListeners) listener(normalized)
110
- }
111
-
112
- /**
113
- * @param {string} type 事件名
114
- * @param {(event: unknown) => void} listener 回调
115
- * @param {boolean | AddEventListenerOptions} [options] 监听选项
116
- * @returns {void}
117
- */
118
- addEventListener(type, listener, options) {
119
- if (!baseRoutesIce && type === 'icecandidate') {
120
- this.#iceListeners.add(listener)
121
- return
122
- }
123
- return super.addEventListener(type, listener, options)
124
- }
125
-
126
- /**
127
- * @param {string} type 事件名
128
- * @param {(event: unknown) => void} listener 回调
129
- * @param {boolean | EventListenerOptions} [options] 监听选项
130
- * @returns {void}
131
- */
132
- removeEventListener(type, listener, options) {
133
- if (!baseRoutesIce && type === 'icecandidate') {
134
- this.#iceListeners.delete(listener)
135
- return
136
- }
137
- return super.removeEventListener(type, listener, options)
104
+ if (normalized === event) return super.dispatchEvent(event)
105
+ return super.dispatchEvent(normalized)
138
106
  }
139
107
  }
140
108
  }
@@ -4,7 +4,6 @@ import { getRtcPolyfillCacheEpoch, getSignalingRuntimeConfig } from '../../node/
4
4
  import { nodeDebug } from '../../node/log.mjs'
5
5
 
6
6
  import { wrapRtcPeerConnectionForIceLocalHostname } from './ice_local_hostname.mjs'
7
- import { bridgePeerConnection } from './w3c_bridge.mjs'
8
7
 
9
8
  /** @type {boolean} */
10
9
  let exitCleanupHooked = false
@@ -20,11 +19,9 @@ let cachedDefaultPolyfillEpoch = -1
20
19
  * RTCPeerConnection: typeof RTCPeerConnection,
21
20
  * RTCIceCandidate: typeof RTCIceCandidate,
22
21
  * backend: string,
23
- * forcesTrickleIce: boolean,
24
22
  * }} LoadedRtcPolyfill
25
23
  * @typedef {{
26
24
  * id: string,
27
- * forcesTrickleIce?: boolean,
28
25
  * load: () => Promise<{ RTCPeerConnection: typeof RTCPeerConnection, RTCIceCandidate: typeof RTCIceCandidate }>,
29
26
  * }} RtcBackend
30
27
  */
@@ -57,12 +54,9 @@ async function ensureNodeDatachannelExitCleanup() {
57
54
  * @returns {Promise<{ RTCPeerConnection: typeof RTCPeerConnection, RTCIceCandidate: typeof RTCIceCandidate }>} node-datachannel 构造器
58
55
  */
59
56
  async function loadNodeDatachannelBackend() {
60
- const mod = await import('node-datachannel/polyfill')
57
+ const module = await import('node-datachannel/polyfill')
61
58
  await ensureNodeDatachannelExitCleanup()
62
- return {
63
- RTCPeerConnection: mod.RTCPeerConnection,
64
- RTCIceCandidate: mod.RTCIceCandidate,
65
- }
59
+ return module
66
60
  }
67
61
 
68
62
  /**
@@ -70,17 +64,12 @@ async function loadNodeDatachannelBackend() {
70
64
  * @returns {Promise<{ RTCPeerConnection: typeof RTCPeerConnection, RTCIceCandidate: typeof RTCIceCandidate }>} node-rtc-connection 构造器
71
65
  */
72
66
  async function loadNodeRtcConnectionBackend() {
73
- const module = await import('node-rtc-connection')
74
- return {
75
- RTCPeerConnection: /** @type {typeof RTCPeerConnection} */ bridgePeerConnection(module.RTCPeerConnection),
76
- RTCIceCandidate: module.RTCIceCandidate,
77
- }
67
+ return import('node-rtc-connection')
78
68
  }
79
69
 
80
70
  /** @type {RtcBackend} */
81
71
  const PURE_JS_BACKEND = {
82
72
  id: 'node-rtc-connection',
83
- forcesTrickleIce: true,
84
73
  load: loadNodeRtcConnectionBackend,
85
74
  }
86
75
 
@@ -119,7 +108,6 @@ async function loadNodeRtcPolyfillUncached(options) {
119
108
  ),
120
109
  RTCIceCandidate: mod.RTCIceCandidate,
121
110
  backend: backend.id,
122
- forcesTrickleIce: backend.forcesTrickleIce === true,
123
111
  }
124
112
  }
125
113
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steve02081504/fount-p2p",
3
- "version": "0.0.30",
3
+ "version": "0.0.31",
4
4
  "description": "fount federation P2P layer — link, trust graph, mailbox, DAG, EVFS.",
5
5
  "keywords": [
6
6
  "network",
@@ -1,152 +0,0 @@
1
- /**
2
- * 把 EventEmitter 风格的 RTC(如 node-rtc-connection)桥成 W3C 属性 handler。
3
- * 其余链路只认 onicecandidate / onmessage / onbufferedamountlow 等。
4
- */
5
-
6
- /** @type {WeakSet<object>} */
7
- const bridgedChannels = new WeakSet()
8
-
9
- /**
10
- * @param {object} target EventEmitter 目标
11
- * @param {string} property W3C 属性名(如 onmessage)
12
- * @param {string} eventName EventEmitter 事件名
13
- * @param {(payload: unknown) => unknown} [adapt] 事件载荷适配
14
- * @returns {void}
15
- */
16
- function defineEmitterHandler(target, property, eventName, adapt = payload => payload) {
17
- let handler = null
18
- target.on(eventName, payload => handler?.(adapt(payload)))
19
- Object.defineProperty(target, property, {
20
- configurable: true,
21
- enumerable: true,
22
- /**
23
- * @returns {((payload: unknown) => void) | null} 当前 handler
24
- */
25
- get: () => handler,
26
- /**
27
- * @param {((payload: unknown) => void) | null} value 新 handler
28
- * @returns {void}
29
- */
30
- set: value => { handler = value },
31
- })
32
- }
33
-
34
- /**
35
- * @param {RTCDataChannel} channel 原始 data channel
36
- * @returns {RTCDataChannel} 已挂 W3C handler 的通道
37
- */
38
- export function bridgeDataChannel(channel) {
39
- if (bridgedChannels.has(channel) || !channel.on) return channel
40
- bridgedChannels.add(channel)
41
- defineEmitterHandler(channel, 'onmessage', 'message')
42
- defineEmitterHandler(channel, 'onopen', 'open')
43
- defineEmitterHandler(channel, 'onclose', 'close')
44
- defineEmitterHandler(channel, 'onbufferedamountlow', 'bufferedamountlow')
45
- return channel
46
- }
47
-
48
- /**
49
- * @param {typeof RTCPeerConnection} BaseRTC EventEmitter 风格 RTCPeerConnection
50
- * @returns {typeof RTCPeerConnection} W3C handler 版
51
- */
52
- export function bridgePeerConnection(BaseRTC) {
53
- return class W3cRtcPeerConnection extends BaseRTC {
54
- /** @type {((event: RTCPeerConnectionIceEvent) => void) | null} */
55
- #iceHandler = null
56
- /** @type {((event: { channel: RTCDataChannel }) => void) | null} */
57
- #dataChannelHandler = null
58
- /** @type {(() => void) | null} */
59
- #connectionStateHandler = null
60
- /** @type {Map<string, Set<(event: unknown) => void>>} */
61
- #listeners = new Map()
62
-
63
- /**
64
- * @param {RTCConfiguration} [config] RTC 配置
65
- */
66
- constructor(config) {
67
- super(config)
68
- super.on('icecandidate', event => {
69
- const normalized = this.prepareIceCandidateEvent(event)
70
- if (normalized == null) return
71
- this.#iceHandler?.(normalized)
72
- this.#emit('icecandidate', normalized)
73
- })
74
- super.on('datachannel', event => {
75
- const adapted = { channel: bridgeDataChannel(event.channel) }
76
- this.#dataChannelHandler?.(adapted)
77
- this.#emit('datachannel', adapted)
78
- })
79
- super.on('connectionstatechange', () => {
80
- this.#connectionStateHandler?.()
81
- this.#emit('connectionstatechange', undefined)
82
- })
83
- }
84
-
85
- /**
86
- * ICE 事件规范化钩子;子类可覆盖(drop 返回 null,rewrite 返回替换后的事件)。
87
- * @param {RTCPeerConnectionIceEvent | { candidate?: unknown }} event 原始 ICE 事件
88
- * @returns {RTCPeerConnectionIceEvent | { candidate?: unknown } | null} 规范化后的事件
89
- */
90
- prepareIceCandidateEvent(event) {
91
- return event
92
- }
93
-
94
- /**
95
- * @param {string} type 事件名
96
- * @param {unknown} event 事件载荷(icecandidate 须已规范化)
97
- * @returns {void}
98
- */
99
- #emit(type, event) {
100
- const listeners = this.#listeners.get(type)
101
- if (!listeners) return
102
- for (const listener of listeners) listener(event)
103
- }
104
-
105
- /**
106
- * @param {string} type 事件名
107
- * @param {(event: unknown) => void} listener 回调
108
- * @returns {void}
109
- */
110
- addEventListener(type, listener) {
111
- let listeners = this.#listeners.get(type)
112
- if (!listeners) {
113
- listeners = new Set()
114
- this.#listeners.set(type, listeners)
115
- }
116
- listeners.add(listener)
117
- }
118
-
119
- /**
120
- * @param {string} type 事件名
121
- * @param {(event: unknown) => void} listener 回调
122
- * @returns {void}
123
- */
124
- removeEventListener(type, listener) {
125
- this.#listeners.get(type)?.delete(listener)
126
- }
127
-
128
- /** @returns {((event: RTCPeerConnectionIceEvent) => void) | null} ICE candidate handler */
129
- get onicecandidate() { return this.#iceHandler }
130
- /** @param {((event: RTCPeerConnectionIceEvent) => void) | null} handler ICE candidate handler */
131
- set onicecandidate(handler) { this.#iceHandler = handler }
132
-
133
- /** @returns {((event: { channel: RTCDataChannel }) => void) | null} data channel handler */
134
- get ondatachannel() { return this.#dataChannelHandler }
135
- /** @param {((event: { channel: RTCDataChannel }) => void) | null} handler data channel handler */
136
- set ondatachannel(handler) { this.#dataChannelHandler = handler }
137
-
138
- /** @returns {(() => void) | null} connection state handler */
139
- get onconnectionstatechange() { return this.#connectionStateHandler }
140
- /** @param {(() => void) | null} handler connection state handler */
141
- set onconnectionstatechange(handler) { this.#connectionStateHandler = handler }
142
-
143
- /**
144
- * @param {string} label data channel 标签
145
- * @param {RTCDataChannelInit} [init] 创建选项
146
- * @returns {RTCDataChannel} 已桥接 W3C handler 的通道
147
- */
148
- createDataChannel(label, init) {
149
- return bridgeDataChannel(super.createDataChannel(label, init))
150
- }
151
- }
152
- }