@steve02081504/fount-p2p 0.0.19 → 0.0.21

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.
@@ -0,0 +1,139 @@
1
+ /**
2
+ * 服务端 WebRTC polyfill 在 Windows 等环境常产出 `.local` host candidate,
3
+ * 远端无法解析。按 iceLocalHostnamePolicy 改写为 loopback 或丢弃。
4
+ */
5
+
6
+ /** @typedef {'none' | 'rewrite-loopback' | 'drop'} IceLocalHostnamePolicy */
7
+
8
+ /**
9
+ * @param {string | null | undefined} candidateSdp ICE candidate SDP 行
10
+ * @param {IceLocalHostnamePolicy} policy 处理策略
11
+ * @returns {string | null} 处理后的 SDP;drop 策略下不可用时返回 null
12
+ */
13
+ export function applyIceLocalHostnamePolicy(candidateSdp, policy) {
14
+ const sdp = String(candidateSdp || '').trim()
15
+ if (!sdp || !/\.local/i.test(sdp)) return sdp || null
16
+ if (!/\btyp host\b/i.test(sdp)) return sdp
17
+ if (policy === 'drop') return null
18
+ if (policy === 'rewrite-loopback') {
19
+ const rewritten = sdp.replace(/(\s)[\w-]+\.local(\s|$)/gi, '$1127.0.0.1$2')
20
+ return rewritten === sdp ? null : rewritten
21
+ }
22
+ return sdp
23
+ }
24
+
25
+ /**
26
+ * @param {RTCIceCandidate | { candidate?: string } | null | undefined} candidate ICE candidate
27
+ * @param {typeof RTCIceCandidate} RTCIceCandidateCtor 构造函数
28
+ * @param {IceLocalHostnamePolicy} policy 处理策略
29
+ * @returns {RTCIceCandidate | { candidate?: string } | null | undefined} 过滤/改写后的 candidate
30
+ */
31
+ export function filterIceLocalHostnameCandidate(candidate, RTCIceCandidateCtor, policy) {
32
+ if (!candidate || policy === 'none') return candidate
33
+ const raw = candidate.candidate ?? candidate.toJSON?.()?.candidate ?? ''
34
+ const rewritten = applyIceLocalHostnamePolicy(raw, policy)
35
+ if (!rewritten) return null
36
+ if (rewritten === raw) return candidate
37
+ const init = typeof candidate.toJSON === 'function'
38
+ ? { ...candidate.toJSON(), candidate: rewritten }
39
+ : { candidate: rewritten, sdpMid: candidate.sdpMid, sdpMLineIndex: candidate.sdpMLineIndex }
40
+ return new RTCIceCandidateCtor(init)
41
+ }
42
+
43
+ /**
44
+ * @param {typeof RTCPeerConnection} BaseRTC 原始 polyfill 类
45
+ * @param {typeof RTCIceCandidate} RTCIceCandidate ICE candidate 构造函数
46
+ * @param {IceLocalHostnamePolicy} [policy='drop'] 策略
47
+ * @returns {typeof RTCPeerConnection} 包装后的 RTCPeerConnection 类(none 时原样返回)
48
+ */
49
+ export function wrapRtcPeerConnectionForIceLocalHostname(BaseRTC, RTCIceCandidate, policy = 'drop') {
50
+ if (policy === 'none') return BaseRTC
51
+
52
+ const baseRoutesIce = typeof BaseRTC.prototype.prepareIceCandidateEvent === 'function'
53
+
54
+ return class IceLocalHostnameFilteredRTCPeerConnection extends BaseRTC {
55
+ /** @type {((event: RTCPeerConnectionIceEvent) => void) | null} */
56
+ #userIceHandler = null
57
+ /** @type {Set<(event: unknown) => void>} */
58
+ #iceListeners = new Set()
59
+ /** 去重:同一次 native 派发可能既走 attribute 又走 listener */
60
+ #lastIceEvent = null
61
+
62
+ /**
63
+ * drop:不派发;rewrite:仅派发替换 candidate 后的事件。
64
+ * @param {RTCPeerConnectionIceEvent | { candidate?: unknown }} event 原始 ICE 事件
65
+ * @returns {RTCPeerConnectionIceEvent | { candidate?: unknown } | null} 规范化后的事件;drop 时为 null
66
+ */
67
+ prepareIceCandidateEvent(event) {
68
+ if (!event?.candidate) return event
69
+ const filtered = filterIceLocalHostnameCandidate(event.candidate, RTCIceCandidate, policy)
70
+ if (!filtered) return null
71
+ return filtered === event.candidate ? event : { candidate: filtered }
72
+ }
73
+
74
+ /**
75
+ * @param {RTCConfiguration} [config] RTC 配置
76
+ */
77
+ constructor(config) {
78
+ super(config)
79
+ if (baseRoutesIce) return
80
+
81
+ // native EventTarget:在派发前规范化,自管 listener,不依赖 stopImmediatePropagation。
82
+ Object.defineProperty(this, 'onicecandidate', {
83
+ configurable: true,
84
+ enumerable: true,
85
+ /**
86
+ * @returns {((event: RTCPeerConnectionIceEvent) => void) | null} 用户 ICE handler
87
+ */
88
+ get: () => this.#userIceHandler,
89
+ /**
90
+ * @param {((event: RTCPeerConnectionIceEvent) => void) | null} handler 用户 ICE handler
91
+ * @returns {void}
92
+ */
93
+ set: handler => { this.#userIceHandler = handler },
94
+ })
95
+ super.addEventListener('icecandidate', event => this.#deliverIce(event))
96
+ }
97
+
98
+ /**
99
+ * @param {RTCPeerConnectionIceEvent | { candidate?: unknown }} event 原始 ICE 事件
100
+ * @returns {void}
101
+ */
102
+ #deliverIce = event => {
103
+ if (this.#lastIceEvent === event) return
104
+ this.#lastIceEvent = event
105
+ const normalized = this.prepareIceCandidateEvent(event)
106
+ if (normalized == null) return
107
+ this.#userIceHandler?.(normalized)
108
+ for (const listener of this.#iceListeners) listener(normalized)
109
+ }
110
+
111
+ /**
112
+ * @param {string} type 事件名
113
+ * @param {(event: unknown) => void} listener 回调
114
+ * @param {boolean | AddEventListenerOptions} [options] 监听选项
115
+ * @returns {void}
116
+ */
117
+ addEventListener(type, listener, options) {
118
+ if (!baseRoutesIce && type === 'icecandidate') {
119
+ this.#iceListeners.add(listener)
120
+ return
121
+ }
122
+ return super.addEventListener(type, listener, options)
123
+ }
124
+
125
+ /**
126
+ * @param {string} type 事件名
127
+ * @param {(event: unknown) => void} listener 回调
128
+ * @param {boolean | EventListenerOptions} [options] 监听选项
129
+ * @returns {void}
130
+ */
131
+ removeEventListener(type, listener, options) {
132
+ if (!baseRoutesIce && type === 'icecandidate') {
133
+ this.#iceListeners.delete(listener)
134
+ return
135
+ }
136
+ return super.removeEventListener(type, listener, options)
137
+ }
138
+ }
139
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * 等待 data channel open/close(re-export)。
3
+ */
4
+ export { waitForChannelState } from './channel.mjs'
5
+ /**
6
+ * ICE candidate 本地主机名策略(re-export)。
7
+ */
8
+ export {
9
+ applyIceLocalHostnamePolicy,
10
+ filterIceLocalHostnameCandidate,
11
+ wrapRtcPeerConnectionForIceLocalHostname,
12
+ } from './ice_local_hostname.mjs'
13
+ /**
14
+ * Node WebRTC polyfill 加载(re-export)。
15
+ */
16
+ export { clearNodeRtcPolyfillCache, loadNodeRtcPolyfill } from './polyfill.mjs'
@@ -0,0 +1,159 @@
1
+ import process from 'node:process'
2
+
3
+ import { getRtcPolyfillCacheEpoch, getSignalingRuntimeConfig } from '../../node/instance.mjs'
4
+ import { nodeDebug } from '../../node/log.mjs'
5
+
6
+ import { wrapRtcPeerConnectionForIceLocalHostname } from './ice_local_hostname.mjs'
7
+ import { bridgePeerConnection } from './w3c_bridge.mjs'
8
+
9
+ /** @type {boolean} */
10
+ let exitCleanupHooked = false
11
+
12
+ /** @type {Promise<LoadedRtcPolyfill> | null} */
13
+ let cachedDefaultPolyfill = null
14
+
15
+ /** @type {number} 与 cachedDefaultPolyfill 绑定的策略世代 */
16
+ let cachedDefaultPolyfillEpoch = -1
17
+
18
+ /**
19
+ * @typedef {{
20
+ * RTCPeerConnection: typeof RTCPeerConnection,
21
+ * RTCIceCandidate: typeof RTCIceCandidate,
22
+ * backend: string,
23
+ * forcesTrickleIce: boolean,
24
+ * }} LoadedRtcPolyfill
25
+ * @typedef {{
26
+ * id: string,
27
+ * forcesTrickleIce?: boolean,
28
+ * load: () => Promise<{ RTCPeerConnection: typeof RTCPeerConnection, RTCIceCandidate: typeof RTCIceCandidate }>,
29
+ * }} RtcBackend
30
+ */
31
+
32
+ /**
33
+ * 清除默认后端加载缓存(iceLocalHostnamePolicy 变更后调用)。
34
+ * @returns {void}
35
+ */
36
+ export function clearNodeRtcPolyfillCache() {
37
+ cachedDefaultPolyfill = null
38
+ cachedDefaultPolyfillEpoch = -1
39
+ }
40
+
41
+ /**
42
+ * 注册进程退出时销毁 libdatachannel 原生资源(首次成功加载后挂一次)。
43
+ * libdatachannel 的原生线程在 pc.close() 后仍需时间回收;进程退出时若原生资源未同步销毁,
44
+ * Windows 上会触发堆损坏(退出码 0xC0000374)。
45
+ * @returns {Promise<void>}
46
+ */
47
+ async function ensureNodeDatachannelExitCleanup() {
48
+ if (exitCleanupHooked) return
49
+ exitCleanupHooked = true
50
+ const { cleanup = undefined } = await import('node-datachannel').catch(() => ({}))
51
+ process.on('exit', () => {
52
+ try { cleanup?.() } catch { /* already torn down */ }
53
+ })
54
+ }
55
+
56
+ /**
57
+ * @returns {Promise<{ RTCPeerConnection: typeof RTCPeerConnection, RTCIceCandidate: typeof RTCIceCandidate }>} node-datachannel 构造器
58
+ */
59
+ async function loadNodeDatachannelBackend() {
60
+ const mod = await import('node-datachannel/polyfill')
61
+ await ensureNodeDatachannelExitCleanup()
62
+ return {
63
+ RTCPeerConnection: mod.RTCPeerConnection,
64
+ RTCIceCandidate: mod.RTCIceCandidate,
65
+ }
66
+ }
67
+
68
+ /**
69
+ * 纯 JS WebRTC DataChannel(Termux / 无 native prebuild 时的 fallback)。
70
+ * @returns {Promise<{ RTCPeerConnection: typeof RTCPeerConnection, RTCIceCandidate: typeof RTCIceCandidate }>} node-rtc-connection 构造器
71
+ */
72
+ 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
+ }
78
+ }
79
+
80
+ /** @type {RtcBackend} */
81
+ const PURE_JS_BACKEND = {
82
+ id: 'node-rtc-connection',
83
+ forcesTrickleIce: true,
84
+ load: loadNodeRtcConnectionBackend,
85
+ }
86
+
87
+ /**
88
+ * @returns {RtcBackend[]} 默认后端顺序:优先 native,失败再纯 JS
89
+ */
90
+ function defaultRtcBackends() {
91
+ /** @type {RtcBackend[]} */
92
+ const backends = []
93
+ // Android/Termux:无官方 prebuild,且 Bionic 不能跑 linux-arm64 glibc 包;直接走纯 JS。
94
+ if (process.platform !== 'android')
95
+ backends.push({ id: 'node-datachannel', load: loadNodeDatachannelBackend })
96
+ backends.push(PURE_JS_BACKEND)
97
+ return backends
98
+ }
99
+
100
+ /**
101
+ * @param {{ backends?: RtcBackend[] }} options 后端列表
102
+ * @returns {Promise<LoadedRtcPolyfill>} 首个可用后端的 polyfill
103
+ */
104
+ async function loadNodeRtcPolyfillUncached(options) {
105
+ const backends = options.backends?.length
106
+ ? [...options.backends, PURE_JS_BACKEND]
107
+ : defaultRtcBackends()
108
+ /** @type {unknown} */
109
+ let lastError = null
110
+ for (const backend of backends)
111
+ try {
112
+ const mod = await backend.load()
113
+ const { iceLocalHostnamePolicy } = getSignalingRuntimeConfig()
114
+ return {
115
+ RTCPeerConnection: wrapRtcPeerConnectionForIceLocalHostname(
116
+ mod.RTCPeerConnection,
117
+ mod.RTCIceCandidate,
118
+ iceLocalHostnamePolicy,
119
+ ),
120
+ RTCIceCandidate: mod.RTCIceCandidate,
121
+ backend: backend.id,
122
+ forcesTrickleIce: backend.forcesTrickleIce === true,
123
+ }
124
+ }
125
+ catch (error) {
126
+ lastError = error
127
+ nodeDebug('p2p:webrtc backend unavailable', {
128
+ backend: backend.id,
129
+ err: String(error?.message ?? error).replace(/\s+/g, ' ').slice(0, 240),
130
+ })
131
+ }
132
+
133
+ throw lastError instanceof Error ? lastError : new Error(String(lastError ?? 'no rtc backend'))
134
+ }
135
+
136
+ /**
137
+ * 加载 RTC polyfill(node-datachannel 优先,失败则 node-rtc-connection),并按配置包装 RTCPeerConnection。
138
+ * 默认后端路径会缓存首次成功结果;注入 backends 时不走缓存。
139
+ * @param {{ backends?: RtcBackend[] }} [options] 可注入后端列表(测试用)
140
+ * @returns {Promise<LoadedRtcPolyfill>} RTC 构造器
141
+ */
142
+ export async function loadNodeRtcPolyfill(options = {}) {
143
+ if (options.backends?.length)
144
+ return loadNodeRtcPolyfillUncached(options)
145
+ const epoch = getRtcPolyfillCacheEpoch()
146
+ if (!cachedDefaultPolyfill || cachedDefaultPolyfillEpoch !== epoch) {
147
+ cachedDefaultPolyfill = null
148
+ cachedDefaultPolyfillEpoch = epoch
149
+ const pending = loadNodeRtcPolyfillUncached(options).catch(error => {
150
+ if (cachedDefaultPolyfill === pending) {
151
+ cachedDefaultPolyfill = null
152
+ cachedDefaultPolyfillEpoch = -1
153
+ }
154
+ throw error
155
+ })
156
+ cachedDefaultPolyfill = pending
157
+ }
158
+ return cachedDefaultPolyfill
159
+ }
@@ -0,0 +1,152 @@
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) || typeof channel.on !== 'function') 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
+ }
package/mailbox/store.mjs CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  } from './prune.mjs'
21
21
 
