virlen-remote 0.1.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/CHANGELOG.md +40 -0
- package/LICENSE +21 -0
- package/README.md +192 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol/answer.d.ts +48 -0
- package/dist/protocol/answer.d.ts.map +1 -0
- package/dist/protocol/answer.js +65 -0
- package/dist/protocol/answer.js.map +1 -0
- package/dist/protocol/api.d.ts +576 -0
- package/dist/protocol/api.d.ts.map +1 -0
- package/dist/protocol/api.js +38 -0
- package/dist/protocol/api.js.map +1 -0
- package/dist/protocol/endpoint.d.ts +76 -0
- package/dist/protocol/endpoint.d.ts.map +1 -0
- package/dist/protocol/endpoint.js +307 -0
- package/dist/protocol/endpoint.js.map +1 -0
- package/dist/protocol/errors.d.ts +32 -0
- package/dist/protocol/errors.d.ts.map +1 -0
- package/dist/protocol/errors.js +56 -0
- package/dist/protocol/errors.js.map +1 -0
- package/dist/protocol/frame.d.ts +51 -0
- package/dist/protocol/frame.d.ts.map +1 -0
- package/dist/protocol/frame.js +154 -0
- package/dist/protocol/frame.js.map +1 -0
- package/dist/protocol/hello.d.ts +73 -0
- package/dist/protocol/hello.d.ts.map +1 -0
- package/dist/protocol/hello.js +37 -0
- package/dist/protocol/hello.js.map +1 -0
- package/dist/protocol/host.d.ts +116 -0
- package/dist/protocol/host.d.ts.map +1 -0
- package/dist/protocol/host.js +52 -0
- package/dist/protocol/host.js.map +1 -0
- package/dist/protocol/identity.d.ts +107 -0
- package/dist/protocol/identity.d.ts.map +1 -0
- package/dist/protocol/identity.js +143 -0
- package/dist/protocol/identity.js.map +1 -0
- package/dist/protocol/ids.d.ts +2 -0
- package/dist/protocol/ids.d.ts.map +1 -0
- package/dist/protocol/ids.js +20 -0
- package/dist/protocol/ids.js.map +1 -0
- package/dist/protocol/pairing.d.ts +43 -0
- package/dist/protocol/pairing.d.ts.map +1 -0
- package/dist/protocol/pairing.js +78 -0
- package/dist/protocol/pairing.js.map +1 -0
- package/dist/testing/index.d.ts +6 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +5 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/mock-host.d.ts +66 -0
- package/dist/testing/mock-host.d.ts.map +1 -0
- package/dist/testing/mock-host.js +573 -0
- package/dist/testing/mock-host.js.map +1 -0
- package/dist/transport/broadcast.d.ts +26 -0
- package/dist/transport/broadcast.d.ts.map +1 -0
- package/dist/transport/broadcast.js +71 -0
- package/dist/transport/broadcast.js.map +1 -0
- package/dist/transport/ice.d.ts +130 -0
- package/dist/transport/ice.d.ts.map +1 -0
- package/dist/transport/ice.js +338 -0
- package/dist/transport/ice.js.map +1 -0
- package/dist/transport/memory.d.ts +52 -0
- package/dist/transport/memory.d.ts.map +1 -0
- package/dist/transport/memory.js +135 -0
- package/dist/transport/memory.js.map +1 -0
- package/dist/transport/rtc.d.ts +76 -0
- package/dist/transport/rtc.d.ts.map +1 -0
- package/dist/transport/rtc.js +310 -0
- package/dist/transport/rtc.js.map +1 -0
- package/dist/transport/signaling.d.ts +101 -0
- package/dist/transport/signaling.d.ts.map +1 -0
- package/dist/transport/signaling.js +235 -0
- package/dist/transport/signaling.js.map +1 -0
- package/dist/transport/types.d.ts +41 -0
- package/dist/transport/types.d.ts.map +1 -0
- package/dist/transport/types.js +11 -0
- package/dist/transport/types.js.map +1 -0
- package/package.json +69 -0
- package/src/index.ts +155 -0
- package/src/protocol/answer.ts +89 -0
- package/src/protocol/api.ts +517 -0
- package/src/protocol/endpoint.ts +405 -0
- package/src/protocol/errors.ts +90 -0
- package/src/protocol/frame.ts +196 -0
- package/src/protocol/hello.ts +112 -0
- package/src/protocol/host.ts +150 -0
- package/src/protocol/identity.ts +184 -0
- package/src/protocol/ids.ts +20 -0
- package/src/protocol/pairing.ts +95 -0
- package/src/testing/index.ts +5 -0
- package/src/testing/mock-host.ts +691 -0
- package/src/transport/broadcast.ts +81 -0
- package/src/transport/ice.ts +402 -0
- package/src/transport/memory.ts +164 -0
- package/src/transport/rtc.ts +344 -0
- package/src/transport/signaling.ts +317 -0
- package/src/transport/types.ts +50 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { Transport, TransportState } from '../transport/types.js';
|
|
2
|
+
export interface CallContext {
|
|
3
|
+
readonly requestId: string;
|
|
4
|
+
readonly method: string;
|
|
5
|
+
readonly origin: 'remote';
|
|
6
|
+
}
|
|
7
|
+
export interface EventContext {
|
|
8
|
+
readonly topic: string;
|
|
9
|
+
readonly origin: 'remote';
|
|
10
|
+
}
|
|
11
|
+
export type RpcHandler = (params: unknown, ctx: CallContext) => unknown | Promise<unknown>;
|
|
12
|
+
export type EventSubscriber = (payload: unknown, ctx: EventContext) => void;
|
|
13
|
+
export interface CallOptions {
|
|
14
|
+
timeoutMs?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface EndpointOptions {
|
|
17
|
+
transport: Transport;
|
|
18
|
+
/** 协议层默认超时;可随 hello 协商调整(§3.2)。 <=0 表示不超时。 */
|
|
19
|
+
defaultTimeoutMs?: number;
|
|
20
|
+
maxFramePayload?: number;
|
|
21
|
+
/** 幂等去重表容量(LRU)。 */
|
|
22
|
+
dedupeSize?: number;
|
|
23
|
+
/** 幂等去重表存活时间。 */
|
|
24
|
+
dedupeTtlMs?: number;
|
|
25
|
+
}
|
|
26
|
+
export declare class Endpoint {
|
|
27
|
+
private readonly transport;
|
|
28
|
+
private readonly defaultTimeoutMs;
|
|
29
|
+
private readonly maxFramePayload;
|
|
30
|
+
private readonly dedupeSize;
|
|
31
|
+
private readonly dedupeTtlMs;
|
|
32
|
+
private readonly handlers;
|
|
33
|
+
private readonly subscribers;
|
|
34
|
+
private readonly pending;
|
|
35
|
+
/** requestId → 上次结果(插入序即 LRU 淘汰序)。 */
|
|
36
|
+
private readonly dedupe;
|
|
37
|
+
private readonly reassembler;
|
|
38
|
+
private readonly detachers;
|
|
39
|
+
private msgId;
|
|
40
|
+
private lastState;
|
|
41
|
+
private disposed;
|
|
42
|
+
constructor(options: EndpointOptions);
|
|
43
|
+
/** 注册一个 handler;返回注销函数。 */
|
|
44
|
+
handle(method: string, fn: RpcHandler): () => void;
|
|
45
|
+
/** 订阅一个 topic;返回取消订阅函数。 */
|
|
46
|
+
subscribe(topic: string, fn: EventSubscriber): () => void;
|
|
47
|
+
/**
|
|
48
|
+
* 发起一次 RPC。链路非 open → **立即失败**(不发请求、不等待)。
|
|
49
|
+
* 长任务不要等它 resolve:见 §3.3,`host.session.send` 只回「投递确认」,过程靠事件推。
|
|
50
|
+
*/
|
|
51
|
+
call(method: string, params?: unknown, options?: CallOptions): Promise<unknown>;
|
|
52
|
+
/** 发出一个事件(fire-and-forget);返回是否发出。 */
|
|
53
|
+
emit(topic: string, payload?: unknown): boolean;
|
|
54
|
+
/** 发送一个心跳 ping(pong 由对端应答)。 */
|
|
55
|
+
ping(): boolean;
|
|
56
|
+
dispose(reason?: string): void;
|
|
57
|
+
get pendingCount(): number;
|
|
58
|
+
get transportState(): TransportState;
|
|
59
|
+
private onFrame;
|
|
60
|
+
private onCall;
|
|
61
|
+
private onResult;
|
|
62
|
+
private onEvent;
|
|
63
|
+
private onCtrl;
|
|
64
|
+
private onState;
|
|
65
|
+
/** 链路恢复:未决的 CALL 用同一 requestId 重发(对端靠去重表返回上次结果)。 */
|
|
66
|
+
private replayPending;
|
|
67
|
+
private finishCall;
|
|
68
|
+
private sendResult;
|
|
69
|
+
private sendCtrl;
|
|
70
|
+
private sendFrames;
|
|
71
|
+
private nextMsgId;
|
|
72
|
+
private clearPendingTimer;
|
|
73
|
+
private lookupDedupe;
|
|
74
|
+
private rememberDedupe;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=endpoint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint.d.ts","sourceRoot":"","sources":["../../src/protocol/endpoint.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Endpoint —— 协议层核心:把「四类接口」统一为 **2 个原语 × 2 个方向**
|
|
3
|
+
* (docs/phone-control-bridge.md §2.1)。
|
|
4
|
+
*
|
|
5
|
+
* ```
|
|
6
|
+
* handle(method, fn) 我实现的、供对方调用 ← 请求接口
|
|
7
|
+
* subscribe(topic, fn) 我订阅的、对方发来的事件 ← 事件接口
|
|
8
|
+
* call(method, params) 我发起的调用 ← 服务接口
|
|
9
|
+
* emit(topic, payload) 我发出的事件 ← 事件接口
|
|
10
|
+
* ```
|
|
11
|
+
* 四类接口是本形状的特例;各写一个模块会得到 4 份重复的序列化/超时/重连代码,且行为必然漂移。
|
|
12
|
+
*
|
|
13
|
+
* 内置可靠性(§3.2):
|
|
14
|
+
* - **分片**:载荷 > 12KB 自动分片,接收侧按 msgId 归组,不完整则整体丢弃;
|
|
15
|
+
* - **幂等**:收到的 CALL 按 `requestId` 去重(LRU + TTL),命中则**回上次结果而非重放**;
|
|
16
|
+
* - **重连重放**:链路恢复时,未决的 CALL 用**同一 requestId** 重发 → 对端去重,避免重复执行;
|
|
17
|
+
* - **超时分层**:链路非 open → 立即 `E_TRANSPORT`;协议层默认 10s(可随 hello 协商调整)。
|
|
18
|
+
*/
|
|
19
|
+
import { BridgeError, toBridgeError } from './errors.js';
|
|
20
|
+
import { DEFAULT_MAX_FRAME_PAYLOAD, FrameKind, Reassembler, encodeFrames, PROTOCOL_VERSION } from './frame.js';
|
|
21
|
+
import { newRequestId } from './ids.js';
|
|
22
|
+
const DEFAULT_TIMEOUT_MS = 10000;
|
|
23
|
+
const DEFAULT_DEDUPE_SIZE = 200;
|
|
24
|
+
const DEFAULT_DEDUPE_TTL_MS = 5 * 60000;
|
|
25
|
+
export class Endpoint {
|
|
26
|
+
constructor(options) {
|
|
27
|
+
this.handlers = new Map();
|
|
28
|
+
this.subscribers = new Map();
|
|
29
|
+
this.pending = new Map();
|
|
30
|
+
/** requestId → 上次结果(插入序即 LRU 淘汰序)。 */
|
|
31
|
+
this.dedupe = new Map();
|
|
32
|
+
this.reassembler = new Reassembler();
|
|
33
|
+
this.detachers = [];
|
|
34
|
+
this.msgId = 0;
|
|
35
|
+
this.disposed = false;
|
|
36
|
+
this.transport = options.transport;
|
|
37
|
+
this.defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
38
|
+
this.maxFramePayload = options.maxFramePayload ?? DEFAULT_MAX_FRAME_PAYLOAD;
|
|
39
|
+
this.dedupeSize = options.dedupeSize ?? DEFAULT_DEDUPE_SIZE;
|
|
40
|
+
this.dedupeTtlMs = options.dedupeTtlMs ?? DEFAULT_DEDUPE_TTL_MS;
|
|
41
|
+
this.lastState = options.transport.state;
|
|
42
|
+
this.detachers.push(this.transport.onMessage((bytes) => this.onFrame(bytes)));
|
|
43
|
+
this.detachers.push(this.transport.onStateChange((state) => this.onState(state)));
|
|
44
|
+
}
|
|
45
|
+
// ───────────────────────────── 我实现的、供对方调用 ─────────────────────────────
|
|
46
|
+
/** 注册一个 handler;返回注销函数。 */
|
|
47
|
+
handle(method, fn) {
|
|
48
|
+
this.handlers.set(method, fn);
|
|
49
|
+
return () => {
|
|
50
|
+
if (this.handlers.get(method) === fn)
|
|
51
|
+
this.handlers.delete(method);
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** 订阅一个 topic;返回取消订阅函数。 */
|
|
55
|
+
subscribe(topic, fn) {
|
|
56
|
+
let set = this.subscribers.get(topic);
|
|
57
|
+
if (!set) {
|
|
58
|
+
set = new Set();
|
|
59
|
+
this.subscribers.set(topic, set);
|
|
60
|
+
}
|
|
61
|
+
set.add(fn);
|
|
62
|
+
return () => {
|
|
63
|
+
set.delete(fn);
|
|
64
|
+
if (set.size === 0)
|
|
65
|
+
this.subscribers.delete(topic);
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// ───────────────────────────── 我发起的调用 / 我发出的事件 ─────────────────────────────
|
|
69
|
+
/**
|
|
70
|
+
* 发起一次 RPC。链路非 open → **立即失败**(不发请求、不等待)。
|
|
71
|
+
* 长任务不要等它 resolve:见 §3.3,`host.session.send` 只回「投递确认」,过程靠事件推。
|
|
72
|
+
*/
|
|
73
|
+
call(method, params, options) {
|
|
74
|
+
if (this.disposed) {
|
|
75
|
+
return Promise.reject(new BridgeError('E_INTERNAL', 'endpoint disposed'));
|
|
76
|
+
}
|
|
77
|
+
if (this.transport.state !== 'open') {
|
|
78
|
+
return Promise.reject(new BridgeError('E_TRANSPORT', `transport is ${this.transport.state}`));
|
|
79
|
+
}
|
|
80
|
+
const requestId = newRequestId();
|
|
81
|
+
const call = { requestId, method, params };
|
|
82
|
+
const frames = encodeFrames(FrameKind.CALL, this.nextMsgId(), call, this.maxFramePayload);
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
const entry = { requestId, method, frames, resolve, reject };
|
|
85
|
+
const timeoutMs = options?.timeoutMs ?? this.defaultTimeoutMs;
|
|
86
|
+
if (Number.isFinite(timeoutMs) && timeoutMs > 0) {
|
|
87
|
+
entry.timer = setTimeout(() => {
|
|
88
|
+
if (this.pending.delete(requestId)) {
|
|
89
|
+
reject(new BridgeError('E_TIMEOUT', `${method} timed out after ${timeoutMs}ms`));
|
|
90
|
+
}
|
|
91
|
+
}, timeoutMs);
|
|
92
|
+
}
|
|
93
|
+
this.pending.set(requestId, entry);
|
|
94
|
+
this.sendFrames(frames);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/** 发出一个事件(fire-and-forget);返回是否发出。 */
|
|
98
|
+
emit(topic, payload) {
|
|
99
|
+
if (this.disposed || this.transport.state !== 'open')
|
|
100
|
+
return false;
|
|
101
|
+
const event = { topic, payload };
|
|
102
|
+
this.sendFrames(encodeFrames(FrameKind.EVENT, this.nextMsgId(), event, this.maxFramePayload));
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
/** 发送一个心跳 ping(pong 由对端应答)。 */
|
|
106
|
+
ping() {
|
|
107
|
+
if (this.disposed || this.transport.state !== 'open')
|
|
108
|
+
return false;
|
|
109
|
+
this.sendCtrl({ subtype: 'ping', ts: Date.now() });
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
dispose(reason = 'endpoint disposed') {
|
|
113
|
+
if (this.disposed)
|
|
114
|
+
return;
|
|
115
|
+
this.disposed = true;
|
|
116
|
+
for (const detach of this.detachers) {
|
|
117
|
+
try {
|
|
118
|
+
detach();
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
/* 忽略 */
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const error = new BridgeError('E_TRANSPORT', reason);
|
|
125
|
+
for (const entry of this.pending.values()) {
|
|
126
|
+
this.clearPendingTimer(entry);
|
|
127
|
+
entry.reject(error);
|
|
128
|
+
}
|
|
129
|
+
this.pending.clear();
|
|
130
|
+
}
|
|
131
|
+
get pendingCount() {
|
|
132
|
+
return this.pending.size;
|
|
133
|
+
}
|
|
134
|
+
get transportState() {
|
|
135
|
+
return this.transport.state;
|
|
136
|
+
}
|
|
137
|
+
// ───────────────────────────── 接收入口 ─────────────────────────────
|
|
138
|
+
onFrame(bytes) {
|
|
139
|
+
if (this.disposed)
|
|
140
|
+
return;
|
|
141
|
+
let message;
|
|
142
|
+
try {
|
|
143
|
+
message = this.reassembler.accept(bytes);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// 坏帧/坏分片直接丢弃,不影响后续
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (!message)
|
|
150
|
+
return;
|
|
151
|
+
if (message.header.version !== PROTOCOL_VERSION)
|
|
152
|
+
return; // 版本不符:丢弃
|
|
153
|
+
switch (message.header.kind) {
|
|
154
|
+
case FrameKind.CALL:
|
|
155
|
+
void this.onCall(message.payload);
|
|
156
|
+
break;
|
|
157
|
+
case FrameKind.RESULT:
|
|
158
|
+
this.onResult(message.payload);
|
|
159
|
+
break;
|
|
160
|
+
case FrameKind.EVENT:
|
|
161
|
+
this.onEvent(message.payload);
|
|
162
|
+
break;
|
|
163
|
+
case FrameKind.CTRL:
|
|
164
|
+
this.onCtrl(message.payload);
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async onCall(call) {
|
|
169
|
+
if (!call || typeof call.requestId !== 'string' || typeof call.method !== 'string')
|
|
170
|
+
return;
|
|
171
|
+
// 幂等:命中去重表则回上次结果,**不重放**
|
|
172
|
+
const cached = this.lookupDedupe(call.requestId);
|
|
173
|
+
if (cached) {
|
|
174
|
+
this.sendResult(cached.result);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const handler = this.handlers.get(call.method);
|
|
178
|
+
if (!handler) {
|
|
179
|
+
this.finishCall(call.requestId, {
|
|
180
|
+
requestId: call.requestId,
|
|
181
|
+
ok: false,
|
|
182
|
+
error: new BridgeError('E_UNSUPPORTED', `unsupported method: ${call.method}`).toWire(),
|
|
183
|
+
});
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
let result;
|
|
187
|
+
try {
|
|
188
|
+
const data = await handler(call.params, {
|
|
189
|
+
requestId: call.requestId,
|
|
190
|
+
method: call.method,
|
|
191
|
+
origin: 'remote',
|
|
192
|
+
});
|
|
193
|
+
result = { requestId: call.requestId, ok: true, data };
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
const error = toBridgeError(err);
|
|
197
|
+
result = { requestId: call.requestId, ok: false, error: error.toWire() };
|
|
198
|
+
}
|
|
199
|
+
this.finishCall(call.requestId, result);
|
|
200
|
+
}
|
|
201
|
+
onResult(result) {
|
|
202
|
+
if (!result || typeof result.requestId !== 'string')
|
|
203
|
+
return;
|
|
204
|
+
const entry = this.pending.get(result.requestId);
|
|
205
|
+
if (!entry)
|
|
206
|
+
return; // 未知/超时后的迟到响应:忽略
|
|
207
|
+
this.pending.delete(result.requestId);
|
|
208
|
+
this.clearPendingTimer(entry);
|
|
209
|
+
if (result.ok) {
|
|
210
|
+
entry.resolve(result.data);
|
|
211
|
+
}
|
|
212
|
+
else if (result.error) {
|
|
213
|
+
entry.reject(BridgeError.fromWire(result.error));
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
entry.reject(new BridgeError('E_INTERNAL', 'malformed RESULT (ok=false without error)'));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
onEvent(event) {
|
|
220
|
+
if (!event || typeof event.topic !== 'string')
|
|
221
|
+
return;
|
|
222
|
+
const set = this.subscribers.get(event.topic);
|
|
223
|
+
if (!set)
|
|
224
|
+
return;
|
|
225
|
+
const ctx = { topic: event.topic, origin: 'remote' };
|
|
226
|
+
for (const fn of [...set]) {
|
|
227
|
+
try {
|
|
228
|
+
fn(event.payload, ctx);
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// 单个订阅者抛错不得中断分发
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
onCtrl(ctrl) {
|
|
236
|
+
if (!ctrl || typeof ctrl.subtype !== 'string')
|
|
237
|
+
return;
|
|
238
|
+
if (ctrl.subtype === 'ping') {
|
|
239
|
+
this.sendCtrl({ subtype: 'pong', ts: ctrl.ts });
|
|
240
|
+
}
|
|
241
|
+
// 'pong' 由心跳逻辑消费(M2/M3);一期无自动心跳
|
|
242
|
+
}
|
|
243
|
+
// ───────────────────────────── 内部工具 ─────────────────────────────
|
|
244
|
+
onState(state) {
|
|
245
|
+
const was = this.lastState;
|
|
246
|
+
this.lastState = state;
|
|
247
|
+
if (state === 'open' && was !== 'open') {
|
|
248
|
+
// 断线期间的分片已失效,重置归组器再重放
|
|
249
|
+
this.reassembler.reset();
|
|
250
|
+
this.replayPending();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/** 链路恢复:未决的 CALL 用同一 requestId 重发(对端靠去重表返回上次结果)。 */
|
|
254
|
+
replayPending() {
|
|
255
|
+
for (const entry of this.pending.values()) {
|
|
256
|
+
this.sendFrames(entry.frames);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
finishCall(requestId, result) {
|
|
260
|
+
this.rememberDedupe(requestId, result);
|
|
261
|
+
this.sendResult(result);
|
|
262
|
+
}
|
|
263
|
+
sendResult(result) {
|
|
264
|
+
this.sendFrames(encodeFrames(FrameKind.RESULT, this.nextMsgId(), result, this.maxFramePayload));
|
|
265
|
+
}
|
|
266
|
+
sendCtrl(ctrl) {
|
|
267
|
+
this.sendFrames(encodeFrames(FrameKind.CTRL, this.nextMsgId(), ctrl, this.maxFramePayload));
|
|
268
|
+
}
|
|
269
|
+
sendFrames(frames) {
|
|
270
|
+
if (this.transport.state !== 'open')
|
|
271
|
+
return;
|
|
272
|
+
for (const frame of frames)
|
|
273
|
+
this.transport.send(frame);
|
|
274
|
+
}
|
|
275
|
+
nextMsgId() {
|
|
276
|
+
this.msgId = (this.msgId + 1) >>> 0;
|
|
277
|
+
if (this.msgId === 0)
|
|
278
|
+
this.msgId = 1;
|
|
279
|
+
return this.msgId;
|
|
280
|
+
}
|
|
281
|
+
clearPendingTimer(entry) {
|
|
282
|
+
if (entry.timer !== undefined) {
|
|
283
|
+
clearTimeout(entry.timer);
|
|
284
|
+
entry.timer = undefined;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
lookupDedupe(requestId) {
|
|
288
|
+
const entry = this.dedupe.get(requestId);
|
|
289
|
+
if (!entry)
|
|
290
|
+
return undefined;
|
|
291
|
+
if (Date.now() - entry.at > this.dedupeTtlMs) {
|
|
292
|
+
this.dedupe.delete(requestId);
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
return entry;
|
|
296
|
+
}
|
|
297
|
+
rememberDedupe(requestId, result) {
|
|
298
|
+
this.dedupe.set(requestId, { result, at: Date.now() });
|
|
299
|
+
while (this.dedupe.size > this.dedupeSize) {
|
|
300
|
+
const oldest = this.dedupe.keys().next().value;
|
|
301
|
+
if (oldest === undefined)
|
|
302
|
+
break;
|
|
303
|
+
this.dedupe.delete(oldest);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
//# sourceMappingURL=endpoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint.js","sourceRoot":"","sources":["../../src/protocol/endpoint.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 错误模型(见 docs/phone-control-bridge.md §3.4)。
|
|
3
|
+
*
|
|
4
|
+
* 只有 `retryable === true` 才允许 UI 给「重试」按钮。
|
|
5
|
+
* `E_UNSUPPORTED` 是**版本兼容的关键**:老手机调新方法 → 明确报错 → UI 隐藏该功能,
|
|
6
|
+
* **永远不要靠版本号 if-else 分支**。
|
|
7
|
+
*/
|
|
8
|
+
export type ErrorCode = 'E_BAD_REQUEST' | 'E_NOT_FOUND' | 'E_DENIED' | 'E_UNSUPPORTED' | 'E_BUSY' | 'E_CONFLICT' | 'E_CONFIRM_REQUIRED' | 'E_TIMEOUT' | 'E_INTERNAL' | 'E_TRANSPORT' | 'E_REPLACED';
|
|
9
|
+
/** 线上(wire)错误形态 —— 可序列化为 JSON。 */
|
|
10
|
+
export interface WireError {
|
|
11
|
+
code: ErrorCode;
|
|
12
|
+
message: string;
|
|
13
|
+
retryable: boolean;
|
|
14
|
+
data?: unknown;
|
|
15
|
+
}
|
|
16
|
+
export interface BridgeErrorOptions {
|
|
17
|
+
retryable?: boolean;
|
|
18
|
+
data?: unknown;
|
|
19
|
+
cause?: unknown;
|
|
20
|
+
}
|
|
21
|
+
export declare class BridgeError extends Error {
|
|
22
|
+
readonly code: ErrorCode;
|
|
23
|
+
readonly retryable: boolean;
|
|
24
|
+
readonly data?: unknown;
|
|
25
|
+
constructor(code: ErrorCode, message: string, options?: BridgeErrorOptions);
|
|
26
|
+
toWire(): WireError;
|
|
27
|
+
static fromWire(w: WireError): BridgeError;
|
|
28
|
+
static is(value: unknown): value is BridgeError;
|
|
29
|
+
}
|
|
30
|
+
/** 把任意抛出物归一为 BridgeError(非 BridgeError 一律按 E_INTERNAL 处理)。 */
|
|
31
|
+
export declare function toBridgeError(err: unknown): BridgeError;
|
|
32
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/protocol/errors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,SAAS,GACjB,eAAe,GACf,aAAa,GACb,UAAU,GACV,eAAe,GACf,QAAQ,GACR,YAAY,GACZ,oBAAoB,GACpB,WAAW,GACX,YAAY,GACZ,aAAa,GACb,YAAY,CAAA;AAEhB,kCAAkC;AAClC,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,SAAS,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAgBD,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;IAEvB,YAAY,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,EAUzE;IAED,MAAM,IAAI,SAAS,CAOlB;IAED,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,GAAG,WAAW,CAEzC;IAED,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAE9C;CACF;AAED,8DAA8D;AAC9D,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,WAAW,CAIvD"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 错误模型(见 docs/phone-control-bridge.md §3.4)。
|
|
3
|
+
*
|
|
4
|
+
* 只有 `retryable === true` 才允许 UI 给「重试」按钮。
|
|
5
|
+
* `E_UNSUPPORTED` 是**版本兼容的关键**:老手机调新方法 → 明确报错 → UI 隐藏该功能,
|
|
6
|
+
* **永远不要靠版本号 if-else 分支**。
|
|
7
|
+
*/
|
|
8
|
+
const RETRYABLE = {
|
|
9
|
+
E_BAD_REQUEST: false,
|
|
10
|
+
E_NOT_FOUND: false,
|
|
11
|
+
E_DENIED: false,
|
|
12
|
+
E_UNSUPPORTED: false,
|
|
13
|
+
E_BUSY: true,
|
|
14
|
+
E_CONFLICT: false,
|
|
15
|
+
E_CONFIRM_REQUIRED: false,
|
|
16
|
+
E_TIMEOUT: true,
|
|
17
|
+
E_INTERNAL: false,
|
|
18
|
+
E_TRANSPORT: true,
|
|
19
|
+
E_REPLACED: false,
|
|
20
|
+
};
|
|
21
|
+
export class BridgeError extends Error {
|
|
22
|
+
constructor(code, message, options) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = 'BridgeError';
|
|
25
|
+
this.code = code;
|
|
26
|
+
this.retryable = options?.retryable ?? RETRYABLE[code] ?? false;
|
|
27
|
+
this.data = options?.data;
|
|
28
|
+
if (options?.cause !== undefined) {
|
|
29
|
+
// 避免依赖 ES2022 的 ErrorOptions,直接挂 cause 字段
|
|
30
|
+
;
|
|
31
|
+
this.cause = options.cause;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
toWire() {
|
|
35
|
+
return {
|
|
36
|
+
code: this.code,
|
|
37
|
+
message: this.message,
|
|
38
|
+
retryable: this.retryable,
|
|
39
|
+
...(this.data !== undefined ? { data: this.data } : {}),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
static fromWire(w) {
|
|
43
|
+
return new BridgeError(w.code, w.message, { retryable: w.retryable, data: w.data });
|
|
44
|
+
}
|
|
45
|
+
static is(value) {
|
|
46
|
+
return value instanceof BridgeError;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** 把任意抛出物归一为 BridgeError(非 BridgeError 一律按 E_INTERNAL 处理)。 */
|
|
50
|
+
export function toBridgeError(err) {
|
|
51
|
+
if (err instanceof BridgeError)
|
|
52
|
+
return err;
|
|
53
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
54
|
+
return new BridgeError('E_INTERNAL', message, { cause: err });
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/protocol/errors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAuBH,MAAM,SAAS,GAA+B;IAC5C,aAAa,EAAE,KAAK;IACpB,WAAW,EAAE,KAAK;IAClB,QAAQ,EAAE,KAAK;IACf,aAAa,EAAE,KAAK;IACpB,MAAM,EAAE,IAAI;IACZ,UAAU,EAAE,KAAK;IACjB,kBAAkB,EAAE,KAAK;IACzB,SAAS,EAAE,IAAI;IACf,UAAU,EAAE,KAAK;IACjB,WAAW,EAAE,IAAI;IACjB,UAAU,EAAE,KAAK;CAClB,CAAA;AAQD,MAAM,OAAO,WAAY,SAAQ,KAAK;IAKpC,YAAY,IAAe,EAAE,OAAe,EAAE,OAA4B;QACxE,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,aAAa,CAAA;QACzB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,KAAK,CAAA;QAC/D,IAAI,CAAC,IAAI,GAAG,OAAO,EAAE,IAAI,CAAA;QACzB,IAAI,OAAO,EAAE,KAAK,KAAK,SAAS,EAAE,CAAC;YACjC,0CAA0C;YAC1C,CAAC;YAAC,IAA4B,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAA;QACtD,CAAC;IACH,CAAC;IAED,MAAM;QACJ,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxD,CAAA;IACH,CAAC;IAED,MAAM,CAAC,QAAQ,CAAC,CAAY;QAC1B,OAAO,IAAI,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;IACrF,CAAC;IAED,MAAM,CAAC,EAAE,CAAC,KAAc;QACtB,OAAO,KAAK,YAAY,WAAW,CAAA;IACrC,CAAC;CACF;AAED,8DAA8D;AAC9D,MAAM,UAAU,aAAa,CAAC,GAAY;IACxC,IAAI,GAAG,YAAY,WAAW;QAAE,OAAO,GAAG,CAAA;IAC1C,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAChE,OAAO,IAAI,WAAW,CAAC,YAAY,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAA;AAC/D,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export declare const PROTOCOL_VERSION = 1;
|
|
2
|
+
export declare const HEADER_SIZE = 12;
|
|
3
|
+
/** 超过该阈值的载荷即分片(见 §3.2)。 */
|
|
4
|
+
export declare const DEFAULT_MAX_FRAME_PAYLOAD: number;
|
|
5
|
+
export declare const FrameKind: {
|
|
6
|
+
readonly CALL: 1;
|
|
7
|
+
readonly RESULT: 2;
|
|
8
|
+
readonly EVENT: 3;
|
|
9
|
+
readonly CTRL: 4;
|
|
10
|
+
};
|
|
11
|
+
export type FrameKind = (typeof FrameKind)[keyof typeof FrameKind];
|
|
12
|
+
export interface FrameHeader {
|
|
13
|
+
version: number;
|
|
14
|
+
kind: FrameKind;
|
|
15
|
+
chunkIndex: number;
|
|
16
|
+
chunkCount: number;
|
|
17
|
+
flags: number;
|
|
18
|
+
msgId: number;
|
|
19
|
+
}
|
|
20
|
+
/** 一个已完整还原的逻辑消息。 */
|
|
21
|
+
export interface DecodedMessage<T = unknown> {
|
|
22
|
+
header: FrameHeader;
|
|
23
|
+
payload: T;
|
|
24
|
+
}
|
|
25
|
+
export declare function encodeHeader(header: FrameHeader): Uint8Array;
|
|
26
|
+
export declare function decodeHeader(bytes: Uint8Array, offset?: number): FrameHeader;
|
|
27
|
+
/** 读取帧体(不含头)。 */
|
|
28
|
+
export declare function frameBody(frame: Uint8Array): Uint8Array;
|
|
29
|
+
/**
|
|
30
|
+
* 把一条逻辑消息编码为 1..N 个帧。载荷 > `maxFramePayload` 时自动分片。
|
|
31
|
+
* 返回的数组顺序即分片顺序(chunkIndex 0..n-1)。
|
|
32
|
+
*/
|
|
33
|
+
export declare function encodeFrames(kind: FrameKind, msgId: number, payload: unknown, maxFramePayload?: number): Uint8Array[];
|
|
34
|
+
/**
|
|
35
|
+
* 分片归组器 —— 按 `msgId` 收集分片,**完整才交付**(不完整则整体丢弃,不做部分应用)。
|
|
36
|
+
*
|
|
37
|
+
* 天然容忍:分片乱序、多条消息交错(不同 msgId 各自独立累积)。
|
|
38
|
+
*/
|
|
39
|
+
export declare class Reassembler {
|
|
40
|
+
private readonly maxPartials;
|
|
41
|
+
private readonly partials;
|
|
42
|
+
constructor(maxPartials?: number);
|
|
43
|
+
/** 喂入一帧;返回已完整还原的消息,否则 null。 */
|
|
44
|
+
accept(frame: Uint8Array): DecodedMessage | null;
|
|
45
|
+
/** 正在等待后续分片的逻辑消息数(诊断用)。 */
|
|
46
|
+
get pendingCount(): number;
|
|
47
|
+
reset(): void;
|
|
48
|
+
private decodePayload;
|
|
49
|
+
private evictOldest;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=frame.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame.d.ts","sourceRoot":"","sources":["../../src/protocol/frame.ts"],"names":[],"mappings":"AAoBA,eAAO,MAAM,gBAAgB,IAAI,CAAA;AACjC,eAAO,MAAM,WAAW,KAAK,CAAA;AAC7B,2BAA2B;AAC3B,eAAO,MAAM,yBAAyB,QAAY,CAAA;AAElD,eAAO,MAAM,SAAS;aACpB,IAAI,EAAE,CAAC;aACP,MAAM,EAAE,CAAC;aACT,KAAK,EAAE,CAAC;aACR,IAAI,EAAE,CAAC;CACC,CAAA;AACV,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAA;AAOlE,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,SAAS,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd;AAED,oBAAoB;AACpB,MAAM,WAAW,cAAc,CAAC,CAAC,GAAG,OAAO;IACzC,MAAM,EAAE,WAAW,CAAA;IACnB,OAAO,EAAE,CAAC,CAAA;CACX;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,UAAU,CAU5D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,SAAI,GAAG,WAAW,CAavE;AAED,iBAAiB;AACjB,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAEvD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,SAAS,EACf,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,OAAO,EAChB,eAAe,GAAE,MAAkC,GAClD,UAAU,EAAE,CAgBd;AAQD;;;;GAIG;AACH,qBAAa,WAAW;IAGV,OAAO,CAAC,QAAQ,CAAC,WAAW;IAFxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoC;IAE7D,YAA6B,WAAW,SAAK,EAAI;IAEjD,+BAA+B;IAC/B,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAgC/C;IAED,2BAA2B;IAC3B,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,KAAK,IAAI,IAAI,CAEZ;IAED,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,WAAW;CAIpB"}
|