@coclaw/openclaw-coclaw 0.13.1 → 0.13.2
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/package.json +1 -1
- package/src/webrtc/webrtc-peer.js +64 -6
package/package.json
CHANGED
|
@@ -5,6 +5,14 @@ import { remoteLog } from '../remote-log.js';
|
|
|
5
5
|
// 用于诊断 dump:过大会撑爆 remoteLog 单帧,20 足以覆盖典型多文件传输会话。
|
|
6
6
|
const FILE_CHANNEL_HISTORY_LIMIT = 20;
|
|
7
7
|
|
|
8
|
+
// Failed session 保留 24 小时,支持 Capacitor 长时间后台恢复后 ICE restart。
|
|
9
|
+
// 超时后 session 被回收释放 IPC listeners 和 Go 侧资源。
|
|
10
|
+
const FAILED_SESSION_TTL_MS = 24 * 60 * 60 * 1000;
|
|
11
|
+
|
|
12
|
+
// Session 总数上限(活跃 + failed)。溢出时淘汰最旧的 failed session。
|
|
13
|
+
// 20 足以覆盖多 UI 实例(浏览器多标签 + 移动端)的典型场景。
|
|
14
|
+
const MAX_SESSIONS = 20;
|
|
15
|
+
|
|
8
16
|
/**
|
|
9
17
|
* 管理多个 WebRTC PeerConnection(以 connId 为粒度)。
|
|
10
18
|
* Plugin 作为被叫方:收到 UI 的 offer → 回复 answer。
|
|
@@ -55,6 +63,11 @@ export class WebRtcPeer {
|
|
|
55
63
|
async closeByConnId(connId) {
|
|
56
64
|
const session = this.__sessions.get(connId);
|
|
57
65
|
if (!session) return;
|
|
66
|
+
// 清理 failed TTL 定时器
|
|
67
|
+
if (session.__failedTimer) {
|
|
68
|
+
clearTimeout(session.__failedTimer);
|
|
69
|
+
session.__failedTimer = null;
|
|
70
|
+
}
|
|
58
71
|
this.__sessions.delete(connId);
|
|
59
72
|
// 先 detach 事件,防止 pc.close() 异步触发 onconnectionstatechange 删除新 session
|
|
60
73
|
session.pc.onconnectionstatechange = null;
|
|
@@ -105,7 +118,12 @@ export class WebRtcPeer {
|
|
|
105
118
|
toConnId: connId,
|
|
106
119
|
payload: { reason: 'impl_unsupported' },
|
|
107
120
|
});
|
|
108
|
-
return;
|
|
121
|
+
return; // TTL timer 保持不变(reject 是同步的,不影响 timer 正常工作)
|
|
122
|
+
}
|
|
123
|
+
// 暂停 failed TTL timer:pion restart 涉及异步协商,期间不应被回收
|
|
124
|
+
if (existing.__failedTimer) {
|
|
125
|
+
clearTimeout(existing.__failedTimer);
|
|
126
|
+
existing.__failedTimer = null;
|
|
109
127
|
}
|
|
110
128
|
this.__remoteLog(`rtc.ice-restart conn=${connId}`);
|
|
111
129
|
this.logger.info?.(`${this.__rtcTag} ICE restart offer from ${connId}, renegotiating`);
|
|
@@ -155,6 +173,11 @@ export class WebRtcPeer {
|
|
|
155
173
|
await this.closeByConnId(connId);
|
|
156
174
|
}
|
|
157
175
|
|
|
176
|
+
// session 总数限制:溢出时淘汰最旧的 failed session
|
|
177
|
+
if (this.__sessions.size >= MAX_SESSIONS) {
|
|
178
|
+
this.__evictOldestFailed();
|
|
179
|
+
}
|
|
180
|
+
|
|
158
181
|
// 从 Server 注入的 turnCreds 构建 iceServers
|
|
159
182
|
// werift 的 urls 必须是单个 string,每个 URL 独立一个对象
|
|
160
183
|
const iceServers = [];
|
|
@@ -222,6 +245,12 @@ export class WebRtcPeer {
|
|
|
222
245
|
const cur = this.__sessions.get(connId);
|
|
223
246
|
if (!cur || cur.pc !== pc) return;
|
|
224
247
|
|
|
248
|
+
// 离开 failed 状态时清理 TTL timer(ICE restart 恢复、自然关闭等)
|
|
249
|
+
if (state !== 'failed' && cur.__failedTimer) {
|
|
250
|
+
clearTimeout(cur.__failedTimer);
|
|
251
|
+
cur.__failedTimer = null;
|
|
252
|
+
}
|
|
253
|
+
|
|
225
254
|
if (state === 'connected') {
|
|
226
255
|
// 重置 dump 去重水位(disconnected → connected → disconnected 仍能再 dump)
|
|
227
256
|
cur.__lastDumpState = null;
|
|
@@ -238,16 +267,25 @@ export class WebRtcPeer {
|
|
|
238
267
|
// pion: pair 通过独立的 selectedcandidatepairchange 事件上报
|
|
239
268
|
} else if (state === 'disconnected' || state === 'failed' || state === 'closed') {
|
|
240
269
|
// 诊断 dump:失败/断连/关闭时输出当前 PC 上 DC 状态,定位"PC 假活/DC 死"现象
|
|
241
|
-
// - closed
|
|
270
|
+
// - closed 由 closeByConnId 接管清理,dump 收敛诊断噪声
|
|
242
271
|
// - disconnected 可能反复触发,去重避免噪声
|
|
243
272
|
if (state !== 'closed' && cur.__lastDumpState !== state) {
|
|
244
273
|
cur.__lastDumpState = state;
|
|
245
274
|
this.__dumpSessionState(connId, cur, state);
|
|
246
275
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
276
|
+
if (state === 'failed') {
|
|
277
|
+
// 启动 TTL 定时器:超时后回收 session 释放 IPC listeners 和 Go 侧资源。
|
|
278
|
+
// unref() 确保定时器不阻止进程退出(gateway 由其他连接保活)。
|
|
279
|
+
if (cur.__failedTimer) clearTimeout(cur.__failedTimer);
|
|
280
|
+
cur.__failedTimer = setTimeout(() => {
|
|
281
|
+
this.__remoteLog(`rtc.session-expired conn=${connId} ttl=${FAILED_SESSION_TTL_MS / 1000}s`);
|
|
282
|
+
this.logger.info?.(`${this.__rtcTag} [${connId}] session TTL expired, closing`);
|
|
283
|
+
this.closeByConnId(connId).catch(() => {});
|
|
284
|
+
}, FAILED_SESSION_TTL_MS);
|
|
285
|
+
cur.__failedTimer.unref?.();
|
|
286
|
+
} else if (state === 'closed') {
|
|
287
|
+
// 自然进入 closed 时也需通过 closeByConnId 释放 IPC listeners 和 Go 资源
|
|
288
|
+
this.closeByConnId(connId).catch(() => {});
|
|
251
289
|
}
|
|
252
290
|
}
|
|
253
291
|
};
|
|
@@ -298,6 +336,10 @@ export class WebRtcPeer {
|
|
|
298
336
|
// SDP 协商失败 → 清理已入 Map 的 session,避免泄漏
|
|
299
337
|
const cur = this.__sessions.get(connId);
|
|
300
338
|
if (cur && cur.pc === pc) {
|
|
339
|
+
if (cur.__failedTimer) {
|
|
340
|
+
clearTimeout(cur.__failedTimer);
|
|
341
|
+
cur.__failedTimer = null;
|
|
342
|
+
}
|
|
301
343
|
this.__sessions.delete(connId);
|
|
302
344
|
}
|
|
303
345
|
await pc.close().catch(() => {});
|
|
@@ -404,9 +446,25 @@ export class WebRtcPeer {
|
|
|
404
446
|
remoteLog(this.__impl ? `${msg} rtc=${this.__impl}` : msg);
|
|
405
447
|
}
|
|
406
448
|
|
|
449
|
+
/** 淘汰最旧的 failed session(Map 迭代序 ≈ 创建时间序),用于 queue length 限制 */
|
|
450
|
+
__evictOldestFailed() {
|
|
451
|
+
for (const [connId, session] of this.__sessions) {
|
|
452
|
+
if (session.pc.connectionState === 'failed') {
|
|
453
|
+
this.__remoteLog(`rtc.session-evicted conn=${connId} sessions=${this.__sessions.size}`);
|
|
454
|
+
this.logger.info?.(`${this.__rtcTag} [${connId}] session evicted (limit ${MAX_SESSIONS}), closing`);
|
|
455
|
+
this.closeByConnId(connId).catch(() => {});
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
this.logger.warn?.(`${this.__rtcTag} session limit (${MAX_SESSIONS}) reached, no failed sessions to evict`);
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
|
|
407
463
|
__logDebug(message) {
|
|
408
464
|
if (typeof this.logger?.debug === 'function') {
|
|
409
465
|
this.logger.debug(`${this.__rtcTag} ${message}`);
|
|
410
466
|
}
|
|
411
467
|
}
|
|
412
468
|
}
|
|
469
|
+
|
|
470
|
+
export { FAILED_SESSION_TTL_MS, MAX_SESSIONS };
|