@gtc6244/immediacy-daemon 0.2.1 → 0.2.3

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 (63) hide show
  1. package/dist/src/crypto.d.ts +18 -39
  2. package/dist/src/crypto.js +33 -152
  3. package/dist/src/crypto.js.map +1 -1
  4. package/dist/src/devices.d.ts +46 -0
  5. package/dist/src/devices.js +114 -0
  6. package/dist/src/devices.js.map +1 -0
  7. package/dist/src/envelope.d.ts +3 -3
  8. package/dist/src/envelope.js +3 -3
  9. package/dist/src/forward.d.ts +4 -4
  10. package/dist/src/forward.js +1 -1
  11. package/dist/src/forward.js.map +1 -1
  12. package/dist/src/framing.d.ts +76 -0
  13. package/dist/src/framing.js +133 -0
  14. package/dist/src/framing.js.map +1 -0
  15. package/dist/src/hub.d.ts +29 -9
  16. package/dist/src/hub.js +20 -5
  17. package/dist/src/hub.js.map +1 -1
  18. package/dist/src/index.d.ts +3 -3
  19. package/dist/src/index.js +79 -17
  20. package/dist/src/index.js.map +1 -1
  21. package/dist/src/lan.d.ts +103 -0
  22. package/dist/src/lan.js +182 -0
  23. package/dist/src/lan.js.map +1 -0
  24. package/dist/src/pairing.d.ts +69 -0
  25. package/dist/src/pairing.js +125 -0
  26. package/dist/src/pairing.js.map +1 -0
  27. package/dist/src/protocol.d.ts +66 -23
  28. package/dist/src/protocol.js +4 -1
  29. package/dist/src/protocol.js.map +1 -1
  30. package/dist/src/push.d.ts +104 -0
  31. package/dist/src/push.js +339 -0
  32. package/dist/src/push.js.map +1 -0
  33. package/dist/src/relay-transport.d.ts +62 -0
  34. package/dist/src/relay-transport.js +180 -0
  35. package/dist/src/relay-transport.js.map +1 -0
  36. package/dist/src/relay-wire.d.ts +25 -0
  37. package/dist/src/relay-wire.js +37 -0
  38. package/dist/src/relay-wire.js.map +1 -0
  39. package/dist/src/rendezvous.d.ts +57 -0
  40. package/dist/src/rendezvous.js +152 -0
  41. package/dist/src/rendezvous.js.map +1 -0
  42. package/dist/src/server.d.ts +19 -7
  43. package/dist/src/server.js +180 -63
  44. package/dist/src/server.js.map +1 -1
  45. package/dist/src/session-socket.d.ts +34 -0
  46. package/dist/src/session-socket.js +84 -0
  47. package/dist/src/session-socket.js.map +1 -0
  48. package/dist/src/session.d.ts +151 -0
  49. package/dist/src/session.js +241 -0
  50. package/dist/src/session.js.map +1 -0
  51. package/dist/src/tcp-transport.d.ts +37 -0
  52. package/dist/src/tcp-transport.js +106 -0
  53. package/dist/src/tcp-transport.js.map +1 -0
  54. package/dist/src/telemetry.d.ts +69 -0
  55. package/dist/src/telemetry.js +64 -0
  56. package/dist/src/telemetry.js.map +1 -0
  57. package/dist/src/transport.d.ts +75 -0
  58. package/dist/src/transport.js +136 -0
  59. package/dist/src/transport.js.map +1 -0
  60. package/dist/src/workspace.d.ts +20 -5
  61. package/dist/src/workspace.js +19 -1
  62. package/dist/src/workspace.js.map +1 -1
  63. package/package.json +17 -4