22
22
  /**
23
- *
23
+ * mailbox 桶裁剪与计量辅助(re-export)。
24
24
  */
25
25
  export { MAX_BUCKET_BYTES, MAX_BUCKET_ENTRIES, mailboxBucketKey, mailboxRecordBytes }
26
26
 
package/node/instance.mjs CHANGED
@@ -22,6 +22,16 @@ let runtime = null
22
22
  /** @type {Set<(event: string, payload?: unknown) => void>} */
23
23
  const changeListeners = new Set()
24
24
 
25
+ /** RTC polyfill 缓存世代:策略变更时同步推进,避免动态 import 清缓存前仍命中旧构造器 */
26
+ let rtcPolyfillCacheEpoch = 0
27
+
28
+ /**
29
+ * @returns {number} 当前 RTC polyfill 缓存世代
30
+ */
31
+ export function getRtcPolyfillCacheEpoch() {
32
+ return rtcPolyfillCacheEpoch
33
+ }
34
+
25
35
  /**
26
36
  * @param {{ nodeDir: string, entityStore?: import('./entity_store.mjs').EntityStore }} options - 节点目录与可选 entity store
27
37
  * @returns {NodeRuntime} 初始化后的运行时
@@ -72,7 +82,10 @@ export function setNodeLogger(logger) {
72
82
  */
