@oharness/server 0.9.3 → 0.10.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.
Files changed (59) hide show
  1. package/dist/i18n.js +354 -62
  2. package/dist/i18n.js.map +1 -1
  3. package/dist/main.js +4 -0
  4. package/dist/main.js.map +1 -1
  5. package/dist/page/client/account.js +430 -23
  6. package/dist/page/client/account.js.map +1 -1
  7. package/dist/page/client/base.js +20 -1
  8. package/dist/page/client/base.js.map +1 -1
  9. package/dist/page/client/composer.js +693 -54
  10. package/dist/page/client/composer.js.map +1 -1
  11. package/dist/page/client/computer.d.ts +27 -0
  12. package/dist/page/client/computer.js +358 -0
  13. package/dist/page/client/computer.js.map +1 -0
  14. package/dist/page/client/dialogs.js +93 -6
  15. package/dist/page/client/dialogs.js.map +1 -1
  16. package/dist/page/client/events.js +94 -7
  17. package/dist/page/client/events.js.map +1 -1
  18. package/dist/page/client/files.js +86 -1
  19. package/dist/page/client/files.js.map +1 -1
  20. package/dist/page/client/index.js +4 -0
  21. package/dist/page/client/index.js.map +1 -1
  22. package/dist/page/client/panels.js +10 -6
  23. package/dist/page/client/panels.js.map +1 -1
  24. package/dist/page/client/routines.js +296 -41
  25. package/dist/page/client/routines.js.map +1 -1
  26. package/dist/page/client/shell.js +187 -51
  27. package/dist/page/client/shell.js.map +1 -1
  28. package/dist/page/client/state.js +273 -19
  29. package/dist/page/client/state.js.map +1 -1
  30. package/dist/page/client/tasks.d.ts +8 -0
  31. package/dist/page/client/tasks.js +293 -0
  32. package/dist/page/client/tasks.js.map +1 -0
  33. package/dist/page/client/transcript.js +137 -5
  34. package/dist/page/client/transcript.js.map +1 -1
  35. package/dist/page/client/usage.js +1 -11
  36. package/dist/page/client/usage.js.map +1 -1
  37. package/dist/page/style.js +568 -60
  38. package/dist/page/style.js.map +1 -1
  39. package/dist/page.js +90 -11
  40. package/dist/page.js.map +1 -1
  41. package/dist/prefs.d.ts +14 -0
  42. package/dist/prefs.js +8 -1
  43. package/dist/prefs.js.map +1 -1
  44. package/dist/remote-crypto.d.ts +78 -0
  45. package/dist/remote-crypto.js +216 -0
  46. package/dist/remote-crypto.js.map +1 -0
  47. package/dist/remote.d.ts +103 -0
  48. package/dist/remote.js +498 -0
  49. package/dist/remote.js.map +1 -0
  50. package/dist/server.d.ts +70 -0
  51. package/dist/server.js +1291 -49
  52. package/dist/server.js.map +1 -1
  53. package/dist/starters.d.ts +92 -0
  54. package/dist/starters.js +213 -0
  55. package/dist/starters.js.map +1 -0
  56. package/dist/turn-shots.d.ts +27 -0
  57. package/dist/turn-shots.js +71 -0
  58. package/dist/turn-shots.js.map +1 -0
  59. package/package.json +2 -2
