@workerdeck/relay 3.3.0 → 3.4.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/build/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { i as runRelayCli, n as RelayUsageError, t as RELAY_HELP } from "./command-myavxZoY.mjs";
2
+ import { i as runRelayCli, n as RelayUsageError, t as RELAY_HELP } from "./command-_C1jiMvx.mjs";
3
3
  //#region src/cli.ts
4
4
  runRelayCli(process.argv.slice(2)).then((code) => process.exit(code), (error) => {
5
5
  console.error(`workerdeck-relay: ${error instanceof Error ? error.message : String(error)}${error instanceof RelayUsageError ? `\n\n${RELAY_HELP}` : ""}`);
@@ -1,11 +1,12 @@
1
1
  import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
2
- import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
+ import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
3
3
  import { dirname, join, resolve } from "node:path";
4
4
  import { RELAY_CLOSE, RELAY_FRAME_MAX_BYTES, RELAY_GATEWAY_NAME, RELAY_MAX_HOPS, RELAY_MESSAGE_MAX_CHARS, RELAY_OPS, RELAY_WIRE_VERSION, canonicalJson, decodeFrame, encodeFrame, isRelayOp, parseRelayPeerId, registryHash, relayPeerId } from "@workerdeck/relay-client";
5
- import { createServer } from "node:http";
5
+ import { createServer, request } from "node:http";
6
6
  import { createServer as createServer$1 } from "node:https";
7
7
  import { unwatchFile, watchFile } from "node:fs";
8
8
  import { WebSocketServer } from "ws";
9
+ import { createConnection } from "node:net";
9
10
  import { homedir } from "node:os";
10
11
  //#region src/enrollment.ts
11
12
  const ENROLLMENT_FILE = "gateways.json";
@@ -126,8 +127,81 @@ function allowedOps(rules, from, to, entry, ceiling) {
126
127
  return RELAY_OPS.filter((op) => ops.has(op) && ceiling.has(op));
127
128
  }
128
129
  //#endregion
130
+ //#region src/status.ts
131
+ function statusSocketPath(stateDir) {
132
+ return join(stateDir, "relay.sock");
133
+ }
134
+ function socketAnswers(path) {
135
+ return new Promise((resolve) => {
136
+ const probe = createConnection(path);
137
+ probe.once("connect", () => {
138
+ probe.end();
139
+ resolve(true);
140
+ });
141
+ probe.once("error", () => resolve(false));
142
+ });
143
+ }
144
+ async function serveStatusSocket(stateDir, status) {
145
+ const path = statusSocketPath(stateDir);
146
+ await mkdir(stateDir, {
147
+ recursive: true,
148
+ mode: 448
149
+ });
150
+ if (await socketAnswers(path)) throw new Error(`another relay is already serving ${stateDir} (${path} answers)`);
151
+ await rm(path, { force: true });
152
+ const server = createServer((req, res) => {
153
+ if (req.url !== "/status" || req.method !== "GET") {
154
+ res.writeHead(404).end();
155
+ return;
156
+ }
157
+ res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify(status()));
158
+ });
159
+ await new Promise((resolve, reject) => {
160
+ server.once("error", reject);
161
+ server.listen(path, () => {
162
+ server.off("error", reject);
163
+ resolve();
164
+ });
165
+ });
166
+ await chmod(path, 384);
167
+ return server;
168
+ }
169
+ function fetchRelayStatus(stateDir, timeoutMs = 5e3) {
170
+ const socketPath = statusSocketPath(stateDir);
171
+ return new Promise((resolve, reject) => {
172
+ const req = request({
173
+ socketPath,
174
+ path: "/status",
175
+ method: "GET",
176
+ timeout: timeoutMs
177
+ }, (res) => {
178
+ if (res.statusCode !== 200) {
179
+ res.resume();
180
+ reject(/* @__PURE__ */ new Error(`HTTP ${res.statusCode}`));
181
+ return;
182
+ }
183
+ let body = "";
184
+ res.setEncoding("utf8");
185
+ res.on("data", (chunk) => body += chunk);
186
+ res.on("end", () => {
187
+ try {
188
+ resolve(JSON.parse(body));
189
+ } catch (error) {
190
+ reject(error);
191
+ }
192
+ });
193
+ });
194
+ req.on("timeout", () => req.destroy(/* @__PURE__ */ new Error("timed out")));
195
+ req.on("error", reject);
196
+ req.end();
197
+ });
198
+ }
199
+ //#endregion
129
200
  //#region src/relay.ts
130
201
  const WINDOW_MS = 6e4;
202
+ function notFound(_req, res) {
203
+ res.writeHead(404).end();
204
+ }
131
205
  function isLoopback(address) {
132
206
  return address === "127.0.0.1" || address === "::1" || address === "::ffff:127.0.0.1";
133
207
  }
@@ -399,13 +473,6 @@ async function startRelay(options) {
399
473
  if (gateway) drop(gateway, 1e3, "disconnected");
400
474
  });
401
475
  };
