@ai-matrx/desktop-protocol 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 +35 -0
- package/LICENSE +21 -0
- package/README.md +90 -0
- package/dist/client.cjs +1309 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +181 -0
- package/dist/client.d.ts +181 -0
- package/dist/client.js +1286 -0
- package/dist/client.js.map +1 -0
- package/dist/frame.cjs +132 -0
- package/dist/frame.cjs.map +1 -0
- package/dist/frame.d.cts +46 -0
- package/dist/frame.d.ts +46 -0
- package/dist/frame.js +109 -0
- package/dist/frame.js.map +1 -0
- package/dist/index.cjs +876 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +935 -0
- package/dist/index.d.ts +935 -0
- package/dist/index.js +853 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +129 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +924 -0
- package/dist/react.d.ts +924 -0
- package/dist/react.js +109 -0
- package/dist/react.js.map +1 -0
- package/dist/registry-DTcTGycI.d.cts +2330 -0
- package/dist/registry-DTcTGycI.d.ts +2330 -0
- package/package.json +124 -0
package/dist/client.cjs
ADDED
|
@@ -0,0 +1,1309 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/client.ts
|
|
21
|
+
var client_exports = {};
|
|
22
|
+
__export(client_exports, {
|
|
23
|
+
DesktopProtocolError: () => DesktopProtocolError,
|
|
24
|
+
RECONNECT_ALARM_ATTEMPTS: () => import_realtime.RECONNECT_ALARM_ATTEMPTS,
|
|
25
|
+
createDesktopClient: () => createDesktopClient,
|
|
26
|
+
isDesktopProtocolError: () => isDesktopProtocolError
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(client_exports);
|
|
29
|
+
|
|
30
|
+
// src/client/client.ts
|
|
31
|
+
var import_realtime = require("@ai-matrx/realtime");
|
|
32
|
+
|
|
33
|
+
// src/error.ts
|
|
34
|
+
var DesktopProtocolError = class extends Error {
|
|
35
|
+
code;
|
|
36
|
+
retryable;
|
|
37
|
+
data;
|
|
38
|
+
constructor(code, message, options = {}) {
|
|
39
|
+
super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
|
|
40
|
+
this.name = "DesktopProtocolError";
|
|
41
|
+
this.code = code;
|
|
42
|
+
this.retryable = options.retryable ?? false;
|
|
43
|
+
this.data = options.data;
|
|
44
|
+
}
|
|
45
|
+
/** The wire body (ProtocolErrorBody) — what a core or relay sends for this error. */
|
|
46
|
+
toBody() {
|
|
47
|
+
return this.data === void 0 ? { code: this.code, message: this.message, retryable: this.retryable } : { code: this.code, message: this.message, retryable: this.retryable, data: this.data };
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
function isDesktopProtocolError(value) {
|
|
51
|
+
return value instanceof DesktopProtocolError || value instanceof Error && value.name === "DesktopProtocolError" && "code" in value;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/frame.ts
|
|
55
|
+
var FRAME_HEADER_BYTES = 16;
|
|
56
|
+
var FRAME_VERSION = 1;
|
|
57
|
+
var FrameChannel = {
|
|
58
|
+
/** core→client: pty output, fs.read_stream bytes */
|
|
59
|
+
DATA: 0,
|
|
60
|
+
/** core→client: stderr of a non-pty process (reserved for exec.spawn in v1.1) */
|
|
61
|
+
STDERR: 1,
|
|
62
|
+
/** client→core: keystrokes / stdin to sid */
|
|
63
|
+
STDIN: 2,
|
|
64
|
+
/** core→client: serialized terminal screen (attach when since_seq is outside the ring) */
|
|
65
|
+
SNAPSHOT: 3,
|
|
66
|
+
/** relay↔device hop only: a JSON envelope carried inside a binary frame so the relay can route
|
|
67
|
+
* by cid without parsing JSON. Never seen by clients. */
|
|
68
|
+
ENVELOPE: 255
|
|
69
|
+
};
|
|
70
|
+
var U32_MAX = 4294967295;
|
|
71
|
+
var CHANNELS = new Set(Object.values(FrameChannel));
|
|
72
|
+
function frameError(message) {
|
|
73
|
+
return new DesktopProtocolError("PROTOCOL_ERROR", message);
|
|
74
|
+
}
|
|
75
|
+
function checkU32(name, value) {
|
|
76
|
+
if (!Number.isInteger(value) || value < 0 || value > U32_MAX) throw frameError(`frame ${name} is not a u32: ${value}`);
|
|
77
|
+
}
|
|
78
|
+
function assertFrameHeader(h) {
|
|
79
|
+
if (h.version !== FRAME_VERSION) throw frameError(`unsupported frame version ${String(h.version)}`);
|
|
80
|
+
if (!Number.isInteger(h.flags) || h.flags < 0 || h.flags > 3) throw frameError(`reserved frame flag bits set: ${h.flags}`);
|
|
81
|
+
if (!CHANNELS.has(h.channel)) throw frameError(`unknown frame channel ${String(h.channel)}`);
|
|
82
|
+
checkU32("cid", h.cid);
|
|
83
|
+
checkU32("sid", h.sid);
|
|
84
|
+
checkU32("seq", h.seq);
|
|
85
|
+
}
|
|
86
|
+
function encodeFrame(h, payload) {
|
|
87
|
+
assertFrameHeader(h);
|
|
88
|
+
const out = new Uint8Array(FRAME_HEADER_BYTES + payload.byteLength);
|
|
89
|
+
const v = new DataView(out.buffer);
|
|
90
|
+
v.setUint8(0, h.version);
|
|
91
|
+
v.setUint8(1, h.flags);
|
|
92
|
+
v.setUint8(2, h.channel);
|
|
93
|
+
v.setUint8(3, 0);
|
|
94
|
+
v.setUint32(4, h.cid);
|
|
95
|
+
v.setUint32(8, h.sid);
|
|
96
|
+
v.setUint32(12, h.seq);
|
|
97
|
+
out.set(payload, FRAME_HEADER_BYTES);
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
function decodeFrame(buf) {
|
|
101
|
+
const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf);
|
|
102
|
+
if (bytes.byteLength < FRAME_HEADER_BYTES) throw frameError(`short frame: ${bytes.byteLength} bytes`);
|
|
103
|
+
const v = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
104
|
+
if (v.getUint8(3) !== 0) throw frameError("reserved frame byte 3 is not 0");
|
|
105
|
+
const header = {
|
|
106
|
+
version: v.getUint8(0),
|
|
107
|
+
flags: v.getUint8(1),
|
|
108
|
+
channel: v.getUint8(2),
|
|
109
|
+
cid: v.getUint32(4),
|
|
110
|
+
sid: v.getUint32(8),
|
|
111
|
+
seq: v.getUint32(12)
|
|
112
|
+
};
|
|
113
|
+
assertFrameHeader(header);
|
|
114
|
+
return { header, payload: bytes.subarray(FRAME_HEADER_BYTES) };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/schema/envelope.ts
|
|
118
|
+
var import_zod3 = require("zod");
|
|
119
|
+
|
|
120
|
+
// src/schema/errors.ts
|
|
121
|
+
var import_zod2 = require("zod");
|
|
122
|
+
|
|
123
|
+
// src/schema/primitives.ts
|
|
124
|
+
var import_zod = require("zod");
|
|
125
|
+
var PROTOCOL_MAJOR = 1;
|
|
126
|
+
var PROTOCOL_MINOR = 0;
|
|
127
|
+
var ProtocolVersion = import_zod.z.string().regex(/^[1-9][0-9]{0,2}\.[0-9]{1,3}$/);
|
|
128
|
+
var RequestId = import_zod.z.string().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/);
|
|
129
|
+
var ResourceId = import_zod.z.string().regex(/^(pty|watch)_[A-Za-z0-9]{16,40}$/);
|
|
130
|
+
var DeviceId = import_zod.z.string().regex(/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/);
|
|
131
|
+
var AbsPath = import_zod.z.string().min(1).max(4096).regex(/^(\/|[A-Za-z]:\/)[^\\\u0000]*$/);
|
|
132
|
+
var u32 = import_zod.z.int().min(0).max(4294967295);
|
|
133
|
+
var nonNegInt = import_zod.z.int().min(0);
|
|
134
|
+
var epochMs = import_zod.z.int().min(0);
|
|
135
|
+
|
|
136
|
+
// src/schema/errors.ts
|
|
137
|
+
var ErrorCode = import_zod2.z.enum([
|
|
138
|
+
"AUTH_REQUIRED",
|
|
139
|
+
"AUTH_INVALID",
|
|
140
|
+
"AUTH_EXPIRED",
|
|
141
|
+
"AUTH_FORBIDDEN",
|
|
142
|
+
// signed in, but not this device's owner
|
|
143
|
+
"AUTH_DEVICE_REVOKED",
|
|
144
|
+
// kill switch
|
|
145
|
+
"PERMISSION_DENIED_OS",
|
|
146
|
+
"NOT_FOUND",
|
|
147
|
+
"CONFLICT",
|
|
148
|
+
"INVALID_PARAMS",
|
|
149
|
+
"RESOURCE_BUSY",
|
|
150
|
+
"RESOURCE_GONE",
|
|
151
|
+
"CANCELLED",
|
|
152
|
+
"TIMEOUT",
|
|
153
|
+
"PLATFORM_UNSUPPORTED",
|
|
154
|
+
"RATE_LIMITED",
|
|
155
|
+
"INTERNAL",
|
|
156
|
+
"PROTOCOL_VERSION_UNSUPPORTED",
|
|
157
|
+
// NEW: hello refused, carries data.upgrade
|
|
158
|
+
"PROTOCOL_ERROR",
|
|
159
|
+
// NEW: malformed envelope/frame, credit overrun, unknown sid
|
|
160
|
+
"UNKNOWN_OPERATION",
|
|
161
|
+
// NEW: cap.op not served by this core version
|
|
162
|
+
"DEVICE_OFFLINE",
|
|
163
|
+
// NEW: relay has no live device socket (retryable)
|
|
164
|
+
"PAYLOAD_TOO_LARGE"
|
|
165
|
+
// NEW: message or frame above limits.max_*_bytes
|
|
166
|
+
]);
|
|
167
|
+
var ErrorReason = import_zod2.z.enum([
|
|
168
|
+
"not_a_directory",
|
|
169
|
+
"is_a_directory",
|
|
170
|
+
"directory_not_empty",
|
|
171
|
+
"already_exists",
|
|
172
|
+
"stale_write",
|
|
173
|
+
// fs.write if_match_mtime_ms mismatch → CONFLICT
|
|
174
|
+
"binary_content",
|
|
175
|
+
// utf8 read of non-UTF-8 bytes → INVALID_PARAMS
|
|
176
|
+
"not_utf8_boundary",
|
|
177
|
+
"patch_no_match",
|
|
178
|
+
"patch_ambiguous",
|
|
179
|
+
"resource_exited",
|
|
180
|
+
"resource_expired",
|
|
181
|
+
"control_taken",
|
|
182
|
+
// another client of the same user took control
|
|
183
|
+
"window_exhausted"
|
|
184
|
+
]);
|
|
185
|
+
var ProtocolErrorBody = import_zod2.z.strictObject({
|
|
186
|
+
code: ErrorCode,
|
|
187
|
+
message: import_zod2.z.string().max(300),
|
|
188
|
+
// short, human, never a stack trace or a token
|
|
189
|
+
retryable: import_zod2.z.boolean(),
|
|
190
|
+
data: import_zod2.z.strictObject({
|
|
191
|
+
reason: ErrorReason.optional(),
|
|
192
|
+
cap: import_zod2.z.string().max(32).optional(),
|
|
193
|
+
op: import_zod2.z.string().max(64).optional(),
|
|
194
|
+
path: AbsPath.optional(),
|
|
195
|
+
settings_url: import_zod2.z.string().max(512).optional(),
|
|
196
|
+
// PERMISSION_DENIED_OS deep link
|
|
197
|
+
retry_after_ms: nonNegInt.optional(),
|
|
198
|
+
limit_bytes: nonNegInt.optional(),
|
|
199
|
+
upgrade: import_zod2.z.strictObject({
|
|
200
|
+
min_version: ProtocolVersion,
|
|
201
|
+
max_version: ProtocolVersion,
|
|
202
|
+
download_url: import_zod2.z.url().optional()
|
|
203
|
+
}).optional()
|
|
204
|
+
}).optional()
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// src/schema/envelope.ts
|
|
208
|
+
var ClientType = import_zod3.z.enum(["screen", "web", "extension", "server", "test"]);
|
|
209
|
+
var Limits = import_zod3.z.strictObject({
|
|
210
|
+
max_message_bytes: import_zod3.z.int().min(65536),
|
|
211
|
+
max_frame_payload_bytes: import_zod3.z.int().min(4096),
|
|
212
|
+
default_window_bytes: import_zod3.z.int().min(4096),
|
|
213
|
+
max_streams_per_client: import_zod3.z.int().min(1),
|
|
214
|
+
max_resources_per_user: import_zod3.z.int().min(1)
|
|
215
|
+
});
|
|
216
|
+
var OpDescriptor = import_zod3.z.strictObject({ cap: import_zod3.z.string().max(32), op: import_zod3.z.string().max(64), since: ProtocolVersion });
|
|
217
|
+
var Welcome = import_zod3.z.strictObject({
|
|
218
|
+
protocol_version: ProtocolVersion,
|
|
219
|
+
// negotiated = min(client, core) within the same major
|
|
220
|
+
session_id: import_zod3.z.string().min(8).max(64),
|
|
221
|
+
client_id: import_zod3.z.string().min(8).max(64),
|
|
222
|
+
// this connection's identity in the Session Manager
|
|
223
|
+
device_id: DeviceId,
|
|
224
|
+
device_name: import_zod3.z.string().max(128),
|
|
225
|
+
app_version: import_zod3.z.string().max(32),
|
|
226
|
+
platform: import_zod3.z.enum(["darwin", "win32", "linux"]),
|
|
227
|
+
ops: import_zod3.z.array(OpDescriptor),
|
|
228
|
+
limits: Limits,
|
|
229
|
+
server_time_ms: epochMs
|
|
230
|
+
});
|
|
231
|
+
var HelloMsg = import_zod3.z.strictObject({
|
|
232
|
+
type: import_zod3.z.literal("hello"),
|
|
233
|
+
id: RequestId,
|
|
234
|
+
// answered by response{id, result: Welcome} or error{id}
|
|
235
|
+
protocolVersion: ProtocolVersion,
|
|
236
|
+
clientType: ClientType,
|
|
237
|
+
clientVersion: import_zod3.z.string().min(1).max(32)
|
|
238
|
+
});
|
|
239
|
+
var RequestMsg = import_zod3.z.strictObject({
|
|
240
|
+
id: RequestId,
|
|
241
|
+
type: import_zod3.z.literal("request"),
|
|
242
|
+
cap: import_zod3.z.string().min(1).max(32),
|
|
243
|
+
op: import_zod3.z.string().min(1).max(64),
|
|
244
|
+
params: import_zod3.z.unknown()
|
|
245
|
+
// validated by the Router against OPS[cap.op].params — once, there
|
|
246
|
+
});
|
|
247
|
+
var ResponseMsg = import_zod3.z.strictObject({ id: RequestId, type: import_zod3.z.literal("response"), result: import_zod3.z.unknown() });
|
|
248
|
+
var ErrorMsg = import_zod3.z.strictObject({ id: RequestId, type: import_zod3.z.literal("error"), error: ProtocolErrorBody });
|
|
249
|
+
var StreamMeta = import_zod3.z.discriminatedUnion("kind", [
|
|
250
|
+
/** Binds request id → numeric sid; binary frames for this stream follow. */
|
|
251
|
+
import_zod3.z.strictObject({
|
|
252
|
+
kind: import_zod3.z.literal("open"),
|
|
253
|
+
sid: u32,
|
|
254
|
+
window_bytes: import_zod3.z.int().min(4096),
|
|
255
|
+
resource_id: ResourceId.optional()
|
|
256
|
+
}),
|
|
257
|
+
/** Frames after this (channel SNAPSHOT) are a serialized screen; live DATA resumes after. */
|
|
258
|
+
import_zod3.z.strictObject({ kind: import_zod3.z.literal("snapshot"), cols: import_zod3.z.int().min(1), rows: import_zod3.z.int().min(1) }),
|
|
259
|
+
/** Frames [from_seq, to_seq] are gone from the ring; a snapshot follows. */
|
|
260
|
+
import_zod3.z.strictObject({ kind: import_zod3.z.literal("gap"), from_seq: u32, to_seq: u32 })
|
|
261
|
+
]);
|
|
262
|
+
var StreamMsg = import_zod3.z.strictObject({ id: RequestId, type: import_zod3.z.literal("stream"), seq: u32, meta: StreamMeta });
|
|
263
|
+
var EndReason = import_zod3.z.enum(["completed", "cancelled", "detached", "resource_exited", "error"]);
|
|
264
|
+
var EndMsg = import_zod3.z.strictObject({ id: RequestId, type: import_zod3.z.literal("end"), seq: u32, reason: EndReason.optional() });
|
|
265
|
+
var CancelMsg = import_zod3.z.strictObject({ id: RequestId, type: import_zod3.z.literal("cancel") });
|
|
266
|
+
var CreditMsg = import_zod3.z.strictObject({ id: RequestId, type: import_zod3.z.literal("credit"), n: import_zod3.z.int().min(1).max(2147483647) });
|
|
267
|
+
var EventMsg = import_zod3.z.strictObject({
|
|
268
|
+
type: import_zod3.z.literal("event"),
|
|
269
|
+
cap: import_zod3.z.string().min(1).max(32),
|
|
270
|
+
name: import_zod3.z.string().min(1).max(64),
|
|
271
|
+
payload: import_zod3.z.unknown(),
|
|
272
|
+
// validated against EVENTS[cap.name]
|
|
273
|
+
resourceId: ResourceId.optional()
|
|
274
|
+
});
|
|
275
|
+
var RelayMsg = import_zod3.z.discriminatedUnion("op", [
|
|
276
|
+
/** Fresh access token before the current one expires (relay closes 4008 at exp + 60 s). */
|
|
277
|
+
import_zod3.z.strictObject({ type: import_zod3.z.literal("relay"), op: import_zod3.z.literal("reauth"), token: import_zod3.z.string().min(16).max(8192) })
|
|
278
|
+
]);
|
|
279
|
+
var PING_TEXT = '{"type":"ping"}';
|
|
280
|
+
var PONG_TEXT = '{"type":"pong"}';
|
|
281
|
+
var ClientToCoreMsg = import_zod3.z.discriminatedUnion("type", [HelloMsg, RequestMsg, CancelMsg, CreditMsg]);
|
|
282
|
+
var CoreToClientMsg = import_zod3.z.discriminatedUnion("type", [ResponseMsg, ErrorMsg, StreamMsg, EndMsg, CreditMsg, EventMsg]);
|
|
283
|
+
var CloseCode = {
|
|
284
|
+
NORMAL: 1e3,
|
|
285
|
+
GOING_AWAY: 1001,
|
|
286
|
+
// relay deploy / app quit — reconnect immediately
|
|
287
|
+
AUTH_INVALID: 4001,
|
|
288
|
+
AUTH_FORBIDDEN: 4003,
|
|
289
|
+
DEVICE_NOT_FOUND: 4004,
|
|
290
|
+
AUTH_EXPIRED: 4008,
|
|
291
|
+
// no reauth before exp + 60 s
|
|
292
|
+
REPLACED: 4009,
|
|
293
|
+
// a newer device socket for the same device took over
|
|
294
|
+
DEVICE_REVOKED: 4010,
|
|
295
|
+
// kill switch — do NOT reconnect
|
|
296
|
+
PROTOCOL_ERROR: 4400,
|
|
297
|
+
UPGRADE_REQUIRED: 4426
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// src/schema/frame.ts
|
|
301
|
+
var import_zod4 = require("zod");
|
|
302
|
+
var FrameHeader = import_zod4.z.strictObject({
|
|
303
|
+
version: import_zod4.z.literal(FRAME_VERSION),
|
|
304
|
+
flags: import_zod4.z.int().min(0).max(3),
|
|
305
|
+
channel: import_zod4.z.union([import_zod4.z.literal(0), import_zod4.z.literal(1), import_zod4.z.literal(2), import_zod4.z.literal(3), import_zod4.z.literal(255)]),
|
|
306
|
+
cid: u32,
|
|
307
|
+
sid: u32,
|
|
308
|
+
seq: u32
|
|
309
|
+
});
|
|
310
|
+
var DEFAULT_WINDOW_BYTES = 262144;
|
|
311
|
+
var DEFAULT_MAX_FRAME_PAYLOAD_BYTES = 65536;
|
|
312
|
+
|
|
313
|
+
// src/schema/sysinfo.ts
|
|
314
|
+
var import_zod5 = require("zod");
|
|
315
|
+
var OsPermissionName = import_zod5.z.enum([
|
|
316
|
+
"full_disk_access",
|
|
317
|
+
"accessibility",
|
|
318
|
+
"screen_recording",
|
|
319
|
+
"microphone",
|
|
320
|
+
"camera",
|
|
321
|
+
"automation",
|
|
322
|
+
"contacts",
|
|
323
|
+
"calendars",
|
|
324
|
+
"photos",
|
|
325
|
+
"location",
|
|
326
|
+
"local_network",
|
|
327
|
+
"bluetooth",
|
|
328
|
+
"notifications"
|
|
329
|
+
]);
|
|
330
|
+
var SysinfoGetParams = import_zod5.z.strictObject({});
|
|
331
|
+
var SysinfoGetResult = import_zod5.z.strictObject({
|
|
332
|
+
device_id: DeviceId,
|
|
333
|
+
device_name: import_zod5.z.string().max(128),
|
|
334
|
+
hostname: import_zod5.z.string().max(255),
|
|
335
|
+
platform: import_zod5.z.enum(["darwin", "win32", "linux"]),
|
|
336
|
+
os_version: import_zod5.z.string().max(64),
|
|
337
|
+
arch: import_zod5.z.enum(["arm64", "x64"]),
|
|
338
|
+
app_version: import_zod5.z.string().max(32),
|
|
339
|
+
protocol_version: ProtocolVersion,
|
|
340
|
+
user: import_zod5.z.strictObject({ username: import_zod5.z.string().max(128), shell: import_zod5.z.string().max(512) }),
|
|
341
|
+
paths: import_zod5.z.strictObject({
|
|
342
|
+
home: AbsPath,
|
|
343
|
+
desktop: AbsPath,
|
|
344
|
+
documents: AbsPath,
|
|
345
|
+
downloads: AbsPath,
|
|
346
|
+
app_data: AbsPath,
|
|
347
|
+
temp: AbsPath
|
|
348
|
+
}),
|
|
349
|
+
cpu: import_zod5.z.strictObject({ model: import_zod5.z.string().max(128), cores: import_zod5.z.int().min(1) }),
|
|
350
|
+
memory: import_zod5.z.strictObject({ total_bytes: nonNegInt, free_bytes: nonNegInt }),
|
|
351
|
+
uptime_seconds: nonNegInt,
|
|
352
|
+
on_battery: import_zod5.z.boolean().nullable(),
|
|
353
|
+
sleep_prevented: import_zod5.z.boolean(),
|
|
354
|
+
// true while any remote session holds the power blocker
|
|
355
|
+
permissions: import_zod5.z.array(
|
|
356
|
+
import_zod5.z.strictObject({
|
|
357
|
+
name: OsPermissionName,
|
|
358
|
+
state: import_zod5.z.enum(["granted", "denied", "not_determined", "not_applicable"]),
|
|
359
|
+
settings_url: import_zod5.z.string().max(512).nullable()
|
|
360
|
+
})
|
|
361
|
+
)
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// src/schema/fs.ts
|
|
365
|
+
var import_zod6 = require("zod");
|
|
366
|
+
var FsKind = import_zod6.z.enum(["file", "dir", "symlink"]);
|
|
367
|
+
var FsEntry = import_zod6.z.strictObject({
|
|
368
|
+
name: import_zod6.z.string().max(1024),
|
|
369
|
+
path: AbsPath,
|
|
370
|
+
kind: FsKind,
|
|
371
|
+
size: nonNegInt,
|
|
372
|
+
mtime: import_zod6.z.number().min(0),
|
|
373
|
+
// seconds, float — daemon parity
|
|
374
|
+
mtime_ms: epochMs,
|
|
375
|
+
mode: import_zod6.z.int().min(0).max(4095),
|
|
376
|
+
target: AbsPath.nullable(),
|
|
377
|
+
// symlink target, never followed
|
|
378
|
+
hidden: import_zod6.z.boolean()
|
|
379
|
+
});
|
|
380
|
+
var FsListParams = import_zod6.z.strictObject({
|
|
381
|
+
path: AbsPath,
|
|
382
|
+
recursive: import_zod6.z.boolean().default(false),
|
|
383
|
+
depth: import_zod6.z.int().min(1).max(32).default(1),
|
|
384
|
+
pattern: import_zod6.z.string().max(256).optional(),
|
|
385
|
+
// glob on name
|
|
386
|
+
limit: import_zod6.z.int().min(1).max(5e3).default(500),
|
|
387
|
+
page_token: import_zod6.z.string().max(256).optional(),
|
|
388
|
+
show_hidden: import_zod6.z.boolean().default(false),
|
|
389
|
+
sort: import_zod6.z.enum(["name", "mtime", "size", "kind"]).default("kind")
|
|
390
|
+
});
|
|
391
|
+
var FsListResult = import_zod6.z.strictObject({
|
|
392
|
+
entries: import_zod6.z.array(FsEntry),
|
|
393
|
+
truncated: import_zod6.z.boolean(),
|
|
394
|
+
next_page_token: import_zod6.z.string().max(256).nullable(),
|
|
395
|
+
scanned: nonNegInt
|
|
396
|
+
});
|
|
397
|
+
var FsStatParams = import_zod6.z.strictObject({ path: AbsPath });
|
|
398
|
+
var FsStatResult = FsEntry;
|
|
399
|
+
var FsEncoding = import_zod6.z.enum(["utf8", "base64"]);
|
|
400
|
+
var FsReadParams = import_zod6.z.strictObject({
|
|
401
|
+
path: AbsPath,
|
|
402
|
+
encoding: FsEncoding.default("utf8"),
|
|
403
|
+
offset: nonNegInt.default(0),
|
|
404
|
+
limit: import_zod6.z.int().min(1).max(4194304).default(262144)
|
|
405
|
+
});
|
|
406
|
+
var FsReadResult = import_zod6.z.strictObject({
|
|
407
|
+
content: import_zod6.z.string(),
|
|
408
|
+
encoding: FsEncoding,
|
|
409
|
+
size: nonNegInt,
|
|
410
|
+
offset: nonNegInt,
|
|
411
|
+
next_offset: nonNegInt,
|
|
412
|
+
truncated: import_zod6.z.boolean(),
|
|
413
|
+
mime: import_zod6.z.string().max(128).nullable(),
|
|
414
|
+
mtime_ms: epochMs
|
|
415
|
+
// pass back as if_match_mtime_ms to guard a later write
|
|
416
|
+
});
|
|
417
|
+
var FsReadStreamParams = import_zod6.z.strictObject({
|
|
418
|
+
path: AbsPath,
|
|
419
|
+
offset: nonNegInt.default(0),
|
|
420
|
+
length: nonNegInt.optional(),
|
|
421
|
+
// absent = to EOF
|
|
422
|
+
window_bytes: import_zod6.z.int().min(4096).max(16777216).optional()
|
|
423
|
+
});
|
|
424
|
+
var FsReadStreamResult = import_zod6.z.strictObject({ size: nonNegInt, mime: import_zod6.z.string().max(128).nullable(), mtime_ms: epochMs });
|
|
425
|
+
var FsWriteParams = import_zod6.z.strictObject({
|
|
426
|
+
path: AbsPath,
|
|
427
|
+
content: import_zod6.z.string().max(5592406),
|
|
428
|
+
// ≤ 4 MiB of bytes after base64 decode
|
|
429
|
+
encoding: FsEncoding.default("utf8"),
|
|
430
|
+
mode: import_zod6.z.int().min(0).max(4095).optional(),
|
|
431
|
+
create_parents: import_zod6.z.boolean().default(false),
|
|
432
|
+
/** Optimistic concurrency: CONFLICT/stale_write if the file changed since this mtime. */
|
|
433
|
+
if_match_mtime_ms: epochMs.optional(),
|
|
434
|
+
/** Refuse to replace an existing file (CONFLICT/already_exists). */
|
|
435
|
+
exclusive: import_zod6.z.boolean().default(false)
|
|
436
|
+
});
|
|
437
|
+
var FsWriteResult = import_zod6.z.strictObject({ path: AbsPath, size: nonNegInt, mtime_ms: epochMs, created: import_zod6.z.boolean() });
|
|
438
|
+
var FsMkdirParams = import_zod6.z.strictObject({ path: AbsPath, parents: import_zod6.z.boolean().default(false) });
|
|
439
|
+
var FsMkdirResult = import_zod6.z.strictObject({ path: AbsPath, created: import_zod6.z.boolean() });
|
|
440
|
+
var FsMoveParams = import_zod6.z.strictObject({ from_path: AbsPath, to_path: AbsPath, overwrite: import_zod6.z.boolean().default(false) });
|
|
441
|
+
var FsMoveResult = import_zod6.z.strictObject({ from_path: AbsPath, to_path: AbsPath });
|
|
442
|
+
var FsDeleteParams = import_zod6.z.strictObject({
|
|
443
|
+
path: AbsPath,
|
|
444
|
+
recursive: import_zod6.z.boolean().default(false),
|
|
445
|
+
to_trash: import_zod6.z.boolean().default(true)
|
|
446
|
+
});
|
|
447
|
+
var FsDeleteResult = import_zod6.z.strictObject({ deleted: import_zod6.z.literal(true), trashed_to: AbsPath.nullable() });
|
|
448
|
+
var FsWatchParams = import_zod6.z.strictObject({
|
|
449
|
+
path: AbsPath,
|
|
450
|
+
recursive: import_zod6.z.boolean().default(false),
|
|
451
|
+
ignore: import_zod6.z.array(import_zod6.z.string().max(256)).max(64).default([]),
|
|
452
|
+
debounce_ms: import_zod6.z.int().min(0).max(5e3).default(50)
|
|
453
|
+
});
|
|
454
|
+
var FsWatchResult = import_zod6.z.strictObject({ resource_id: ResourceId, path: AbsPath });
|
|
455
|
+
var FsPatchParams = import_zod6.z.strictObject({
|
|
456
|
+
path: AbsPath,
|
|
457
|
+
edits: import_zod6.z.array(
|
|
458
|
+
import_zod6.z.strictObject({
|
|
459
|
+
old_text: import_zod6.z.string(),
|
|
460
|
+
new_text: import_zod6.z.string(),
|
|
461
|
+
replace_all: import_zod6.z.boolean().default(false)
|
|
462
|
+
})
|
|
463
|
+
).min(1).max(256),
|
|
464
|
+
create_if_missing: import_zod6.z.boolean().default(false)
|
|
465
|
+
});
|
|
466
|
+
var FsPatchResult = import_zod6.z.strictObject({ path: AbsPath, applied: nonNegInt, size: nonNegInt, mtime_ms: epochMs });
|
|
467
|
+
var FsSearchPathsParams = import_zod6.z.strictObject({
|
|
468
|
+
pattern: import_zod6.z.string().min(1).max(512),
|
|
469
|
+
cwd: AbsPath,
|
|
470
|
+
hidden: import_zod6.z.boolean().default(false),
|
|
471
|
+
max_results: import_zod6.z.int().min(1).max(5e3).default(100)
|
|
472
|
+
});
|
|
473
|
+
var FsSearchPathsResult = import_zod6.z.strictObject({ paths: import_zod6.z.array(AbsPath), truncated: import_zod6.z.boolean() });
|
|
474
|
+
var FsSearchContentParams = import_zod6.z.strictObject({
|
|
475
|
+
query: import_zod6.z.string().min(1).max(1024),
|
|
476
|
+
cwd: AbsPath,
|
|
477
|
+
regex: import_zod6.z.boolean().default(true),
|
|
478
|
+
case_sensitive: import_zod6.z.boolean().default(false),
|
|
479
|
+
include: import_zod6.z.array(import_zod6.z.string().max(256)).max(64).optional(),
|
|
480
|
+
exclude: import_zod6.z.array(import_zod6.z.string().max(256)).max(64).optional(),
|
|
481
|
+
max_results: import_zod6.z.int().min(1).max(5e3).default(100)
|
|
482
|
+
});
|
|
483
|
+
var FsSearchContentResult = import_zod6.z.strictObject({
|
|
484
|
+
results: import_zod6.z.array(
|
|
485
|
+
import_zod6.z.strictObject({
|
|
486
|
+
path: AbsPath,
|
|
487
|
+
lines: import_zod6.z.string(),
|
|
488
|
+
line_number: import_zod6.z.int().min(1),
|
|
489
|
+
submatches: import_zod6.z.array(import_zod6.z.strictObject({ match: import_zod6.z.string(), start: nonNegInt, end: nonNegInt }))
|
|
490
|
+
})
|
|
491
|
+
),
|
|
492
|
+
truncated: import_zod6.z.boolean()
|
|
493
|
+
});
|
|
494
|
+
var FsChange = import_zod6.z.strictObject({
|
|
495
|
+
type: import_zod6.z.enum(["created", "modified", "deleted", "moved"]),
|
|
496
|
+
path: AbsPath,
|
|
497
|
+
to_path: AbsPath.optional(),
|
|
498
|
+
kind: import_zod6.z.enum(["file", "dir", "symlink", "unknown"])
|
|
499
|
+
});
|
|
500
|
+
var FsChangedEvent = import_zod6.z.strictObject({ changes: import_zod6.z.array(FsChange).min(1).max(1e3), overflow: import_zod6.z.boolean() });
|
|
501
|
+
|
|
502
|
+
// src/schema/exec.ts
|
|
503
|
+
var import_zod7 = require("zod");
|
|
504
|
+
var Signal = import_zod7.z.enum(["SIGINT", "SIGTERM", "SIGKILL", "SIGHUP", "SIGQUIT", "SIGTSTP", "SIGCONT", "SIGUSR1", "SIGUSR2"]);
|
|
505
|
+
var cols = import_zod7.z.int().min(2).max(1e3);
|
|
506
|
+
var rows = import_zod7.z.int().min(1).max(500);
|
|
507
|
+
var ExecPtyStartParams = import_zod7.z.strictObject({
|
|
508
|
+
cols,
|
|
509
|
+
rows,
|
|
510
|
+
cwd: AbsPath.optional(),
|
|
511
|
+
// default: home
|
|
512
|
+
shell: import_zod7.z.string().max(512).optional(),
|
|
513
|
+
// default: user's login shell (PowerShell on Windows)
|
|
514
|
+
args: import_zod7.z.array(import_zod7.z.string().max(4096)).max(64).optional(),
|
|
515
|
+
env: import_zod7.z.record(import_zod7.z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).max(128), import_zod7.z.string().max(32768)).optional(),
|
|
516
|
+
name: import_zod7.z.string().max(64).optional(),
|
|
517
|
+
// tab label
|
|
518
|
+
window_bytes: import_zod7.z.int().min(4096).max(16777216).optional()
|
|
519
|
+
});
|
|
520
|
+
var PtyInfo = import_zod7.z.strictObject({
|
|
521
|
+
resource_id: ResourceId,
|
|
522
|
+
pid: import_zod7.z.int().min(1),
|
|
523
|
+
shell: import_zod7.z.string().max(512),
|
|
524
|
+
cwd: AbsPath,
|
|
525
|
+
// live, from OSC 7 / process cwd
|
|
526
|
+
title: import_zod7.z.string().max(256),
|
|
527
|
+
name: import_zod7.z.string().max(64).nullable(),
|
|
528
|
+
cols,
|
|
529
|
+
rows,
|
|
530
|
+
state: import_zod7.z.enum(["running", "exited"]),
|
|
531
|
+
exit_code: import_zod7.z.int().nullable(),
|
|
532
|
+
exit_signal: Signal.nullable(),
|
|
533
|
+
last_seq: u32,
|
|
534
|
+
created_at_ms: epochMs,
|
|
535
|
+
controller_client_id: import_zod7.z.string().max(64).nullable(),
|
|
536
|
+
// null = orphaned (in grace period)
|
|
537
|
+
attached_clients: nonNegInt,
|
|
538
|
+
orphan_expires_at_ms: epochMs.nullable()
|
|
539
|
+
});
|
|
540
|
+
var ExecPtyStartResult = PtyInfo;
|
|
541
|
+
var ExecPtyWriteParams = import_zod7.z.strictObject({ resource_id: ResourceId, data: import_zod7.z.string().min(1).max(65536) });
|
|
542
|
+
var ExecPtyWriteResult = import_zod7.z.strictObject({ written: nonNegInt });
|
|
543
|
+
var ExecPtyResizeParams = import_zod7.z.strictObject({ resource_id: ResourceId, cols, rows });
|
|
544
|
+
var ExecPtyResizeResult = import_zod7.z.strictObject({ cols, rows });
|
|
545
|
+
var ExecPtySignalParams = import_zod7.z.strictObject({ resource_id: ResourceId, signal: Signal });
|
|
546
|
+
var ExecPtySignalResult = import_zod7.z.strictObject({ delivered: import_zod7.z.boolean() });
|
|
547
|
+
var ExecPtyKillParams = import_zod7.z.strictObject({ resource_id: ResourceId, grace_ms: import_zod7.z.int().min(0).max(3e4).default(2e3) });
|
|
548
|
+
var ExecPtyKillResult = import_zod7.z.strictObject({ exit_code: import_zod7.z.int().nullable(), exit_signal: Signal.nullable() });
|
|
549
|
+
var ExecListParams = import_zod7.z.strictObject({});
|
|
550
|
+
var ExecListResult = import_zod7.z.strictObject({ sessions: import_zod7.z.array(PtyInfo) });
|
|
551
|
+
var ExecRunParams = import_zod7.z.strictObject({
|
|
552
|
+
command: import_zod7.z.string().min(1).max(131072),
|
|
553
|
+
cwd: AbsPath.optional(),
|
|
554
|
+
cwd_key: import_zod7.z.string().max(128).optional(),
|
|
555
|
+
env: import_zod7.z.record(import_zod7.z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).max(128), import_zod7.z.string().max(32768)).optional(),
|
|
556
|
+
stdin: import_zod7.z.string().max(1048576).optional(),
|
|
557
|
+
timeout: import_zod7.z.int().min(1).max(3600).default(60),
|
|
558
|
+
// seconds, daemon parity
|
|
559
|
+
max_output_bytes: import_zod7.z.int().min(1024).max(8388608).default(1048576)
|
|
560
|
+
});
|
|
561
|
+
var ExecRunResult = import_zod7.z.strictObject({
|
|
562
|
+
exit_code: import_zod7.z.int(),
|
|
563
|
+
stdout: import_zod7.z.string(),
|
|
564
|
+
stderr: import_zod7.z.string(),
|
|
565
|
+
cwd: AbsPath,
|
|
566
|
+
duration_ms: nonNegInt,
|
|
567
|
+
timed_out: import_zod7.z.boolean(),
|
|
568
|
+
output_truncated: import_zod7.z.boolean()
|
|
569
|
+
});
|
|
570
|
+
var ExecExitedEvent = import_zod7.z.strictObject({ exit_code: import_zod7.z.int().nullable(), exit_signal: Signal.nullable() });
|
|
571
|
+
var ExecTitleEvent = import_zod7.z.strictObject({ title: import_zod7.z.string().max(256) });
|
|
572
|
+
var ExecCwdEvent = import_zod7.z.strictObject({ cwd: AbsPath });
|
|
573
|
+
|
|
574
|
+
// src/schema/session.ts
|
|
575
|
+
var import_zod8 = require("zod");
|
|
576
|
+
var SessionAttachParams = import_zod8.z.strictObject({
|
|
577
|
+
resource_id: ResourceId,
|
|
578
|
+
mode: import_zod8.z.enum(["control", "read_only"]).default("control"),
|
|
579
|
+
since_seq: u32.optional(),
|
|
580
|
+
window_bytes: import_zod8.z.int().min(4096).max(16777216).optional()
|
|
581
|
+
});
|
|
582
|
+
var ResourceInfo = import_zod8.z.discriminatedUnion("kind", [
|
|
583
|
+
import_zod8.z.strictObject({ kind: import_zod8.z.literal("pty"), pty: PtyInfo }),
|
|
584
|
+
import_zod8.z.strictObject({
|
|
585
|
+
kind: import_zod8.z.literal("watch"),
|
|
586
|
+
resource_id: ResourceId,
|
|
587
|
+
path: AbsPath,
|
|
588
|
+
orphan_expires_at_ms: epochMs.nullable()
|
|
589
|
+
})
|
|
590
|
+
]);
|
|
591
|
+
var SessionAttachResult = ResourceInfo;
|
|
592
|
+
var SessionDetachParams = import_zod8.z.strictObject({ resource_id: ResourceId });
|
|
593
|
+
var SessionDetachResult = import_zod8.z.strictObject({ orphan_expires_at_ms: epochMs.nullable() });
|
|
594
|
+
var SessionReleaseParams = import_zod8.z.strictObject({ resource_id: ResourceId });
|
|
595
|
+
var SessionReleaseResult = import_zod8.z.strictObject({ released: import_zod8.z.literal(true) });
|
|
596
|
+
var SessionListParams = import_zod8.z.strictObject({});
|
|
597
|
+
var SessionListResult = import_zod8.z.strictObject({ resources: import_zod8.z.array(ResourceInfo) });
|
|
598
|
+
var SessionControlChangedEvent = import_zod8.z.strictObject({ controller_client_id: import_zod8.z.string().max(64).nullable(), your_mode: import_zod8.z.enum(["control", "read_only"]) });
|
|
599
|
+
|
|
600
|
+
// src/schema/relay.ts
|
|
601
|
+
var import_zod9 = require("zod");
|
|
602
|
+
var RelayDeviceStatusEvent = import_zod9.z.strictObject({
|
|
603
|
+
online: import_zod9.z.boolean(),
|
|
604
|
+
since_ms: epochMs,
|
|
605
|
+
app_version: import_zod9.z.string().max(32).nullable(),
|
|
606
|
+
protocol_version: ProtocolVersion.nullable()
|
|
607
|
+
});
|
|
608
|
+
var RelayAuthExpiringEvent = import_zod9.z.strictObject({ expires_at_ms: epochMs });
|
|
609
|
+
var RelayCallRequest = import_zod9.z.strictObject({
|
|
610
|
+
cap: import_zod9.z.string().min(1).max(32),
|
|
611
|
+
op: import_zod9.z.string().min(1).max(64),
|
|
612
|
+
params: import_zod9.z.unknown(),
|
|
613
|
+
timeout_ms: import_zod9.z.int().min(100).max(37e5).default(7e4)
|
|
614
|
+
});
|
|
615
|
+
var RelayCallResponse = import_zod9.z.discriminatedUnion("ok", [
|
|
616
|
+
import_zod9.z.strictObject({ ok: import_zod9.z.literal(true), result: import_zod9.z.unknown() }),
|
|
617
|
+
import_zod9.z.strictObject({ ok: import_zod9.z.literal(false), error: ProtocolErrorBody })
|
|
618
|
+
]);
|
|
619
|
+
|
|
620
|
+
// src/schema/registry.ts
|
|
621
|
+
var FS_ERR = ["NOT_FOUND", "PERMISSION_DENIED_OS", "INVALID_PARAMS", "RESOURCE_BUSY", "INTERNAL"];
|
|
622
|
+
var OPS = {
|
|
623
|
+
"sysinfo.get": { params: SysinfoGetParams, result: SysinfoGetResult, kind: "unary", since: "1.0", errors: ["INTERNAL"] },
|
|
624
|
+
"fs.list": { params: FsListParams, result: FsListResult, kind: "unary", since: "1.0", errors: FS_ERR },
|
|
625
|
+
"fs.stat": { params: FsStatParams, result: FsStatResult, kind: "unary", since: "1.0", errors: FS_ERR },
|
|
626
|
+
"fs.read": { params: FsReadParams, result: FsReadResult, kind: "unary", since: "1.0", errors: [...FS_ERR, "PAYLOAD_TOO_LARGE"] },
|
|
627
|
+
"fs.read_stream": { params: FsReadStreamParams, result: FsReadStreamResult, kind: "stream", since: "1.0", errors: [...FS_ERR, "CANCELLED"] },
|
|
628
|
+
"fs.write": { params: FsWriteParams, result: FsWriteResult, kind: "unary", since: "1.0", errors: [...FS_ERR, "CONFLICT", "PAYLOAD_TOO_LARGE"] },
|
|
629
|
+
"fs.mkdir": { params: FsMkdirParams, result: FsMkdirResult, kind: "unary", since: "1.0", errors: [...FS_ERR, "CONFLICT"] },
|
|
630
|
+
"fs.move": { params: FsMoveParams, result: FsMoveResult, kind: "unary", since: "1.0", errors: [...FS_ERR, "CONFLICT"] },
|
|
631
|
+
"fs.delete": { params: FsDeleteParams, result: FsDeleteResult, kind: "unary", since: "1.0", errors: FS_ERR },
|
|
632
|
+
"fs.watch": { params: FsWatchParams, result: FsWatchResult, kind: "resource", since: "1.0", errors: [...FS_ERR, "RATE_LIMITED"] },
|
|
633
|
+
"fs.patch": { params: FsPatchParams, result: FsPatchResult, kind: "unary", since: "1.0", errors: [...FS_ERR, "CONFLICT"] },
|
|
634
|
+
"fs.search_paths": { params: FsSearchPathsParams, result: FsSearchPathsResult, kind: "unary", since: "1.0", errors: [...FS_ERR, "TIMEOUT"] },
|
|
635
|
+
"fs.search_content": { params: FsSearchContentParams, result: FsSearchContentResult, kind: "unary", since: "1.0", errors: [...FS_ERR, "TIMEOUT"] },
|
|
636
|
+
"exec.pty.start": { params: ExecPtyStartParams, result: ExecPtyStartResult, kind: "resource", since: "1.0", errors: ["INVALID_PARAMS", "NOT_FOUND", "RATE_LIMITED", "PLATFORM_UNSUPPORTED", "INTERNAL"] },
|
|
637
|
+
"exec.pty.write": { params: ExecPtyWriteParams, result: ExecPtyWriteResult, kind: "unary", since: "1.0", errors: ["RESOURCE_GONE", "AUTH_FORBIDDEN", "INVALID_PARAMS"] },
|
|
638
|
+
"exec.pty.resize": { params: ExecPtyResizeParams, result: ExecPtyResizeResult, kind: "unary", since: "1.0", errors: ["RESOURCE_GONE", "AUTH_FORBIDDEN"] },
|
|
639
|
+
"exec.pty.signal": { params: ExecPtySignalParams, result: ExecPtySignalResult, kind: "unary", since: "1.0", errors: ["RESOURCE_GONE", "AUTH_FORBIDDEN", "PLATFORM_UNSUPPORTED"] },
|
|
640
|
+
"exec.pty.kill": { params: ExecPtyKillParams, result: ExecPtyKillResult, kind: "unary", since: "1.0", errors: ["RESOURCE_GONE", "AUTH_FORBIDDEN"] },
|
|
641
|
+
"exec.list": { params: ExecListParams, result: ExecListResult, kind: "unary", since: "1.0", errors: ["INTERNAL"] },
|
|
642
|
+
"exec.run": { params: ExecRunParams, result: ExecRunResult, kind: "unary", since: "1.0", errors: ["INVALID_PARAMS", "NOT_FOUND", "TIMEOUT", "CANCELLED", "INTERNAL"] },
|
|
643
|
+
"session.attach": { params: SessionAttachParams, result: SessionAttachResult, kind: "stream", since: "1.0", errors: ["RESOURCE_GONE", "AUTH_FORBIDDEN", "RESOURCE_BUSY"] },
|
|
644
|
+
"session.detach": { params: SessionDetachParams, result: SessionDetachResult, kind: "unary", since: "1.0", errors: ["RESOURCE_GONE"] },
|
|
645
|
+
"session.release": { params: SessionReleaseParams, result: SessionReleaseResult, kind: "unary", since: "1.0", errors: ["RESOURCE_GONE", "AUTH_FORBIDDEN"] },
|
|
646
|
+
"session.list": { params: SessionListParams, result: SessionListResult, kind: "unary", since: "1.0", errors: ["INTERNAL"] }
|
|
647
|
+
};
|
|
648
|
+
var EVENTS = {
|
|
649
|
+
"fs.changed": FsChangedEvent,
|
|
650
|
+
"exec.exited": ExecExitedEvent,
|
|
651
|
+
"exec.title": ExecTitleEvent,
|
|
652
|
+
"exec.cwd": ExecCwdEvent,
|
|
653
|
+
"session.control_changed": SessionControlChangedEvent,
|
|
654
|
+
"relay.device_status": RelayDeviceStatusEvent,
|
|
655
|
+
"relay.auth_expiring": RelayAuthExpiringEvent
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
// src/client/client.ts
|
|
659
|
+
var WS_OPEN = 1;
|
|
660
|
+
var TERMINAL_CLOSE = /* @__PURE__ */ new Map([
|
|
661
|
+
[CloseCode.DEVICE_REVOKED, ["AUTH_DEVICE_REVOKED", "This device was revoked"]],
|
|
662
|
+
[CloseCode.AUTH_FORBIDDEN, ["AUTH_FORBIDDEN", "Not this device's owner"]],
|
|
663
|
+
[CloseCode.DEVICE_NOT_FOUND, ["NOT_FOUND", "Device not found"]],
|
|
664
|
+
[CloseCode.REPLACED, ["CONFLICT", "Replaced by a newer connection"]],
|
|
665
|
+
[CloseCode.UPGRADE_REQUIRED, ["PROTOCOL_VERSION_UNSUPPORTED", "Protocol upgrade required"]]
|
|
666
|
+
]);
|
|
667
|
+
var defaultTimers = {
|
|
668
|
+
setTimeout: (fn, ms) => globalThis.setTimeout(fn, ms),
|
|
669
|
+
clearTimeout: (handle) => globalThis.clearTimeout(handle),
|
|
670
|
+
now: () => Date.now()
|
|
671
|
+
};
|
|
672
|
+
function defaultWebSocket() {
|
|
673
|
+
return (url, protocols) => {
|
|
674
|
+
const Ctor = globalThis.WebSocket;
|
|
675
|
+
if (!Ctor) {
|
|
676
|
+
throw new DesktopProtocolError("PLATFORM_UNSUPPORTED", "No global WebSocket \u2014 pass options.webSocket");
|
|
677
|
+
}
|
|
678
|
+
return new Ctor(url, protocols);
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
function capOf(op) {
|
|
682
|
+
const dot = op.indexOf(".");
|
|
683
|
+
return { cap: op.slice(0, dot), op: op.slice(dot + 1) };
|
|
684
|
+
}
|
|
685
|
+
function fromBody(body) {
|
|
686
|
+
return body.data === void 0 ? new DesktopProtocolError(body.code, body.message, { retryable: body.retryable }) : new DesktopProtocolError(body.code, body.message, { retryable: body.retryable, data: body.data });
|
|
687
|
+
}
|
|
688
|
+
function toBytes(data) {
|
|
689
|
+
if (data instanceof ArrayBuffer) return new Uint8Array(data);
|
|
690
|
+
if (ArrayBuffer.isView(data)) return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
691
|
+
return null;
|
|
692
|
+
}
|
|
693
|
+
function createDesktopClient(options) {
|
|
694
|
+
const timers = options.timers ?? defaultTimers;
|
|
695
|
+
const makeSocket = options.webSocket ?? defaultWebSocket();
|
|
696
|
+
const heartbeatMs = options.heartbeatMs ?? 25e3;
|
|
697
|
+
const requestTimeoutMs = options.requestTimeoutMs ?? 7e4;
|
|
698
|
+
const protocolVersion = options.protocolVersion ?? `${PROTOCOL_MAJOR}.${PROTOCOL_MINOR}`;
|
|
699
|
+
const backoff = (0, import_realtime.createBackoff)({ ...options.backoff, now: timers.now });
|
|
700
|
+
const diagnose = options.onDiagnostic ?? ((d) => {
|
|
701
|
+
console.warn("[desktop-protocol]", d.kind, d);
|
|
702
|
+
});
|
|
703
|
+
let state = {
|
|
704
|
+
status: "idle",
|
|
705
|
+
welcome: null,
|
|
706
|
+
attempts: 0,
|
|
707
|
+
retryInMs: null,
|
|
708
|
+
lastError: null,
|
|
709
|
+
lastCloseCode: null
|
|
710
|
+
};
|
|
711
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
712
|
+
const readyWaiters = /* @__PURE__ */ new Set();
|
|
713
|
+
const pending = /* @__PURE__ */ new Map();
|
|
714
|
+
const streams = /* @__PURE__ */ new Map();
|
|
715
|
+
const bySid = /* @__PURE__ */ new Map();
|
|
716
|
+
const eventHandlers = /* @__PURE__ */ new Map();
|
|
717
|
+
let socket = null;
|
|
718
|
+
let socketGeneration = 0;
|
|
719
|
+
let nextId = 0;
|
|
720
|
+
let helloId = null;
|
|
721
|
+
let reconnectTimer = null;
|
|
722
|
+
let heartbeatTimer = null;
|
|
723
|
+
let lastHeardAt = 0;
|
|
724
|
+
let closedByUser = false;
|
|
725
|
+
const newId = (prefix) => `${prefix}${(++nextId).toString(36)}`;
|
|
726
|
+
function setState(patch) {
|
|
727
|
+
state = { ...state, ...patch };
|
|
728
|
+
for (const listener of [...listeners]) {
|
|
729
|
+
try {
|
|
730
|
+
listener();
|
|
731
|
+
} catch (error) {
|
|
732
|
+
diagnose({ kind: "handler_threw", where: "subscribe listener", error });
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
function sendJson(message) {
|
|
737
|
+
if (!socket || socket.readyState !== WS_OPEN) return false;
|
|
738
|
+
socket.send(JSON.stringify(message));
|
|
739
|
+
return true;
|
|
740
|
+
}
|
|
741
|
+
function connect() {
|
|
742
|
+
if (state.status === "connecting" || state.status === "handshaking" || state.status === "open") return;
|
|
743
|
+
closedByUser = false;
|
|
744
|
+
if (reconnectTimer !== null) {
|
|
745
|
+
timers.clearTimeout(reconnectTimer);
|
|
746
|
+
reconnectTimer = null;
|
|
747
|
+
}
|
|
748
|
+
void openSocket();
|
|
749
|
+
}
|
|
750
|
+
async function openSocket() {
|
|
751
|
+
const generation = ++socketGeneration;
|
|
752
|
+
setState({ status: "connecting", retryInMs: null });
|
|
753
|
+
let ws;
|
|
754
|
+
try {
|
|
755
|
+
const url = typeof options.url === "function" ? await options.url() : options.url;
|
|
756
|
+
const protocols = options.protocols ? await options.protocols() : [];
|
|
757
|
+
if (generation !== socketGeneration || closedByUser) return;
|
|
758
|
+
ws = makeSocket(url, protocols);
|
|
759
|
+
} catch (error) {
|
|
760
|
+
if (generation !== socketGeneration) return;
|
|
761
|
+
const err = error instanceof DesktopProtocolError ? error : new DesktopProtocolError("INTERNAL", "Could not open the connection", { retryable: true, cause: error });
|
|
762
|
+
onSocketGone(generation, null, err);
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
socket = ws;
|
|
766
|
+
ws.binaryType = "arraybuffer";
|
|
767
|
+
ws.onopen = () => {
|
|
768
|
+
if (generation !== socketGeneration) return;
|
|
769
|
+
lastHeardAt = timers.now();
|
|
770
|
+
helloId = newId("h");
|
|
771
|
+
setState({ status: "handshaking" });
|
|
772
|
+
sendJson({ type: "hello", id: helloId, protocolVersion, clientType: options.clientType, clientVersion: options.clientVersion });
|
|
773
|
+
scheduleHeartbeat(generation);
|
|
774
|
+
};
|
|
775
|
+
ws.onmessage = (event) => {
|
|
776
|
+
if (generation !== socketGeneration) return;
|
|
777
|
+
lastHeardAt = timers.now();
|
|
778
|
+
onMessage(event.data);
|
|
779
|
+
};
|
|
780
|
+
ws.onerror = () => {
|
|
781
|
+
};
|
|
782
|
+
ws.onclose = (event) => {
|
|
783
|
+
if (generation !== socketGeneration) return;
|
|
784
|
+
onSocketGone(generation, event.code, null);
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
function scheduleHeartbeat(generation) {
|
|
788
|
+
if (heartbeatTimer !== null) timers.clearTimeout(heartbeatTimer);
|
|
789
|
+
heartbeatTimer = timers.setTimeout(() => {
|
|
790
|
+
heartbeatTimer = null;
|
|
791
|
+
if (generation !== socketGeneration || !socket) return;
|
|
792
|
+
const silentMs = timers.now() - lastHeardAt;
|
|
793
|
+
if (silentMs >= heartbeatMs * 2) {
|
|
794
|
+
diagnose({ kind: "heartbeat_timeout", silentMs });
|
|
795
|
+
const dead = socket;
|
|
796
|
+
onSocketGone(generation, null, new DesktopProtocolError("TIMEOUT", "Connection went silent", { retryable: true }));
|
|
797
|
+
try {
|
|
798
|
+
dead.close(CloseCode.NORMAL, "heartbeat timeout");
|
|
799
|
+
} catch {
|
|
800
|
+
}
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
if (socket.readyState === WS_OPEN) socket.send(PING_TEXT);
|
|
804
|
+
scheduleHeartbeat(generation);
|
|
805
|
+
}, heartbeatMs);
|
|
806
|
+
}
|
|
807
|
+
function onSocketGone(generation, code, error) {
|
|
808
|
+
if (generation !== socketGeneration) return;
|
|
809
|
+
socketGeneration++;
|
|
810
|
+
if (socket) {
|
|
811
|
+
socket.onopen = socket.onmessage = socket.onclose = socket.onerror = null;
|
|
812
|
+
}
|
|
813
|
+
socket = null;
|
|
814
|
+
helloId = null;
|
|
815
|
+
if (heartbeatTimer !== null) {
|
|
816
|
+
timers.clearTimeout(heartbeatTimer);
|
|
817
|
+
heartbeatTimer = null;
|
|
818
|
+
}
|
|
819
|
+
const terminal = code === null ? void 0 : TERMINAL_CLOSE.get(code);
|
|
820
|
+
const lost = error ?? (terminal ? new DesktopProtocolError(terminal[0], terminal[1]) : new DesktopProtocolError("DEVICE_OFFLINE", "Connection lost", { retryable: true }));
|
|
821
|
+
for (const [id, p] of pending) {
|
|
822
|
+
pending.delete(id);
|
|
823
|
+
p.cleanup();
|
|
824
|
+
p.reject(lost);
|
|
825
|
+
}
|
|
826
|
+
bySid.clear();
|
|
827
|
+
for (const [id, s] of streams) {
|
|
828
|
+
if (s.resourceId !== null && terminal === void 0 && !closedByUser) {
|
|
829
|
+
s.sid = null;
|
|
830
|
+
s.reattaching = false;
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
streams.delete(id);
|
|
834
|
+
endStream(s, null, lost);
|
|
835
|
+
}
|
|
836
|
+
if (closedByUser || terminal) {
|
|
837
|
+
setState({ status: "closed", welcome: null, lastError: closedByUser ? state.lastError : lost, lastCloseCode: code, retryInMs: null });
|
|
838
|
+
rejectReadyWaiters(lost);
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
const delay = backoff.nextDelayMs();
|
|
842
|
+
if (backoff.isAlarming()) diagnose({ kind: "reconnect_alarm", attempts: backoff.attempts() });
|
|
843
|
+
setState({ status: "reconnecting", welcome: null, attempts: backoff.attempts(), retryInMs: delay, lastError: lost, lastCloseCode: code });
|
|
844
|
+
reconnectTimer = timers.setTimeout(() => {
|
|
845
|
+
reconnectTimer = null;
|
|
846
|
+
if (!closedByUser) void openSocket();
|
|
847
|
+
}, delay);
|
|
848
|
+
}
|
|
849
|
+
function rejectReadyWaiters(error) {
|
|
850
|
+
for (const w of [...readyWaiters]) {
|
|
851
|
+
readyWaiters.delete(w);
|
|
852
|
+
w.reject(error);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
function close() {
|
|
856
|
+
closedByUser = true;
|
|
857
|
+
if (reconnectTimer !== null) {
|
|
858
|
+
timers.clearTimeout(reconnectTimer);
|
|
859
|
+
reconnectTimer = null;
|
|
860
|
+
}
|
|
861
|
+
const cancelled = new DesktopProtocolError("CANCELLED", "Client closed");
|
|
862
|
+
const ws = socket;
|
|
863
|
+
if (ws) {
|
|
864
|
+
onSocketGone(socketGeneration, CloseCode.NORMAL, cancelled);
|
|
865
|
+
try {
|
|
866
|
+
ws.close(CloseCode.NORMAL, "client closed");
|
|
867
|
+
} catch {
|
|
868
|
+
}
|
|
869
|
+
} else {
|
|
870
|
+
for (const [id, s] of streams) {
|
|
871
|
+
streams.delete(id);
|
|
872
|
+
endStream(s, null, cancelled);
|
|
873
|
+
}
|
|
874
|
+
setState({ status: "closed", retryInMs: null });
|
|
875
|
+
rejectReadyWaiters(cancelled);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
function onMessage(data) {
|
|
879
|
+
if (typeof data === "string") {
|
|
880
|
+
if (data === PONG_TEXT) return;
|
|
881
|
+
let json;
|
|
882
|
+
try {
|
|
883
|
+
json = JSON.parse(data);
|
|
884
|
+
} catch {
|
|
885
|
+
diagnose({ kind: "malformed_message", detail: "not JSON" });
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
const parsed = CoreToClientMsg.safeParse(json);
|
|
889
|
+
if (!parsed.success) {
|
|
890
|
+
diagnose({ kind: "malformed_message", detail: parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ") });
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
onEnvelope(parsed.data);
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
const bytes = toBytes(data);
|
|
897
|
+
if (bytes === null) {
|
|
898
|
+
diagnose({ kind: "malformed_message", detail: "binary message is not an ArrayBuffer/view (set binaryType)" });
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
let frame;
|
|
902
|
+
try {
|
|
903
|
+
frame = decodeFrame(bytes);
|
|
904
|
+
} catch (error) {
|
|
905
|
+
diagnose({ kind: "malformed_message", detail: error instanceof Error ? error.message : String(error) });
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
onFrame(frame.header, frame.payload);
|
|
909
|
+
}
|
|
910
|
+
function onEnvelope(msg) {
|
|
911
|
+
switch (msg.type) {
|
|
912
|
+
case "response": {
|
|
913
|
+
if (msg.id === helloId) return onWelcome(msg.result);
|
|
914
|
+
const s = streams.get(msg.id);
|
|
915
|
+
if (s) return onStreamResult(s, msg.result);
|
|
916
|
+
const p = pending.get(msg.id);
|
|
917
|
+
if (!p) return diagnose({ kind: "unknown_request", id: msg.id, type: msg.type });
|
|
918
|
+
pending.delete(msg.id);
|
|
919
|
+
p.cleanup();
|
|
920
|
+
p.resolve(msg.result);
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
case "error": {
|
|
924
|
+
const error = fromBody(msg.error);
|
|
925
|
+
if (msg.id === helloId) return onHelloRefused(error);
|
|
926
|
+
const s = streams.get(msg.id);
|
|
927
|
+
if (s) {
|
|
928
|
+
if (s.reattaching && s.resourceId !== null) diagnose({ kind: "reattach_failed", resourceId: s.resourceId, error });
|
|
929
|
+
streams.delete(msg.id);
|
|
930
|
+
if (s.sid !== null) bySid.delete(s.sid);
|
|
931
|
+
return endStream(s, null, error);
|
|
932
|
+
}
|
|
933
|
+
const p = pending.get(msg.id);
|
|
934
|
+
if (!p) return diagnose({ kind: "unknown_request", id: msg.id, type: msg.type });
|
|
935
|
+
pending.delete(msg.id);
|
|
936
|
+
p.cleanup();
|
|
937
|
+
p.reject(error);
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
case "stream": {
|
|
941
|
+
const s = streams.get(msg.id);
|
|
942
|
+
if (!s) return diagnose({ kind: "unknown_request", id: msg.id, type: msg.type });
|
|
943
|
+
onStreamMeta(s, msg.meta);
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
case "end": {
|
|
947
|
+
const s = streams.get(msg.id);
|
|
948
|
+
if (!s) return diagnose({ kind: "unknown_request", id: msg.id, type: msg.type });
|
|
949
|
+
void s.chain.then(() => {
|
|
950
|
+
streams.delete(s.id);
|
|
951
|
+
if (s.sid !== null) bySid.delete(s.sid);
|
|
952
|
+
endStream(s, msg.reason ?? "completed", null);
|
|
953
|
+
});
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
case "credit": {
|
|
957
|
+
const s = streams.get(msg.id);
|
|
958
|
+
if (!s) return diagnose({ kind: "unknown_request", id: msg.id, type: msg.type });
|
|
959
|
+
s.sendWindow += msg.n;
|
|
960
|
+
flushSend(s);
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
case "event": {
|
|
964
|
+
const key = `${msg.cap}.${msg.name}`;
|
|
965
|
+
const schema = EVENTS[key];
|
|
966
|
+
if (!schema) return diagnose({ kind: "malformed_message", detail: `unknown event ${key}` });
|
|
967
|
+
const payload = schema.safeParse(msg.payload);
|
|
968
|
+
if (!payload.success) return diagnose({ kind: "malformed_message", detail: `event ${key}: invalid payload` });
|
|
969
|
+
if (key === "relay.auth_expiring" && options.getToken) {
|
|
970
|
+
options.getToken().then(reauth, (error) => diagnose({ kind: "reauth_failed", error }));
|
|
971
|
+
}
|
|
972
|
+
for (const handler of [...eventHandlers.get(key) ?? []]) {
|
|
973
|
+
try {
|
|
974
|
+
handler(payload.data, msg.resourceId);
|
|
975
|
+
} catch (error) {
|
|
976
|
+
diagnose({ kind: "handler_threw", where: `event ${key}`, error });
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
function onWelcome(result) {
|
|
984
|
+
const welcome = Welcome.safeParse(result);
|
|
985
|
+
if (!welcome.success) {
|
|
986
|
+
onHelloRefused(new DesktopProtocolError("PROTOCOL_ERROR", "Malformed welcome"));
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
helloId = null;
|
|
990
|
+
backoff.markConnected();
|
|
991
|
+
setState({ status: "open", welcome: welcome.data, attempts: 0, retryInMs: null, lastError: null });
|
|
992
|
+
for (const w of [...readyWaiters]) {
|
|
993
|
+
readyWaiters.delete(w);
|
|
994
|
+
w.resolve(welcome.data);
|
|
995
|
+
}
|
|
996
|
+
for (const s of [...streams.values()]) {
|
|
997
|
+
if (s.sid === null && s.resourceId !== null && !s.ended) reattach(s, welcome.data);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
function onHelloRefused(error) {
|
|
1001
|
+
const ws = socket;
|
|
1002
|
+
onSocketGone(socketGeneration, error.code === "PROTOCOL_VERSION_UNSUPPORTED" ? CloseCode.UPGRADE_REQUIRED : null, error);
|
|
1003
|
+
try {
|
|
1004
|
+
ws?.close(CloseCode.NORMAL, "hello refused");
|
|
1005
|
+
} catch {
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
function onStreamResult(s, result) {
|
|
1009
|
+
if (s.reattaching) {
|
|
1010
|
+
s.reattaching = false;
|
|
1011
|
+
safeCall("onReattached", () => s.handlers.onReattached?.());
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
const rid = result?.resource_id;
|
|
1015
|
+
if (typeof rid === "string") s.resourceId = rid;
|
|
1016
|
+
if (!s.resultSettled) {
|
|
1017
|
+
s.resultSettled = true;
|
|
1018
|
+
s.resolveResult(result);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
function onStreamMeta(s, meta) {
|
|
1022
|
+
if (meta.kind === "open") {
|
|
1023
|
+
if (s.sid !== null) bySid.delete(s.sid);
|
|
1024
|
+
s.sid = meta.sid;
|
|
1025
|
+
s.windowBytes = meta.window_bytes;
|
|
1026
|
+
if (meta.resource_id !== void 0) s.resourceId = meta.resource_id;
|
|
1027
|
+
bySid.set(meta.sid, s);
|
|
1028
|
+
flushSend(s);
|
|
1029
|
+
} else {
|
|
1030
|
+
s.expectSeq = null;
|
|
1031
|
+
}
|
|
1032
|
+
safeCall("onMeta", () => s.handlers.onMeta?.(meta));
|
|
1033
|
+
}
|
|
1034
|
+
function onFrame(header, payload) {
|
|
1035
|
+
const s = bySid.get(header.sid);
|
|
1036
|
+
if (!s) {
|
|
1037
|
+
diagnose({ kind: "unknown_stream", sid: header.sid });
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
const isData = header.channel === FrameChannel.DATA || header.channel === FrameChannel.STDERR;
|
|
1041
|
+
const isSnapshot = header.channel === FrameChannel.SNAPSHOT;
|
|
1042
|
+
if (!isData && !isSnapshot) {
|
|
1043
|
+
diagnose({ kind: "malformed_message", detail: `channel ${header.channel} is not client-bound` });
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
if (isData) {
|
|
1047
|
+
if (s.expectSeq !== null && header.seq !== s.expectSeq) {
|
|
1048
|
+
diagnose({ kind: "seq_gap", id: s.id, expected: s.expectSeq, received: header.seq });
|
|
1049
|
+
}
|
|
1050
|
+
s.expectSeq = header.seq + 1;
|
|
1051
|
+
}
|
|
1052
|
+
const bytes = payload.slice();
|
|
1053
|
+
const handler = isData ? s.handlers.onData : s.handlers.onSnapshot;
|
|
1054
|
+
s.chain = s.chain.then(async () => {
|
|
1055
|
+
if (s.ended) return;
|
|
1056
|
+
try {
|
|
1057
|
+
await handler?.(bytes, header);
|
|
1058
|
+
} catch (error) {
|
|
1059
|
+
diagnose({ kind: "handler_threw", where: isData ? "onData" : "onSnapshot", error });
|
|
1060
|
+
}
|
|
1061
|
+
if (isData) s.lastSeq = header.seq;
|
|
1062
|
+
grantCredit(s, bytes.byteLength);
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
function grantCredit(s, consumed) {
|
|
1066
|
+
s.consumedUncredited += consumed;
|
|
1067
|
+
if (s.consumedUncredited >= Math.max(1, Math.floor(s.windowBytes / 4))) {
|
|
1068
|
+
if (sendJson({ type: "credit", id: s.id, n: s.consumedUncredited })) s.consumedUncredited = 0;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
function flushSend(s) {
|
|
1072
|
+
if (s.sid === null || !socket || socket.readyState !== WS_OPEN) return;
|
|
1073
|
+
const maxPayload = state.welcome?.limits.max_frame_payload_bytes ?? DEFAULT_MAX_FRAME_PAYLOAD_BYTES;
|
|
1074
|
+
while (s.sendQueue.length > 0 && s.sendWindow > 0) {
|
|
1075
|
+
const head = s.sendQueue[0];
|
|
1076
|
+
const n = Math.min(head.byteLength, s.sendWindow, maxPayload);
|
|
1077
|
+
const chunk = head.subarray(0, n);
|
|
1078
|
+
if (n === head.byteLength) s.sendQueue.shift();
|
|
1079
|
+
else s.sendQueue[0] = head.subarray(n);
|
|
1080
|
+
s.sendWindow -= n;
|
|
1081
|
+
s.sendSeq += 1;
|
|
1082
|
+
socket.send(encodeFrame({ version: FRAME_VERSION, flags: 0, channel: FrameChannel.STDIN, cid: 0, sid: s.sid, seq: s.sendSeq }, chunk));
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
function endStream(s, reason, error) {
|
|
1086
|
+
if (s.ended) return;
|
|
1087
|
+
s.ended = true;
|
|
1088
|
+
s.sendQueue = [];
|
|
1089
|
+
if (!s.resultSettled) {
|
|
1090
|
+
s.resultSettled = true;
|
|
1091
|
+
s.rejectResult(error ?? new DesktopProtocolError("INTERNAL", "Stream ended before its result"));
|
|
1092
|
+
}
|
|
1093
|
+
if (error) s.rejectDone(error);
|
|
1094
|
+
else s.resolveDone(reason ?? "completed");
|
|
1095
|
+
}
|
|
1096
|
+
function reattach(s, welcome) {
|
|
1097
|
+
const id = newId("a");
|
|
1098
|
+
streams.delete(s.id);
|
|
1099
|
+
s.id = id;
|
|
1100
|
+
s.reattaching = true;
|
|
1101
|
+
s.expectSeq = s.lastSeq > 0 ? s.lastSeq + 1 : null;
|
|
1102
|
+
s.consumedUncredited = 0;
|
|
1103
|
+
s.sendWindow = welcome.limits.default_window_bytes;
|
|
1104
|
+
s.sendSeq = 0;
|
|
1105
|
+
streams.set(id, s);
|
|
1106
|
+
const params = { resource_id: s.resourceId, mode: s.options.mode ?? "control" };
|
|
1107
|
+
if (s.lastSeq > 0) params.since_seq = s.lastSeq;
|
|
1108
|
+
if (s.options.windowBytes !== void 0) params.window_bytes = s.options.windowBytes;
|
|
1109
|
+
sendJson({ id, type: "request", cap: "session", op: "attach", params });
|
|
1110
|
+
}
|
|
1111
|
+
function safeCall(where, fn) {
|
|
1112
|
+
try {
|
|
1113
|
+
fn();
|
|
1114
|
+
} catch (error) {
|
|
1115
|
+
diagnose({ kind: "handler_threw", where, error });
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
function ready(opts = {}) {
|
|
1119
|
+
if (state.status === "open" && state.welcome) return Promise.resolve(state.welcome);
|
|
1120
|
+
if (state.status === "closed") {
|
|
1121
|
+
return Promise.reject(state.lastError ?? new DesktopProtocolError("CANCELLED", "Client is closed"));
|
|
1122
|
+
}
|
|
1123
|
+
if (state.status === "idle") connect();
|
|
1124
|
+
return new Promise((resolve, reject) => {
|
|
1125
|
+
const waiter = {
|
|
1126
|
+
resolve: (w) => {
|
|
1127
|
+
done();
|
|
1128
|
+
resolve(w);
|
|
1129
|
+
},
|
|
1130
|
+
reject: (e) => {
|
|
1131
|
+
done();
|
|
1132
|
+
reject(e);
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
const timeoutMs = opts.timeoutMs ?? requestTimeoutMs;
|
|
1136
|
+
const timer = timers.setTimeout(() => waiter.reject(new DesktopProtocolError("DEVICE_OFFLINE", "Device not reachable", { retryable: true })), timeoutMs);
|
|
1137
|
+
const onAbort = () => waiter.reject(new DesktopProtocolError("CANCELLED", "Aborted"));
|
|
1138
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1139
|
+
function done() {
|
|
1140
|
+
readyWaiters.delete(waiter);
|
|
1141
|
+
timers.clearTimeout(timer);
|
|
1142
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
1143
|
+
}
|
|
1144
|
+
readyWaiters.add(waiter);
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
async function request(op, params, opts = {}) {
|
|
1148
|
+
if (OPS[op].kind !== "unary") {
|
|
1149
|
+
throw new DesktopProtocolError("INVALID_PARAMS", `${op} is a ${OPS[op].kind} op \u2014 use stream()`);
|
|
1150
|
+
}
|
|
1151
|
+
await ready(opts);
|
|
1152
|
+
const id = newId("r");
|
|
1153
|
+
return new Promise((resolve, reject) => {
|
|
1154
|
+
const timeoutMs = opts.timeoutMs ?? requestTimeoutMs;
|
|
1155
|
+
const timer = timers.setTimeout(() => {
|
|
1156
|
+
pending.delete(id);
|
|
1157
|
+
cleanup();
|
|
1158
|
+
sendJson({ id, type: "cancel" });
|
|
1159
|
+
reject(new DesktopProtocolError("TIMEOUT", `${op} timed out`, { retryable: true }));
|
|
1160
|
+
}, timeoutMs);
|
|
1161
|
+
const onAbort = () => {
|
|
1162
|
+
pending.delete(id);
|
|
1163
|
+
cleanup();
|
|
1164
|
+
sendJson({ id, type: "cancel" });
|
|
1165
|
+
reject(new DesktopProtocolError("CANCELLED", "Aborted"));
|
|
1166
|
+
};
|
|
1167
|
+
function cleanup() {
|
|
1168
|
+
timers.clearTimeout(timer);
|
|
1169
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
1170
|
+
}
|
|
1171
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1172
|
+
pending.set(id, { kind: "unary", op, resolve, reject, cleanup });
|
|
1173
|
+
if (!sendJson({ id, type: "request", ...capOf(op), params })) {
|
|
1174
|
+
pending.delete(id);
|
|
1175
|
+
cleanup();
|
|
1176
|
+
reject(new DesktopProtocolError("DEVICE_OFFLINE", "Connection lost", { retryable: true }));
|
|
1177
|
+
}
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
function stream(op, params, handlers = {}, opts = {}) {
|
|
1181
|
+
let resolveResult;
|
|
1182
|
+
let rejectResult;
|
|
1183
|
+
let resolveDone;
|
|
1184
|
+
let rejectDone;
|
|
1185
|
+
const result = new Promise((res, rej) => {
|
|
1186
|
+
resolveResult = res;
|
|
1187
|
+
rejectResult = rej;
|
|
1188
|
+
});
|
|
1189
|
+
const done = new Promise((res, rej) => {
|
|
1190
|
+
resolveDone = res;
|
|
1191
|
+
rejectDone = rej;
|
|
1192
|
+
});
|
|
1193
|
+
result.catch(() => void 0);
|
|
1194
|
+
done.catch(() => void 0);
|
|
1195
|
+
const s = {
|
|
1196
|
+
op,
|
|
1197
|
+
handlers,
|
|
1198
|
+
options: opts,
|
|
1199
|
+
id: newId("s"),
|
|
1200
|
+
sid: null,
|
|
1201
|
+
resourceId: null,
|
|
1202
|
+
lastSeq: 0,
|
|
1203
|
+
expectSeq: 1,
|
|
1204
|
+
windowBytes: opts.windowBytes ?? DEFAULT_WINDOW_BYTES,
|
|
1205
|
+
consumedUncredited: 0,
|
|
1206
|
+
sendWindow: DEFAULT_WINDOW_BYTES,
|
|
1207
|
+
sendSeq: 0,
|
|
1208
|
+
sendQueue: [],
|
|
1209
|
+
chain: Promise.resolve(),
|
|
1210
|
+
resultSettled: false,
|
|
1211
|
+
resolveResult,
|
|
1212
|
+
rejectResult,
|
|
1213
|
+
resolveDone,
|
|
1214
|
+
rejectDone,
|
|
1215
|
+
ended: false,
|
|
1216
|
+
reattaching: false
|
|
1217
|
+
};
|
|
1218
|
+
const start = async () => {
|
|
1219
|
+
let welcome;
|
|
1220
|
+
try {
|
|
1221
|
+
welcome = await ready(opts);
|
|
1222
|
+
} catch (error) {
|
|
1223
|
+
endStream(s, null, error);
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
if (s.ended) return;
|
|
1227
|
+
s.sendWindow = welcome.limits.default_window_bytes;
|
|
1228
|
+
if (opts.windowBytes === void 0) s.windowBytes = welcome.limits.default_window_bytes;
|
|
1229
|
+
streams.set(s.id, s);
|
|
1230
|
+
const wire = { ...params };
|
|
1231
|
+
if (opts.windowBytes !== void 0 && "window_bytes" in OPS[op].params.shape) wire.window_bytes = opts.windowBytes;
|
|
1232
|
+
if (!sendJson({ id: s.id, type: "request", ...capOf(op), params: wire })) {
|
|
1233
|
+
streams.delete(s.id);
|
|
1234
|
+
endStream(s, null, new DesktopProtocolError("DEVICE_OFFLINE", "Connection lost", { retryable: true }));
|
|
1235
|
+
}
|
|
1236
|
+
};
|
|
1237
|
+
void start();
|
|
1238
|
+
opts.signal?.addEventListener("abort", () => handle.cancel(), { once: true });
|
|
1239
|
+
const encoder = new TextEncoder();
|
|
1240
|
+
const handle = {
|
|
1241
|
+
get id() {
|
|
1242
|
+
return s.id;
|
|
1243
|
+
},
|
|
1244
|
+
result,
|
|
1245
|
+
done,
|
|
1246
|
+
get resourceId() {
|
|
1247
|
+
return s.resourceId;
|
|
1248
|
+
},
|
|
1249
|
+
get lastSeq() {
|
|
1250
|
+
return s.lastSeq;
|
|
1251
|
+
},
|
|
1252
|
+
write(data) {
|
|
1253
|
+
if (s.ended) throw new DesktopProtocolError("RESOURCE_GONE", "Stream has ended");
|
|
1254
|
+
const bytes = typeof data === "string" ? encoder.encode(data) : data;
|
|
1255
|
+
if (bytes.byteLength === 0) return;
|
|
1256
|
+
s.sendQueue.push(bytes);
|
|
1257
|
+
flushSend(s);
|
|
1258
|
+
},
|
|
1259
|
+
cancel() {
|
|
1260
|
+
if (s.ended) return;
|
|
1261
|
+
if (!sendJson({ id: s.id, type: "cancel" })) {
|
|
1262
|
+
streams.delete(s.id);
|
|
1263
|
+
endStream(s, "cancelled", null);
|
|
1264
|
+
}
|
|
1265
|
+
},
|
|
1266
|
+
async detach() {
|
|
1267
|
+
if (s.ended) return;
|
|
1268
|
+
if (s.resourceId === null) throw new DesktopProtocolError("INVALID_PARAMS", "Only resource streams can be detached");
|
|
1269
|
+
const resourceId = s.resourceId;
|
|
1270
|
+
streams.delete(s.id);
|
|
1271
|
+
if (s.sid !== null) bySid.delete(s.sid);
|
|
1272
|
+
endStream(s, "detached", null);
|
|
1273
|
+
await request("session.detach", { resource_id: resourceId });
|
|
1274
|
+
}
|
|
1275
|
+
};
|
|
1276
|
+
return handle;
|
|
1277
|
+
}
|
|
1278
|
+
function on(name, handler) {
|
|
1279
|
+
let set = eventHandlers.get(name);
|
|
1280
|
+
if (!set) {
|
|
1281
|
+
set = /* @__PURE__ */ new Set();
|
|
1282
|
+
eventHandlers.set(name, set);
|
|
1283
|
+
}
|
|
1284
|
+
set.add(handler);
|
|
1285
|
+
return () => {
|
|
1286
|
+
set.delete(handler);
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
function reauth(token) {
|
|
1290
|
+
sendJson({ type: "relay", op: "reauth", token });
|
|
1291
|
+
}
|
|
1292
|
+
return {
|
|
1293
|
+
connect,
|
|
1294
|
+
close,
|
|
1295
|
+
getState: () => state,
|
|
1296
|
+
subscribe(listener) {
|
|
1297
|
+
listeners.add(listener);
|
|
1298
|
+
return () => {
|
|
1299
|
+
listeners.delete(listener);
|
|
1300
|
+
};
|
|
1301
|
+
},
|
|
1302
|
+
ready,
|
|
1303
|
+
request,
|
|
1304
|
+
stream,
|
|
1305
|
+
on,
|
|
1306
|
+
reauth
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
//# sourceMappingURL=client.cjs.map
|