dsh-remote-control 1.0.0
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/LICENSE +21 -0
- package/cordis.patch.yml +21 -0
- package/dist/bundle/index.js +33956 -0
- package/dist/src/core/clock.d.ts +45 -0
- package/dist/src/core/clock.js +83 -0
- package/dist/src/core/clock.js.map +1 -0
- package/dist/src/core/keys.d.ts +98 -0
- package/dist/src/core/keys.js +176 -0
- package/dist/src/core/keys.js.map +1 -0
- package/dist/src/core/pairing-window.d.ts +95 -0
- package/dist/src/core/pairing-window.js +92 -0
- package/dist/src/core/pairing-window.js.map +1 -0
- package/dist/src/core/runtime.d.ts +104 -0
- package/dist/src/core/runtime.js +491 -0
- package/dist/src/core/runtime.js.map +1 -0
- package/dist/src/core/sleep-policy.d.ts +81 -0
- package/dist/src/core/sleep-policy.js +134 -0
- package/dist/src/core/sleep-policy.js.map +1 -0
- package/dist/src/core/window.d.ts +88 -0
- package/dist/src/core/window.js +185 -0
- package/dist/src/core/window.js.map +1 -0
- package/dist/src/index.d.ts +70 -0
- package/dist/src/index.js +882 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/platform/carrier-services.d.ts +96 -0
- package/dist/src/platform/carrier-services.js +982 -0
- package/dist/src/platform/carrier-services.js.map +1 -0
- package/dist/src/platform/guard.d.ts +60 -0
- package/dist/src/platform/guard.js +124 -0
- package/dist/src/platform/guard.js.map +1 -0
- package/dist/src/platform/mock-kernel.d.ts +95 -0
- package/dist/src/platform/mock-kernel.js +180 -0
- package/dist/src/platform/mock-kernel.js.map +1 -0
- package/dist/src/platform/qr.d.ts +28 -0
- package/dist/src/platform/qr.js +80 -0
- package/dist/src/platform/qr.js.map +1 -0
- package/dist/src/platform/sleep-posix.d.ts +17 -0
- package/dist/src/platform/sleep-posix.js +91 -0
- package/dist/src/platform/sleep-posix.js.map +1 -0
- package/dist/src/ports/index.d.ts +246 -0
- package/dist/src/ports/index.js +2 -0
- package/dist/src/ports/index.js.map +1 -0
- package/dist/src/shell/config.d.ts +56 -0
- package/dist/src/shell/config.js +212 -0
- package/dist/src/shell/config.js.map +1 -0
- package/dist/src/shell/pairing-text.d.ts +38 -0
- package/dist/src/shell/pairing-text.js +22 -0
- package/dist/src/shell/pairing-text.js.map +1 -0
- package/dist/src/shell/status.d.ts +39 -0
- package/dist/src/shell/status.js +119 -0
- package/dist/src/shell/status.js.map +1 -0
- package/dist/src/transport/relay.d.ts +107 -0
- package/dist/src/transport/relay.js +359 -0
- package/dist/src/transport/relay.js.map +1 -0
- package/dist/tests/carrier-events.test.d.ts +1 -0
- package/dist/tests/carrier-events.test.js +88 -0
- package/dist/tests/carrier-events.test.js.map +1 -0
- package/dist/tests/carrier-services.test.d.ts +1 -0
- package/dist/tests/carrier-services.test.js +562 -0
- package/dist/tests/carrier-services.test.js.map +1 -0
- package/dist/tests/carrier-vocabulary.test.d.ts +1 -0
- package/dist/tests/carrier-vocabulary.test.js +149 -0
- package/dist/tests/carrier-vocabulary.test.js.map +1 -0
- package/dist/tests/clock-probe-timers.test.d.ts +1 -0
- package/dist/tests/clock-probe-timers.test.js +73 -0
- package/dist/tests/clock-probe-timers.test.js.map +1 -0
- package/dist/tests/command-result.test.d.ts +1 -0
- package/dist/tests/command-result.test.js +185 -0
- package/dist/tests/command-result.test.js.map +1 -0
- package/dist/tests/config.test.d.ts +1 -0
- package/dist/tests/config.test.js +223 -0
- package/dist/tests/config.test.js.map +1 -0
- package/dist/tests/guard.test.d.ts +1 -0
- package/dist/tests/guard.test.js +175 -0
- package/dist/tests/guard.test.js.map +1 -0
- package/dist/tests/ids-and-keys.test.d.ts +1 -0
- package/dist/tests/ids-and-keys.test.js +211 -0
- package/dist/tests/ids-and-keys.test.js.map +1 -0
- package/dist/tests/pairing-text.test.d.ts +1 -0
- package/dist/tests/pairing-text.test.js +62 -0
- package/dist/tests/pairing-text.test.js.map +1 -0
- package/dist/tests/pairing-window.test.d.ts +1 -0
- package/dist/tests/pairing-window.test.js +165 -0
- package/dist/tests/pairing-window.test.js.map +1 -0
- package/dist/tests/relay-client.test.d.ts +1 -0
- package/dist/tests/relay-client.test.js +557 -0
- package/dist/tests/relay-client.test.js.map +1 -0
- package/dist/tests/runtime.test.d.ts +1 -0
- package/dist/tests/runtime.test.js +963 -0
- package/dist/tests/runtime.test.js.map +1 -0
- package/dist/tests/sleep.test.d.ts +1 -0
- package/dist/tests/sleep.test.js +264 -0
- package/dist/tests/sleep.test.js.map +1 -0
- package/dist/tests/status.test.d.ts +1 -0
- package/dist/tests/status.test.js +238 -0
- package/dist/tests/status.test.js.map +1 -0
- package/dist/tests/window.test.d.ts +1 -0
- package/dist/tests/window.test.js +210 -0
- package/dist/tests/window.test.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* relay — 主机侧的中继客户端:鉴权、重连、配对通道密钥簿、数据面密封。
|
|
3
|
+
*
|
|
4
|
+
* 三条来自线上事故的硬要求(取证 docs/legacy-spec/relay-and-wireformat.md §2.3):
|
|
5
|
+
*
|
|
6
|
+
* 1. **`pair-ready.ttlMs` 是服务端权威 TTL**,必须据此改写本地过期时间。
|
|
7
|
+
* 忘了这一步 → 主机提前清掉 PSK → `peer-joined` 找不到密钥 → 静默丢 peer
|
|
8
|
+
* → 手机端表现为"配对成功但列表永远空白"。
|
|
9
|
+
* 2. **`peer-joined` 里的 `pairingToken` 是唯一合法的取密钥依据**。
|
|
10
|
+
* 多张码同时挂着时按"手机实际用的那张"取 PSK;取不到就拒绝这条配对,
|
|
11
|
+
* 绝不退化成"取最新的一个"(那正是当初的 bug)。
|
|
12
|
+
* 3. **中继重启(generation 变化)后旧配对全部作废**:PSK 是主机在内存里发的,
|
|
13
|
+
* 中继的会话表一清空,这些 convId 就再也没人路由了。
|
|
14
|
+
*
|
|
15
|
+
* 方向约定:主机用 `h2c` 密钥**加密下行**、用 `c2h` 密钥**解密上行**;
|
|
16
|
+
* 与小程序侧正好互为镜像,两把密钥由同一 PSK + convId 派生(冻结项 B5)。
|
|
17
|
+
*/
|
|
18
|
+
import { WebSocket } from 'ws';
|
|
19
|
+
import { randomBytes } from 'node:crypto';
|
|
20
|
+
import { base64Text, open, parseCmdPayload, parseRelayFrameText, seal, } from 'dsh-remote-wire';
|
|
21
|
+
import { ConversationBook, DEFAULT_PRUNE_POLICY } from '../core/keys.js';
|
|
22
|
+
const BACKOFF_MIN_MS = 1000;
|
|
23
|
+
const BACKOFF_MAX_MS = 30_000;
|
|
24
|
+
const HANDSHAKE_TIMEOUT_MS = 10_000;
|
|
25
|
+
/** 已声明作废的 convId 记住多少条:远超一次会话里可能出现的配对通道数,又不会无界增长。 */
|
|
26
|
+
const VOIDED_MAX = 256;
|
|
27
|
+
export class RelayClient {
|
|
28
|
+
options;
|
|
29
|
+
conversations = new ConversationBook();
|
|
30
|
+
socket;
|
|
31
|
+
generation = 0;
|
|
32
|
+
attempts = 0;
|
|
33
|
+
stopped = false;
|
|
34
|
+
reconnectTimer;
|
|
35
|
+
handshakeTimer;
|
|
36
|
+
/** 连续解不开的会话:解不开说明这端已经没有对应密钥,留着它只会让手机永远转圈。 */
|
|
37
|
+
undecryptable = new Map();
|
|
38
|
+
/** 已经向中继声明过的 convId(防重复 `session-leave`);有上界,见 {@link VOIDED_MAX}。 */
|
|
39
|
+
voided = new Set();
|
|
40
|
+
constructor(options) {
|
|
41
|
+
this.options = options;
|
|
42
|
+
}
|
|
43
|
+
/** 已配对的会话数(`status.json` 与"有没有人能收"的判据都读它)。 */
|
|
44
|
+
get conversationCount() {
|
|
45
|
+
return this.conversations.size;
|
|
46
|
+
}
|
|
47
|
+
connect() {
|
|
48
|
+
if (this.stopped)
|
|
49
|
+
return;
|
|
50
|
+
this.closeSocket();
|
|
51
|
+
this.options.log('relay connecting', { url: this.options.url });
|
|
52
|
+
let socket;
|
|
53
|
+
try {
|
|
54
|
+
socket = new WebSocket(this.options.url);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
this.scheduleReconnect(`socket 创建失败:${messageOf(error)}`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
this.socket = socket;
|
|
61
|
+
this.emitState('connecting');
|
|
62
|
+
const timeout = this.options.clock.setTimeout(() => {
|
|
63
|
+
if (socket.readyState !== WebSocket.OPEN) {
|
|
64
|
+
this.options.log('relay handshake timeout');
|
|
65
|
+
socket.terminate();
|
|
66
|
+
this.scheduleReconnect('握手超时');
|
|
67
|
+
}
|
|
68
|
+
}, this.options.handshakeTimeoutMs ?? HANDSHAKE_TIMEOUT_MS);
|
|
69
|
+
this.handshakeTimer = timeout;
|
|
70
|
+
socket.on('open', () => {
|
|
71
|
+
this.options.clock.clearTimeout(timeout);
|
|
72
|
+
this.attempts = 0;
|
|
73
|
+
// D5:主机与客户端共用一条注册帧。PSK 从不上网(D1),这里只有 host token。
|
|
74
|
+
this.raw({
|
|
75
|
+
t: 'hello',
|
|
76
|
+
role: 'host',
|
|
77
|
+
protocol: 1,
|
|
78
|
+
token: this.options.token,
|
|
79
|
+
hostId: this.options.hostId,
|
|
80
|
+
label: this.options.label,
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
socket.on('message', (raw) => this.onFrame(String(raw)));
|
|
84
|
+
socket.on('close', (code, reason) => {
|
|
85
|
+
this.options.clock.clearTimeout(timeout);
|
|
86
|
+
this.emitState('offline', `连接关闭 ${code} ${reason ? reason.toString() : ''}`.trim());
|
|
87
|
+
this.options.log('relay closed', { code });
|
|
88
|
+
this.scheduleReconnect(`closed ${code}`);
|
|
89
|
+
});
|
|
90
|
+
socket.on('error', (error) => {
|
|
91
|
+
this.options.log('relay socket error', { message: messageOf(error) });
|
|
92
|
+
this.emitState('offline', messageOf(error));
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
stop() {
|
|
96
|
+
this.stopped = true;
|
|
97
|
+
if (this.reconnectTimer !== undefined)
|
|
98
|
+
this.options.clock.clearTimeout(this.reconnectTimer);
|
|
99
|
+
this.closeSocket();
|
|
100
|
+
this.emitState('offline', 'stopped');
|
|
101
|
+
}
|
|
102
|
+
/** 发布一张新的配对码(PSK 留在本地,中继只拿到 6 位码)。
|
|
103
|
+
*
|
|
104
|
+
* 返回 false = socket 不在 OPEN 状态、这条 `pair-begin` **根本没上过网**。
|
|
105
|
+
* 调用方不能把这张码记成"有效",否则 status.json 会展示一张中继没见过的码,
|
|
106
|
+
* 而手机扫它必然失败(取证 docs/legacy-spec/host-plugin-runtime.md §6.2)。
|
|
107
|
+
*/
|
|
108
|
+
publishPairing(slot) {
|
|
109
|
+
return this.raw({ t: 'pair-begin', pairingToken: slot.token });
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 按策略剪掉"再也不会被用"的配对通道(一天没收发,或超出条数上界)。
|
|
113
|
+
*
|
|
114
|
+
* 必须**同时**告诉中继与上层:只删本地内存的话,中继仍会把手机的密文转过来,
|
|
115
|
+
* 而手机看到的是一个不再有人应答的对端——正是复核 R1 那个静默黑洞的另一个入口。
|
|
116
|
+
* 所以这里走 `voidConversation`(发 `session-leave` + 回调 `onConversationGone`)。
|
|
117
|
+
*/
|
|
118
|
+
pruneConversations(now = this.options.clock.now()) {
|
|
119
|
+
const dropped = this.conversations.pruneStale(now, this.options.prunePolicy ?? DEFAULT_PRUNE_POLICY);
|
|
120
|
+
for (const id of dropped)
|
|
121
|
+
this.voidConversation(id);
|
|
122
|
+
if (dropped.length > 0)
|
|
123
|
+
this.options.log('pruned stale conversations', { count: dropped.length, remaining: this.conversations.size });
|
|
124
|
+
return dropped;
|
|
125
|
+
}
|
|
126
|
+
/** 向一条配对会话发一条载荷;返回 false = 这条会话已经没了或没人接。 */
|
|
127
|
+
send(conversationId, payload) {
|
|
128
|
+
const conversation = this.conversations.get(conversationId);
|
|
129
|
+
if (!conversation)
|
|
130
|
+
return false;
|
|
131
|
+
const record = seal(conversation.kH2C, payload);
|
|
132
|
+
conversation.lastActivityAt = this.options.clock.now();
|
|
133
|
+
return this.raw({ t: 'enc', sessionId: conversationId, seq: ++conversation.seqHost, ciphertext: record.ciphertext });
|
|
134
|
+
}
|
|
135
|
+
broadcast(payload) {
|
|
136
|
+
let sent = 0;
|
|
137
|
+
for (const id of this.conversations.ids()) {
|
|
138
|
+
if (this.send(id, payload))
|
|
139
|
+
sent += 1;
|
|
140
|
+
}
|
|
141
|
+
return sent;
|
|
142
|
+
}
|
|
143
|
+
hasPeer(conversationId) {
|
|
144
|
+
return this.conversations.has(conversationId);
|
|
145
|
+
}
|
|
146
|
+
conversationIds() {
|
|
147
|
+
return this.conversations.ids();
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 作废一条配对通道:本地删密钥 + 告诉中继"这条别再转给我了" + 通知上层。
|
|
151
|
+
*
|
|
152
|
+
* **本端没有记录时同样要发 `session-leave`**(复核 R1①)。这是"主机重启之后"的出口:
|
|
153
|
+
* PSK 随进程没了,中继却还留着会话并继续把手机的密文转过来。若这里因为
|
|
154
|
+
* `!has(id)` 直接 return(第一版就是这么写的),中继永远不知道该删这条会话,
|
|
155
|
+
* 手机也就永远撞不上 `unknown_session` → 表现是"发什么都没反应、也不提示重新配对",
|
|
156
|
+
* 而这是整条链路上唯一能让手机脱身的机制。
|
|
157
|
+
*
|
|
158
|
+
* 幂等:同一条通道只声明一次(`voided` 有上界,避免长会话里堆内存)。
|
|
159
|
+
* 重新配对(`peer-joined`)会清掉这条记录,所以下次作废仍能声明。
|
|
160
|
+
*/
|
|
161
|
+
voidConversation(conversationId) {
|
|
162
|
+
if (this.voided.has(conversationId))
|
|
163
|
+
return;
|
|
164
|
+
if (this.voided.size >= VOIDED_MAX) {
|
|
165
|
+
const oldest = this.voided.values().next().value;
|
|
166
|
+
if (oldest !== undefined)
|
|
167
|
+
this.voided.delete(oldest);
|
|
168
|
+
}
|
|
169
|
+
this.voided.add(conversationId);
|
|
170
|
+
this.undecryptable.delete(conversationId);
|
|
171
|
+
this.conversations.close(conversationId);
|
|
172
|
+
this.raw({ t: 'session-leave', sessionId: conversationId });
|
|
173
|
+
this.options.onConversationGone(conversationId);
|
|
174
|
+
}
|
|
175
|
+
raw(frame) {
|
|
176
|
+
if (!this.socket || this.socket.readyState !== WebSocket.OPEN)
|
|
177
|
+
return false;
|
|
178
|
+
try {
|
|
179
|
+
this.socket.send(JSON.stringify(frame));
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
this.options.log('relay send failed', { message: messageOf(error) });
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
onFrame(text) {
|
|
188
|
+
const frame = parseRelayFrameText(text);
|
|
189
|
+
if (!frame) {
|
|
190
|
+
// 只记形状不记内容:坏帧的原文可能是半条配对码,日志里不许出现凭据与业务明文。
|
|
191
|
+
this.options.log('relay frame rejected', { bytes: text.length });
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
switch (frame.t) {
|
|
195
|
+
case 'hello-ok': {
|
|
196
|
+
this.generation += 1;
|
|
197
|
+
// 中继重启过:它手里的 convId 全没了,本地留着也只会解不开。
|
|
198
|
+
const dropped = this.conversations.closeBefore(this.generation);
|
|
199
|
+
for (const id of dropped)
|
|
200
|
+
this.options.onConversationGone(id);
|
|
201
|
+
this.emitState('online');
|
|
202
|
+
this.raw({ t: 'resync', sessionIds: this.conversations.ids() });
|
|
203
|
+
this.options.log('relay online', { generation: this.generation, conversations: this.conversations.size });
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
case 'pair-ready':
|
|
207
|
+
this.options.onPairReady(frame.pairingToken, frame.ttlMs);
|
|
208
|
+
return;
|
|
209
|
+
case 'peer-joined': {
|
|
210
|
+
// 只有带 pairingToken 的那一条才是"新客户端加入";重连通知没有 token(客户端的 id 填在 clientId 位)。
|
|
211
|
+
if (!frame.pairingToken)
|
|
212
|
+
return;
|
|
213
|
+
const slot = this.options.lookupPairingSlot(frame.pairingToken);
|
|
214
|
+
if (!slot) {
|
|
215
|
+
this.options.log('peer-joined without a known pairing token', { sessionId: frame.sessionId });
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
this.conversations.open({
|
|
219
|
+
id: frame.sessionId,
|
|
220
|
+
psk: slot.psk,
|
|
221
|
+
generation: this.generation,
|
|
222
|
+
now: this.options.clock.now(),
|
|
223
|
+
});
|
|
224
|
+
// 这条通道现在又有密钥了:清掉"已声明作废"的记号,否则下次真要作废时发不出去。
|
|
225
|
+
this.voided.delete(frame.sessionId);
|
|
226
|
+
this.raw({ t: 'resync', sessionIds: this.conversations.ids() });
|
|
227
|
+
this.emitState('online');
|
|
228
|
+
this.options.onPeerJoined(frame.sessionId, frame.pairingToken);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
case 'peer-left':
|
|
232
|
+
// 发给主机的这一帧含义是"某个客户端走了",会话**必须留着**(D3):
|
|
233
|
+
// 手机回前台时用同一个 convId 回来,成员表还在,路由就能重建。
|
|
234
|
+
// 反过来,发给客户端的 peer-left 只有一个合法触发(主机离开),
|
|
235
|
+
// 那由中继侧保证,本端不猜。
|
|
236
|
+
this.options.onClientLeft(frame.sessionId, frame.clientId);
|
|
237
|
+
this.emitState('online');
|
|
238
|
+
return;
|
|
239
|
+
case 'paired':
|
|
240
|
+
return;
|
|
241
|
+
case 'pair-fail':
|
|
242
|
+
// 第一版这个 case 根本没有:手机扫码后中继回了"这张码已用过/已过期",
|
|
243
|
+
// 主机一侧完全无痕,status.json 也看不出配对为什么没成。
|
|
244
|
+
// 清槽是安全的:这张码的 PSK 已经不可能再被用上了。
|
|
245
|
+
this.options.log('pair failed', { reason: frame.reason });
|
|
246
|
+
this.options.onPairFail?.(frame.reason);
|
|
247
|
+
return;
|
|
248
|
+
case 'error':
|
|
249
|
+
// 中继只会把错误码发给"该对它负责"的那一端;本端把它记进状态,
|
|
250
|
+
// 会话清理交给 enc 路径(那里才知道具体是哪条通道)。
|
|
251
|
+
this.options.log('relay error frame', {
|
|
252
|
+
code: frame.code,
|
|
253
|
+
...(frame.message === undefined ? {} : { message: frame.message }),
|
|
254
|
+
});
|
|
255
|
+
return;
|
|
256
|
+
case 'pong':
|
|
257
|
+
return;
|
|
258
|
+
case 'enc':
|
|
259
|
+
case 'enc-batch':
|
|
260
|
+
this.onEncrypted(frame);
|
|
261
|
+
return;
|
|
262
|
+
default:
|
|
263
|
+
// F1 的纪律:新帧名必须被静默忽略、不许破坏状态,但**必须留痕**。
|
|
264
|
+
// 没有这一条的话,中继侧加一个名字、主机侧永远是哑的,只能靠猜。
|
|
265
|
+
this.options.log('relay frame ignored (no handler)', { t: frame.t ?? '?' });
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
onEncrypted(frame) {
|
|
270
|
+
const conversation = this.conversations.get(frame.sessionId);
|
|
271
|
+
if (!conversation) {
|
|
272
|
+
// 本端已经没有这把钥匙:留着通道只会让手机对着一个听不见的对端说话。
|
|
273
|
+
this.voidConversation(frame.sessionId);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const records = frame.items ?? (frame.ciphertext === undefined ? [] : [{ ciphertext: frame.ciphertext }]);
|
|
277
|
+
let decryptedAny = false;
|
|
278
|
+
for (const record of records) {
|
|
279
|
+
if (!base64Text.safeParse(record.ciphertext).success)
|
|
280
|
+
continue;
|
|
281
|
+
const decrypted = open(conversation.kC2H, { ciphertext: record.ciphertext });
|
|
282
|
+
if (!decrypted) {
|
|
283
|
+
// 解不开 = 这端已经没有对应密钥,交给"两次就作废"那条出口。
|
|
284
|
+
this.noteUndecryptable(frame.sessionId);
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
// MAC 过了不等于形状对。`onCommand` 下游是 `switch (cmd.t)` + 真实内核调用,
|
|
288
|
+
// 把未校验的对象直接递下去 = 让手机用一条密文决定宿主被怎么调用。
|
|
289
|
+
const cmd = parseCmdPayload(decrypted);
|
|
290
|
+
if (!cmd) {
|
|
291
|
+
const name = typeof decrypted.t === 'string' ? String(decrypted.t) : '?';
|
|
292
|
+
this.options.log('inbound payload failed cmd schema', { sessionId: frame.sessionId, t: name });
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
decryptedAny = true;
|
|
296
|
+
this.options.onCommand(frame.sessionId, cmd, frame.clientId);
|
|
297
|
+
}
|
|
298
|
+
// 有实际收发就不算"闲置":剪枝看的是最后活动时刻,不是创建时刻。
|
|
299
|
+
if (decryptedAny)
|
|
300
|
+
conversation.lastActivityAt = this.options.clock.now();
|
|
301
|
+
if (decryptedAny)
|
|
302
|
+
this.undecryptable.delete(frame.sessionId);
|
|
303
|
+
}
|
|
304
|
+
/** 两次解不开就作废(与手机端"两帧解不开就丢配对"对称,避免一端永久静默)。 */
|
|
305
|
+
noteUndecryptable(conversationId) {
|
|
306
|
+
const count = (this.undecryptable.get(conversationId) ?? 0) + 1;
|
|
307
|
+
this.undecryptable.set(conversationId, count);
|
|
308
|
+
this.options.log('inbound frame could not be decrypted', { conversationId, count });
|
|
309
|
+
if (count >= 2)
|
|
310
|
+
this.voidConversation(conversationId);
|
|
311
|
+
}
|
|
312
|
+
scheduleReconnect(problem) {
|
|
313
|
+
if (this.stopped)
|
|
314
|
+
return;
|
|
315
|
+
this.emitState('offline', problem);
|
|
316
|
+
const base = Math.min(BACKOFF_MAX_MS, BACKOFF_MIN_MS * 2 ** this.attempts);
|
|
317
|
+
this.attempts += 1;
|
|
318
|
+
const delay = base + Math.floor((randomBytes(2).readUInt16BE(0) / 65536) * 500);
|
|
319
|
+
this.reconnectTimer = this.options.clock.setTimeout(() => this.connect(), delay);
|
|
320
|
+
}
|
|
321
|
+
closeSocket() {
|
|
322
|
+
const socket = this.socket;
|
|
323
|
+
this.socket = undefined;
|
|
324
|
+
if (this.handshakeTimer !== undefined) {
|
|
325
|
+
this.options.clock.clearTimeout(this.handshakeTimer);
|
|
326
|
+
this.handshakeTimer = undefined;
|
|
327
|
+
}
|
|
328
|
+
if (!socket)
|
|
329
|
+
return;
|
|
330
|
+
socket.removeAllListeners();
|
|
331
|
+
// 摘完监听器必须补一个 error 兜底,**不能只靠下面的 try/catch**。
|
|
332
|
+
// 原因:socket 还在 CONNECTING 时 ws 的 close() 会走 abortHandshake,那条路径是
|
|
333
|
+
// `process.nextTick(emitErrorAndClose, ...)` —— 错误是**异步**发出来的,
|
|
334
|
+
// 同步的 try/catch 根本拦不到;而此时 removeAllListeners() 已经把 error 监听器
|
|
335
|
+
// 一起摘了,于是它无人接手、升级成 uncaughtException 把进程带崩。
|
|
336
|
+
// 表现就是 e2e 偶发红:'WebSocket was closed before the connection was established'
|
|
337
|
+
// (只有在"连接还没建好就停机"这个窗口里才触发,所以时红时绿)。
|
|
338
|
+
socket.on('error', () => { });
|
|
339
|
+
try {
|
|
340
|
+
socket.close();
|
|
341
|
+
}
|
|
342
|
+
catch {
|
|
343
|
+
/* 已经关了 */
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
emitState(relay, problem) {
|
|
347
|
+
this.options.onState({
|
|
348
|
+
relay,
|
|
349
|
+
...(problem === undefined ? {} : { problem }),
|
|
350
|
+
clients: this.conversations.size,
|
|
351
|
+
conversations: this.conversations.size,
|
|
352
|
+
generation: this.generation,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function messageOf(error) {
|
|
357
|
+
return String(error?.message ?? error);
|
|
358
|
+
}
|
|
359
|
+
//# sourceMappingURL=relay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relay.js","sourceRoot":"","sources":["../../../src/transport/relay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAA;AAC9B,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAGL,UAAU,EACV,IAAI,EACJ,eAAe,EACf,mBAAmB,EACnB,IAAI,GACL,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAmC,MAAM,iBAAiB,CAAA;AAiDzG,MAAM,cAAc,GAAG,IAAI,CAAA;AAC3B,MAAM,cAAc,GAAG,MAAM,CAAA;AAC7B,MAAM,oBAAoB,GAAG,MAAM,CAAA;AACnC,qDAAqD;AACrD,MAAM,UAAU,GAAG,GAAG,CAAA;AAEtB,MAAM,OAAO,WAAW;IAaO;IAZpB,aAAa,GAAG,IAAI,gBAAgB,EAAE,CAAA;IACvC,MAAM,CAAuB;IAC7B,UAAU,GAAG,CAAC,CAAA;IACd,QAAQ,GAAG,CAAC,CAAA;IACZ,OAAO,GAAG,KAAK,CAAA;IACf,cAAc,CAAS;IACvB,cAAc,CAAS;IAC/B,6CAA6C;IAC5B,aAAa,GAAG,IAAI,GAAG,EAAkB,CAAA;IAC1D,sEAAsE;IACrD,MAAM,GAAG,IAAI,GAAG,EAAU,CAAA;IAE3C,YAA6B,OAA2B;QAA3B,YAAO,GAAP,OAAO,CAAoB;IAAG,CAAC;IAE5D,8CAA8C;IAC9C,IAAI,iBAAiB;QACnB,OAAO,IAAI,CAAC,aAAa,CAAC,IAAI,CAAA;IAChC,CAAC;IAED,OAAO;QACL,IAAI,IAAI,CAAC,OAAO;YAAE,OAAM;QACxB,IAAI,CAAC,WAAW,EAAE,CAAA;QAClB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;QAC/D,IAAI,MAAiB,CAAA;QACrB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAC1C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,iBAAiB,CAAC,eAAe,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;YACzD,OAAM;QACR,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QACpB,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,CAAA;QAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE;YACjD,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;gBACzC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAA;gBAC3C,MAAM,CAAC,SAAS,EAAE,CAAA;gBAClB,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAA;YAChC,CAAC;QACH,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,kBAAkB,IAAI,oBAAoB,CAAC,CAAA;QAC3D,IAAI,CAAC,cAAc,GAAG,OAAO,CAAA;QAE7B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;YACrB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,CAAA;YACxC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAA;YACjB,iDAAiD;YACjD,IAAI,CAAC,GAAG,CAAC;gBACP,CAAC,EAAE,OAAO;gBACV,IAAI,EAAE,MAAM;gBACZ,QAAQ,EAAE,CAAC;gBACX,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK;gBACzB,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM;gBAC3B,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK;aAC1B,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;QACF,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;QACxD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;YAClC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,CAAA;YACxC,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,QAAQ,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,CAAA;YACnF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;YAC1C,IAAI,CAAC,iBAAiB,CAAC,UAAU,IAAI,EAAE,CAAC,CAAA;QAC1C,CAAC,CAAC,CAAA;QACF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YAC3B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,EAAE,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;YACrE,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,CAAA;QAC7C,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,IAAI;QACF,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;QACnB,IAAI,IAAI,CAAC,cAAc,KAAK,SAAS;YAAE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;QAC3F,IAAI,CAAC,WAAW,EAAE,CAAA;QAClB,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;IACtC,CAAC;IAED;;;;;OAKG;IACH,cAAc,CAAC,IAAc;QAC3B,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,YAAY,EAAE,YAAY,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAA;IAChE,CAAC;IAED;;;;;;OAMG;IACH,kBAAkB,CAAC,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE;QAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,WAAW,IAAI,oBAAoB,CAAC,CAAA;QACpG,KAAK,MAAM,EAAE,IAAI,OAAO;YAAE,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAA;QACnD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YACpB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,4BAA4B,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,CAAA;QAC/G,OAAO,OAAO,CAAA;IAChB,CAAC;IAED,4CAA4C;IAC5C,IAAI,CAAC,cAAsB,EAAE,OAAkB;QAC7C,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,cAAc,CAAC,CAAA;QAC3D,IAAI,CAAC,YAAY;YAAE,OAAO,KAAK,CAAA;QAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QAC/C,YAAY,CAAC,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,CAAA;QACtD,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,GAAG,EAAE,EAAE,YAAY,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC,CAAA;IACtH,CAAC;IAED,SAAS,CAAC,OAAkB;QAC1B,IAAI,IAAI,GAAG,CAAC,CAAA;QACZ,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,EAAE,CAAC;YAC1C,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,OAAO,CAAC;gBAAE,IAAI,IAAI,CAAC,CAAA;QACvC,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,OAAO,CAAC,cAAsB;QAC5B,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,cAAc,CAAC,CAAA;IAC/C,CAAC;IAED,eAAe;QACb,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,CAAA;IACjC,CAAC;IAED;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,cAAsB;QACrC,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC;YAAE,OAAM;QAC3C,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,UAAU,EAAE,CAAC;YACnC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAA;YAChD,IAAI,MAAM,KAAK,SAAS;gBAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACtD,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAA;QAC/B,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,cAAc,CAAC,CAAA;QACzC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,cAAc,CAAC,CAAA;QACxC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,eAAe,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC,CAAA;QAC3D,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,cAAc,CAAC,CAAA;IACjD,CAAC;IAEO,GAAG,CAAC,KAA8B;QACxC,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI;YAAE,OAAO,KAAK,CAAA;QAC3E,IAAI,CAAC;YACH,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAA;YACvC,OAAO,IAAI,CAAA;QACb,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,EAAE,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;YACpE,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC;IAEO,OAAO,CAAC,IAAY;QAC1B,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAA;QACvC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,yCAAyC;YACzC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAA;YAChE,OAAM;QACR,CAAC;QACD,QAAQ,KAAK,CAAC,CAAC,EAAE,CAAC;YAChB,KAAK,UAAU,CAAC,CAAC,CAAC;gBAChB,IAAI,CAAC,UAAU,IAAI,CAAC,CAAA;gBACpB,oCAAoC;gBACpC,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;gBAC/D,KAAK,MAAM,EAAE,IAAI,OAAO;oBAAE,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAA;gBAC7D,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;gBACxB,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;gBAC/D,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,CAAA;gBACzG,OAAM;YACR,CAAC;YACD,KAAK,YAAY;gBACf,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;gBACzD,OAAM;YACR,KAAK,aAAa,CAAC,CAAC,CAAC;gBACnB,uEAAuE;gBACvE,IAAI,CAAC,KAAK,CAAC,YAAY;oBAAE,OAAM;gBAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,KAAK,CAAC,YAAY,CAAC,CAAA;gBAC/D,IAAI,CAAC,IAAI,EAAE,CAAC;oBACV,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,2CAA2C,EAAE,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,CAAA;oBAC7F,OAAM;gBACR,CAAC;gBACD,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC;oBACtB,EAAE,EAAE,KAAK,CAAC,SAAS;oBACnB,GAAG,EAAE,IAAI,CAAC,GAAG;oBACb,UAAU,EAAE,IAAI,CAAC,UAAU;oBAC3B,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE;iBAC9B,CAAC,CAAA;gBACF,0CAA0C;gBAC1C,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;gBACnC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;gBAC/D,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;gBACxB,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,YAAY,CAAC,CAAA;gBAC9D,OAAM;YACR,CAAC;YACD,KAAK,WAAW;gBACd,uCAAuC;gBACvC,qCAAqC;gBACrC,uCAAuC;gBACvC,gBAAgB;gBAChB,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;gBAC1D,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;gBACxB,OAAM;YACR,KAAK,QAAQ;gBACX,OAAM;YACR,KAAK,WAAW;gBACd,yCAAyC;gBACzC,oCAAoC;gBACpC,8BAA8B;gBAC9B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAA;gBACzD,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;gBACvC,OAAM;YACR,KAAK,OAAO;gBACV,kCAAkC;gBAClC,+BAA+B;gBAC/B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE;oBACpC,IAAI,EAAE,KAAK,CAAC,IAAI;oBAChB,GAAG,CAAC,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;iBACnE,CAAC,CAAA;gBACF,OAAM;YACR,KAAK,MAAM;gBACT,OAAM;YACR,KAAK,KAAK,CAAC;YACX,KAAK,WAAW;gBACd,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;gBACvB,OAAM;YACR;gBACE,sCAAsC;gBACtC,kCAAkC;gBAClC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,kCAAkC,EAAE,EAAE,CAAC,EAAG,KAAwB,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC,CAAA;gBAC/F,OAAM;QACV,CAAC;IACH,CAAC;IAEO,WAAW,CAAC,KAAwF;QAC1G,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;QAC5D,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,oCAAoC;YACpC,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;YACtC,OAAM;QACR,CAAC;QACD,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAA;QACzG,IAAI,YAAY,GAAG,KAAK,CAAA;QACxB,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,OAAO;gBAAE,SAAQ;YAC9D,MAAM,SAAS,GAAG,IAAI,CAAU,YAAY,CAAC,IAAI,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC,CAAA;YACrF,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,kCAAkC;gBAClC,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;gBACvC,SAAQ;YACV,CAAC;YACD,0DAA0D;YAC1D,oCAAoC;YACpC,MAAM,GAAG,GAAG,eAAe,CAAC,SAAS,CAAC,CAAA;YACtC,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,MAAM,IAAI,GACR,OAAQ,SAA6B,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAE,SAA6B,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;gBACvG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,mCAAmC,EAAE,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAA;gBAC9F,SAAQ;YACV,CAAC;YACD,YAAY,GAAG,IAAI,CAAA;YACnB,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,SAAS,EAAE,GAAG,EAAG,KAA+B,CAAC,QAAQ,CAAC,CAAA;QACzF,CAAC;QACD,mCAAmC;QACnC,IAAI,YAAY;YAAE,YAAY,CAAC,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,CAAA;QACxE,IAAI,YAAY;YAAE,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;IAC9D,CAAC;IAED,4CAA4C;IACpC,iBAAiB,CAAC,cAAsB;QAC9C,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAA;QAC/D,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,cAAc,EAAE,KAAK,CAAC,CAAA;QAC7C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,sCAAsC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CAAA;QACnF,IAAI,KAAK,IAAI,CAAC;YAAE,IAAI,CAAC,gBAAgB,CAAC,cAAc,CAAC,CAAA;IACvD,CAAC;IAEO,iBAAiB,CAAC,OAAe;QACvC,IAAI,IAAI,CAAC,OAAO;YAAE,OAAM;QACxB,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;QAClC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,cAAc,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAA;QAC1E,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAA;QAClB,MAAM,KAAK,GAAG,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,CAAA;QAC/E,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,CAAA;IAClF,CAAC;IAEO,WAAW;QACjB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAA;QAC1B,IAAI,CAAC,MAAM,GAAG,SAAS,CAAA;QACvB,IAAI,IAAI,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;YACtC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;YACpD,IAAI,CAAC,cAAc,GAAG,SAAS,CAAA;QACjC,CAAC;QACD,IAAI,CAAC,MAAM;YAAE,OAAM;QACnB,MAAM,CAAC,kBAAkB,EAAE,CAAA;QAC3B,6CAA6C;QAC7C,iEAAiE;QACjE,+DAA+D;QAC/D,6DAA6D;QAC7D,4CAA4C;QAC5C,4EAA4E;QAC5E,mCAAmC;QACnC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;QAC5B,IAAI,CAAC;YACH,MAAM,CAAC,KAAK,EAAE,CAAA;QAChB,CAAC;QAAC,MAAM,CAAC;YACP,UAAU;QACZ,CAAC;IACH,CAAC;IAEO,SAAS,CAAC,KAA8B,EAAE,OAAgB;QAChE,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC;YACnB,KAAK;YACL,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;YAC7C,OAAO,EAAE,IAAI,CAAC,aAAa,CAAC,IAAI;YAChC,aAAa,EAAE,IAAI,CAAC,aAAa,CAAC,IAAI;YACtC,UAAU,EAAE,IAAI,CAAC,UAAU;SAC5B,CAAC,CAAA;IACJ,CAAC;CACF;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,OAAO,MAAM,CAAE,KAAe,EAAE,OAAO,IAAI,KAAK,CAAC,CAAA;AACnD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* carrier-events.test — 内核 `turn/end` 的映射(`platform/carrier-services.ts`)。
|
|
3
|
+
*
|
|
4
|
+
* 这一条不是"顺手补的测试",而是**真机取证抓出来的缺陷**:
|
|
5
|
+
* 本机 Harness 里跑真插件时,宿主在 prompt 装配阶段抛
|
|
6
|
+
* `prompt variable "{{model}}" has no value for this assembly (section "deployment:persona-prefix")`,
|
|
7
|
+
* 而插件把 `turn/end` 的 `data.reason` **整个丢掉**,只发 done + idle。结果:
|
|
8
|
+
* 手机上"一个字都没有的失败回合"与"成功的回合"长得一模一样,
|
|
9
|
+
* live-e2e 十五项断言全绿,实际一个字的回复都没有(取证 scripts/read-session-log.mjs 的 turn/end 行)。
|
|
10
|
+
*
|
|
11
|
+
* 所以这里守的是两件事:
|
|
12
|
+
* 1. **失败必须可见**(中文 + 原始原因),且不破坏 F9 的"done 永不丢、先 done 后 idle";
|
|
13
|
+
* 2. 成功路径**不许被顺带改坏**(不能凭空多出错误文本,也不能把 done 挪位)。
|
|
14
|
+
*/
|
|
15
|
+
import { test } from 'node:test';
|
|
16
|
+
import assert from 'node:assert/strict';
|
|
17
|
+
import { turnEndKernelEvents } from '../src/platform/carrier-services.js';
|
|
18
|
+
const deltas = (events) => events.filter((event) => event.kind === 'delta');
|
|
19
|
+
const runStates = (events) => events.filter((event) => event.kind === 'run-state');
|
|
20
|
+
test('成功回合:只有 done 一条 delta + 一条 idle,不凭空造错误文本', () => {
|
|
21
|
+
const events = turnEndKernelEvents({ sessionId: 'ses_1', seq: 7, data: { reason: { kind: 'completed' } } });
|
|
22
|
+
assert.equal(events.length, 2, `成功回合产出了 ${events.length} 条事件:多出来的每一条都会变成手机上的一行`);
|
|
23
|
+
const [delta] = deltas(events);
|
|
24
|
+
assert.equal(delta?.text, '', '成功回合的收尾 delta 必须是空串');
|
|
25
|
+
assert.equal(delta?.done, true, 'done 不能丢(F9:手机靠它停止转圈)');
|
|
26
|
+
assert.equal(delta?.messageId, 'turn_7', 'seq 要落进 messageId:同一条回合一串事件共用一个消息行');
|
|
27
|
+
const [state] = runStates(events);
|
|
28
|
+
assert.equal(state?.state, 'idle');
|
|
29
|
+
assert.equal(state?.detail, undefined, '成功回合不许带 detail');
|
|
30
|
+
assert.deepEqual(events.map((event) => event.kind), ['delta', 'run-state'], '顺序必须是先 done 再 idle:反过来手机的转圈会一直停在那里');
|
|
31
|
+
});
|
|
32
|
+
test('失败回合:中文 + 原始内核原因都要出现在手机上(这就是当初看不见的那个东西)', () => {
|
|
33
|
+
const events = turnEndKernelEvents({
|
|
34
|
+
sessionId: 'ses_1',
|
|
35
|
+
seq: 8,
|
|
36
|
+
data: {
|
|
37
|
+
reason: {
|
|
38
|
+
kind: 'error',
|
|
39
|
+
error: { message: 'prompt variable "{{model}}" has no value for this assembly', code: 'UNKNOWN' },
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
const written = deltas(events);
|
|
44
|
+
assert.equal(written.length, 2, `失败回合只产出 ${written.length} 条 delta:错误文本没进 delta,手机上就什么都看不见`);
|
|
45
|
+
const [notice, tail] = written;
|
|
46
|
+
assert.match(notice?.text ?? '', /这一轮没有产出回复/, '必须是中文打底的句子,不是英文栈');
|
|
47
|
+
assert.match(notice?.text ?? '', /\{\{model\}\}/, '原始原因要照抄:只说"失败了"用户无从下手');
|
|
48
|
+
assert.match(notice?.text ?? '', /has no value/, '原因里的关键短句不能被我自己的加工吃掉');
|
|
49
|
+
assert.match(notice?.text ?? '', /UNKNOWN/, '错误码也带上,便于对照内核 session log');
|
|
50
|
+
assert.equal(notice?.role, 'system', '错误通知是系统口吻,不能伪装成助手回答');
|
|
51
|
+
assert.equal(notice?.done, false, '错误文本自己不是收尾帧');
|
|
52
|
+
assert.equal(tail?.done, true, 'done 仍然必须是最后一条(F9 不因这次改动而松动)');
|
|
53
|
+
assert.equal(tail?.text, '', '收尾 delta 仍是空串');
|
|
54
|
+
assert.deepEqual(written.map((event) => event.messageId), ['turn_8', 'turn_8'], '两条必须同一个 messageId:DeltaWindow 靠它把错误文本与 done 合进同一个消息行');
|
|
55
|
+
const [state] = runStates(events);
|
|
56
|
+
assert.equal(state?.state, 'idle');
|
|
57
|
+
assert.equal(state?.detail, 'turn-error', 'run_state 要带 detail:手机的状态行与排错入口都读它');
|
|
58
|
+
});
|
|
59
|
+
test('内核 message 又长又带换行时夹住长度并压成一行(一帧不能顶到中继上限,也不能刷爆手机屏幕)', () => {
|
|
60
|
+
const long = `Error: ${'x'.repeat(5_000)}\n at Object.<anonymous> (/Users/someone/secret-path/index.js:1:1)`;
|
|
61
|
+
const events = turnEndKernelEvents({
|
|
62
|
+
sessionId: 'ses_1',
|
|
63
|
+
seq: 1,
|
|
64
|
+
data: { reason: { kind: 'error', error: { message: long } } },
|
|
65
|
+
});
|
|
66
|
+
const [notice] = deltas(events);
|
|
67
|
+
const text = notice?.text ?? '';
|
|
68
|
+
assert.ok(text.length < 400, `错误文本 ${text.length} 字符:没夹长度`);
|
|
69
|
+
assert.equal(text.includes('\n'), false, '换行没压掉:手机上会顶出一段看不清的栈');
|
|
70
|
+
assert.match(text, /这一轮没有产出回复/, '夹长度之后中文主干仍在');
|
|
71
|
+
});
|
|
72
|
+
test('reason 缺字段时不许出现 undefined 字样:拿不到原因也要说一句人话', () => {
|
|
73
|
+
const inputs = [
|
|
74
|
+
{ reason: { kind: 'aborted' } }, // 用户中断:本来就没有回复,不该造出错误文本
|
|
75
|
+
{ reason: { kind: 'error', error: { code: 'E' } } }, // 有 code 没 message
|
|
76
|
+
{ reason: { kind: 'error' } }, // 什么都没有
|
|
77
|
+
{}, // reason 整个缺失
|
|
78
|
+
];
|
|
79
|
+
for (const data of inputs) {
|
|
80
|
+
const events = turnEndKernelEvents({ sessionId: 'ses_1', seq: 2, data });
|
|
81
|
+
const text = deltas(events)
|
|
82
|
+
.map((event) => event.text)
|
|
83
|
+
.join('');
|
|
84
|
+
assert.equal(text.includes('undefined'), false, `错误文本里出现了 undefined:${text}`);
|
|
85
|
+
assert.ok(deltas(events).some((event) => event.done === true), `无论 reason 长成什么样,done 都不能丢:${JSON.stringify(data)}`);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
//# sourceMappingURL=carrier-events.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"carrier-events.test.js","sourceRoot":"","sources":["../../tests/carrier-events.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|