402
- const serveStatus = (req, res) => {
403
- if (req.url !== "/status" || req.method !== "GET" || !isLoopback(req.socket.remoteAddress)) {
404
- res.writeHead(404).end();
405
- return;
406
- }
407
- res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify(status()));
408
- };
409
476
  const status = () => {
410
477
  const names = /* @__PURE__ */ new Set([...Object.keys(enrollments.gateways), ...gateways.keys()]);
411
478
  return {
@@ -430,7 +497,7 @@ async function startRelay(options) {
430
497
  const server = options.tls ? createServer$1({
431
498
  cert: options.tls.cert,
432
499
  key: options.tls.key
433
- }, serveStatus) : createServer(serveStatus);
500
+ }, notFound) : createServer(notFound);
434
501
  const wss = new WebSocketServer({
435
502
  server,
436
503
  maxPayload: RELAY_FRAME_MAX_BYTES
@@ -454,13 +521,23 @@ async function startRelay(options) {
454
521
  persistent: false
455
522
  }, () => void reload());
456
523
  const host = options.host ?? "127.0.0.1";
457
- await new Promise((resolve, reject) => {
458
- server.once("error", reject);
459
- server.listen(options.port ?? 7777, host, () => {
460
- server.off("error", reject);
461
- resolve();
524
+ const statusServer = await serveStatusSocket(options.stateDir, status);
525
+ const closeStatus = async () => {
526
+ await new Promise((resolve) => statusServer.close(() => resolve()));
527
+ await rm(statusSocketPath(options.stateDir), { force: true });
528
+ };
529
+ try {
530
+ await new Promise((resolve, reject) => {
531
+ server.once("error", reject);
532
+ server.listen(options.port ?? 7777, host, () => {
533
+ server.off("error", reject);
534
+ resolve();
535
+ });
462
536
  });
463
- });
537
+ } catch (error) {
538
+ await closeStatus();
539
+ throw error;
540
+ }
464
541
  const port = server.address().port;
465
542
  const url = `${options.tls ? "wss" : "ws"}://${host.includes(":") ? `[${host}]` : host}:${port}`;
466
543
  if (!options.tls && !isLoopback(host) && host !== "localhost") log(`relay: serving plain ws:// on ${host}; gateway keys cross the network unencrypted unless this is a tailnet or a TLS proxy`);
@@ -476,6 +553,7 @@ async function startRelay(options) {
476
553
  for (const client of wss.clients) client.terminate();
477
554
  await new Promise((resolve) => wss.close(() => resolve()));
478
555
  await new Promise((resolve) => server.close(() => resolve()));
556
+ await closeStatus();
479
557
  }
480
558
  };
481
559
  }
@@ -500,10 +578,10 @@ Usage
500
578
  workerdeck-relay enroll <name> [--rotate] enroll a gateway and print its key once
501
579
  workerdeck-relay revoke <name> revoke a gateway; a running relay drops it within seconds
502
580
  workerdeck-relay list list enrolled gateways
503
- workerdeck-relay status [options] ask a running relay on this machine who is online
581
+ workerdeck-relay status ask the relay serving this state dir who is online
504
582
 
505
583
  Options
506
- --state-dir <path> enrollments (gateways.json) and rules (rules.json); default ~/.workerdeck/relay
584
+ --state-dir <path> enrollments, rules and the status socket (relay.sock); default ~/.workerdeck/relay
507
585
  --host <addr> listen address (default 127.0.0.1)
508
586
  --port <n> listen port (default 7777)
509
587
  --tls-cert <file> serve wss:// with this certificate (optional, plain ws:// otherwise)
@@ -613,14 +691,12 @@ async function runRelayCli(argv) {
613
691
  return 0;
614
692
  }
615
693
  case "status": {
616
- const url = `${flags.tlsCert ? "https" : "http"}://127.0.0.1:${flags.port ?? 7777}/status`;
617
694
  let status;
618
695
  try {
619
- const res = await fetch(url);
620
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
621
- status = await res.json();
696
+ status = await fetchRelayStatus(flags.stateDir);
622
697
  } catch (error) {
623
- console.error(`workerdeck-relay: no relay answered at ${url} (${error instanceof Error ? error.message : String(error)})`);
698
+ const socket = statusSocketPath(flags.stateDir);
699
+ console.error(`workerdeck-relay: no relay answered on ${socket} (${error instanceof Error ? error.message : String(error)})`);
624
700
  return 2;
625
701
  }
626
702
  for (const gateway of status.gateways) console.log(gateway.online ? `${gateway.name}\tonline\t${gateway.sessions} session(s)\taccepts ${gateway.ops.join(", ") || "nothing"}` : `${gateway.name}\toffline`);
@@ -632,6 +708,6 @@ async function runRelayCli(argv) {
632
708
  }
633
709
  }
634
710
  //#endregion
635
- export { newGatewayKey as _, startRelay as a, parseRules as c, ENROLLMENT_FILE as d, checkGatewayName as f, keyMatches as g, hashKey as h, runRelayCli as i, readRules as l, enrollmentPath as m, RelayUsageError as n, RULES_FILE as o, enrollGateway as p, parseRelayArgs as r, allowedOps as s, RELAY_HELP as t, rulesPath as u, readEnrollments as v, revokeGateway as y };
711
+ export { revokeGateway as S, enrollmentPath as _, startRelay as a, newGatewayKey as b, statusSocketPath as c, parseRules as d, readRules as f, enrollGateway as g, checkGatewayName as h, runRelayCli as i, RULES_FILE as l, ENROLLMENT_FILE as m, RelayUsageError as n, fetchRelayStatus as o, rulesPath as p, parseRelayArgs as r, serveStatusSocket as s, RELAY_HELP as t, allowedOps as u, hashKey as v, readEnrollments as x, keyMatches as y };
636
712
 
637
- //# sourceMappingURL=command-myavxZoY.mjs.map
713
+ //# sourceMappingURL=command-_C1jiMvx.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-_C1jiMvx.mjs","names":["createHttpsServer","createHttpServer"],"sources":["../src/enrollment.ts","../src/rules.ts","../src/status.ts","../src/relay.ts","../src/command.ts"],"sourcesContent":["import { createHash, randomBytes, timingSafeEqual } from 'node:crypto'\nimport { mkdir, readFile, rename, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport { RELAY_GATEWAY_NAME } from '@workerdeck/relay-client'\n\nexport type Enrollment = { hash: string; enrolledAt: number }\n\nexport type EnrollmentFile = { version: 1; gateways: Record<string, Enrollment> }\n\nexport const ENROLLMENT_FILE = 'gateways.json'\n\nexport function enrollmentPath(stateDir: string): string {\n return join(stateDir, ENROLLMENT_FILE)\n}\n\nexport function hashKey(key: string): string {\n return createHash('sha256').update(key).digest('hex')\n}\n\nexport function newGatewayKey(): string {\n return `wdr_${randomBytes(32).toString('base64url')}`\n}\n\nexport function keyMatches(enrollment: Enrollment | undefined, key: string): boolean {\n const presented = Buffer.from(hashKey(key), 'hex')\n const stored = Buffer.from(enrollment?.hash ?? hashKey(''), 'hex')\n return presented.length === stored.length && timingSafeEqual(presented, stored) && enrollment !== undefined\n}\n\nexport function checkGatewayName(name: string): string | null {\n return RELAY_GATEWAY_NAME.test(name)\n ? null\n : 'a gateway name is 1 to 63 lowercase letters, digits or dashes, starting with a letter or digit'\n}\n\nexport async function readEnrollments(stateDir: string): Promise<EnrollmentFile> {\n let text: string\n try {\n text = await readFile(enrollmentPath(stateDir), 'utf8')\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n return { version: 1, gateways: {} }\n }\n throw error\n }\n const parsed = JSON.parse(text) as Partial<EnrollmentFile>\n const gateways: Record<string, Enrollment> = {}\n for (const [name, entry] of Object.entries(parsed.gateways ?? {})) {\n if (checkGatewayName(name) === null && typeof entry?.hash === 'string' && /^[0-9a-f]{64}$/.test(entry.hash)) {\n gateways[name] = { hash: entry.hash, enrolledAt: typeof entry.enrolledAt === 'number' ? entry.enrolledAt : 0 }\n }\n }\n return { version: 1, gateways }\n}\n\nasync function writeEnrollments(stateDir: string, file: EnrollmentFile): Promise<void> {\n const path = enrollmentPath(stateDir)\n await mkdir(dirname(path), { recursive: true, mode: 0o700 })\n const tmp = `${path}.${process.pid}.tmp`\n await writeFile(tmp, `${JSON.stringify(file, null, 2)}\\n`, { mode: 0o600 })\n await rename(tmp, path)\n}\n\nexport async function enrollGateway(stateDir: string, name: string, options?: { rotate?: boolean }): Promise<string> {\n const invalid = checkGatewayName(name)\n if (invalid) {\n throw new Error(invalid)\n }\n const file = await readEnrollments(stateDir)\n if (file.gateways[name] && !options?.rotate) {\n throw new Error(`gateway ${name} is already enrolled; pass --rotate to issue a new key`)\n }\n const key = newGatewayKey()\n file.gateways[name] = { hash: hashKey(key), enrolledAt: Date.now() }\n await writeEnrollments(stateDir, file)\n return key\n}\n\nexport async function revokeGateway(stateDir: string, name: string): Promise<boolean> {\n const file = await readEnrollments(stateDir)\n if (!file.gateways[name]) {\n return false\n }\n delete file.gateways[name]\n await writeEnrollments(stateDir, file)\n return true\n}\n","import { readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { RELAY_OPS, isRelayOp, type RelayOp, type RelaySessionEntry } from '@workerdeck/relay-client'\n\nexport type RelayRule = {\n from: string\n to: string\n allow: RelayOp[]\n projects?: string[]\n}\n\nexport const RULES_FILE = 'rules.json'\n\nexport function rulesPath(stateDir: string): string {\n return join(stateDir, RULES_FILE)\n}\n\nexport function parseRules(value: unknown): RelayRule[] {\n const list = (value as { rules?: unknown } | null)?.rules\n if (!Array.isArray(list)) {\n throw new Error('rules file must be an object with a \"rules\" array')\n }\n return list.map((raw, index) => {\n const rule = raw as { from?: unknown; to?: unknown; allow?: unknown; scope?: { projects?: unknown } }\n const where = `rules[${index}]`\n if (typeof rule?.from !== 'string' || typeof rule.to !== 'string' || !rule.from || !rule.to) {\n throw new Error(`${where}: \"from\" and \"to\" must be gateway names or \"*\"`)\n }\n if (rule.allow !== undefined && (!Array.isArray(rule.allow) || !rule.allow.every(isRelayOp))) {\n throw new Error(`${where}: \"allow\" must list \"send\" and/or \"peek\"`)\n }\n const projects = rule.scope?.projects\n if (\n projects !== undefined &&\n (!Array.isArray(projects) || !projects.every((path) => typeof path === 'string' && path.startsWith('/')))\n ) {\n throw new Error(`${where}: \"scope.projects\" must list absolute paths`)\n }\n return {\n from: rule.from,\n to: rule.to,\n allow: rule.allow === undefined ? [...RELAY_OPS] : [...new Set(rule.allow as RelayOp[])],\n ...(projects ? { projects: projects as string[] } : {}),\n }\n })\n}\n\nexport async function readRules(stateDir: string): Promise<RelayRule[]> {\n let text: string\n try {\n text = await readFile(rulesPath(stateDir), 'utf8')\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n return []\n }\n throw error\n }\n return parseRules(JSON.parse(text))\n}\n\nfunction underPath(path: string | undefined, root: string): boolean {\n if (!path) {\n return false\n }\n const base = root.length > 1 && root.endsWith('/') ? root.slice(0, -1) : root\n return path === base || path.startsWith(base === '/' ? '/' : `${base}/`)\n}\n\nfunction inScope(rule: RelayRule, entry: RelaySessionEntry): boolean {\n return !rule.projects || rule.projects.some((root) => underPath(entry.cwd, root) || underPath(entry.project?.root, root))\n}\n\n// Union of every matching rule, intersected with what the target gateway accepts at all. Empty means\n// the session is invisible to the requester.\nexport function allowedOps(\n rules: readonly RelayRule[],\n from: string,\n to: string,\n entry: RelaySessionEntry,\n ceiling: ReadonlySet<RelayOp>,\n): RelayOp[] {\n const ops = new Set<RelayOp>()\n for (const rule of rules) {\n if ((rule.from === '*' || rule.from === from) && (rule.to === '*' || rule.to === to) && inScope(rule, entry)) {\n for (const op of rule.allow) {\n ops.add(op)\n }\n }\n }\n return RELAY_OPS.filter((op) => ops.has(op) && ceiling.has(op))\n}\n","import { chmod, mkdir, rm } from 'node:fs/promises'\nimport { createServer, request, type Server } from 'node:http'\nimport { createConnection } from 'node:net'\nimport { join } from 'node:path'\nimport type { RelayStatus } from './relay.ts'\n\nexport function statusSocketPath(stateDir: string): string {\n return join(stateDir, 'relay.sock')\n}\n\nfunction socketAnswers(path: string): Promise<boolean> {\n return new Promise((resolve) => {\n const probe = createConnection(path)\n probe.once('connect', () => {\n probe.end()\n resolve(true)\n })\n probe.once('error', () => resolve(false))\n })\n}\n\nexport async function serveStatusSocket(stateDir: string, status: () => RelayStatus): Promise<Server> {\n const path = statusSocketPath(stateDir)\n await mkdir(stateDir, { recursive: true, mode: 0o700 })\n if (await socketAnswers(path)) {\n throw new Error(`another relay is already serving ${stateDir} (${path} answers)`)\n }\n await rm(path, { force: true })\n const server = createServer((req, res) => {\n if (req.url !== '/status' || req.method !== 'GET') {\n res.writeHead(404).end()\n return\n }\n res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify(status()))\n })\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject)\n server.listen(path, () => {\n server.off('error', reject)\n resolve()\n })\n })\n await chmod(path, 0o600)\n return server\n}\n\nexport function fetchRelayStatus(stateDir: string, timeoutMs = 5_000): Promise<RelayStatus> {\n const socketPath = statusSocketPath(stateDir)\n return new Promise((resolve, reject) => {\n const req = request({ socketPath, path: '/status', method: 'GET', timeout: timeoutMs }, (res) => {\n if (res.statusCode !== 200) {\n res.resume()\n reject(new Error(`HTTP ${res.statusCode}`))\n return\n }\n let body = ''\n res.setEncoding('utf8')\n res.on('data', (chunk: string) => (body += chunk))\n res.on('end', () => {\n try {\n resolve(JSON.parse(body) as RelayStatus)\n } catch (error) {\n reject(error)\n }\n })\n })\n req.on('timeout', () => req.destroy(new Error('timed out')))\n req.on('error', reject)\n req.end()\n })\n}\n","import { createServer as createHttpServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http'\nimport { createServer as createHttpsServer } from 'node:https'\nimport type { AddressInfo } from 'node:net'\nimport { unwatchFile, watchFile } from 'node:fs'\nimport { rm } from 'node:fs/promises'\nimport { WebSocketServer, type RawData, type WebSocket } from 'ws'\nimport {\n RELAY_CLOSE,\n RELAY_FRAME_MAX_BYTES,\n RELAY_MAX_HOPS,\n RELAY_MESSAGE_MAX_CHARS,\n RELAY_WIRE_VERSION,\n canonicalJson,\n decodeFrame,\n encodeFrame,\n isRelayOp,\n parseRelayPeerId,\n registryHash,\n relayPeerId,\n type InboundPeek,\n type InboundSend,\n type RelayFrame,\n type RelayOp,\n type RelayOrigin,\n type RelayPeerRow,\n type RelaySendResult,\n type RelaySessionEntry,\n} from '@workerdeck/relay-client'\nimport { enrollmentPath, keyMatches, readEnrollments, type EnrollmentFile } from './enrollment.ts'\nimport { allowedOps, readRules, rulesPath, type RelayRule } from './rules.ts'\nimport { serveStatusSocket, statusSocketPath } from './status.ts'\n\nexport type RelayOptions = {\n stateDir: string\n host?: string\n port?: number\n tls?: { cert: string | Buffer; key: string | Buffer }\n requestTimeoutMs?: number\n heartbeatMs?: number\n helloTimeoutMs?: number\n perMinute?: number\n maxHops?: number\n maxMessageChars?: number\n watchIntervalMs?: number\n log?: (line: string) => void\n}\n\nexport type RelayGatewayStatus = { name: string; online: boolean; sessions: number; connectedAt?: number; ops: RelayOp[] }\n\nexport type RelayStatus = { version: number; gateways: RelayGatewayStatus[] }\n\nexport type Relay = {\n url: string\n port: number\n status(): RelayStatus\n reload(): Promise<void>\n close(): Promise<void>\n}\n\ntype Gateway = {\n name: string\n socket: WebSocket\n ops: Set<RelayOp>\n connectedAt: number\n seq: number\n entries: Map<string, RelaySessionEntry>\n json: Map<string, string>\n missed: number\n}\n\ntype Routed = { gateway: string; timer: NodeJS.Timeout; settle: (frame: { ok: boolean; result?: unknown; error?: string }) => void }\n\ntype Frame = { t: string; [key: string]: unknown }\n\nconst WINDOW_MS = 60_000\n\nfunction notFound(_req: IncomingMessage, res: ServerResponse): void {\n res.writeHead(404).end()\n}\n\nfunction isLoopback(address: string | undefined): boolean {\n return address === '127.0.0.1' || address === '::1' || address === '::ffff:127.0.0.1'\n}\n\nfunction readEntries(value: unknown): RelaySessionEntry[] | undefined {\n if (!Array.isArray(value)) {\n return undefined\n }\n const entries = value.filter(\n (entry): entry is RelaySessionEntry =>\n typeof entry === 'object' && entry !== null && typeof entry.id === 'string' && entry.id.length > 0 && typeof entry.cwd === 'string',\n )\n return entries.length === value.length ? entries : undefined\n}\n\nexport async function startRelay(options: RelayOptions): Promise<Relay> {\n const log = options.log ?? ((line: string) => console.log(line))\n const requestTimeoutMs = options.requestTimeoutMs ?? 8_000\n const heartbeatMs = options.heartbeatMs ?? 15_000\n const helloTimeoutMs = options.helloTimeoutMs ?? 10_000\n const perMinute = options.perMinute ?? 10\n const maxHops = options.maxHops ?? RELAY_MAX_HOPS\n const maxChars = options.maxMessageChars ?? RELAY_MESSAGE_MAX_CHARS\n const watchIntervalMs = options.watchIntervalMs ?? 2_000\n\n let enrollments: EnrollmentFile = await readEnrollments(options.stateDir)\n let rules: RelayRule[] = await readRules(options.stateDir)\n const gateways = new Map<string, Gateway>()\n const routed = new Map<string, Routed>()\n const sent = new Map<string, number[]>()\n let nextId = 0\n\n const drop = (gateway: Gateway, code: number, reason: string): void => {\n if (gateways.get(gateway.name) === gateway) {\n gateways.delete(gateway.name)\n for (const [id, entry] of routed) {\n if (entry.gateway === gateway.name) {\n clearTimeout(entry.timer)\n routed.delete(id)\n entry.settle({ ok: false, error: `gateway ${gateway.name} went offline` })\n }\n }\n log(`relay: ${gateway.name} offline (${reason})`)\n }\n if (gateway.socket.readyState === gateway.socket.OPEN || gateway.socket.readyState === gateway.socket.CONNECTING) {\n gateway.socket.close(code, reason)\n }\n }\n\n const reload = async (): Promise<void> => {\n try {\n enrollments = await readEnrollments(options.stateDir)\n } catch (error) {\n log(`relay: keeping the previous enrollments, ${enrollmentPath(options.stateDir)} is unreadable: ${String(error)}`)\n }\n try {\n rules = await readRules(options.stateDir)\n } catch (error) {\n log(\n `relay: keeping the previous rules, ${rulesPath(options.stateDir)} is invalid: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n for (const gateway of gateways.values()) {\n if (!enrollments.gateways[gateway.name]) {\n drop(gateway, RELAY_CLOSE.revoked, 'revoked')\n }\n }\n }\n\n const underRateLimit = (key: string): boolean => {\n const now = Date.now()\n const recent = (sent.get(key) ?? []).filter((at) => now - at < WINDOW_MS)\n const ok = recent.length < perMinute\n if (ok) {\n recent.push(now)\n }\n sent.set(key, recent)\n return ok\n }\n\n const route = (target: Gateway, frame: Omit<InboundPeek, 'id'> | Omit<InboundSend, 'id'>): Promise<unknown> => {\n const id = `r${++nextId}`\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n routed.delete(id)\n reject(new Error(`gateway ${target.name} did not answer in time`))\n }, requestTimeoutMs)\n routed.set(id, {\n gateway: target.name,\n timer,\n settle: (res) => (res.ok ? resolve(res.result) : reject(new Error(res.error ?? 'request failed'))),\n })\n send(target.socket, { ...frame, id } as RelayFrame)\n })\n }\n\n const visible = (from: Gateway, target: Gateway, entry: RelaySessionEntry): RelayOp[] =>\n target === from ? [] : allowedOps(rules, from.name, target.name, entry, target.ops)\n\n const list = (from: Gateway): RelayPeerRow[] => {\n const rows: RelayPeerRow[] = []\n for (const target of gateways.values()) {\n for (const entry of target.entries.values()) {\n const allow = visible(from, target, entry)\n if (allow.length > 0) {\n rows.push({ ...entry, gateway: target.name, allow })\n }\n }\n }\n return rows.sort((a, b) => (b.lastActivityAt ?? b.createdAt) - (a.lastActivityAt ?? a.createdAt))\n }\n\n const resolveTarget = (from: Gateway, to: unknown, op: RelayOp): { target: Gateway; entry: RelaySessionEntry } | undefined => {\n const where = to as { gateway?: unknown; id?: unknown } | undefined\n if (typeof where?.gateway !== 'string' || typeof where.id !== 'string') {\n return undefined\n }\n const target = gateways.get(where.gateway)\n const entry = target?.entries.get(where.id)\n if (!target || !entry || !visible(from, target, entry).includes(op)) {\n return undefined\n }\n return { target, entry }\n }\n\n const peerRequest = async (from: Gateway, frame: Frame): Promise<unknown> => {\n const sender = typeof frame.from === 'string' ? from.entries.get(frame.from) : undefined\n if (frame.t === 'peer.list') {\n return sender ? list(from) : []\n }\n const to = frame.to as { gateway?: string; id?: string } | undefined\n const label = `${to?.gateway ?? '?'}:${to?.id ?? '?'}`\n if (frame.t === 'peer.peek') {\n const found = sender && resolveTarget(from, frame.to, 'peek')\n if (!found) {\n return null\n }\n const recent = typeof frame.recent === 'number' ? frame.recent : undefined\n return route(found.target, { t: 'peer.peek', origin: originOf(from, sender, []), to: found.entry.id, recent })\n }\n const text = typeof frame.text === 'string' ? frame.text : ''\n const found = sender && resolveTarget(from, frame.to, 'send')\n if (!found || !sender) {\n return { delivered: false, reason: `no such session: ${label}` } satisfies RelaySendResult\n }\n if (text.length === 0 || text.length > maxChars) {\n return {\n delivered: false,\n reason: `message is ${text.length} characters; the limit is ${maxChars}. Write it to a file and send the path.`,\n }\n }\n const hops = (Array.isArray(frame.hops) ? frame.hops : [])\n .filter((hop): hop is string => typeof hop === 'string')\n .map((hop) => (parseRelayPeerId(hop) ? hop : relayPeerId(from.name, hop)))\n if (hops.length > maxHops) {\n return {\n delivered: false,\n reason: `${hops.length} messages have passed between sessions without a person speaking; stop and ask your user before continuing.`,\n }\n }\n if (!underRateLimit(`${relayPeerId(from.name, sender.id)}->${relayPeerId(found.target.name, found.entry.id)}`)) {\n return { delivered: false, reason: `rate limit: at most ${perMinute} messages a minute to one session. Batch what you have to say.` }\n }\n return route(found.target, { t: 'peer.send', origin: originOf(from, sender, hops), to: found.entry.id, text })\n }\n\n const onGatewayFrame = (gateway: Gateway, frame: Frame): void => {\n switch (frame.t) {\n case 'registry.snapshot': {\n const entries = readEntries(frame.entries)\n if (!entries || typeof frame.seq !== 'number') {\n return\n }\n gateway.entries = new Map(entries.map((entry) => [entry.id, entry]))\n gateway.json = new Map(entries.map((entry) => [entry.id, canonicalJson(entry)]))\n gateway.seq = frame.seq\n return\n }\n case 'registry.delta': {\n const upsert = readEntries(frame.upsert)\n const remove = Array.isArray(frame.remove) ? frame.remove.filter((id): id is string => typeof id === 'string') : undefined\n if (!upsert || !remove || frame.seq !== gateway.seq + 1) {\n send(gateway.socket, { t: 'registry.resync' })\n return\n }\n for (const id of remove) {\n gateway.entries.delete(id)\n gateway.json.delete(id)\n }\n for (const entry of upsert) {\n gateway.entries.set(entry.id, entry)\n gateway.json.set(entry.id, canonicalJson(entry))\n }\n gateway.seq = frame.seq\n return\n }\n case 'registry.digest': {\n if (frame.seq !== gateway.seq) {\n return\n }\n if (frame.count !== gateway.entries.size || frame.hash !== registryHash(gateway.json)) {\n log(`relay: ${gateway.name} registry drifted; asking for a snapshot`)\n send(gateway.socket, { t: 'registry.resync' })\n }\n return\n }\n case 'res': {\n const id = typeof frame.id === 'string' ? frame.id : ''\n const entry = routed.get(id)\n if (!entry || entry.gateway !== gateway.name) {\n return\n }\n routed.delete(id)\n clearTimeout(entry.timer)\n entry.settle({ ok: frame.ok === true, result: frame.result, error: typeof frame.error === 'string' ? frame.error : undefined })\n return\n }\n case 'peer.list':\n case 'peer.peek':\n case 'peer.send': {\n const id = typeof frame.id === 'string' ? frame.id : undefined\n if (!id) {\n return\n }\n peerRequest(gateway, frame).then(\n (result) => send(gateway.socket, { t: 'res', id, ok: true, result: result ?? null }),\n (error: unknown) =>\n send(gateway.socket, { t: 'res', id, ok: false, error: error instanceof Error ? error.message : String(error) }),\n )\n return\n }\n }\n }\n\n const onHello = (socket: WebSocket, frame: Frame): Gateway | undefined => {\n const name = typeof frame.gateway === 'string' ? frame.gateway : ''\n if (frame.t !== 'hello' || !name || typeof frame.key !== 'string') {\n socket.close(RELAY_CLOSE.badHello, 'expected hello')\n return undefined\n }\n if (frame.version !== RELAY_WIRE_VERSION) {\n socket.close(RELAY_CLOSE.versionMismatch, `relay speaks wire version ${RELAY_WIRE_VERSION}`)\n return undefined\n }\n const enrollment = enrollments.gateways[name]\n if (!keyMatches(enrollment, frame.key)) {\n log(`relay: refused a hello for ${JSON.stringify(name.slice(0, 64))}`)\n socket.close(RELAY_CLOSE.unauthorized, 'unauthorized')\n return undefined\n }\n const ceiling = (frame.ceiling as { ops?: unknown } | undefined)?.ops\n const previous = gateways.get(name)\n if (previous) {\n drop(previous, RELAY_CLOSE.replaced, 'replaced by a newer connection')\n }\n const gateway: Gateway = {\n name,\n socket,\n ops: new Set(Array.isArray(ceiling) ? ceiling.filter(isRelayOp) : []),\n connectedAt: Date.now(),\n seq: 0,\n entries: new Map(),\n json: new Map(),\n missed: 0,\n }\n gateways.set(name, gateway)\n log(`relay: ${name} online`)\n send(socket, { t: 'welcome', relayVersion: RELAY_WIRE_VERSION })\n return gateway\n }\n\n const onConnection = (socket: WebSocket): void => {\n let gateway: Gateway | undefined\n const helloTimer = setTimeout(() => socket.close(RELAY_CLOSE.timeout, 'no hello'), helloTimeoutMs)\n socket.on('message', (raw: RawData) => {\n const frame = decodeFrame(Array.isArray(raw) ? Buffer.concat(raw) : raw instanceof ArrayBuffer ? Buffer.from(raw) : raw)\n if (gateway) {\n gateway.missed = 0\n if (frame) {\n onGatewayFrame(gateway, frame)\n }\n return\n }\n clearTimeout(helloTimer)\n gateway = frame ? onHello(socket, frame) : undefined\n if (!gateway && socket.readyState === socket.OPEN) {\n socket.close(RELAY_CLOSE.badHello, 'expected hello')\n }\n })\n socket.on('pong', () => {\n if (gateway) {\n gateway.missed = 0\n }\n })\n socket.on('error', () => {})\n socket.on('close', () => {\n clearTimeout(helloTimer)\n if (gateway) {\n drop(gateway, 1000, 'disconnected')\n }\n })\n }\n\n const status = (): RelayStatus => {\n const names = new Set([...Object.keys(enrollments.gateways), ...gateways.keys()])\n return {\n version: RELAY_WIRE_VERSION,\n gateways: [...names].sort().map((name) => {\n const gateway = gateways.get(name)\n return gateway\n ? { name, online: true, sessions: gateway.entries.size, connectedAt: gateway.connectedAt, ops: [...gateway.ops] }\n : { name, online: false, sessions: 0, ops: [] }\n }),\n }\n }\n\n const server: Server = options.tls\n ? createHttpsServer({ cert: options.tls.cert, key: options.tls.key }, notFound)\n : createHttpServer(notFound)\n const wss = new WebSocketServer({ server, maxPayload: RELAY_FRAME_MAX_BYTES })\n wss.on('connection', onConnection)\n\n const heartbeat = setInterval(() => {\n for (const gateway of gateways.values()) {\n gateway.missed += 1\n if (gateway.missed > 2) {\n drop(gateway, RELAY_CLOSE.timeout, 'heartbeat')\n gateway.socket.terminate()\n continue\n }\n gateway.socket.ping()\n }\n }, heartbeatMs)\n heartbeat.unref()\n\n const watched = [enrollmentPath(options.stateDir), rulesPath(options.stateDir)]\n for (const path of watched) {\n watchFile(path, { interval: watchIntervalMs, persistent: false }, () => void reload())\n }\n\n const host = options.host ?? '127.0.0.1'\n const statusServer = await serveStatusSocket(options.stateDir, status)\n const closeStatus = async (): Promise<void> => {\n await new Promise<void>((resolve) => statusServer.close(() => resolve()))\n await rm(statusSocketPath(options.stateDir), { force: true })\n }\n try {\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject)\n server.listen(options.port ?? 7777, host, () => {\n server.off('error', reject)\n resolve()\n })\n })\n } catch (error) {\n await closeStatus()\n throw error\n }\n const port = (server.address() as AddressInfo).port\n const scheme = options.tls ? 'wss' : 'ws'\n const url = `${scheme}://${host.includes(':') ? `[${host}]` : host}:${port}`\n if (!options.tls && !isLoopback(host) && host !== 'localhost') {\n log(`relay: serving plain ws:// on ${host}; gateway keys cross the network unencrypted unless this is a tailnet or a TLS proxy`)\n }\n\n return {\n url,\n port,\n status,\n reload,\n close: async () => {\n clearInterval(heartbeat)\n for (const path of watched) {\n unwatchFile(path)\n }\n for (const gateway of gateways.values()) {\n drop(gateway, 1001, 'relay shutting down')\n }\n for (const client of wss.clients) {\n client.terminate()\n }\n await new Promise<void>((resolve) => wss.close(() => resolve()))\n await new Promise<void>((resolve) => server.close(() => resolve()))\n await closeStatus()\n },\n }\n}\n\nfunction send(socket: WebSocket, frame: RelayFrame): void {\n if (socket.readyState === socket.OPEN) {\n socket.send(encodeFrame(frame))\n }\n}\n\nfunction originOf(from: Gateway, sender: RelaySessionEntry, hops: string[]): RelayOrigin {\n return { gateway: from.name, sessionId: sender.id, name: sender.title, engine: sender.engine, hops }\n}\n","import { readFile } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { join, resolve } from 'node:path'\nimport { enrollGateway, readEnrollments, revokeGateway } from './enrollment.ts'\nimport { startRelay, type RelayStatus } from './relay.ts'\nimport { fetchRelayStatus, statusSocketPath } from './status.ts'\nimport { readRules, rulesPath } from './rules.ts'\n\nexport const RELAY_HELP = `workerdeck-relay - the cross-gateway peer relay. Also runs as \\`workerdeck relay\\`.\n\nUsage\n workerdeck-relay serve [options] run the relay\n workerdeck-relay enroll <name> [--rotate] enroll a gateway and print its key once\n workerdeck-relay revoke <name> revoke a gateway; a running relay drops it within seconds\n workerdeck-relay list list enrolled gateways\n workerdeck-relay status ask the relay serving this state dir who is online\n\nOptions\n --state-dir <path> enrollments, rules and the status socket (relay.sock); default ~/.workerdeck/relay\n --host <addr> listen address (default 127.0.0.1)\n --port <n> listen port (default 7777)\n --tls-cert <file> serve wss:// with this certificate (optional, plain ws:// otherwise)\n --tls-key <file> the certificate's private key\n -h, --help this text\n`\n\nexport type RelayFlags = {\n command?: string\n name?: string\n stateDir: string\n host?: string\n port?: number\n tlsCert?: string\n tlsKey?: string\n rotate?: boolean\n help?: boolean\n}\n\nexport class RelayUsageError extends Error {}\n\nexport function parseRelayArgs(argv: readonly string[], env: NodeJS.ProcessEnv = process.env): RelayFlags {\n const flags: RelayFlags = { stateDir: env.WORKERDECK_RELAY_STATE_DIR ?? join(homedir(), '.workerdeck', 'relay') }\n const positional: string[] = []\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i]!\n const value = (): string => {\n const next = argv[++i]\n if (next === undefined) {\n throw new RelayUsageError(`${arg} needs a value`)\n }\n return next\n }\n switch (arg) {\n case '--state-dir': {\n flags.stateDir = resolve(value())\n break\n }\n case '--host': {\n flags.host = value()\n break\n }\n case '--port': {\n const port = Number(value())\n if (!Number.isInteger(port) || port < 0 || port > 65535) {\n throw new RelayUsageError('--port must be a port number')\n }\n flags.port = port\n break\n }\n case '--tls-cert': {\n flags.tlsCert = resolve(value())\n break\n }\n case '--tls-key': {\n flags.tlsKey = resolve(value())\n break\n }\n case '--rotate': {\n flags.rotate = true\n break\n }\n case '-h':\n case '--help': {\n flags.help = true\n break\n }\n default: {\n if (arg.startsWith('-')) {\n throw new RelayUsageError(`unknown option: ${arg}`)\n }\n positional.push(arg)\n }\n }\n }\n flags.command = positional[0]\n flags.name = positional[1]\n if (Boolean(flags.tlsCert) !== Boolean(flags.tlsKey)) {\n throw new RelayUsageError('--tls-cert and --tls-key go together')\n }\n return flags\n}\n\nexport async function runRelayCli(argv: readonly string[]): Promise<number> {\n let flags: RelayFlags\n try {\n flags = parseRelayArgs(argv)\n } catch (error) {\n if (error instanceof RelayUsageError) {\n console.error(`workerdeck-relay: ${error.message}\\n\\n${RELAY_HELP}`)\n return 2\n }\n throw error\n }\n if (flags.help || !flags.command) {\n console.log(RELAY_HELP)\n return flags.help ? 0 : 2\n }\n switch (flags.command) {\n case 'serve': {\n await readRules(flags.stateDir)\n const relay = await startRelay({\n stateDir: flags.stateDir,\n host: flags.host,\n port: flags.port,\n tls: flags.tlsCert && flags.tlsKey ? { cert: await readFile(flags.tlsCert), key: await readFile(flags.tlsKey) } : undefined,\n })\n const enrolled = Object.keys((await readEnrollments(flags.stateDir)).gateways).length\n console.log(`workerdeck-relay listening on ${relay.url} (${enrolled} gateway(s) enrolled, rules at ${rulesPath(flags.stateDir)})`)\n await new Promise<void>((done) => {\n const stop = () => void relay.close().then(done)\n process.once('SIGINT', stop)\n process.once('SIGTERM', stop)\n })\n return 0\n }\n case 'enroll': {\n if (!flags.name) {\n throw new RelayUsageError('enroll needs a gateway name')\n }\n const key = await enrollGateway(flags.stateDir, flags.name, { rotate: flags.rotate })\n console.log(\n `Enrolled ${flags.name}. Its key, shown once:\\n\\n ${key}\\n\\n` +\n `Save it on that gateway (for example ~/.workerdeck/relay.key, mode 0600) and point relay.keyFile at it.`,\n )\n return 0\n }\n case 'revoke': {\n if (!flags.name) {\n throw new RelayUsageError('revoke needs a gateway name')\n }\n const removed = await revokeGateway(flags.stateDir, flags.name)\n console.log(removed ? `Revoked ${flags.name}.` : `${flags.name} was not enrolled.`)\n return removed ? 0 : 1\n }\n case 'list': {\n const file = await readEnrollments(flags.stateDir)\n const names = Object.entries(file.gateways).sort(([a], [b]) => (a < b ? -1 : 1))\n if (names.length === 0) {\n console.log('No gateways enrolled.')\n }\n for (const [name, entry] of names) {\n console.log(`${name}\\tenrolled ${new Date(entry.enrolledAt).toISOString()}`)\n }\n return 0\n }\n case 'status': {\n let status: RelayStatus\n try {\n status = await fetchRelayStatus(flags.stateDir)\n } catch (error) {\n const socket = statusSocketPath(flags.stateDir)\n console.error(`workerdeck-relay: no relay answered on ${socket} (${error instanceof Error ? error.message : String(error)})`)\n return 2\n }\n for (const gateway of status.gateways) {\n console.log(\n gateway.online\n ? `${gateway.name}\\tonline\\t${gateway.sessions} session(s)\\taccepts ${gateway.ops.join(', ') || 'nothing'}`\n : `${gateway.name}\\toffline`,\n )\n }\n return 0\n }\n default: {\n console.error(`workerdeck-relay: unknown command ${flags.command}\\n\\n${RELAY_HELP}`)\n return 2\n }\n }\n}\n"],"mappings":"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"}
package/build/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { RelayOp, RelaySessionEntry } from "@workerdeck/relay-client";
2
+ import { Server } from "node:http";
2
3
  //#region src/enrollment.d.ts
3
4
  type Enrollment = {
4
5
  hash: string;
@@ -71,8 +72,13 @@ type Relay = {
71
72
  };
72
73
  declare function startRelay(options: RelayOptions): Promise<Relay>;
73
74
  //#endregion
75
+ //#region src/status.d.ts
76
+ declare function statusSocketPath(stateDir: string): string;
77
+ declare function serveStatusSocket(stateDir: string, status: () => RelayStatus): Promise<Server>;
78
+ declare function fetchRelayStatus(stateDir: string, timeoutMs?: number): Promise<RelayStatus>;
79
+ //#endregion
74
80
  //#region src/command.d.ts
75
- declare const RELAY_HELP = "workerdeck-relay - the cross-gateway peer relay. Also runs as `workerdeck relay`.\n\nUsage\n workerdeck-relay serve [options] run the relay\n workerdeck-relay enroll <name> [--rotate] enroll a gateway and print its key once\n workerdeck-relay revoke <name> revoke a gateway; a running relay drops it within seconds\n workerdeck-relay list list enrolled gateways\n workerdeck-relay status [options] ask a running relay on this machine who is online\n\nOptions\n --state-dir <path> enrollments (gateways.json) and rules (rules.json); default ~/.workerdeck/relay\n --host <addr> listen address (default 127.0.0.1)\n --port <n> listen port (default 7777)\n --tls-cert <file> serve wss:// with this certificate (optional, plain ws:// otherwise)\n --tls-key <file> the certificate's private key\n -h, --help this text\n";
81
+ declare const RELAY_HELP = "workerdeck-relay - the cross-gateway peer relay. Also runs as `workerdeck relay`.\n\nUsage\n workerdeck-relay serve [options] run the relay\n workerdeck-relay enroll <name> [--rotate] enroll a gateway and print its key once\n workerdeck-relay revoke <name> revoke a gateway; a running relay drops it within seconds\n workerdeck-relay list list enrolled gateways\n workerdeck-relay status ask the relay serving this state dir who is online\n\nOptions\n --state-dir <path> enrollments, rules and the status socket (relay.sock); default ~/.workerdeck/relay\n --host <addr> listen address (default 127.0.0.1)\n --port <n> listen port (default 7777)\n --tls-cert <file> serve wss:// with this certificate (optional, plain ws:// otherwise)\n --tls-key <file> the certificate's private key\n -h, --help this text\n";
76
82
  type RelayFlags = {
77
83
  command?: string;
78
84
  name?: string;
@@ -88,5 +94,5 @@ declare class RelayUsageError extends Error {}
88
94
  declare function parseRelayArgs(argv: readonly string[], env?: NodeJS.ProcessEnv): RelayFlags;
89
95
  declare function runRelayCli(argv: readonly string[]): Promise<number>;
90
96
  //#endregion
91
- export { ENROLLMENT_FILE, Enrollment, EnrollmentFile, RELAY_HELP, RULES_FILE, Relay, RelayFlags, RelayGatewayStatus, RelayOptions, RelayRule, RelayStatus, RelayUsageError, allowedOps, checkGatewayName, enrollGateway, enrollmentPath, hashKey, keyMatches, newGatewayKey, parseRelayArgs, parseRules, readEnrollments, readRules, revokeGateway, rulesPath, runRelayCli, startRelay };
97
+ export { ENROLLMENT_FILE, Enrollment, EnrollmentFile, RELAY_HELP, RULES_FILE, Relay, RelayFlags, RelayGatewayStatus, RelayOptions, RelayRule, RelayStatus, RelayUsageError, allowedOps, checkGatewayName, enrollGateway, enrollmentPath, fetchRelayStatus, hashKey, keyMatches, newGatewayKey, parseRelayArgs, parseRules, readEnrollments, readRules, revokeGateway, rulesPath, runRelayCli, serveStatusSocket, startRelay, statusSocketPath };
92
98
  //# sourceMappingURL=index.d.mts.map
package/build/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { _ as newGatewayKey, a as startRelay, c as parseRules, d as ENROLLMENT_FILE, f as checkGatewayName, g as keyMatches, h as hashKey, i as runRelayCli, l as readRules, m as enrollmentPath, n as RelayUsageError, o as RULES_FILE, p as enrollGateway, r as parseRelayArgs, s as allowedOps, t as RELAY_HELP, u as rulesPath, v as readEnrollments, y as revokeGateway } from "./command-myavxZoY.mjs";
2
- export { ENROLLMENT_FILE, RELAY_HELP, RULES_FILE, RelayUsageError, allowedOps, checkGatewayName, enrollGateway, enrollmentPath, hashKey, keyMatches, newGatewayKey, parseRelayArgs, parseRules, readEnrollments, readRules, revokeGateway, rulesPath, runRelayCli, startRelay };
1
+ import { S as revokeGateway, _ as enrollmentPath, a as startRelay, b as newGatewayKey, c as statusSocketPath, d as parseRules, f as readRules, g as enrollGateway, h as checkGatewayName, i as runRelayCli, l as RULES_FILE, m as ENROLLMENT_FILE, n as RelayUsageError, o as fetchRelayStatus, p as rulesPath, r as parseRelayArgs, s as serveStatusSocket, t as RELAY_HELP, u as allowedOps, v as hashKey, x as readEnrollments, y as keyMatches } from "./command-_C1jiMvx.mjs";
2
+ export { ENROLLMENT_FILE, RELAY_HELP, RULES_FILE, RelayUsageError, allowedOps, checkGatewayName, enrollGateway, enrollmentPath, fetchRelayStatus, hashKey, keyMatches, newGatewayKey, parseRelayArgs, parseRules, readEnrollments, readRules, revokeGateway, rulesPath, runRelayCli, serveStatusSocket, startRelay, statusSocketPath };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workerdeck/relay",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "type": "module",
5
5
  "description": "The WorkerDeck relay: one always-on process every gateway dials out to. Holds the cross-gateway session registry, authenticates enrolled gateways, enforces the visibility rules and routes peer requests between them.",
6
6
  "license": "MIT",
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "ws": "^8.21.3",
24
- "@workerdeck/relay-client": "3.3.0"
24
+ "@workerdeck/relay-client": "3.4.0"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/node": "^26.6.1",
@@ -1 +0,0 @@
1
- {"version":3,"file":"command-myavxZoY.mjs","names":["createHttpsServer","createHttpServer"],"sources":["../src/enrollment.ts","../src/rules.ts","../src/relay.ts","../src/command.ts"],"sourcesContent":["import { createHash, randomBytes, timingSafeEqual } from 'node:crypto'\nimport { mkdir, readFile, rename, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport { RELAY_GATEWAY_NAME } from '@workerdeck/relay-client'\n\nexport type Enrollment = { hash: string; enrolledAt: number }\n\nexport type EnrollmentFile = { version: 1; gateways: Record<string, Enrollment> }\n\nexport const ENROLLMENT_FILE = 'gateways.json'\n\nexport function enrollmentPath(stateDir: string): string {\n return join(stateDir, ENROLLMENT_FILE)\n}\n\nexport function hashKey(key: string): string {\n return createHash('sha256').update(key).digest('hex')\n}\n\nexport function newGatewayKey(): string {\n return `wdr_${randomBytes(32).toString('base64url')}`\n}\n\nexport function keyMatches(enrollment: Enrollment | undefined, key: string): boolean {\n const presented = Buffer.from(hashKey(key), 'hex')\n const stored = Buffer.from(enrollment?.hash ?? hashKey(''), 'hex')\n return presented.length === stored.length && timingSafeEqual(presented, stored) && enrollment !== undefined\n}\n\nexport function checkGatewayName(name: string): string | null {\n return RELAY_GATEWAY_NAME.test(name)\n ? null\n : 'a gateway name is 1 to 63 lowercase letters, digits or dashes, starting with a letter or digit'\n}\n\nexport async function readEnrollments(stateDir: string): Promise<EnrollmentFile> {\n let text: string\n try {\n text = await readFile(enrollmentPath(stateDir), 'utf8')\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n return { version: 1, gateways: {} }\n }\n throw error\n }\n const parsed = JSON.parse(text) as Partial<EnrollmentFile>\n const gateways: Record<string, Enrollment> = {}\n for (const [name, entry] of Object.entries(parsed.gateways ?? {})) {\n if (checkGatewayName(name) === null && typeof entry?.hash === 'string' && /^[0-9a-f]{64}$/.test(entry.hash)) {\n gateways[name] = { hash: entry.hash, enrolledAt: typeof entry.enrolledAt === 'number' ? entry.enrolledAt : 0 }\n }\n }\n return { version: 1, gateways }\n}\n\nasync function writeEnrollments(stateDir: string, file: EnrollmentFile): Promise<void> {\n const path = enrollmentPath(stateDir)\n await mkdir(dirname(path), { recursive: true, mode: 0o700 })\n const tmp = `${path}.${process.pid}.tmp`\n await writeFile(tmp, `${JSON.stringify(file, null, 2)}\\n`, { mode: 0o600 })\n await rename(tmp, path)\n}\n\nexport async function enrollGateway(stateDir: string, name: string, options?: { rotate?: boolean }): Promise<string> {\n const invalid = checkGatewayName(name)\n if (invalid) {\n throw new Error(invalid)\n }\n const file = await readEnrollments(stateDir)\n if (file.gateways[name] && !options?.rotate) {\n throw new Error(`gateway ${name} is already enrolled; pass --rotate to issue a new key`)\n }\n const key = newGatewayKey()\n file.gateways[name] = { hash: hashKey(key), enrolledAt: Date.now() }\n await writeEnrollments(stateDir, file)\n return key\n}\n\nexport async function revokeGateway(stateDir: string, name: string): Promise<boolean> {\n const file = await readEnrollments(stateDir)\n if (!file.gateways[name]) {\n return false\n }\n delete file.gateways[name]\n await writeEnrollments(stateDir, file)\n return true\n}\n","import { readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { RELAY_OPS, isRelayOp, type RelayOp, type RelaySessionEntry } from '@workerdeck/relay-client'\n\nexport type RelayRule = {\n from: string\n to: string\n allow: RelayOp[]\n projects?: string[]\n}\n\nexport const RULES_FILE = 'rules.json'\n\nexport function rulesPath(stateDir: string): string {\n return join(stateDir, RULES_FILE)\n}\n\nexport function parseRules(value: unknown): RelayRule[] {\n const list = (value as { rules?: unknown } | null)?.rules\n if (!Array.isArray(list)) {\n throw new Error('rules file must be an object with a \"rules\" array')\n }\n return list.map((raw, index) => {\n const rule = raw as { from?: unknown; to?: unknown; allow?: unknown; scope?: { projects?: unknown } }\n const where = `rules[${index}]`\n if (typeof rule?.from !== 'string' || typeof rule.to !== 'string' || !rule.from || !rule.to) {\n throw new Error(`${where}: \"from\" and \"to\" must be gateway names or \"*\"`)\n }\n if (rule.allow !== undefined && (!Array.isArray(rule.allow) || !rule.allow.every(isRelayOp))) {\n throw new Error(`${where}: \"allow\" must list \"send\" and/or \"peek\"`)\n }\n const projects = rule.scope?.projects\n if (\n projects !== undefined &&\n (!Array.isArray(projects) || !projects.every((path) => typeof path === 'string' && path.startsWith('/')))\n ) {\n throw new Error(`${where}: \"scope.projects\" must list absolute paths`)\n }\n return {\n from: rule.from,\n to: rule.to,\n allow: rule.allow === undefined ? [...RELAY_OPS] : [...new Set(rule.allow as RelayOp[])],\n ...(projects ? { projects: projects as string[] } : {}),\n }\n })\n}\n\nexport async function readRules(stateDir: string): Promise<RelayRule[]> {\n let text: string\n try {\n text = await readFile(rulesPath(stateDir), 'utf8')\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n return []\n }\n throw error\n }\n return parseRules(JSON.parse(text))\n}\n\nfunction underPath(path: string | undefined, root: string): boolean {\n if (!path) {\n return false\n }\n const base = root.length > 1 && root.endsWith('/') ? root.slice(0, -1) : root\n return path === base || path.startsWith(base === '/' ? '/' : `${base}/`)\n}\n\nfunction inScope(rule: RelayRule, entry: RelaySessionEntry): boolean {\n return !rule.projects || rule.projects.some((root) => underPath(entry.cwd, root) || underPath(entry.project?.root, root))\n}\n\n// Union of every matching rule, intersected with what the target gateway accepts at all. Empty means\n// the session is invisible to the requester.\nexport function allowedOps(\n rules: readonly RelayRule[],\n from: string,\n to: string,\n entry: RelaySessionEntry,\n ceiling: ReadonlySet<RelayOp>,\n): RelayOp[] {\n const ops = new Set<RelayOp>()\n for (const rule of rules) {\n if ((rule.from === '*' || rule.from === from) && (rule.to === '*' || rule.to === to) && inScope(rule, entry)) {\n for (const op of rule.allow) {\n ops.add(op)\n }\n }\n }\n return RELAY_OPS.filter((op) => ops.has(op) && ceiling.has(op))\n}\n","import { createServer as createHttpServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http'\nimport { createServer as createHttpsServer } from 'node:https'\nimport type { AddressInfo } from 'node:net'\nimport { unwatchFile, watchFile } from 'node:fs'\nimport { WebSocketServer, type RawData, type WebSocket } from 'ws'\nimport {\n RELAY_CLOSE,\n RELAY_FRAME_MAX_BYTES,\n RELAY_MAX_HOPS,\n RELAY_MESSAGE_MAX_CHARS,\n RELAY_WIRE_VERSION,\n canonicalJson,\n decodeFrame,\n encodeFrame,\n isRelayOp,\n parseRelayPeerId,\n registryHash,\n relayPeerId,\n type InboundPeek,\n type InboundSend,\n type RelayFrame,\n type RelayOp,\n type RelayOrigin,\n type RelayPeerRow,\n type RelaySendResult,\n type RelaySessionEntry,\n} from '@workerdeck/relay-client'\nimport { enrollmentPath, keyMatches, readEnrollments, type EnrollmentFile } from './enrollment.ts'\nimport { allowedOps, readRules, rulesPath, type RelayRule } from './rules.ts'\n\nexport type RelayOptions = {\n stateDir: string\n host?: string\n port?: number\n tls?: { cert: string | Buffer; key: string | Buffer }\n requestTimeoutMs?: number\n heartbeatMs?: number\n helloTimeoutMs?: number\n perMinute?: number\n maxHops?: number\n maxMessageChars?: number\n watchIntervalMs?: number\n log?: (line: string) => void\n}\n\nexport type RelayGatewayStatus = { name: string; online: boolean; sessions: number; connectedAt?: number; ops: RelayOp[] }\n\nexport type RelayStatus = { version: number; gateways: RelayGatewayStatus[] }\n\nexport type Relay = {\n url: string\n port: number\n status(): RelayStatus\n reload(): Promise<void>\n close(): Promise<void>\n}\n\ntype Gateway = {\n name: string\n socket: WebSocket\n ops: Set<RelayOp>\n connectedAt: number\n seq: number\n entries: Map<string, RelaySessionEntry>\n json: Map<string, string>\n missed: number\n}\n\ntype Routed = { gateway: string; timer: NodeJS.Timeout; settle: (frame: { ok: boolean; result?: unknown; error?: string }) => void }\n\ntype Frame = { t: string; [key: string]: unknown }\n\nconst WINDOW_MS = 60_000\n\nfunction isLoopback(address: string | undefined): boolean {\n return address === '127.0.0.1' || address === '::1' || address === '::ffff:127.0.0.1'\n}\n\nfunction readEntries(value: unknown): RelaySessionEntry[] | undefined {\n if (!Array.isArray(value)) {\n return undefined\n }\n const entries = value.filter(\n (entry): entry is RelaySessionEntry =>\n typeof entry === 'object' && entry !== null && typeof entry.id === 'string' && entry.id.length > 0 && typeof entry.cwd === 'string',\n )\n return entries.length === value.length ? entries : undefined\n}\n\nexport async function startRelay(options: RelayOptions): Promise<Relay> {\n const log = options.log ?? ((line: string) => console.log(line))\n const requestTimeoutMs = options.requestTimeoutMs ?? 8_000\n const heartbeatMs = options.heartbeatMs ?? 15_000\n const helloTimeoutMs = options.helloTimeoutMs ?? 10_000\n const perMinute = options.perMinute ?? 10\n const maxHops = options.maxHops ?? RELAY_MAX_HOPS\n const maxChars = options.maxMessageChars ?? RELAY_MESSAGE_MAX_CHARS\n const watchIntervalMs = options.watchIntervalMs ?? 2_000\n\n let enrollments: EnrollmentFile = await readEnrollments(options.stateDir)\n let rules: RelayRule[] = await readRules(options.stateDir)\n const gateways = new Map<string, Gateway>()\n const routed = new Map<string, Routed>()\n const sent = new Map<string, number[]>()\n let nextId = 0\n\n const drop = (gateway: Gateway, code: number, reason: string): void => {\n if (gateways.get(gateway.name) === gateway) {\n gateways.delete(gateway.name)\n for (const [id, entry] of routed) {\n if (entry.gateway === gateway.name) {\n clearTimeout(entry.timer)\n routed.delete(id)\n entry.settle({ ok: false, error: `gateway ${gateway.name} went offline` })\n }\n }\n log(`relay: ${gateway.name} offline (${reason})`)\n }\n if (gateway.socket.readyState === gateway.socket.OPEN || gateway.socket.readyState === gateway.socket.CONNECTING) {\n gateway.socket.close(code, reason)\n }\n }\n\n const reload = async (): Promise<void> => {\n try {\n enrollments = await readEnrollments(options.stateDir)\n } catch (error) {\n log(`relay: keeping the previous enrollments, ${enrollmentPath(options.stateDir)} is unreadable: ${String(error)}`)\n }\n try {\n rules = await readRules(options.stateDir)\n } catch (error) {\n log(\n `relay: keeping the previous rules, ${rulesPath(options.stateDir)} is invalid: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n for (const gateway of gateways.values()) {\n if (!enrollments.gateways[gateway.name]) {\n drop(gateway, RELAY_CLOSE.revoked, 'revoked')\n }\n }\n }\n\n const underRateLimit = (key: string): boolean => {\n const now = Date.now()\n const recent = (sent.get(key) ?? []).filter((at) => now - at < WINDOW_MS)\n const ok = recent.length < perMinute\n if (ok) {\n recent.push(now)\n }\n sent.set(key, recent)\n return ok\n }\n\n const route = (target: Gateway, frame: Omit<InboundPeek, 'id'> | Omit<InboundSend, 'id'>): Promise<unknown> => {\n const id = `r${++nextId}`\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n routed.delete(id)\n reject(new Error(`gateway ${target.name} did not answer in time`))\n }, requestTimeoutMs)\n routed.set(id, {\n gateway: target.name,\n timer,\n settle: (res) => (res.ok ? resolve(res.result) : reject(new Error(res.error ?? 'request failed'))),\n })\n send(target.socket, { ...frame, id } as RelayFrame)\n })\n }\n\n const visible = (from: Gateway, target: Gateway, entry: RelaySessionEntry): RelayOp[] =>\n target === from ? [] : allowedOps(rules, from.name, target.name, entry, target.ops)\n\n const list = (from: Gateway): RelayPeerRow[] => {\n const rows: RelayPeerRow[] = []\n for (const target of gateways.values()) {\n for (const entry of target.entries.values()) {\n const allow = visible(from, target, entry)\n if (allow.length > 0) {\n rows.push({ ...entry, gateway: target.name, allow })\n }\n }\n }\n return rows.sort((a, b) => (b.lastActivityAt ?? b.createdAt) - (a.lastActivityAt ?? a.createdAt))\n }\n\n const resolveTarget = (from: Gateway, to: unknown, op: RelayOp): { target: Gateway; entry: RelaySessionEntry } | undefined => {\n const where = to as { gateway?: unknown; id?: unknown } | undefined\n if (typeof where?.gateway !== 'string' || typeof where.id !== 'string') {\n return undefined\n }\n const target = gateways.get(where.gateway)\n const entry = target?.entries.get(where.id)\n if (!target || !entry || !visible(from, target, entry).includes(op)) {\n return undefined\n }\n return { target, entry }\n }\n\n const peerRequest = async (from: Gateway, frame: Frame): Promise<unknown> => {\n const sender = typeof frame.from === 'string' ? from.entries.get(frame.from) : undefined\n if (frame.t === 'peer.list') {\n return sender ? list(from) : []\n }\n const to = frame.to as { gateway?: string; id?: string } | undefined\n const label = `${to?.gateway ?? '?'}:${to?.id ?? '?'}`\n if (frame.t === 'peer.peek') {\n const found = sender && resolveTarget(from, frame.to, 'peek')\n if (!found) {\n return null\n }\n const recent = typeof frame.recent === 'number' ? frame.recent : undefined\n return route(found.target, { t: 'peer.peek', origin: originOf(from, sender, []), to: found.entry.id, recent })\n }\n const text = typeof frame.text === 'string' ? frame.text : ''\n const found = sender && resolveTarget(from, frame.to, 'send')\n if (!found || !sender) {\n return { delivered: false, reason: `no such session: ${label}` } satisfies RelaySendResult\n }\n if (text.length === 0 || text.length > maxChars) {\n return {\n delivered: false,\n reason: `message is ${text.length} characters; the limit is ${maxChars}. Write it to a file and send the path.`,\n }\n }\n const hops = (Array.isArray(frame.hops) ? frame.hops : [])\n .filter((hop): hop is string => typeof hop === 'string')\n .map((hop) => (parseRelayPeerId(hop) ? hop : relayPeerId(from.name, hop)))\n if (hops.length > maxHops) {\n return {\n delivered: false,\n reason: `${hops.length} messages have passed between sessions without a person speaking; stop and ask your user before continuing.`,\n }\n }\n if (!underRateLimit(`${relayPeerId(from.name, sender.id)}->${relayPeerId(found.target.name, found.entry.id)}`)) {\n return { delivered: false, reason: `rate limit: at most ${perMinute} messages a minute to one session. Batch what you have to say.` }\n }\n return route(found.target, { t: 'peer.send', origin: originOf(from, sender, hops), to: found.entry.id, text })\n }\n\n const onGatewayFrame = (gateway: Gateway, frame: Frame): void => {\n switch (frame.t) {\n case 'registry.snapshot': {\n const entries = readEntries(frame.entries)\n if (!entries || typeof frame.seq !== 'number') {\n return\n }\n gateway.entries = new Map(entries.map((entry) => [entry.id, entry]))\n gateway.json = new Map(entries.map((entry) => [entry.id, canonicalJson(entry)]))\n gateway.seq = frame.seq\n return\n }\n case 'registry.delta': {\n const upsert = readEntries(frame.upsert)\n const remove = Array.isArray(frame.remove) ? frame.remove.filter((id): id is string => typeof id === 'string') : undefined\n if (!upsert || !remove || frame.seq !== gateway.seq + 1) {\n send(gateway.socket, { t: 'registry.resync' })\n return\n }\n for (const id of remove) {\n gateway.entries.delete(id)\n gateway.json.delete(id)\n }\n for (const entry of upsert) {\n gateway.entries.set(entry.id, entry)\n gateway.json.set(entry.id, canonicalJson(entry))\n }\n gateway.seq = frame.seq\n return\n }\n case 'registry.digest': {\n if (frame.seq !== gateway.seq) {\n return\n }\n if (frame.count !== gateway.entries.size || frame.hash !== registryHash(gateway.json)) {\n log(`relay: ${gateway.name} registry drifted; asking for a snapshot`)\n send(gateway.socket, { t: 'registry.resync' })\n }\n return\n }\n case 'res': {\n const id = typeof frame.id === 'string' ? frame.id : ''\n const entry = routed.get(id)\n if (!entry || entry.gateway !== gateway.name) {\n return\n }\n routed.delete(id)\n clearTimeout(entry.timer)\n entry.settle({ ok: frame.ok === true, result: frame.result, error: typeof frame.error === 'string' ? frame.error : undefined })\n return\n }\n case 'peer.list':\n case 'peer.peek':\n case 'peer.send': {\n const id = typeof frame.id === 'string' ? frame.id : undefined\n if (!id) {\n return\n }\n peerRequest(gateway, frame).then(\n (result) => send(gateway.socket, { t: 'res', id, ok: true, result: result ?? null }),\n (error: unknown) =>\n send(gateway.socket, { t: 'res', id, ok: false, error: error instanceof Error ? error.message : String(error) }),\n )\n return\n }\n }\n }\n\n const onHello = (socket: WebSocket, frame: Frame): Gateway | undefined => {\n const name = typeof frame.gateway === 'string' ? frame.gateway : ''\n if (frame.t !== 'hello' || !name || typeof frame.key !== 'string') {\n socket.close(RELAY_CLOSE.badHello, 'expected hello')\n return undefined\n }\n if (frame.version !== RELAY_WIRE_VERSION) {\n socket.close(RELAY_CLOSE.versionMismatch, `relay speaks wire version ${RELAY_WIRE_VERSION}`)\n return undefined\n }\n const enrollment = enrollments.gateways[name]\n if (!keyMatches(enrollment, frame.key)) {\n log(`relay: refused a hello for ${JSON.stringify(name.slice(0, 64))}`)\n socket.close(RELAY_CLOSE.unauthorized, 'unauthorized')\n return undefined\n }\n const ceiling = (frame.ceiling as { ops?: unknown } | undefined)?.ops\n const previous = gateways.get(name)\n if (previous) {\n drop(previous, RELAY_CLOSE.replaced, 'replaced by a newer connection')\n }\n const gateway: Gateway = {\n name,\n socket,\n ops: new Set(Array.isArray(ceiling) ? ceiling.filter(isRelayOp) : []),\n connectedAt: Date.now(),\n seq: 0,\n entries: new Map(),\n json: new Map(),\n missed: 0,\n }\n gateways.set(name, gateway)\n log(`relay: ${name} online`)\n send(socket, { t: 'welcome', relayVersion: RELAY_WIRE_VERSION })\n return gateway\n }\n\n const onConnection = (socket: WebSocket): void => {\n let gateway: Gateway | undefined\n const helloTimer = setTimeout(() => socket.close(RELAY_CLOSE.timeout, 'no hello'), helloTimeoutMs)\n socket.on('message', (raw: RawData) => {\n const frame = decodeFrame(Array.isArray(raw) ? Buffer.concat(raw) : raw instanceof ArrayBuffer ? Buffer.from(raw) : raw)\n if (gateway) {\n gateway.missed = 0\n if (frame) {\n onGatewayFrame(gateway, frame)\n }\n return\n }\n clearTimeout(helloTimer)\n gateway = frame ? onHello(socket, frame) : undefined\n if (!gateway && socket.readyState === socket.OPEN) {\n socket.close(RELAY_CLOSE.badHello, 'expected hello')\n }\n })\n socket.on('pong', () => {\n if (gateway) {\n gateway.missed = 0\n }\n })\n socket.on('error', () => {})\n socket.on('close', () => {\n clearTimeout(helloTimer)\n if (gateway) {\n drop(gateway, 1000, 'disconnected')\n }\n })\n }\n\n const serveStatus = (req: IncomingMessage, res: ServerResponse): void => {\n if (req.url !== '/status' || req.method !== 'GET' || !isLoopback(req.socket.remoteAddress)) {\n res.writeHead(404).end()\n return\n }\n res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify(status()))\n }\n\n const status = (): RelayStatus => {\n const names = new Set([...Object.keys(enrollments.gateways), ...gateways.keys()])\n return {\n version: RELAY_WIRE_VERSION,\n gateways: [...names].sort().map((name) => {\n const gateway = gateways.get(name)\n return gateway\n ? { name, online: true, sessions: gateway.entries.size, connectedAt: gateway.connectedAt, ops: [...gateway.ops] }\n : { name, online: false, sessions: 0, ops: [] }\n }),\n }\n }\n\n const server: Server = options.tls\n ? createHttpsServer({ cert: options.tls.cert, key: options.tls.key }, serveStatus)\n : createHttpServer(serveStatus)\n const wss = new WebSocketServer({ server, maxPayload: RELAY_FRAME_MAX_BYTES })\n wss.on('connection', onConnection)\n\n const heartbeat = setInterval(() => {\n for (const gateway of gateways.values()) {\n gateway.missed += 1\n if (gateway.missed > 2) {\n drop(gateway, RELAY_CLOSE.timeout, 'heartbeat')\n gateway.socket.terminate()\n continue\n }\n gateway.socket.ping()\n }\n }, heartbeatMs)\n heartbeat.unref()\n\n const watched = [enrollmentPath(options.stateDir), rulesPath(options.stateDir)]\n for (const path of watched) {\n watchFile(path, { interval: watchIntervalMs, persistent: false }, () => void reload())\n }\n\n const host = options.host ?? '127.0.0.1'\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject)\n server.listen(options.port ?? 7777, host, () => {\n server.off('error', reject)\n resolve()\n })\n })\n const port = (server.address() as AddressInfo).port\n const scheme = options.tls ? 'wss' : 'ws'\n const url = `${scheme}://${host.includes(':') ? `[${host}]` : host}:${port}`\n if (!options.tls && !isLoopback(host) && host !== 'localhost') {\n log(`relay: serving plain ws:// on ${host}; gateway keys cross the network unencrypted unless this is a tailnet or a TLS proxy`)\n }\n\n return {\n url,\n port,\n status,\n reload,\n close: async () => {\n clearInterval(heartbeat)\n for (const path of watched) {\n unwatchFile(path)\n }\n for (const gateway of gateways.values()) {\n drop(gateway, 1001, 'relay shutting down')\n }\n for (const client of wss.clients) {\n client.terminate()\n }\n await new Promise<void>((resolve) => wss.close(() => resolve()))\n await new Promise<void>((resolve) => server.close(() => resolve()))\n },\n }\n}\n\nfunction send(socket: WebSocket, frame: RelayFrame): void {\n if (socket.readyState === socket.OPEN) {\n socket.send(encodeFrame(frame))\n }\n}\n\nfunction originOf(from: Gateway, sender: RelaySessionEntry, hops: string[]): RelayOrigin {\n return { gateway: from.name, sessionId: sender.id, name: sender.title, engine: sender.engine, hops }\n}\n","import { readFile } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { join, resolve } from 'node:path'\nimport { enrollGateway, readEnrollments, revokeGateway } from './enrollment.ts'\nimport { startRelay, type RelayStatus } from './relay.ts'\nimport { readRules, rulesPath } from './rules.ts'\n\nexport const RELAY_HELP = `workerdeck-relay - the cross-gateway peer relay. Also runs as \\`workerdeck relay\\`.\n\nUsage\n workerdeck-relay serve [options] run the relay\n workerdeck-relay enroll <name> [--rotate] enroll a gateway and print its key once\n workerdeck-relay revoke <name> revoke a gateway; a running relay drops it within seconds\n workerdeck-relay list list enrolled gateways\n workerdeck-relay status [options] ask a running relay on this machine who is online\n\nOptions\n --state-dir <path> enrollments (gateways.json) and rules (rules.json); default ~/.workerdeck/relay\n --host <addr> listen address (default 127.0.0.1)\n --port <n> listen port (default 7777)\n --tls-cert <file> serve wss:// with this certificate (optional, plain ws:// otherwise)\n --tls-key <file> the certificate's private key\n -h, --help this text\n`\n\nexport type RelayFlags = {\n command?: string\n name?: string\n stateDir: string\n host?: string\n port?: number\n tlsCert?: string\n tlsKey?: string\n rotate?: boolean\n help?: boolean\n}\n\nexport class RelayUsageError extends Error {}\n\nexport function parseRelayArgs(argv: readonly string[], env: NodeJS.ProcessEnv = process.env): RelayFlags {\n const flags: RelayFlags = { stateDir: env.WORKERDECK_RELAY_STATE_DIR ?? join(homedir(), '.workerdeck', 'relay') }\n const positional: string[] = []\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i]!\n const value = (): string => {\n const next = argv[++i]\n if (next === undefined) {\n throw new RelayUsageError(`${arg} needs a value`)\n }\n return next\n }\n switch (arg) {\n case '--state-dir': {\n flags.stateDir = resolve(value())\n break\n }\n case '--host': {\n flags.host = value()\n break\n }\n case '--port': {\n const port = Number(value())\n if (!Number.isInteger(port) || port < 0 || port > 65535) {\n throw new RelayUsageError('--port must be a port number')\n }\n flags.port = port\n break\n }\n case '--tls-cert': {\n flags.tlsCert = resolve(value())\n break\n }\n case '--tls-key': {\n flags.tlsKey = resolve(value())\n break\n }\n case '--rotate': {\n flags.rotate = true\n break\n }\n case '-h':\n case '--help': {\n flags.help = true\n break\n }\n default: {\n if (arg.startsWith('-')) {\n throw new RelayUsageError(`unknown option: ${arg}`)\n }\n positional.push(arg)\n }\n }\n }\n flags.command = positional[0]\n flags.name = positional[1]\n if (Boolean(flags.tlsCert) !== Boolean(flags.tlsKey)) {\n throw new RelayUsageError('--tls-cert and --tls-key go together')\n }\n return flags\n}\n\nexport async function runRelayCli(argv: readonly string[]): Promise<number> {\n let flags: RelayFlags\n try {\n flags = parseRelayArgs(argv)\n } catch (error) {\n if (error instanceof RelayUsageError) {\n console.error(`workerdeck-relay: ${error.message}\\n\\n${RELAY_HELP}`)\n return 2\n }\n throw error\n }\n if (flags.help || !flags.command) {\n console.log(RELAY_HELP)\n return flags.help ? 0 : 2\n }\n switch (flags.command) {\n case 'serve': {\n await readRules(flags.stateDir)\n const relay = await startRelay({\n stateDir: flags.stateDir,\n host: flags.host,\n port: flags.port,\n tls: flags.tlsCert && flags.tlsKey ? { cert: await readFile(flags.tlsCert), key: await readFile(flags.tlsKey) } : undefined,\n })\n const enrolled = Object.keys((await readEnrollments(flags.stateDir)).gateways).length\n console.log(`workerdeck-relay listening on ${relay.url} (${enrolled} gateway(s) enrolled, rules at ${rulesPath(flags.stateDir)})`)\n await new Promise<void>((done) => {\n const stop = () => void relay.close().then(done)\n process.once('SIGINT', stop)\n process.once('SIGTERM', stop)\n })\n return 0\n }\n case 'enroll': {\n if (!flags.name) {\n throw new RelayUsageError('enroll needs a gateway name')\n }\n const key = await enrollGateway(flags.stateDir, flags.name, { rotate: flags.rotate })\n console.log(\n `Enrolled ${flags.name}. Its key, shown once:\\n\\n ${key}\\n\\n` +\n `Save it on that gateway (for example ~/.workerdeck/relay.key, mode 0600) and point relay.keyFile at it.`,\n )\n return 0\n }\n case 'revoke': {\n if (!flags.name) {\n throw new RelayUsageError('revoke needs a gateway name')\n }\n const removed = await revokeGateway(flags.stateDir, flags.name)\n console.log(removed ? `Revoked ${flags.name}.` : `${flags.name} was not enrolled.`)\n return removed ? 0 : 1\n }\n case 'list': {\n const file = await readEnrollments(flags.stateDir)\n const names = Object.entries(file.gateways).sort(([a], [b]) => (a < b ? -1 : 1))\n if (names.length === 0) {\n console.log('No gateways enrolled.')\n }\n for (const [name, entry] of names) {\n console.log(`${name}\\tenrolled ${new Date(entry.enrolledAt).toISOString()}`)\n }\n return 0\n }\n case 'status': {\n const scheme = flags.tlsCert ? 'https' : 'http'\n const url = `${scheme}://127.0.0.1:${flags.port ?? 7777}/status`\n let status: RelayStatus\n try {\n const res = await fetch(url)\n if (!res.ok) {\n throw new Error(`HTTP ${res.status}`)\n }\n status = (await res.json()) as RelayStatus\n } catch (error) {\n console.error(`workerdeck-relay: no relay answered at ${url} (${error instanceof Error ? error.message : String(error)})`)\n return 2\n }\n for (const gateway of status.gateways) {\n console.log(\n gateway.online\n ? `${gateway.name}\\tonline\\t${gateway.sessions} session(s)\\taccepts ${gateway.ops.join(', ') || 'nothing'}`\n : `${gateway.name}\\toffline`,\n )\n }\n return 0\n }\n default: {\n console.error(`workerdeck-relay: unknown command ${flags.command}\\n\\n${RELAY_HELP}`)\n return 2\n }\n }\n}\n"],"mappings":"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"}