@@ -0,0 +1,104 @@
1
+ /**
2
+ * FCM push — the daemon is the sender (docs/fcm-setup.md).
3
+ *
4
+ * The phone does NOT fight to hold the WebSocket open in the background: it
5
+ * drops the socket and reconnects on resume. So when an agent needs attention
6
+ * (a deferred permission request, a finished turn, a usage limit) and NO client
7
+ * socket is live, the daemon wakes the app via FCM. On resume the app reconnects,
8
+ * the socket replays state, and the actionable card is already there.
9
+ *
10
+ * Three pieces:
11
+ * - {@link PushTokenStore}: the set of device FCM tokens the app registered over
12
+ * the socket (`push.register`), persisted like a credential (0600).
13
+ * - {@link FcmSender}: mints a short-lived OAuth2 access token from a Firebase
14
+ * service account (env `FCM_SERVICE_ACCOUNT` → a JSON key file, or
15
+ * `FCM_SERVICE_ACCOUNT_JSON` → the JSON inline) and POSTs to the FCM v1 API.
16
+ * No `firebase-admin` dependency — a signed JWT + `fetch`, matching the
17
+ * daemon's lightweight-deps rule. Disabled (a silent no-op) when no service
18
+ * account is configured, so a daemon without FCM keeps running unchanged.
19
+ * - {@link PushNotifier}: the seam the WorkspaceManager calls. Gated on "no live
20
+ * client" so a foregrounded app (which already gets the live event + a local
21
+ * notification) is never double-notified; prunes tokens FCM reports as stale.
22
+ */
23
+ import type { PushPlatform } from "./protocol.js";
24
+ /** A device's FCM registration, keyed by the (unique) token. */
25
+ export interface PushToken {
26
+ token: string;
27
+ platform: PushPlatform;
28
+ /** ms epoch of the last register — lets a future prune drop ancient tokens. */
29
+ updatedAt: number;
30
+ }
31
+ /**
32
+ * Durable set of device FCM tokens. Single-user, but several devices may be
33
+ * paired (phone + tablet), so this is a small keyed collection rather than a
34
+ * single value. Written 0600 (a token lets anyone holding the FCM key wake that
35
+ * device), mirroring credentials.ts.
36
+ */
37
+ export declare class PushTokenStore {
38
+ private tokens;
39
+ private writeChain;
40
+ load(): Promise<void>;
41
+ all(): PushToken[];
42
+ /** Add/refresh a device token (idempotent; a re-register just bumps the time). */
43
+ register(token: string, platform: PushPlatform): Promise<boolean>;
44
+ /** Drop a token (client signed off, or FCM reported it stale). */
45
+ unregister(token: string): Promise<void>;
46
+ private persist;
47
+ }
48
+ /** What a send attempt resolved to — `stale` tells the caller to prune the token. */
49
+ export type SendOutcome = "ok" | "stale" | "error" | "disabled";
50
+ /** A ready-to-send push (platform-neutral; the sender maps it to the FCM shape). */
51
+ export interface PushMessage {
52
+ title: string;
53
+ body: string;
54
+ /** All values are strings on the wire — the sender coerces before sending. */
55
+ data: Record<string, string>;
56
+ /** Android notification channel id (matches app/lib/notifications.dart). */
57
+ androidChannel: "permissions" | "activity";
58
+ /** Wake-now (permission/limit) vs. normal (done). Drives priority + APNs. */
59
+ urgent: boolean;
60
+ }
61
+ /**
62
+ * Sends messages to FCM's HTTP v1 API using a service account. Lazily mints and
63
+ * caches an OAuth2 access token (valid ~1h); refreshes a minute before expiry.
64
+ */
65
+ export declare class FcmSender {
66
+ private sa?;
67
+ private access?;
68
+ /** Serializes token minting so a burst of sends does one token exchange. */
69
+ private minting?;
70
+ /** True once a service account has loaded — otherwise every send is a no-op. */
71
+ get enabled(): boolean;
72
+ /**
73
+ * Load the service account from the environment. `FCM_SERVICE_ACCOUNT` points
74
+ * at a JSON key file (kept OUTSIDE the repo); `FCM_SERVICE_ACCOUNT_JSON` holds
75
+ * the JSON inline (for container secrets). Absent → disabled (no throw).
76
+ */
77
+ load(): Promise<void>;
78
+ private accessToken;
79
+ private mint;
80
+ /** Send one message to one device token. Never throws — returns an outcome. */
81
+ send(token: string, msg: PushMessage): Promise<SendOutcome>;
82
+ }
83
+ /**
84
+ * The call seam the WorkspaceManager uses. Fans a wake-up out to every device
85
+ * token, but only when NO client socket is live — a foregrounded app already
86
+ * receives the event over the wire and raises its own local notification, so
87
+ * pushing then would double-notify. Tokens FCM reports as stale are pruned.
88
+ */
89
+ export declare class PushNotifier {
90
+ private tokens;
91
+ private sender;
92
+ /** True while at least one client socket is attached (see Hub). */
93
+ private hasLiveClients;
94
+ constructor(tokens: PushTokenStore, sender: FcmSender,
95
+ /** True while at least one client socket is attached (see Hub). */
96
+ hasLiveClients: () => boolean);
97
+ notifyPermission(ws: string, name: string, tool: string, summary: string, seq?: number): void;
98
+ notifyDone(ws: string, name: string, summary: string | undefined, seq?: number): void;
99
+ notifyLimit(ws: string, name: string, message: string): void;
100
+ /** Build the FCM data map: everything stringified, undefined/empty dropped. */
101
+ private data;
102
+ private dispatch;
103
+ private fanOut;
104
+ }
@@ -0,0 +1,339 @@
1
+ /**
2
+ * FCM push — the daemon is the sender (docs/fcm-setup.md).
3
+ *
4
+ * The phone does NOT fight to hold the WebSocket open in the background: it
5
+ * drops the socket and reconnects on resume. So when an agent needs attention
6
+ * (a deferred permission request, a finished turn, a usage limit) and NO client
7
+ * socket is live, the daemon wakes the app via FCM. On resume the app reconnects,
8
+ * the socket replays state, and the actionable card is already there.
9
+ *
10
+ * Three pieces:
11
+ * - {@link PushTokenStore}: the set of device FCM tokens the app registered over
12
+ * the socket (`push.register`), persisted like a credential (0600).
13
+ * - {@link FcmSender}: mints a short-lived OAuth2 access token from a Firebase
14
+ * service account (env `FCM_SERVICE_ACCOUNT` → a JSON key file, or
15
+ * `FCM_SERVICE_ACCOUNT_JSON` → the JSON inline) and POSTs to the FCM v1 API.
16
+ * No `firebase-admin` dependency — a signed JWT + `fetch`, matching the
17
+ * daemon's lightweight-deps rule. Disabled (a silent no-op) when no service
18
+ * account is configured, so a daemon without FCM keeps running unchanged.
19
+ * - {@link PushNotifier}: the seam the WorkspaceManager calls. Gated on "no live
20
+ * client" so a foregrounded app (which already gets the live event + a local
21
+ * notification) is never double-notified; prunes tokens FCM reports as stale.
22
+ */
23
+ import { createSign } from "node:crypto";
24
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
25
+ import { homedir } from "node:os";
26
+ import { dirname, join } from "node:path";
27
+ import { logger } from "./log.js";
28
+ const TOKENS_FILE = process.env.PUSH_TOKENS_FILE ?? join(homedir(), ".immediacy", "push-tokens.json");
29
+ /** Cap the stored set so a buggy/hostile client can't grow the file unbounded. */
30
+ const MAX_TOKENS = 32;
31
+ /** FCM registration tokens are ~160 chars; refuse anything absurd. */
32
+ const MAX_TOKEN_LEN = 4096;
33
+ const PLATFORMS = ["android", "ios", "macos", "web"];
34
+ function isPlatform(v) {
35
+ return typeof v === "string" && PLATFORMS.includes(v);
36
+ }
37
+ /**
38
+ * Durable set of device FCM tokens. Single-user, but several devices may be
39
+ * paired (phone + tablet), so this is a small keyed collection rather than a
40
+ * single value. Written 0600 (a token lets anyone holding the FCM key wake that
41
+ * device), mirroring credentials.ts.
42
+ */
43
+ export class PushTokenStore {
44
+ tokens = new Map();
45
+ writeChain = Promise.resolve();
46
+ async load() {
47
+ try {
48
+ const raw = await readFile(TOKENS_FILE, "utf8");
49
+ const parsed = JSON.parse(raw);
50
+ if (Array.isArray(parsed)) {
51
+ for (const e of parsed) {
52
+ if (e &&
53
+ typeof e.token === "string" &&
54
+ e.token &&
55
+ isPlatform(e.platform)) {
56
+ this.tokens.set(e.token, {
57
+ token: e.token,
58
+ platform: e.platform,
59
+ updatedAt: typeof e.updatedAt === "number" ? e.updatedAt : Date.now(),
60
+ });
61
+ }
62
+ }
63
+ }
64
+ logger.info("push", `restored ${this.tokens.size} device token(s)`);
65
+ }
66
+ catch {
67
+ /* no prior state */
68
+ }
69
+ }
70
+ all() {
71
+ return [...this.tokens.values()];
72
+ }
73
+ /** Add/refresh a device token (idempotent; a re-register just bumps the time). */
74
+ async register(token, platform) {
75
+ const t = token.trim();
76
+ if (!t || t.length > MAX_TOKEN_LEN || !isPlatform(platform))
77
+ return false;
78
+ this.tokens.set(t, { token: t, platform, updatedAt: Date.now() });
79
+ // Evict the oldest if we somehow exceeded the cap (rotated tokens on one
80
+ // device pile up until FCM prunes the stale ones — bound it defensively).
81
+ if (this.tokens.size > MAX_TOKENS) {
82
+ const oldest = [...this.tokens.values()].sort((a, b) => a.updatedAt - b.updatedAt)[0];
83
+ if (oldest)
84
+ this.tokens.delete(oldest.token);
85
+ }
86
+ await this.persist();
87
+ return true;
88
+ }
89
+ /** Drop a token (client signed off, or FCM reported it stale). */
90
+ async unregister(token) {
91
+ if (this.tokens.delete(token.trim()))
92
+ await this.persist();
93
+ }
94
+ persist() {
95
+ const snapshot = JSON.stringify([...this.tokens.values()], null, 2);
96
+ this.writeChain = this.writeChain
97
+ .catch(() => { })
98
+ .then(async () => {
99
+ await mkdir(dirname(TOKENS_FILE), { recursive: true });
100
+ const tmp = `${TOKENS_FILE}.${process.pid}.tmp`;
101
+ await writeFile(tmp, snapshot, { mode: 0o600 });
102
+ await rename(tmp, TOKENS_FILE);
103
+ });
104
+ return this.writeChain;
105
+ }
106
+ }
107
+ const SCOPE = "https://www.googleapis.com/auth/firebase.messaging";
108
+ const DEFAULT_TOKEN_URI = "https://oauth2.googleapis.com/token";
109
+ /**
110
+ * Sends messages to FCM's HTTP v1 API using a service account. Lazily mints and
111
+ * caches an OAuth2 access token (valid ~1h); refreshes a minute before expiry.
112
+ */
113
+ export class FcmSender {
114
+ sa;
115
+ access;
116
+ /** Serializes token minting so a burst of sends does one token exchange. */
117
+ minting;
118
+ /** True once a service account has loaded — otherwise every send is a no-op. */
119
+ get enabled() {
120
+ return Boolean(this.sa);
121
+ }
122
+ /**
123
+ * Load the service account from the environment. `FCM_SERVICE_ACCOUNT` points
124
+ * at a JSON key file (kept OUTSIDE the repo); `FCM_SERVICE_ACCOUNT_JSON` holds
125
+ * the JSON inline (for container secrets). Absent → disabled (no throw).
126
+ */
127
+ async load() {
128
+ try {
129
+ let raw;
130
+ const path = process.env.FCM_SERVICE_ACCOUNT?.trim();
131
+ const inline = process.env.FCM_SERVICE_ACCOUNT_JSON?.trim();
132
+ if (path)
133
+ raw = await readFile(path, "utf8");
134
+ else if (inline)
135
+ raw = inline;
136
+ if (!raw) {
137
+ logger.info("push", "FCM disabled (no FCM_SERVICE_ACCOUNT configured)");
138
+ return;
139
+ }
140
+ const parsed = JSON.parse(raw);
141
+ if (!parsed.client_email || !parsed.private_key || !parsed.project_id) {
142
+ logger.warn("push", "FCM service account missing client_email/private_key/project_id — disabled");
143
+ return;
144
+ }
145
+ this.sa = {
146
+ client_email: parsed.client_email,
147
+ private_key: parsed.private_key,
148
+ project_id: parsed.project_id,
149
+ token_uri: parsed.token_uri ?? DEFAULT_TOKEN_URI,
150
+ };
151
+ logger.info("push", `FCM enabled for project ${parsed.project_id}`);
152
+ }
153
+ catch (err) {
154
+ logger.warn("push", "failed to load FCM service account — disabled", {
155
+ data: { message: err instanceof Error ? err.message : String(err) },
156
+ });
157
+ }
158
+ }
159
+ async accessToken() {
160
+ if (!this.sa)
161
+ return undefined;
162
+ const now = Math.floor(Date.now() / 1000);
163
+ if (this.access && this.access.expiresAt - 60 > now)
164
+ return this.access.token;
165
+ // Coalesce concurrent refreshes onto one in-flight exchange.
166
+ if (!this.minting) {
167
+ this.minting = this.mint().finally(() => {
168
+ this.minting = undefined;
169
+ });
170
+ }
171
+ await this.minting;
172
+ return this.access?.token;
173
+ }
174
+ async mint() {
175
+ const sa = this.sa;
176
+ if (!sa)
177
+ return;
178
+ const now = Math.floor(Date.now() / 1000);
179
+ const tokenUri = sa.token_uri ?? DEFAULT_TOKEN_URI;
180
+ const header = Buffer.from(JSON.stringify({ alg: "RS256", typ: "JWT" })).toString("base64url");
181
+ const claims = Buffer.from(JSON.stringify({ iss: sa.client_email, scope: SCOPE, aud: tokenUri, iat: now, exp: now + 3600 })).toString("base64url");
182
+ const signingInput = `${header}.${claims}`;
183
+ const signer = createSign("RSA-SHA256");
184
+ signer.update(signingInput);
185
+ signer.end();
186
+ const signature = signer.sign(sa.private_key).toString("base64url");
187
+ const assertion = `${signingInput}.${signature}`;
188
+ const res = await fetch(tokenUri, {
189
+ method: "POST",
190
+ headers: { "content-type": "application/x-www-form-urlencoded" },
191
+ body: new URLSearchParams({
192
+ grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
193
+ assertion,
194
+ }),
195
+ });
196
+ if (!res.ok) {
197
+ throw new Error(`token endpoint ${res.status}: ${(await res.text()).slice(0, 200)}`);
198
+ }
199
+ const json = (await res.json());
200
+ this.access = { token: json.access_token, expiresAt: now + json.expires_in };
201
+ }
202
+ /** Send one message to one device token. Never throws — returns an outcome. */
203
+ async send(token, msg) {
204
+ const sa = this.sa;
205
+ if (!sa)
206
+ return "disabled";
207
+ let access;
208
+ try {
209
+ access = await this.accessToken();
210
+ }
211
+ catch (err) {
212
+ logger.warn("push", "FCM token mint failed", {
213
+ data: { message: err instanceof Error ? err.message : String(err) },
214
+ });
215
+ return "error";
216
+ }
217
+ if (!access)
218
+ return "error";
219
+ const body = {
220
+ message: {
221
+ token,
222
+ notification: { title: msg.title, body: msg.body },
223
+ data: msg.data,
224
+ android: {
225
+ priority: msg.urgent ? "high" : "normal",
226
+ notification: { channel_id: msg.androidChannel },
227
+ },
228
+ apns: {
229
+ headers: { "apns-priority": msg.urgent ? "10" : "5" },
230
+ // content-available wakes a backgrounded app so it can reconnect;
231
+ // an alert + sound covers the terminated case.
232
+ payload: { aps: { sound: "default", "content-available": 1 } },
233
+ },
234
+ },
235
+ };
236
+ try {
237
+ const res = await fetch(`https://fcm.googleapis.com/v1/projects/${sa.project_id}/messages:send`, {
238
+ method: "POST",
239
+ headers: {
240
+ authorization: `Bearer ${access}`,
241
+ "content-type": "application/json",
242
+ },
243
+ body: JSON.stringify(body),
244
+ });
245
+ if (res.ok)
246
+ return "ok";
247
+ const text = (await res.text()).slice(0, 300);
248
+ // 404 UNREGISTERED / 400 INVALID_ARGUMENT on the token ⇒ the app was
249
+ // uninstalled or the token rotated; drop it so we stop trying.
250
+ if (res.status === 404 || /UNREGISTERED|INVALID_ARGUMENT/.test(text)) {
251
+ logger.info("push", "pruning stale FCM token", { data: { status: res.status } });
252
+ return "stale";
253
+ }
254
+ // 401 usually means our cached access token went bad — clear it so the
255
+ // next send re-mints.
256
+ if (res.status === 401)
257
+ this.access = undefined;
258
+ logger.warn("push", "FCM send failed", { data: { status: res.status, body: text } });
259
+ return "error";
260
+ }
261
+ catch (err) {
262
+ logger.warn("push", "FCM send threw", {
263
+ data: { message: err instanceof Error ? err.message : String(err) },
264
+ });
265
+ return "error";
266
+ }
267
+ }
268
+ }
269
+ /**
270
+ * The call seam the WorkspaceManager uses. Fans a wake-up out to every device
271
+ * token, but only when NO client socket is live — a foregrounded app already
272
+ * receives the event over the wire and raises its own local notification, so
273
+ * pushing then would double-notify. Tokens FCM reports as stale are pruned.
274
+ */
275
+ export class PushNotifier {
276
+ tokens;
277
+ sender;
278
+ hasLiveClients;
279
+ constructor(tokens, sender,
280
+ /** True while at least one client socket is attached (see Hub). */
281
+ hasLiveClients) {
282
+ this.tokens = tokens;
283
+ this.sender = sender;
284
+ this.hasLiveClients = hasLiveClients;
285
+ }
286
+ notifyPermission(ws, name, tool, summary, seq) {
287
+ this.dispatch({
288
+ title: `${name} — approval needed`,
289
+ body: summary || `${tool} needs your approval`,
290
+ data: this.data({ ws, kind: "permission", tool, seq }),
291
+ androidChannel: "permissions",
292
+ urgent: true,
293
+ });
294
+ }
295
+ notifyDone(ws, name, summary, seq) {
296
+ this.dispatch({
297
+ title: `${name} — done`,
298
+ body: summary && summary.length ? summary : "The agent finished its turn.",
299
+ data: this.data({ ws, kind: "done", seq }),
300
+ androidChannel: "activity",
301
+ urgent: false,
302
+ });
303
+ }
304
+ notifyLimit(ws, name, message) {
305
+ this.dispatch({
306
+ title: `${name} — usage limit reached`,
307
+ body: message || "The agent hit a usage limit.",
308
+ data: this.data({ ws, kind: "limit" }),
309
+ androidChannel: "activity",
310
+ urgent: true,
311
+ });
312
+ }
313
+ /** Build the FCM data map: everything stringified, undefined/empty dropped. */
314
+ data(fields) {
315
+ const out = {};
316
+ for (const [k, v] of Object.entries(fields)) {
317
+ if (v !== undefined && v !== "")
318
+ out[k] = String(v);
319
+ }
320
+ return out;
321
+ }
322
+ dispatch(msg) {
323
+ // A live socket already delivers this event (and the app raises its own local
324
+ // notification), so only remote-wake when nothing is attached.
325
+ if (this.hasLiveClients())
326
+ return;
327
+ if (!this.sender.enabled)
328
+ return;
329
+ void this.fanOut(msg);
330
+ }
331
+ async fanOut(msg) {
332
+ for (const t of this.tokens.all()) {
333
+ const outcome = await this.sender.send(t.token, msg);
334
+ if (outcome === "stale")
335
+ await this.tokens.unregister(t.token);
336
+ }
337
+ }
338
+ }
339
+ //# sourceMappingURL=push.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push.js","sourceRoot":"","sources":["../../src/push.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Relay transport (daemon side) — docs/WEBRTC_MIGRATION.md Phase 4.
3
+ *
4
+ * The daemon keeps ONE persistent WebSocket to its Durable Object (`/daemon/
5
+ * <rid>`) whenever it's running. The DO multiplexes every client over that single
6
+ * socket, tagged by client id. `RelayDaemon` demuxes: each client id gets its own
7
+ * `Transport` (a `SubTransport` over the shared ws), which feeds a `Mux` + the
8
+ * §5.3 responder handshake — exactly the same session stack the loopback and LAN
9
+ * paths use. The relay only ever sees sealed frames.
10
+ */
11
+ import { Mux } from "./transport.js";
12
+ import type { SecureChannel } from "./session.js";
13
+ import type { RendezvousIdentity } from "./rendezvous.js";
14
+ import type { DeviceTable } from "./devices.js";
15
+ export interface RelaySession {
16
+ clientId: number;
17
+ mux: Mux;
18
+ channel: SecureChannel;
19
+ devicePub: string;
20
+ close(): void;
21
+ /** Register a callback for when this client's relay socket drops. */
22
+ onClose(cb: () => void): void;
23
+ }
24
+ export interface RelayDaemonDeps {
25
+ identity: RendezvousIdentity;
26
+ devices: DeviceTable;
27
+ /**
28
+ * Single-use pairing codes. When present, a brand-new device can pair inline
29
+ * during the handshake (msg1 carries a code). Omit to disable relay pairing.
30
+ */
31
+ pairing?: {
32
+ consume(code: string): boolean;
33
+ };
34
+ }
35
+ /**
36
+ * Maintains the daemon's persistent relay socket and per-client sessions. Call
37
+ * `connect()` to dial; it reconnects with backoff on drop so the daemon is
38
+ * reachable over the relay whenever it's running (the Phase-4 acceptance:
39
+ * "daemon restart auto-reconnects with no user action").
40
+ */
41
+ export declare class RelayDaemon {
42
+ private readonly deps;
43
+ private readonly relayUrl;
44
+ private readonly opts?;
45
+ private ws;
46
+ private readonly subs;
47
+ private closed;
48
+ private attempt;
49
+ private reconnectTimer;
50
+ /** Called for each client that completes the handshake over the relay. */
51
+ onSession?: (session: RelaySession) => void;
52
+ constructor(deps: RelayDaemonDeps, relayUrl: string, opts?: {
53
+ license?: string;
54
+ } | undefined);
55
+ /** The daemon endpoint for this daemon's rendezvous id. */
56
+ private url;
57
+ connect(): void;
58
+ private onMessage;
59
+ private openClient;
60
+ private onClose;
61
+ close(): void;
62
+ }