@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.
- package/link/providers/webrtc.mjs +5 -2
- package/link/rtc/ice_local_hostname.mjs +15 -47
- package/link/rtc/polyfill.mjs +3 -15
- package/package.json +1 -1
- package/link/rtc/w3c_bridge.mjs +0 -152
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
101
|
-
*
|
|
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
|
-
|
|
104
|
-
if (
|
|
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
|
-
|
|
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
|
}
|
package/link/rtc/polyfill.mjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
package/link/rtc/w3c_bridge.mjs
DELETED
|
@@ -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
|
-
}
|