73
83
  export function setSignalingRuntimeConfig(config) {
74
84
  if (!runtime) throw new Error('p2p: setSignalingRuntimeConfig requires initNode')
85
+ const previousPolicy = runtime.signaling.iceLocalHostnamePolicy
75
86
  runtime.signaling = resolveSignalingRuntimeConfig({ ...runtime.signaling, ...config })
87
+ if (runtime.signaling.iceLocalHostnamePolicy !== previousPolicy)
88
+ rtcPolyfillCacheEpoch++
76
89
  emitNodeChange('signaling-changed', runtime.signaling)
77
90
  }
78
91
 
@@ -131,4 +144,5 @@ export function onNodeChange(listener) {
131
144
  export function resetNodeForTests() {
132
145
  runtime = null
133
146
  changeListeners.clear()
147
+ rtcPolyfillCacheEpoch++
134
148
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steve02081504/fount-p2p",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "description": "fount federation P2P layer — link, trust graph, mailbox, DAG, EVFS.",
5
5
  "keywords": [
6
6
  "network",
@@ -71,7 +71,7 @@
71
71
  },
72
72
  "dependencies": {
73
73
  "@noble/curves": "latest",
74
- "node-datachannel": "latest",
74
+ "node-rtc-connection": "latest",
75
75
  "on-shutdown": "latest",
76
76
  "ws": "latest"
77
77
  },
@@ -80,6 +80,10 @@
80
80
  },
81
81
  "optionalDependencies": {
82
82
  "@stoprocent/bleno": "latest",
83
- "@stoprocent/noble": "latest"
83
+ "@stoprocent/noble": "latest",
84
+ "node-datachannel": "latest"
85
+ },
86
+ "allowScripts": {
87
+ "node-datachannel": true
84
88
  }
85
89
  }