@@ -0,0 +1,103 @@
1
+ import { type Identity } from "./remote-crypto.ts";
2
+ /**
3
+ * Remote control: this machine, reachable from a phone through the relay.
4
+ *
5
+ * The Mac dials out — it never listens on anything but loopback — and holds
6
+ * one WebSocket to the relay while remote is on. A phone on the same account
7
+ * opens a channel to it; each request that arrives on the channel is replayed
8
+ * against this process's own HTTP server, so the phone speaks exactly the
9
+ * routes the page speaks and there is no second API to keep in step.
10
+ *
11
+ * A channel is useless until its handshake (remote-crypto.ts) finishes: the
12
+ * phone proves which device it is, the Mac proves it is this Mac, and every
13
+ * frame after that is sealed end to end. A phone this Mac has not seen before
14
+ * is then held until someone at the Mac allows it — the same account is what
15
+ * gets a phone to the door, and the Allow is what opens it. Both screens show
16
+ * a six-digit code derived from the handshake, so a relay that put itself in
17
+ * the middle would be caught by two codes that disagree.
18
+ *
19
+ * Once a channel is ready, inside each sealed frame:
20
+ *
21
+ * ← {pair: "ok"} | {pair: "pending", code} | {pair: "denied"}
22
+ * → {id, method, path, query?, body?} ← {id, status, body}
23
+ * → {id, subscribe: "/events"} ← {sub: id, event} … ← {sub: id, end: true}
24
+ * → {id, unsubscribe: <sub id>} ← {id, status: 200, body: {ok: true}}
25
+ */
26
+ /**
27
+ * What a phone may ask for. Chat and its controls, read-only views of what the
28
+ * agent produced — not the terminal, the browser, the file system, settings,
29
+ * sign-in or computer use. A route not here is refused before anything runs.
30
+ */
31
+ export declare const REMOTE_ROUTES: ReadonlySet<string>;
32
+ /** The relay a Mac dials when nothing names another. */
33
+ export declare const DEFAULT_RELAY = "wss://relay.oharness.ai/v1/connect";
34
+ /**
35
+ * Where remote control connects, and whether this run is told to use it
36
+ * whatever the setting says.
37
+ *
38
+ * OHARNESS_REMOTE_RELAY names another relay for development (a local one is
39
+ * `ws://127.0.0.1:4490/v1/connect`) and, as it did before there was a switch,
40
+ * turns remote on by itself. Without it the relay is ours and the switch in
41
+ * Settings decides.
42
+ */
43
+ export declare function remoteRelay(env?: NodeJS.ProcessEnv): {
44
+ relay: string;
45
+ always: boolean;
46
+ };
47
+ export interface RemoteMachine {
48
+ id: string;
49
+ name: string;
50
+ }
51
+ export interface RemoteOptions {
52
+ /** The relay's WebSocket endpoint, `wss://…/v1/connect` (or ws:// on loopback). */
53
+ relay: string;
54
+ /** The account token to present; asked again on every reconnect, since it expires. */
55
+ token: () => Promise<string | undefined>;
56
+ machine: RemoteMachine;
57
+ /** This Mac's long-term identity, from `remoteMachine()`. */
58
+ identity: Identity;
59
+ /** This process's own server, as `startWebServer` reports it (its URL carries the token). */
60
+ local: string;
61
+ /**
62
+ * Asks the person at the Mac whether a new phone may connect. Settles false
63
+ * when the signal aborts (the phone gave up), and is never asked about a
64
+ * phone that is already paired.
65
+ */
66
+ approve: (request: PairingRequest, signal: AbortSignal) => Promise<boolean>;
67
+ /** Told when an approval is no longer wanted, so its card can close. */
68
+ withdrawn?: (id: string) => void;
69
+ log?: (line: string) => void;
70
+ }
71
+ export interface RemoteHandle {
72
+ readonly connected: boolean;
73
+ /** Ends every channel a phone holds open, once it has been forgotten. */
74
+ forget(deviceId: string): void;
75
+ close(): void;
76
+ }
77
+ /** A phone this Mac has allowed. */
78
+ export interface PairedDevice {
79
+ id: string;
80
+ name: string;
81
+ /** The phone's Ed25519 identity, base64 of 32 raw bytes. */
82
+ publicKey: string;
83
+ pairedAt: number;
84
+ }
85
+ /**
86
+ * This Mac's id and identity for remote control, made on first use and kept
87
+ * in ~/.oharness/remote.json. The identity is what a phone pins: replacing it
88
+ * makes every paired phone refuse this Mac until it is paired again.
89
+ */
90
+ export declare function remoteMachine(): Promise<{
91
+ machine: RemoteMachine;
92
+ identity: Identity;
93
+ }>;
94
+ export declare function pairedDevices(): Promise<PairedDevice[]>;
95
+ export declare function revokeDevice(id: string): Promise<boolean>;
96
+ /** A phone waiting at the door, as the Mac's page is asked about it. */
97
+ export interface PairingRequest {
98
+ /** The channel id, which is also how the answer finds its way back. */
99
+ id: string;
100
+ device: RemoteMachine;
101
+ code: string;
102
+ }
103
+ export declare function startRemote(options: RemoteOptions): RemoteHandle;
package/dist/remote.js ADDED
@@ -0,0 +1,498 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { MacHandshake, SealedSession, b64, loadIdentity, newIdentity, readHello } from "./remote-crypto.js";
3
+ import { hostname } from "node:os";
4
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
5
+ import path from "node:path";
6
+ import { userPath } from "@oharness/engine/paths.ts";
7
+ /**
8
+ * Remote control: this machine, reachable from a phone through the relay.
9
+ *
10
+ * The Mac dials out — it never listens on anything but loopback — and holds
11
+ * one WebSocket to the relay while remote is on. A phone on the same account
12
+ * opens a channel to it; each request that arrives on the channel is replayed
13
+ * against this process's own HTTP server, so the phone speaks exactly the
14
+ * routes the page speaks and there is no second API to keep in step.
15
+ *
16
+ * A channel is useless until its handshake (remote-crypto.ts) finishes: the
17
+ * phone proves which device it is, the Mac proves it is this Mac, and every
18
+ * frame after that is sealed end to end. A phone this Mac has not seen before
19
+ * is then held until someone at the Mac allows it — the same account is what
20
+ * gets a phone to the door, and the Allow is what opens it. Both screens show
21
+ * a six-digit code derived from the handshake, so a relay that put itself in
22
+ * the middle would be caught by two codes that disagree.
23
+ *
24
+ * Once a channel is ready, inside each sealed frame:
25
+ *
26
+ * ← {pair: "ok"} | {pair: "pending", code} | {pair: "denied"}
27
+ * → {id, method, path, query?, body?} ← {id, status, body}
28
+ * → {id, subscribe: "/events"} ← {sub: id, event} … ← {sub: id, end: true}
29
+ * → {id, unsubscribe: <sub id>} ← {id, status: 200, body: {ok: true}}
30
+ */
31
+ /**
32
+ * What a phone may ask for. Chat and its controls, read-only views of what the
33
+ * agent produced — not the terminal, the browser, the file system, settings,
34
+ * sign-in or computer use. A route not here is refused before anything runs.
35
+ */
36
+ export const REMOTE_ROUTES = new Set([
37
+ "GET /state",
38
+ "GET /sessions",
39
+ "GET /open",
40
+ "GET /history",
41
+ "GET /projects",
42
+ "GET /models",
43
+ "GET /changes",
44
+ "GET /artifacts",
45
+ "GET /usage",
46
+ "GET /context",
47
+ "GET /provider-icon",
48
+ "POST /session",
49
+ "POST /prompt",
50
+ "POST /permission",
51
+ "POST /question",
52
+ "POST /interrupt",
53
+ "POST /unqueue",
54
+ "POST /sendnow",
55
+ "POST /mode",
56
+ "POST /effort",
57
+ "POST /upload",
58
+ "POST /upload/drop",
59
+ "POST /model",
60
+ // Background tasks: the list, one task's conversation, and stopping,
61
+ // dismissing or messaging one — the same reach over a conversation's work
62
+ // as /prompt and /interrupt, and the page's own routes for it.
63
+ "GET /tasks",
64
+ "GET /task",
65
+ "POST /task",
66
+ ]);
67
+ /** Subscriptions a phone may hold: the event stream the page itself listens to. */
68
+ const REMOTE_STREAMS = new Set(["/events"]);
69
+ /** `POST /session` does more than open and switch; a phone gets those two only. */
70
+ const SESSION_ACTIONS = new Set(["resume", "new", "focus"]);
71
+ /** The relay a Mac dials when nothing names another. */
72
+ export const DEFAULT_RELAY = "wss://relay.oharness.ai/v1/connect";
73
+ /**
74
+ * Where remote control connects, and whether this run is told to use it
75
+ * whatever the setting says.
76
+ *
77
+ * OHARNESS_REMOTE_RELAY names another relay for development (a local one is
78
+ * `ws://127.0.0.1:4490/v1/connect`) and, as it did before there was a switch,
79
+ * turns remote on by itself. Without it the relay is ours and the switch in
80
+ * Settings decides.
81
+ */
82
+ export function remoteRelay(env = process.env) {
83
+ const named = env["OHARNESS_REMOTE_RELAY"]?.trim();
84
+ return named ? { relay: named, always: true } : { relay: DEFAULT_RELAY, always: false };
85
+ }
86
+ const MAX_BACKOFF_MS = 30_000;
87
+ const NO_TOKEN_RETRY_MS = 5_000;
88
+ const remoteFile = () => userPath("remote.json");
89
+ async function readRemoteFile() {
90
+ try {
91
+ return JSON.parse(await readFile(remoteFile(), "utf8"));
92
+ }
93
+ catch {
94
+ return {};
95
+ }
96
+ }
97
+ async function writeRemoteFile(value) {
98
+ await mkdir(path.dirname(remoteFile()), { recursive: true });
99
+ // 0600: it holds this Mac's private identity key.
100
+ await writeFile(remoteFile(), JSON.stringify(value, null, 2) + "\n", { mode: 0o600 });
101
+ }
102
+ /**
103
+ * This Mac's id and identity for remote control, made on first use and kept
104
+ * in ~/.oharness/remote.json. The identity is what a phone pins: replacing it
105
+ * makes every paired phone refuse this Mac until it is paired again.
106
+ */
107
+ export async function remoteMachine() {
108
+ const stored = await readRemoteFile();
109
+ let id = typeof stored.machineId === "string" && /^[A-Za-z0-9_-]{8,64}$/.test(stored.machineId) ? stored.machineId : "";
110
+ let identity;
111
+ try {
112
+ if (typeof stored.identity === "string")
113
+ identity = loadIdentity(stored.identity);
114
+ }
115
+ catch {
116
+ identity = undefined;
117
+ }
118
+ if (!id || !identity) {
119
+ id ||= `mac-${randomBytes(12).toString("base64url")}`;
120
+ const made = identity ? undefined : newIdentity();
121
+ identity ??= made.identity;
122
+ await writeRemoteFile({ ...stored, machineId: id, ...(made ? { identity: made.pkcs8, devices: [] } : {}) });
123
+ }
124
+ return { machine: { id, name: hostname().replace(/\.local$/, "") }, identity };
125
+ }
126
+ export async function pairedDevices() {
127
+ const stored = await readRemoteFile();
128
+ if (!Array.isArray(stored.devices))
129
+ return [];
130
+ return stored.devices.filter((d) => !!d && typeof d === "object" && typeof d.id === "string" && typeof d.publicKey === "string");
131
+ }
132
+ async function savePairedDevice(device) {
133
+ const stored = await readRemoteFile();
134
+ const others = (await pairedDevices()).filter((d) => d.id !== device.id);
135
+ await writeRemoteFile({ ...stored, devices: [...others, device] });
136
+ }
137
+ export async function revokeDevice(id) {
138
+ const stored = await readRemoteFile();
139
+ const devices = await pairedDevices();
140
+ const kept = devices.filter((d) => d.id !== id);
141
+ if (kept.length === devices.length)
142
+ return false;
143
+ await writeRemoteFile({ ...stored, devices: kept });
144
+ return true;
145
+ }
146
+ export function startRemote(options) {
147
+ const log = options.log ?? (() => { });
148
+ const local = new URL(options.local);
149
+ const localToken = local.searchParams.get("token") ?? "";
150
+ const channels = new Map();
151
+ let socket;
152
+ let connected = false;
153
+ let stopped = false;
154
+ let signedOut = false;
155
+ let backoff = 1000;
156
+ let retry;
157
+ function relaySend(message) {
158
+ if (socket?.readyState === WebSocket.OPEN)
159
+ socket.send(JSON.stringify(message));
160
+ }
161
+ /** A sealed frame to a phone. Nothing but the handshake ever goes out in the clear. */
162
+ function reply(channelId, message) {
163
+ const sealed = channels.get(channelId)?.sealed;
164
+ if (sealed)
165
+ relaySend({ t: "data", channel: channelId, data: sealed.seal(message) });
166
+ }
167
+ function closeChannel(id) {
168
+ const channel = channels.get(id);
169
+ if (!channel)
170
+ return;
171
+ for (const controller of channel.subscriptions.values())
172
+ controller.abort();
173
+ if (channel.waiting) {
174
+ channel.waiting.abort();
175
+ options.withdrawn?.(id);
176
+ }
177
+ channels.delete(id);
178
+ }
179
+ /** Ends a channel this side no longer trusts, and tells the relay so the phone hears it. */
180
+ function drop(id, why) {
181
+ log(`remote: closed a channel — ${why}`);
182
+ closeChannel(id);
183
+ relaySend({ t: "close", channel: id });
184
+ }
185
+ async function forward(request) {
186
+ const method = String(request.method ?? "GET").toUpperCase();
187
+ const route = String(request.path ?? "");
188
+ if (!REMOTE_ROUTES.has(`${method} ${route}`)) {
189
+ return { status: 403, body: { error: `${method} ${route} is not available remotely` } };
190
+ }
191
+ if (route === "/session") {
192
+ const action = request.body?.action;
193
+ if (typeof action !== "string" || !SESSION_ACTIONS.has(action)) {
194
+ return { status: 403, body: { error: "that session action is not available remotely" } };
195
+ }
196
+ }
197
+ // Running with no approvals at all is a decision made at the Mac, not from a phone.
198
+ if (route === "/mode" && request.body?.mode === "bypassPermissions") {
199
+ return { status: 403, body: { error: "bypass mode can only be turned on at the Mac" } };
200
+ }
201
+ const url = new URL(route, local.origin);
202
+ for (const [key, value] of Object.entries(request.query ?? {})) {
203
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
204
+ url.searchParams.set(key, String(value));
205
+ }
206
+ }
207
+ // A file from the phone rides the JSON frame as base64; the upload route
208
+ // takes it as the raw bytes a browser would send, with its name and type.
209
+ const upload = route === "/upload" ? request.body : undefined;
210
+ if (upload && typeof upload.base64 !== "string") {
211
+ return { status: 400, body: { error: "upload needs base64" } };
212
+ }
213
+ const response = await fetch(url, {
214
+ method,
215
+ headers: {
216
+ authorization: `Bearer ${localToken}`,
217
+ ...(upload
218
+ ? {
219
+ "content-type": typeof upload.type === "string" ? upload.type : "application/octet-stream",
220
+ // A header carries ASCII only; the page sends names the same way.
221
+ "x-filename": typeof upload.name === "string" ? upload.name.replace(/[^\x20-\x7e]/g, "_").slice(0, 200) : "file",
222
+ }
223
+ : method === "POST" ? { "content-type": "application/json" } : {}),
224
+ },
225
+ ...(upload
226
+ ? { body: Buffer.from(upload.base64, "base64") }
227
+ : method === "POST" ? { body: JSON.stringify(request.body ?? {}) } : {}),
228
+ signal: AbortSignal.timeout(60_000),
229
+ });
230
+ // An image (a vendor's icon) cannot ride in a JSON frame as it is.
231
+ const type = response.headers.get("content-type") ?? "";
232
+ if (type.startsWith("image/")) {
233
+ const bytes = Buffer.from(await response.arrayBuffer());
234
+ return { status: response.status, body: { type, base64: bytes.toString("base64") } };
235
+ }
236
+ const text = await response.text();
237
+ let body = text;
238
+ try {
239
+ body = text ? JSON.parse(text) : null;
240
+ }
241
+ catch {
242
+ // Not JSON: handed over as the text it is.
243
+ }
244
+ return { status: response.status, body };
245
+ }
246
+ /** Relay one SSE stream from the local server, event by event, until aborted. */
247
+ async function subscribe(channelId, channel, id, route) {
248
+ const controller = new AbortController();
249
+ channel.subscriptions.set(id, controller);
250
+ try {
251
+ const response = await fetch(new URL(route, local.origin), {
252
+ headers: { authorization: `Bearer ${localToken}`, accept: "text/event-stream" },
253
+ signal: controller.signal,
254
+ });
255
+ if (!response.ok || !response.body)
256
+ throw new Error(`HTTP ${response.status}`);
257
+ const decoder = new TextDecoder();
258
+ let buffered = "";
259
+ for await (const chunk of response.body) {
260
+ buffered += decoder.decode(chunk, { stream: true });
261
+ let boundary;
262
+ while ((boundary = buffered.indexOf("\n\n")) >= 0) {
263
+ const block = buffered.slice(0, boundary);
264
+ buffered = buffered.slice(boundary + 2);
265
+ const data = block
266
+ .split("\n")
267
+ .filter((line) => line.startsWith("data:"))
268
+ .map((line) => line.slice(5).replace(/^ /, ""))
269
+ .join("\n");
270
+ if (!data)
271
+ continue;
272
+ let event;
273
+ try {
274
+ event = JSON.parse(data);
275
+ }
276
+ catch {
277
+ continue; // A line that is not JSON is not an event the page would act on either.
278
+ }
279
+ // Another phone's pairing, code and all, is the Mac's business, not this phone's.
280
+ if (typeof event.type === "string" && event.type.startsWith("remote_"))
281
+ continue;
282
+ reply(channelId, { sub: id, event });
283
+ }
284
+ }
285
+ reply(channelId, { sub: id, end: true });
286
+ }
287
+ catch (err) {
288
+ if (!controller.signal.aborted) {
289
+ reply(channelId, { sub: id, end: true, error: err instanceof Error ? err.message : String(err) });
290
+ }
291
+ }
292
+ finally {
293
+ channel.subscriptions.delete(id);
294
+ }
295
+ }
296
+ /**
297
+ * One frame from a phone. Synchronous up to the point a request is handed
298
+ * off, because sealed frames carry counters that must be opened in the
299
+ * order they arrived.
300
+ */
301
+ function onData(channelId, raw) {
302
+ const channel = channels.get(channelId);
303
+ if (!channel)
304
+ return;
305
+ try {
306
+ switch (channel.stage) {
307
+ case "hello": {
308
+ const hello = readHello(JSON.parse(raw));
309
+ if (hello.device.id !== channel.announced.id)
310
+ throw new Error("the hello names a different device");
311
+ channel.handshake = new MacHandshake(channelId, hello, options.identity);
312
+ channel.stage = "proof";
313
+ relaySend({ t: "data", channel: channelId, data: JSON.stringify(channel.handshake.welcome()) });
314
+ return;
315
+ }
316
+ case "proof": {
317
+ const handshake = channel.handshake;
318
+ channel.sealed = handshake.accept(JSON.parse(raw));
319
+ void admit(channelId, channel, handshake);
320
+ return;
321
+ }
322
+ case "pairing":
323
+ // Nothing is acted on before the Mac says yes; opened only to keep the counter in step.
324
+ channel.sealed.open(raw);
325
+ return;
326
+ case "ready":
327
+ void handle(channelId, channel, channel.sealed.open(raw));
328
+ return;
329
+ }
330
+ }
331
+ catch (err) {
332
+ drop(channelId, err instanceof Error ? err.message : String(err));
333
+ }
334
+ }
335
+ /** A known phone goes straight in; a new one waits for the person at the Mac. */
336
+ async function admit(channelId, channel, handshake) {
337
+ const { device, identity } = handshake.hello;
338
+ const publicKey = b64(identity);
339
+ const known = (await pairedDevices()).some((d) => d.id === device.id && d.publicKey === publicKey);
340
+ if (channels.get(channelId) !== channel)
341
+ return;
342
+ if (known) {
343
+ channel.stage = "ready";
344
+ reply(channelId, { pair: "ok" });
345
+ log(`remote: ${device.name} connected`);
346
+ return;
347
+ }
348
+ channel.stage = "pairing";
349
+ channel.waiting = new AbortController();
350
+ reply(channelId, { pair: "pending", code: handshake.code });
351
+ log(`remote: ${device.name} asks to pair, code ${handshake.code}`);
352
+ let allowed = false;
353
+ try {
354
+ allowed = await options.approve({ id: channelId, device, code: handshake.code }, channel.waiting.signal);
355
+ }
356
+ catch {
357
+ allowed = false;
358
+ }
359
+ if (channels.get(channelId) !== channel)
360
+ return;
361
+ channel.waiting = undefined;
362
+ if (!allowed) {
363
+ reply(channelId, { pair: "denied" });
364
+ return drop(channelId, `${device.name} was not allowed`);
365
+ }
366
+ await savePairedDevice({ id: device.id, name: device.name, publicKey, pairedAt: Date.now() });
367
+ channel.stage = "ready";
368
+ reply(channelId, { pair: "ok" });
369
+ log(`remote: ${device.name} paired`);
370
+ }
371
+ async function handle(channelId, channel, request) {
372
+ if (typeof request.id !== "number")
373
+ return;
374
+ if (typeof request.subscribe === "string") {
375
+ if (!REMOTE_STREAMS.has(request.subscribe)) {
376
+ return reply(channelId, { sub: request.id, end: true, error: "not available remotely" });
377
+ }
378
+ void subscribe(channelId, channel, request.id, request.subscribe);
379
+ return;
380
+ }
381
+ if (typeof request.unsubscribe === "number") {
382
+ channel.subscriptions.get(request.unsubscribe)?.abort();
383
+ return reply(channelId, { id: request.id, status: 200, body: { ok: true } });
384
+ }
385
+ try {
386
+ reply(channelId, { id: request.id, ...(await forward(request)) });
387
+ }
388
+ catch (err) {
389
+ reply(channelId, { id: request.id, status: 502, body: { error: err instanceof Error ? err.message : String(err) } });
390
+ }
391
+ }
392
+ function onMessage(raw) {
393
+ let message;
394
+ try {
395
+ message = JSON.parse(raw);
396
+ }
397
+ catch {
398
+ return;
399
+ }
400
+ const channel = typeof message["channel"] === "string" ? message["channel"] : "";
401
+ switch (message["t"]) {
402
+ case "ready":
403
+ connected = true;
404
+ backoff = 1000;
405
+ log(`remote: online as ${options.machine.name}`);
406
+ break;
407
+ case "open": {
408
+ const device = message["device"];
409
+ if (!channel || !device || typeof device.id !== "string")
410
+ return;
411
+ channels.set(channel, { announced: device, stage: "hello", subscriptions: new Map() });
412
+ break;
413
+ }
414
+ case "data":
415
+ if (typeof message["data"] === "string")
416
+ onData(channel, message["data"]);
417
+ break;
418
+ case "close":
419
+ closeChannel(channel);
420
+ break;
421
+ case "error":
422
+ log(`remote: relay said ${String(message["code"])}`);
423
+ break;
424
+ }
425
+ }
426
+ async function connect() {
427
+ if (stopped)
428
+ return;
429
+ const token = await options.token().catch(() => undefined);
430
+ if (stopped)
431
+ return;
432
+ if (!token) {
433
+ // Said once, not every few seconds for as long as nobody signs in.
434
+ if (!signedOut)
435
+ log("remote: no account token — sign in to use remote control");
436
+ signedOut = true;
437
+ // Looked for again soon, so signing in brings the Mac online in seconds
438
+ // rather than at the end of a backoff that grew while nobody was.
439
+ retry = setTimeout(() => void connect(), NO_TOKEN_RETRY_MS);
440
+ retry.unref?.();
441
+ return;
442
+ }
443
+ signedOut = false;
444
+ const ws = new WebSocket(options.relay);
445
+ socket = ws;
446
+ ws.addEventListener("open", () => {
447
+ ws.send(JSON.stringify({ t: "hello", v: 1, role: "host", token, machine: options.machine }));
448
+ });
449
+ ws.addEventListener("message", (event) => {
450
+ if (typeof event.data === "string")
451
+ onMessage(event.data);
452
+ });
453
+ ws.addEventListener("close", (event) => {
454
+ if (socket !== ws)
455
+ return;
456
+ connected = false;
457
+ for (const id of [...channels.keys()])
458
+ closeChannel(id);
459
+ // Refused credentials will be refused again; wait for the longest step
460
+ // rather than hammering the relay with a token it has already turned down.
461
+ if (event.code === 4401)
462
+ backoff = MAX_BACKOFF_MS;
463
+ schedule();
464
+ });
465
+ ws.addEventListener("error", () => {
466
+ // "close" follows and does the retrying.
467
+ });
468
+ }
469
+ function schedule() {
470
+ if (stopped)
471
+ return;
472
+ retry = setTimeout(() => void connect(), backoff);
473
+ retry.unref?.();
474
+ backoff = Math.min(backoff * 2, MAX_BACKOFF_MS);
475
+ }
476
+ void connect();
477
+ return {
478
+ get connected() {
479
+ return connected;
480
+ },
481
+ forget(deviceId) {
482
+ for (const [id, channel] of [...channels]) {
483
+ if (channel.announced.id === deviceId)
484
+ drop(id, `${channel.announced.name} was forgotten`);
485
+ }
486
+ },
487
+ close() {
488
+ stopped = true;
489
+ if (retry)
490
+ clearTimeout(retry);
491
+ for (const id of [...channels.keys()])
492
+ closeChannel(id);
493
+ socket?.close();
494
+ socket = undefined;
495
+ },
496
+ };
497
+ }
498
+ //# sourceMappingURL=remote.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote.js","sourceRoot":"","sources":["../src/remote.ts"],"names":[],"mappings":"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"}