@rynx-ai/remote-runtime-client 0.1.9 → 0.1.10-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser-inspect-client.d.ts +26 -0
- package/dist/browser-inspect-client.js +731 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +8 -3
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type RuntimeBrowserInspectClosedReason, type RuntimeBrowserInspectMessage, type RuntimeBrowserInspectTarget } from "@rynx-ai/protocol/runtime-browser-inspect";
|
|
2
|
+
import { DirectRuntimeClientError } from "./errors.js";
|
|
3
|
+
export interface DirectRuntimeBrowserInspectClientOptions {
|
|
4
|
+
timeoutMs?: number;
|
|
5
|
+
/** Cancels only the connect/authenticate/open barrier. */
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
liveness?: {
|
|
8
|
+
pingIntervalMs?: number;
|
|
9
|
+
inactivityTimeoutMs?: number;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export interface DirectRuntimeBrowserInspectConnection extends AsyncIterable<RuntimeBrowserInspectMessage> {
|
|
13
|
+
readonly target: RuntimeBrowserInspectTarget;
|
|
14
|
+
readonly daemonInstanceId: string;
|
|
15
|
+
/** Resolves after local or remote transport closure. */
|
|
16
|
+
readonly closed: Promise<void>;
|
|
17
|
+
send(message: RuntimeBrowserInspectMessage): Promise<void>;
|
|
18
|
+
/** Idempotently close this dedicated attachment. */
|
|
19
|
+
close(): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
export declare class DirectRuntimeBrowserInspectError extends DirectRuntimeClientError {
|
|
22
|
+
readonly reason: RuntimeBrowserInspectClosedReason;
|
|
23
|
+
constructor(reason: RuntimeBrowserInspectClosedReason, message: string);
|
|
24
|
+
}
|
|
25
|
+
/** Authenticate a fresh V5 socket and open exactly one semantic page target. */
|
|
26
|
+
export declare function openDirectRuntimeBrowserInspect(untrustedCredential: unknown, untrustedTarget: unknown, options?: DirectRuntimeBrowserInspectClientOptions): Promise<DirectRuntimeBrowserInspectConnection>;
|
|
@@ -0,0 +1,731 @@
|
|
|
1
|
+
import { createHash, createPublicKey, randomBytes, sign, verify, } from "node:crypto";
|
|
2
|
+
import { decodeDirectRuntimeFrame, DIRECT_RUNTIME_BROWSER_INSPECT_BINDING_PROTOCOL, DIRECT_RUNTIME_BROWSER_INSPECT_PATH, DIRECT_RUNTIME_CLOSE_CODE, DIRECT_RUNTIME_MAX_HANDSHAKE_FRAME_BYTES, encodeClientAuthenticateCanonical, encodeClientAuthenticateProofPayload, encodeDirectRuntimeFrame, encodeServerAcceptedProofPayload, encodeServerHelloCanonical, encodeServerHelloProofPayload, } from "@rynx-ai/protocol/direct-runtime";
|
|
3
|
+
import { encodeRuntimeBrowserInspectOpenFrame, parseRuntimeBrowserInspectMessage, parseRuntimeBrowserInspectServerFrame, parseRuntimeBrowserInspectTarget, RUNTIME_BROWSER_INSPECT_MAX_CONTROL_FRAME_BYTES, RUNTIME_BROWSER_INSPECT_MAX_MESSAGE_BYTES, } from "@rynx-ai/protocol/runtime-browser-inspect";
|
|
4
|
+
import { REMOTE_RUNTIME_CORE_PROTOCOL } from "@rynx-ai/protocol/remote-runtime";
|
|
5
|
+
import { DIRECT_RUNTIME_E2EE_MAX_ENVELOPE_BYTES, generateClientKeyExchange, } from "@rynx-ai/remote-runtime-e2ee";
|
|
6
|
+
import WebSocket from "ws";
|
|
7
|
+
import { parseDirectRuntimeClientIdentity, parseDirectRuntimeCredential, } from "./credential.js";
|
|
8
|
+
import { clientError, DirectRuntimeClientError, } from "./errors.js";
|
|
9
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
10
|
+
const MIN_TIMEOUT_MS = 100;
|
|
11
|
+
const MAX_TIMEOUT_MS = 120_000;
|
|
12
|
+
const DEFAULT_PING_INTERVAL_MS = 10_000;
|
|
13
|
+
const DEFAULT_INACTIVITY_TIMEOUT_MS = 25_000;
|
|
14
|
+
const MAX_LIVENESS_INTERVAL_MS = 5 * 60_000;
|
|
15
|
+
const MAX_PRE_ACTIVATION_FRAMES = 4;
|
|
16
|
+
const MAX_PRE_ACTIVATION_BYTES = 128 * 1024;
|
|
17
|
+
const MAX_QUEUED_MESSAGE_BYTES = 32 * 1024 * 1024;
|
|
18
|
+
const MAX_TRANSPORT_BUFFERED_BYTES = 32 * 1024 * 1024;
|
|
19
|
+
export class DirectRuntimeBrowserInspectError extends DirectRuntimeClientError {
|
|
20
|
+
reason;
|
|
21
|
+
constructor(reason, message) {
|
|
22
|
+
super(inspectCloseErrorCode(reason), message);
|
|
23
|
+
this.reason = reason;
|
|
24
|
+
this.name = "DirectRuntimeBrowserInspectError";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Authenticate a fresh V5 socket and open exactly one semantic page target. */
|
|
28
|
+
export async function openDirectRuntimeBrowserInspect(untrustedCredential, untrustedTarget, options = {}) {
|
|
29
|
+
if (options.signal?.aborted) {
|
|
30
|
+
throw clientError("closed", "Browser Inspect opening was cancelled");
|
|
31
|
+
}
|
|
32
|
+
const credential = parseDirectRuntimeCredential(untrustedCredential);
|
|
33
|
+
const identity = parseDirectRuntimeClientIdentity(credential, "authentication_failed");
|
|
34
|
+
const target = parseRuntimeBrowserInspectTarget(untrustedTarget);
|
|
35
|
+
const openFrame = {
|
|
36
|
+
type: "browser.inspect.open",
|
|
37
|
+
...target,
|
|
38
|
+
};
|
|
39
|
+
const timeoutMs = parseTimeout(options.timeoutMs);
|
|
40
|
+
const socket = new BrowserInspectSocket(browserInspectEndpoint(credential.directEndpoint), timeoutMs, parseLiveness(options.liveness));
|
|
41
|
+
const abortOpening = () => {
|
|
42
|
+
socket.abort(clientError("closed", "Browser Inspect opening was cancelled"));
|
|
43
|
+
};
|
|
44
|
+
options.signal?.addEventListener("abort", abortOpening, { once: true });
|
|
45
|
+
const deadline = Date.now() + timeoutMs;
|
|
46
|
+
try {
|
|
47
|
+
await socket.open(remaining(deadline));
|
|
48
|
+
const keyExchange = generateClientKeyExchange(credential.serverEncryptionPublicKey);
|
|
49
|
+
await socket.startAuthentication(keyExchange.channel, encodeDirectRuntimeFrame(keyExchange.clientHello));
|
|
50
|
+
const ready = decodeHandshake(await socket.readAuthenticationFrame(remaining(deadline)), "e2ee.ready");
|
|
51
|
+
if (ready.type !== "e2ee.ready") {
|
|
52
|
+
throw clientError("protocol_error", "e2ee.ready must follow e2ee.client-hello");
|
|
53
|
+
}
|
|
54
|
+
const hello = decodeHandshake(await socket.readAuthenticationFrame(remaining(deadline)), "server.hello");
|
|
55
|
+
if (hello.type !== "server.hello") {
|
|
56
|
+
throw clientError("protocol_error", "Browser Inspect server.hello is missing");
|
|
57
|
+
}
|
|
58
|
+
assertInspectHello(hello, credential);
|
|
59
|
+
const helloFields = withoutSignature(hello);
|
|
60
|
+
const daemonPublicKey = createPublicKey({
|
|
61
|
+
key: Buffer.from(credential.identityPublicKey, "base64url"),
|
|
62
|
+
format: "der",
|
|
63
|
+
type: "spki",
|
|
64
|
+
});
|
|
65
|
+
if (!verify(null, encodeServerHelloProofPayload(keyExchange.channelBinding, helloFields), daemonPublicKey, Buffer.from(hello.signature, "base64url"))) {
|
|
66
|
+
throw clientError("authentication_failed", "Browser Inspect server.hello signature is invalid");
|
|
67
|
+
}
|
|
68
|
+
const authenticateFields = {
|
|
69
|
+
type: "client.authenticate",
|
|
70
|
+
mode: "connect",
|
|
71
|
+
role: "browser-inspect",
|
|
72
|
+
daemonId: hello.daemonId,
|
|
73
|
+
daemonInstanceId: hello.daemonInstanceId,
|
|
74
|
+
bindingProtocolVersion: DIRECT_RUNTIME_BROWSER_INSPECT_BINDING_PROTOCOL.currentVersion,
|
|
75
|
+
coreProtocolVersion: selectCoreProtocol(hello),
|
|
76
|
+
clientId: credential.clientId,
|
|
77
|
+
clientPublicKey: credential.clientPublicKey,
|
|
78
|
+
nonce: randomBytes(32).toString("base64url"),
|
|
79
|
+
grantId: credential.grantId,
|
|
80
|
+
};
|
|
81
|
+
const helloHash = sha256(encodeServerHelloCanonical(helloFields));
|
|
82
|
+
const authenticate = {
|
|
83
|
+
...authenticateFields,
|
|
84
|
+
signature: sign(null, encodeClientAuthenticateProofPayload(keyExchange.channelBinding, helloHash, authenticateFields), identity.privateKey).toString("base64url"),
|
|
85
|
+
};
|
|
86
|
+
const authenticateHash = sha256(encodeClientAuthenticateCanonical(authenticateFields));
|
|
87
|
+
await socket.sendAuthenticationText(encodeDirectRuntimeFrame(authenticate));
|
|
88
|
+
const accepted = decodeHandshake(await socket.readAuthenticationFrame(remaining(deadline)), "server.accepted");
|
|
89
|
+
if (accepted.type !== "server.accepted") {
|
|
90
|
+
throw clientError("protocol_error", "Browser Inspect server.accepted is missing");
|
|
91
|
+
}
|
|
92
|
+
assertInspectAccepted(accepted, hello, authenticate, credential);
|
|
93
|
+
if (!verify(null, encodeServerAcceptedProofPayload(keyExchange.channelBinding, helloHash, authenticateHash, withoutSignature(accepted)), daemonPublicKey, Buffer.from(accepted.signature, "base64url"))) {
|
|
94
|
+
throw clientError("authentication_failed", "Browser Inspect server.accepted signature is invalid");
|
|
95
|
+
}
|
|
96
|
+
socket.activateInspect();
|
|
97
|
+
socket.markAuthenticated();
|
|
98
|
+
const connection = new BrowserInspectConnectionImpl(socket, target, hello.daemonInstanceId);
|
|
99
|
+
await socket.sendInspect({
|
|
100
|
+
binary: false,
|
|
101
|
+
data: encodeRuntimeBrowserInspectOpenFrame(openFrame),
|
|
102
|
+
});
|
|
103
|
+
await connection.waitUntilReady(remaining(deadline));
|
|
104
|
+
if (options.signal?.aborted) {
|
|
105
|
+
throw clientError("closed", "Browser Inspect opening was cancelled");
|
|
106
|
+
}
|
|
107
|
+
return connection;
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
const normalized = normalizeError(error, "Browser Inspect handshake failed");
|
|
111
|
+
socket.abort(normalized);
|
|
112
|
+
await socket.close().catch(() => undefined);
|
|
113
|
+
throw normalized;
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
options.signal?.removeEventListener("abort", abortOpening);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
class BrowserInspectConnectionImpl {
|
|
120
|
+
socket;
|
|
121
|
+
target;
|
|
122
|
+
daemonInstanceId;
|
|
123
|
+
closed;
|
|
124
|
+
readyPromise;
|
|
125
|
+
readyResolve;
|
|
126
|
+
readyReject;
|
|
127
|
+
closedResolve;
|
|
128
|
+
ready = false;
|
|
129
|
+
finished = false;
|
|
130
|
+
localClose = false;
|
|
131
|
+
terminal;
|
|
132
|
+
items = [];
|
|
133
|
+
queuedBytes = 0;
|
|
134
|
+
waiter;
|
|
135
|
+
constructor(socket, target, daemonInstanceId) {
|
|
136
|
+
this.socket = socket;
|
|
137
|
+
this.target = target;
|
|
138
|
+
this.daemonInstanceId = daemonInstanceId;
|
|
139
|
+
this.readyPromise = new Promise((resolve, reject) => {
|
|
140
|
+
this.readyResolve = resolve;
|
|
141
|
+
this.readyReject = reject;
|
|
142
|
+
});
|
|
143
|
+
this.closed = new Promise((resolve) => {
|
|
144
|
+
this.closedResolve = resolve;
|
|
145
|
+
});
|
|
146
|
+
socket.setInspectHandlers((message) => this.handleMessage(message), (error) => this.handleTerminal(error));
|
|
147
|
+
}
|
|
148
|
+
async waitUntilReady(timeoutMs) {
|
|
149
|
+
await withTimeout(this.readyPromise, timeoutMs, () => clientError("unreachable", "Browser Inspect ready barrier timed out"));
|
|
150
|
+
}
|
|
151
|
+
async send(untrustedMessage) {
|
|
152
|
+
if (this.finished) {
|
|
153
|
+
throw this.terminal ??
|
|
154
|
+
clientError("closed", "Browser Inspect connection is closed");
|
|
155
|
+
}
|
|
156
|
+
await this.socket.sendInspect(parseRuntimeBrowserInspectMessage(untrustedMessage));
|
|
157
|
+
}
|
|
158
|
+
async close() {
|
|
159
|
+
if (this.localClose)
|
|
160
|
+
return;
|
|
161
|
+
this.localClose = true;
|
|
162
|
+
this.finish();
|
|
163
|
+
await this.socket.close();
|
|
164
|
+
}
|
|
165
|
+
[Symbol.asyncIterator]() {
|
|
166
|
+
return {
|
|
167
|
+
next: () => this.nextMessage(),
|
|
168
|
+
return: async () => {
|
|
169
|
+
await this.close();
|
|
170
|
+
return { done: true, value: undefined };
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
nextMessage() {
|
|
175
|
+
const item = this.items.shift();
|
|
176
|
+
if (item) {
|
|
177
|
+
this.queuedBytes -= item.bytes;
|
|
178
|
+
if (this.items.length === 0)
|
|
179
|
+
this.socket.resumeInspectInput();
|
|
180
|
+
return Promise.resolve({ done: false, value: item.message });
|
|
181
|
+
}
|
|
182
|
+
if (this.terminal)
|
|
183
|
+
return Promise.reject(this.terminal);
|
|
184
|
+
if (this.finished)
|
|
185
|
+
return Promise.resolve({ done: true, value: undefined });
|
|
186
|
+
if (this.waiter) {
|
|
187
|
+
return Promise.reject(clientError("protocol_error", "concurrent Browser Inspect iterator reads are not allowed"));
|
|
188
|
+
}
|
|
189
|
+
return new Promise((resolve, reject) => {
|
|
190
|
+
this.waiter = { resolve, reject };
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
handleMessage(message) {
|
|
194
|
+
try {
|
|
195
|
+
if (!this.ready) {
|
|
196
|
+
if (message.binary) {
|
|
197
|
+
throw new Error("Browser Inspect binary data preceded ready");
|
|
198
|
+
}
|
|
199
|
+
if (Buffer.byteLength(message.data, "utf8") >
|
|
200
|
+
RUNTIME_BROWSER_INSPECT_MAX_CONTROL_FRAME_BYTES) {
|
|
201
|
+
throw new Error("Browser Inspect ready frame is too large");
|
|
202
|
+
}
|
|
203
|
+
const frame = parseRuntimeBrowserInspectServerFrame(JSON.parse(message.data));
|
|
204
|
+
if (frame.type === "browser.inspect.closed") {
|
|
205
|
+
throw new DirectRuntimeBrowserInspectError(frame.reason, frame.message ?? `Browser Inspect closed: ${frame.reason}`);
|
|
206
|
+
}
|
|
207
|
+
this.ready = true;
|
|
208
|
+
this.readyResolve();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
this.enqueue(message);
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
const normalized = error instanceof DirectRuntimeClientError
|
|
215
|
+
? error
|
|
216
|
+
: clientError("protocol_error", "invalid Browser Inspect opening response", error);
|
|
217
|
+
this.socket.abort(normalized);
|
|
218
|
+
this.handleTerminal(normalized);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
enqueue(message) {
|
|
222
|
+
if (this.finished)
|
|
223
|
+
return;
|
|
224
|
+
if (this.waiter) {
|
|
225
|
+
const waiter = this.waiter;
|
|
226
|
+
this.waiter = undefined;
|
|
227
|
+
waiter.resolve({ done: false, value: message });
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const bytes = messageBytes(message);
|
|
231
|
+
if (this.queuedBytes + bytes > MAX_QUEUED_MESSAGE_BYTES) {
|
|
232
|
+
throw new Error("Browser Inspect inbound queue is full");
|
|
233
|
+
}
|
|
234
|
+
this.items.push({ message, bytes });
|
|
235
|
+
this.queuedBytes += bytes;
|
|
236
|
+
if (this.items.length === 1)
|
|
237
|
+
this.socket.pauseInspectInput();
|
|
238
|
+
}
|
|
239
|
+
handleTerminal(error) {
|
|
240
|
+
if (this.finished)
|
|
241
|
+
return;
|
|
242
|
+
if (this.ready &&
|
|
243
|
+
error.code === "closed" &&
|
|
244
|
+
error.webSocketCloseCode === 1000) {
|
|
245
|
+
this.finish();
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
this.terminal = error;
|
|
249
|
+
this.readyReject(error);
|
|
250
|
+
this.finish(error);
|
|
251
|
+
}
|
|
252
|
+
finish(error) {
|
|
253
|
+
if (this.finished)
|
|
254
|
+
return;
|
|
255
|
+
this.finished = true;
|
|
256
|
+
this.closedResolve();
|
|
257
|
+
if (!this.ready && !error) {
|
|
258
|
+
this.readyReject(clientError("closed", "Browser Inspect closed before ready"));
|
|
259
|
+
}
|
|
260
|
+
if (this.waiter) {
|
|
261
|
+
const waiter = this.waiter;
|
|
262
|
+
this.waiter = undefined;
|
|
263
|
+
if (error)
|
|
264
|
+
waiter.reject(error);
|
|
265
|
+
else
|
|
266
|
+
waiter.resolve({ done: true, value: undefined });
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
class BrowserInspectSocket {
|
|
271
|
+
liveness;
|
|
272
|
+
websocket;
|
|
273
|
+
opened;
|
|
274
|
+
openResolve;
|
|
275
|
+
openReject;
|
|
276
|
+
authenticationChannel;
|
|
277
|
+
inspectActive = false;
|
|
278
|
+
awaitingPlaintextReady = false;
|
|
279
|
+
authenticationQueue = [];
|
|
280
|
+
authenticationQueuedBytes = 0;
|
|
281
|
+
authenticationWaiter;
|
|
282
|
+
messageHandler;
|
|
283
|
+
terminalHandler;
|
|
284
|
+
terminal;
|
|
285
|
+
authenticated = false;
|
|
286
|
+
localClose = false;
|
|
287
|
+
livenessTimer;
|
|
288
|
+
lastInboundActivityAt = 0;
|
|
289
|
+
constructor(endpoint, timeoutMs, liveness) {
|
|
290
|
+
this.liveness = liveness;
|
|
291
|
+
this.opened = new Promise((resolve, reject) => {
|
|
292
|
+
this.openResolve = resolve;
|
|
293
|
+
this.openReject = reject;
|
|
294
|
+
});
|
|
295
|
+
this.websocket = new WebSocket(endpoint, {
|
|
296
|
+
rejectUnauthorized: true,
|
|
297
|
+
followRedirects: false,
|
|
298
|
+
handshakeTimeout: timeoutMs,
|
|
299
|
+
perMessageDeflate: false,
|
|
300
|
+
maxPayload: Math.max(DIRECT_RUNTIME_E2EE_MAX_ENVELOPE_BYTES, RUNTIME_BROWSER_INSPECT_MAX_MESSAGE_BYTES),
|
|
301
|
+
});
|
|
302
|
+
this.websocket.on("open", () => {
|
|
303
|
+
this.noteActivity();
|
|
304
|
+
this.startLiveness();
|
|
305
|
+
this.openResolve();
|
|
306
|
+
});
|
|
307
|
+
this.websocket.on("message", (data, isBinary) => {
|
|
308
|
+
this.noteActivity();
|
|
309
|
+
try {
|
|
310
|
+
this.handleWireRecord(rawDataBytes(data), isBinary);
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
this.abort(clientError("protocol_error", "invalid Browser Inspect frame", error));
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
this.websocket.on("ping", () => this.noteActivity());
|
|
317
|
+
this.websocket.on("pong", () => this.noteActivity());
|
|
318
|
+
this.websocket.on("unexpected-response", (_request, response) => {
|
|
319
|
+
response.resume();
|
|
320
|
+
this.abort(clientError(response.statusCode === 401 || response.statusCode === 403
|
|
321
|
+
? "authentication_failed"
|
|
322
|
+
: response.statusCode === 429 || response.statusCode === 503
|
|
323
|
+
? "unreachable"
|
|
324
|
+
: "protocol_error", `Browser Inspect WebSocket upgrade failed with HTTP ${response.statusCode ?? "unknown"}`));
|
|
325
|
+
});
|
|
326
|
+
this.websocket.on("error", (error) => {
|
|
327
|
+
this.fail(this.authenticated ? closedError(error) : mapWebSocketError(error));
|
|
328
|
+
});
|
|
329
|
+
this.websocket.on("close", (code, reason) => {
|
|
330
|
+
const suffix = reason.length === 0
|
|
331
|
+
? ""
|
|
332
|
+
: `: ${reason.toString("utf8").slice(0, 256)}`;
|
|
333
|
+
this.fail(webSocketCloseError(code, suffix, this.localClose));
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
async open(timeoutMs) {
|
|
337
|
+
await withTimeout(this.opened, timeoutMs, () => clientError("unreachable", "Browser Inspect WebSocket connection timed out"));
|
|
338
|
+
}
|
|
339
|
+
async startAuthentication(channel, clientHello) {
|
|
340
|
+
if (this.authenticationChannel) {
|
|
341
|
+
throw clientError("protocol_error", "E2EE is already initialized");
|
|
342
|
+
}
|
|
343
|
+
this.authenticationChannel = channel;
|
|
344
|
+
this.awaitingPlaintextReady = true;
|
|
345
|
+
await this.sendWire(clientHello, false);
|
|
346
|
+
}
|
|
347
|
+
async readAuthenticationFrame(timeoutMs) {
|
|
348
|
+
if (this.authenticationWaiter) {
|
|
349
|
+
throw clientError("protocol_error", "concurrent Browser Inspect handshake reads are not allowed");
|
|
350
|
+
}
|
|
351
|
+
const queued = this.authenticationQueue.shift();
|
|
352
|
+
if (queued) {
|
|
353
|
+
this.authenticationQueuedBytes -= queued.byteLength;
|
|
354
|
+
return queued;
|
|
355
|
+
}
|
|
356
|
+
if (this.terminal)
|
|
357
|
+
throw this.terminal;
|
|
358
|
+
return await new Promise((resolve, reject) => {
|
|
359
|
+
const timer = setTimeout(() => {
|
|
360
|
+
if (this.authenticationWaiter?.timer !== timer)
|
|
361
|
+
return;
|
|
362
|
+
this.authenticationWaiter = undefined;
|
|
363
|
+
reject(clientError("unreachable", "Browser Inspect handshake timed out"));
|
|
364
|
+
}, timeoutMs);
|
|
365
|
+
timer.unref?.();
|
|
366
|
+
this.authenticationWaiter = { resolve, reject, timer };
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
async sendAuthenticationText(plaintext) {
|
|
370
|
+
const channel = this.authenticationChannel;
|
|
371
|
+
if (!channel) {
|
|
372
|
+
throw clientError("protocol_error", "Browser Inspect authentication E2EE is unavailable");
|
|
373
|
+
}
|
|
374
|
+
await this.sendWire(channel.seal(plaintext), false);
|
|
375
|
+
}
|
|
376
|
+
activateInspect() {
|
|
377
|
+
if (this.authenticationWaiter || this.authenticationQueue.length > 0) {
|
|
378
|
+
throw clientError("protocol_error", "Browser Inspect handshake has unread frames");
|
|
379
|
+
}
|
|
380
|
+
this.inspectActive = true;
|
|
381
|
+
this.authenticationChannel = undefined;
|
|
382
|
+
}
|
|
383
|
+
markAuthenticated() {
|
|
384
|
+
this.authenticated = true;
|
|
385
|
+
}
|
|
386
|
+
setInspectHandlers(onMessage, onTerminal) {
|
|
387
|
+
if (this.messageHandler) {
|
|
388
|
+
throw clientError("protocol_error", "Browser Inspect socket is already active");
|
|
389
|
+
}
|
|
390
|
+
this.messageHandler = onMessage;
|
|
391
|
+
this.terminalHandler = onTerminal;
|
|
392
|
+
if (this.terminal)
|
|
393
|
+
onTerminal(this.terminal);
|
|
394
|
+
}
|
|
395
|
+
async sendInspect(untrustedMessage) {
|
|
396
|
+
const message = parseRuntimeBrowserInspectMessage(untrustedMessage);
|
|
397
|
+
if (this.terminal || this.websocket.readyState !== WebSocket.OPEN) {
|
|
398
|
+
throw this.terminal ??
|
|
399
|
+
clientError("closed", "Browser Inspect connection is not open");
|
|
400
|
+
}
|
|
401
|
+
if (!this.inspectActive) {
|
|
402
|
+
throw clientError("protocol_error", "Browser Inspect data stream is unavailable");
|
|
403
|
+
}
|
|
404
|
+
try {
|
|
405
|
+
await this.sendWire(message.data, message.binary);
|
|
406
|
+
}
|
|
407
|
+
catch (error) {
|
|
408
|
+
const normalized = normalizeError(error, "Browser Inspect send failed");
|
|
409
|
+
this.abort(normalized);
|
|
410
|
+
throw normalized;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
pauseInspectInput() {
|
|
414
|
+
if (this.websocket.readyState === WebSocket.OPEN)
|
|
415
|
+
this.websocket.pause();
|
|
416
|
+
}
|
|
417
|
+
resumeInspectInput() {
|
|
418
|
+
if (this.websocket.readyState === WebSocket.OPEN)
|
|
419
|
+
this.websocket.resume();
|
|
420
|
+
}
|
|
421
|
+
abort(error) {
|
|
422
|
+
this.fail(error);
|
|
423
|
+
if (this.websocket.readyState !== WebSocket.CLOSED) {
|
|
424
|
+
this.websocket.terminate();
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
async close() {
|
|
428
|
+
this.stopLiveness();
|
|
429
|
+
if (this.websocket.readyState === WebSocket.CLOSED)
|
|
430
|
+
return;
|
|
431
|
+
this.localClose = true;
|
|
432
|
+
await new Promise((resolve) => {
|
|
433
|
+
const timer = setTimeout(() => {
|
|
434
|
+
if (this.websocket.readyState !== WebSocket.CLOSED) {
|
|
435
|
+
this.websocket.terminate();
|
|
436
|
+
}
|
|
437
|
+
resolve();
|
|
438
|
+
}, 1_000);
|
|
439
|
+
timer.unref?.();
|
|
440
|
+
this.websocket.once("close", () => {
|
|
441
|
+
clearTimeout(timer);
|
|
442
|
+
resolve();
|
|
443
|
+
});
|
|
444
|
+
if (this.websocket.readyState === WebSocket.CONNECTING) {
|
|
445
|
+
this.websocket.terminate();
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
this.websocket.close(1000, "client closed");
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
handleWireRecord(bytes, binary) {
|
|
453
|
+
if (this.inspectActive) {
|
|
454
|
+
this.messageHandler?.(binary
|
|
455
|
+
? { binary: true, data: new Uint8Array(bytes) }
|
|
456
|
+
: { binary: false, data: decodeText(bytes) });
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (binary) {
|
|
460
|
+
throw clientError("protocol_error", "binary traffic preceded Browser Inspect activation");
|
|
461
|
+
}
|
|
462
|
+
const authenticationChannel = this.authenticationChannel;
|
|
463
|
+
if (!authenticationChannel) {
|
|
464
|
+
throw clientError("protocol_error", "server traffic preceded Browser Inspect E2EE setup");
|
|
465
|
+
}
|
|
466
|
+
const plaintext = this.awaitingPlaintextReady
|
|
467
|
+
? (this.awaitingPlaintextReady = false, bytes)
|
|
468
|
+
: Uint8Array.from(Buffer.from(authenticationChannel.open(decodeText(bytes)), "utf8"));
|
|
469
|
+
if (plaintext.byteLength > DIRECT_RUNTIME_MAX_HANDSHAKE_FRAME_BYTES) {
|
|
470
|
+
throw clientError("protocol_error", "Browser Inspect handshake frame is too large");
|
|
471
|
+
}
|
|
472
|
+
this.deliverAuthentication(plaintext);
|
|
473
|
+
}
|
|
474
|
+
deliverAuthentication(frame) {
|
|
475
|
+
if (this.authenticationWaiter) {
|
|
476
|
+
const waiter = this.authenticationWaiter;
|
|
477
|
+
this.authenticationWaiter = undefined;
|
|
478
|
+
clearTimeout(waiter.timer);
|
|
479
|
+
waiter.resolve(frame);
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
if (this.authenticationQueue.length >= MAX_PRE_ACTIVATION_FRAMES ||
|
|
483
|
+
this.authenticationQueuedBytes + frame.byteLength > MAX_PRE_ACTIVATION_BYTES) {
|
|
484
|
+
throw clientError("protocol_error", "too many Browser Inspect handshake frames arrived");
|
|
485
|
+
}
|
|
486
|
+
this.authenticationQueue.push(frame);
|
|
487
|
+
this.authenticationQueuedBytes += frame.byteLength;
|
|
488
|
+
}
|
|
489
|
+
async sendWire(frame, binary) {
|
|
490
|
+
if (this.terminal || this.websocket.readyState !== WebSocket.OPEN) {
|
|
491
|
+
throw this.terminal ??
|
|
492
|
+
clientError("closed", "Browser Inspect connection is not open");
|
|
493
|
+
}
|
|
494
|
+
const bytes = typeof frame === "string"
|
|
495
|
+
? Buffer.byteLength(frame, "utf8")
|
|
496
|
+
: frame.byteLength;
|
|
497
|
+
if (bytes > Math.max(DIRECT_RUNTIME_E2EE_MAX_ENVELOPE_BYTES, RUNTIME_BROWSER_INSPECT_MAX_MESSAGE_BYTES) ||
|
|
498
|
+
this.websocket.bufferedAmount + bytes > MAX_TRANSPORT_BUFFERED_BYTES) {
|
|
499
|
+
throw clientError("unreachable", "Browser Inspect output buffer is full");
|
|
500
|
+
}
|
|
501
|
+
await new Promise((resolve, reject) => {
|
|
502
|
+
try {
|
|
503
|
+
this.websocket.send(frame, { binary, compress: false }, (error) => {
|
|
504
|
+
if (error) {
|
|
505
|
+
reject(this.authenticated
|
|
506
|
+
? closedError(error)
|
|
507
|
+
: mapWebSocketError(error));
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
resolve();
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
catch (error) {
|
|
515
|
+
reject(this.authenticated ? closedError(error) : mapWebSocketError(error));
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
fail(error) {
|
|
520
|
+
if (this.terminal)
|
|
521
|
+
return;
|
|
522
|
+
this.terminal = error;
|
|
523
|
+
this.stopLiveness();
|
|
524
|
+
this.openReject(error);
|
|
525
|
+
if (this.authenticationWaiter) {
|
|
526
|
+
clearTimeout(this.authenticationWaiter.timer);
|
|
527
|
+
this.authenticationWaiter.reject(error);
|
|
528
|
+
this.authenticationWaiter = undefined;
|
|
529
|
+
}
|
|
530
|
+
this.terminalHandler?.(error);
|
|
531
|
+
}
|
|
532
|
+
noteActivity() {
|
|
533
|
+
this.lastInboundActivityAt = Date.now();
|
|
534
|
+
}
|
|
535
|
+
startLiveness() {
|
|
536
|
+
this.livenessTimer = setInterval(() => {
|
|
537
|
+
if (this.terminal)
|
|
538
|
+
return;
|
|
539
|
+
if (Date.now() - this.lastInboundActivityAt >
|
|
540
|
+
this.liveness.inactivityTimeoutMs) {
|
|
541
|
+
this.abort(clientError(this.authenticated ? "closed" : "unreachable", "Browser Inspect peer stopped responding"));
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
if (this.websocket.readyState === WebSocket.OPEN)
|
|
545
|
+
this.websocket.ping();
|
|
546
|
+
}, this.liveness.pingIntervalMs);
|
|
547
|
+
this.livenessTimer.unref?.();
|
|
548
|
+
}
|
|
549
|
+
stopLiveness() {
|
|
550
|
+
if (this.livenessTimer)
|
|
551
|
+
clearInterval(this.livenessTimer);
|
|
552
|
+
this.livenessTimer = undefined;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
function assertInspectHello(hello, credential) {
|
|
556
|
+
if (hello.role !== "browser-inspect") {
|
|
557
|
+
throw clientError("protocol_error", "Browser Inspect server.hello has the wrong role");
|
|
558
|
+
}
|
|
559
|
+
if (hello.daemonId !== credential.daemonId) {
|
|
560
|
+
throw clientError("identity_mismatch", "Browser Inspect daemonId differs from the pin");
|
|
561
|
+
}
|
|
562
|
+
if (hello.identityPublicKey !== credential.identityPublicKey) {
|
|
563
|
+
throw clientError("identity_mismatch", "Browser Inspect identity key differs from the pin");
|
|
564
|
+
}
|
|
565
|
+
if (hello.bindingProtocolVersion !==
|
|
566
|
+
DIRECT_RUNTIME_BROWSER_INSPECT_BINDING_PROTOCOL.currentVersion ||
|
|
567
|
+
hello.minimumCompatibleBindingVersion !==
|
|
568
|
+
DIRECT_RUNTIME_BROWSER_INSPECT_BINDING_PROTOCOL.minimumCompatibleVersion) {
|
|
569
|
+
throw clientError("incompatible", "Browser Inspect binding protocol is incompatible");
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
function assertInspectAccepted(accepted, hello, authenticate, credential) {
|
|
573
|
+
if (accepted.role !== "browser-inspect" ||
|
|
574
|
+
authenticate.role !== "browser-inspect" ||
|
|
575
|
+
accepted.daemonId !== hello.daemonId ||
|
|
576
|
+
accepted.daemonId !== credential.daemonId ||
|
|
577
|
+
accepted.daemonInstanceId !== hello.daemonInstanceId ||
|
|
578
|
+
accepted.bindingProtocolVersion !== authenticate.bindingProtocolVersion ||
|
|
579
|
+
accepted.coreProtocolVersion !== authenticate.coreProtocolVersion ||
|
|
580
|
+
accepted.clientId !== authenticate.clientId ||
|
|
581
|
+
accepted.grantId !== credential.grantId ||
|
|
582
|
+
accepted.authorities.length !== 1 ||
|
|
583
|
+
accepted.authorities[0] !== "daemon.full") {
|
|
584
|
+
throw clientError("authentication_failed", "Browser Inspect acceptance is inconsistent");
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
function selectCoreProtocol(hello) {
|
|
588
|
+
if (hello.minimumCompatibleCoreVersion >
|
|
589
|
+
REMOTE_RUNTIME_CORE_PROTOCOL.currentVersion ||
|
|
590
|
+
REMOTE_RUNTIME_CORE_PROTOCOL.minimumCompatibleVersion >
|
|
591
|
+
hello.coreProtocolVersion) {
|
|
592
|
+
throw clientError("incompatible", "Remote Runtime Core protocol ranges do not overlap");
|
|
593
|
+
}
|
|
594
|
+
return Math.min(hello.coreProtocolVersion, REMOTE_RUNTIME_CORE_PROTOCOL.currentVersion);
|
|
595
|
+
}
|
|
596
|
+
function decodeHandshake(frame, expected) {
|
|
597
|
+
try {
|
|
598
|
+
return decodeDirectRuntimeFrame(frame);
|
|
599
|
+
}
|
|
600
|
+
catch (error) {
|
|
601
|
+
throw clientError("protocol_error", `invalid Browser Inspect ${expected} frame`, error);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
function browserInspectEndpoint(directEndpoint) {
|
|
605
|
+
const url = new URL(directEndpoint);
|
|
606
|
+
url.pathname = DIRECT_RUNTIME_BROWSER_INSPECT_PATH;
|
|
607
|
+
url.search = "";
|
|
608
|
+
url.hash = "";
|
|
609
|
+
return url.toString();
|
|
610
|
+
}
|
|
611
|
+
function withoutSignature(value) {
|
|
612
|
+
const { signature: _signature, ...fields } = value;
|
|
613
|
+
return fields;
|
|
614
|
+
}
|
|
615
|
+
function sha256(value) {
|
|
616
|
+
return Uint8Array.from(createHash("sha256").update(value).digest());
|
|
617
|
+
}
|
|
618
|
+
function parseTimeout(value) {
|
|
619
|
+
const timeout = value ?? DEFAULT_TIMEOUT_MS;
|
|
620
|
+
if (!Number.isSafeInteger(timeout) ||
|
|
621
|
+
timeout < MIN_TIMEOUT_MS ||
|
|
622
|
+
timeout > MAX_TIMEOUT_MS) {
|
|
623
|
+
throw clientError("protocol_error", `timeoutMs must be an integer between ${MIN_TIMEOUT_MS} and ${MAX_TIMEOUT_MS}`);
|
|
624
|
+
}
|
|
625
|
+
return timeout;
|
|
626
|
+
}
|
|
627
|
+
function parseLiveness(value) {
|
|
628
|
+
const pingIntervalMs = parseLivenessInterval(value?.pingIntervalMs, DEFAULT_PING_INTERVAL_MS, "liveness.pingIntervalMs");
|
|
629
|
+
const inactivityTimeoutMs = parseLivenessInterval(value?.inactivityTimeoutMs, DEFAULT_INACTIVITY_TIMEOUT_MS, "liveness.inactivityTimeoutMs");
|
|
630
|
+
if (inactivityTimeoutMs <= pingIntervalMs) {
|
|
631
|
+
throw clientError("protocol_error", "liveness timeout must exceed ping interval");
|
|
632
|
+
}
|
|
633
|
+
return { pingIntervalMs, inactivityTimeoutMs };
|
|
634
|
+
}
|
|
635
|
+
function parseLivenessInterval(value, fallback, field) {
|
|
636
|
+
const interval = value ?? fallback;
|
|
637
|
+
if (!Number.isSafeInteger(interval) ||
|
|
638
|
+
interval <= 0 ||
|
|
639
|
+
interval > MAX_LIVENESS_INTERVAL_MS) {
|
|
640
|
+
throw clientError("protocol_error", `${field} must be a bounded positive integer`);
|
|
641
|
+
}
|
|
642
|
+
return interval;
|
|
643
|
+
}
|
|
644
|
+
function remaining(deadline) {
|
|
645
|
+
return Math.max(1, deadline - Date.now());
|
|
646
|
+
}
|
|
647
|
+
function messageBytes(message) {
|
|
648
|
+
return message.binary
|
|
649
|
+
? message.data.byteLength
|
|
650
|
+
: Buffer.byteLength(message.data, "utf8");
|
|
651
|
+
}
|
|
652
|
+
function rawDataBytes(data) {
|
|
653
|
+
if (Array.isArray(data))
|
|
654
|
+
return Uint8Array.from(Buffer.concat(data));
|
|
655
|
+
if (data instanceof ArrayBuffer)
|
|
656
|
+
return new Uint8Array(data.slice(0));
|
|
657
|
+
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
658
|
+
}
|
|
659
|
+
function decodeText(bytes) {
|
|
660
|
+
try {
|
|
661
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
662
|
+
}
|
|
663
|
+
catch (error) {
|
|
664
|
+
throw clientError("protocol_error", "Browser Inspect frame must be valid UTF-8", error);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
function normalizeError(error, message) {
|
|
668
|
+
return error instanceof DirectRuntimeClientError
|
|
669
|
+
? error
|
|
670
|
+
: clientError("protocol_error", message, error);
|
|
671
|
+
}
|
|
672
|
+
function closedError(cause) {
|
|
673
|
+
return clientError("closed", "Browser Inspect connection was interrupted", cause);
|
|
674
|
+
}
|
|
675
|
+
function webSocketCloseError(closeCode, reasonSuffix, localClose) {
|
|
676
|
+
if (localClose) {
|
|
677
|
+
return clientError("closed", "Browser Inspect connection was closed", undefined, closeCode);
|
|
678
|
+
}
|
|
679
|
+
const code = closeCode === DIRECT_RUNTIME_CLOSE_CODE.unauthorized
|
|
680
|
+
? "authentication_failed"
|
|
681
|
+
: closeCode === DIRECT_RUNTIME_CLOSE_CODE.badProtocol
|
|
682
|
+
? "protocol_error"
|
|
683
|
+
: closeCode === DIRECT_RUNTIME_CLOSE_CODE.authenticationTimeout ||
|
|
684
|
+
closeCode === DIRECT_RUNTIME_CLOSE_CODE.capacity
|
|
685
|
+
? "unreachable"
|
|
686
|
+
: "closed";
|
|
687
|
+
return clientError(code, `Browser Inspect connection closed (${closeCode})${reasonSuffix}`, undefined, closeCode);
|
|
688
|
+
}
|
|
689
|
+
function mapWebSocketError(error) {
|
|
690
|
+
const code = typeof error === "object" &&
|
|
691
|
+
error !== null &&
|
|
692
|
+
"code" in error &&
|
|
693
|
+
typeof error.code === "string"
|
|
694
|
+
? error.code
|
|
695
|
+
: "";
|
|
696
|
+
if (code.startsWith("ERR_TLS_") ||
|
|
697
|
+
code.startsWith("ERR_SSL_") ||
|
|
698
|
+
code.startsWith("CERT_") ||
|
|
699
|
+
[
|
|
700
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
701
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
702
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
703
|
+
"UNABLE_TO_GET_ISSUER_CERT",
|
|
704
|
+
].includes(code)) {
|
|
705
|
+
return clientError("tls_error", "Browser Inspect TLS verification failed", error);
|
|
706
|
+
}
|
|
707
|
+
if (code === "WS_ERR_UNSUPPORTED_MESSAGE_LENGTH") {
|
|
708
|
+
return clientError("protocol_error", "Browser Inspect WebSocket frame is too large", error);
|
|
709
|
+
}
|
|
710
|
+
return clientError("unreachable", "Browser Inspect endpoint is unreachable", error);
|
|
711
|
+
}
|
|
712
|
+
function inspectCloseErrorCode(reason) {
|
|
713
|
+
if (reason === "unauthorized")
|
|
714
|
+
return "authentication_failed";
|
|
715
|
+
if (reason === "capacity")
|
|
716
|
+
return "unreachable";
|
|
717
|
+
return "closed";
|
|
718
|
+
}
|
|
719
|
+
async function withTimeout(promise, timeoutMs, timeoutError) {
|
|
720
|
+
return await new Promise((resolve, reject) => {
|
|
721
|
+
const timer = setTimeout(() => reject(timeoutError()), timeoutMs);
|
|
722
|
+
timer.unref?.();
|
|
723
|
+
promise.then((value) => {
|
|
724
|
+
clearTimeout(timer);
|
|
725
|
+
resolve(value);
|
|
726
|
+
}, (error) => {
|
|
727
|
+
clearTimeout(timer);
|
|
728
|
+
reject(error);
|
|
729
|
+
});
|
|
730
|
+
});
|
|
731
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { connectDirectRuntime, pairDirectRuntime, type DirectRuntimeCallOptions, type DirectRuntimeClientOptions, type DirectRuntimeConnection, type DirectRuntimeSessionEventsOptions, type DirectRuntimeSessionEventsSubscription, type DirectRuntimeSessionTerminal, type DirectRuntimeSessionTerminalOptions, type DirectRuntimeEmulatorSurface, type DirectRuntimeEmulatorSurfaceOptions, type PairDirectRuntimeOptions, type PairDirectRuntimeResult, } from "./client.js";
|
|
2
2
|
export { DirectRuntimeBrowserSurfaceError, openDirectRuntimeBrowserSurface, type DirectRuntimeBrowserSurfaceClientOptions, type DirectRuntimeBrowserSurfaceConnection, type DirectRuntimeBrowserSurfaceEvent, } from "./browser-surface-client.js";
|
|
3
|
+
export { DirectRuntimeBrowserInspectError, openDirectRuntimeBrowserInspect, type DirectRuntimeBrowserInspectClientOptions, type DirectRuntimeBrowserInspectConnection, } from "./browser-inspect-client.js";
|
|
3
4
|
export { generateDirectRuntimeClientIdentity, parseDirectRuntimeCredential, type DirectRuntimeClientIdentity, type DirectRuntimeCredential, type GenerateDirectRuntimeClientIdentityOptions, } from "./credential.js";
|
|
4
5
|
export { DirectRuntimeCallTransportError, DirectRuntimeClientCapacityError, DirectRuntimeClientError, DirectRuntimeRpcError, DirectRuntimeSessionEventsError, DirectRuntimeSessionTerminalError, RemoteRuntimeClientError, RemoteRuntimeRpcCallError, type DirectRuntimeCallOutcome, type DirectRuntimeClientCapacity, type DirectRuntimeClientErrorCode, } from "./errors.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { connectDirectRuntime, pairDirectRuntime, } from "./client.js";
|
|
2
2
|
export { DirectRuntimeBrowserSurfaceError, openDirectRuntimeBrowserSurface, } from "./browser-surface-client.js";
|
|
3
|
+
export { DirectRuntimeBrowserInspectError, openDirectRuntimeBrowserInspect, } from "./browser-inspect-client.js";
|
|
3
4
|
export { generateDirectRuntimeClientIdentity, parseDirectRuntimeCredential, } from "./credential.js";
|
|
4
5
|
export { DirectRuntimeCallTransportError, DirectRuntimeClientCapacityError, DirectRuntimeClientError, DirectRuntimeRpcError, DirectRuntimeSessionEventsError, DirectRuntimeSessionTerminalError, RemoteRuntimeClientError, RemoteRuntimeRpcCallError, } from "./errors.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rynx-ai/remote-runtime-client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10-beta.2",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/rynx-ai/rynx.git",
|
|
7
|
+
"directory": "packages/remote-runtime-client"
|
|
8
|
+
},
|
|
4
9
|
"description": "Authenticated Node.js client for a Rynx Direct Remote Runtime.",
|
|
5
10
|
"type": "module",
|
|
6
11
|
"publishConfig": {
|
|
@@ -20,8 +25,8 @@
|
|
|
20
25
|
},
|
|
21
26
|
"dependencies": {
|
|
22
27
|
"ws": "^8.21.0",
|
|
23
|
-
"@rynx-ai/
|
|
24
|
-
"@rynx-ai/
|
|
28
|
+
"@rynx-ai/remote-runtime-e2ee": "0.1.10-beta.2",
|
|
29
|
+
"@rynx-ai/protocol": "0.1.10-beta.2"
|
|
25
30
|
},
|
|
26
31
|
"devDependencies": {
|
|
27
32
|
"@types/ws": "^8.18.1"
|