@@ -1,12 +1,12 @@
1
1
  /**
2
- *
2
+ * 权限位掩码编解码(re-export)。
3
3
  */
4
4
  export { createPermissionCodec } from './bitmask.mjs'
5
5
  /**
6
- *
6
+ * 角色 deny/allow 覆盖合并(re-export)。
7
7
  */
8
8
  export { applyDenyAllowOverride, mergeRoleOverrides } from './layered.mjs'
9
9
  /**
10
- *
10
+ * 分层权限求值器(re-export)。
11
11
  */
12
12
  export { createLayeredEvaluator } from './evaluator.mjs'
@@ -157,7 +157,7 @@ export function createGroupLinkSet(options) {
157
157
  }
158
158
 
159
159
  /**
160
- *
160
+ * 开启成员自动拨号与 roster 维护。
161
161
  */
162
162
  function startAutoconnect() {
163
163
  autoconnectEnabled = true
@@ -165,7 +165,7 @@ export function createGroupLinkSet(options) {
165
165
  }
166
166
 
167
167
  /**
168
- *
168
+ * 停止自动拨号并清除待执行的拨号定时器。
169
169
  */
170
170
  function stopAutoconnect() {
171
171
  autoconnectEnabled = false
@@ -386,7 +386,7 @@ export function createLinkRegistry(options = {}) {
386
386
  })
387
387
 
388
388
  /**
389
- *
389
+ * 启动 discovery/link runtime 并开启 mesh keepalive。
390
390
  */
391
391
  const ensureRuntimeWithMesh = async () => {
392
392
  await bootstrap.ensureRuntime()
@@ -242,7 +242,7 @@ export function createRuntimeBootstrap(deps) {
242
242
  if (typeof stop === 'function' && generation === gen && isLive()) {
243
243
  const prev = stopPresence
244
244
  /**
245
- *
245
+ * 停止 presence 广播并链式调用上一轮清理。
246
246
  */
247
247
  stopPresence = () => { try { stop() } catch { /* ignore */ }; prev?.() }
248
248
  }
@@ -256,7 +256,7 @@ export function createRuntimeBootstrap(deps) {
256
256
  if (typeof stop === 'function' && generation === gen && isLive()) {
257
257
  const prev = stopSignalListener
258
258
  /**
259
- *
259
+ * 停止信令监听并链式调用上一轮清理。
260
260
  */
261
261
  stopSignalListener = () => { try { stop() } catch { /* ignore */ }; prev?.() }
262
262
  }