workerdeck 2.2.1 → 2.3.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.
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"instance-DeDxfiBR.mjs","names":[],"sources":["../src/apns/client.ts","../src/lib/http.ts","../src/apns/devices.ts","../src/apns/forwarder.ts","../src/auth/auth-key.ts","../src/auth/auth-sessions.ts","../src/auth/auth.ts","../src/config.ts","../src/auth/login-page.ts","../src/lib/keep-awake.ts","../src/lib/static.ts","../src/lib/instance.ts"],"sourcesContent":["import { createPrivateKey, type KeyObject, sign } from 'node:crypto'\nimport { readFile } from 'node:fs/promises'\nimport { type ClientHttp2Session, connect, constants, type SecureClientSessionOptions } from 'node:http2'\n\nexport type ApnsEnvironment = 'development' | 'production'\n\nexport type ApnsConfig = {\n // A path, never the contents: a secret pasted into a config file is a secret in every backup of it.\n keyFile: string\n keyId: string\n teamId: string\n topic: string\n // Only the fallback for a device that registered without naming one; a device that named one is always routed by its answer.\n production?: boolean\n}\n\nconst HOSTS: Record<ApnsEnvironment, string> = {\n development: 'https://api.sandbox.push.apple.com',\n production: 'https://api.push.apple.com',\n}\n\nconst TOKEN_TTL_MS = 40 * 60 * 1000\nconst REQUEST_TIMEOUT_MS = 10_000\nconst DIAL_ATTEMPT_TIMEOUT_MS = 2_000\n\nexport type ApnsRequest = {\n deviceToken: string\n environment: ApnsEnvironment\n payload: unknown\n priority?: 5 | 10\n // Unix seconds after which Apple stops trying; 0 is not \"no expiry\" but \"attempt once, never store\".\n expiration?: number\n // Apple caps this at 64 bytes, so never pass an identifier of unbounded length.\n collapseId?: string\n}\n\nexport type ApnsResult =\n | { ok: true; apnsId?: string }\n | {\n ok: false\n status: number\n reason: string\n unregistered: boolean\n }\n\nexport type ApnsClient = {\n send(request: ApnsRequest): Promise<ApnsResult>\n close(): void\n}\n\nfunction base64url(input: Buffer | string): string {\n return Buffer.from(input).toString('base64url')\n}\n\nexport async function loadApnsKey(keyFile: string): Promise<KeyObject> {\n let pem: string\n try {\n pem = await readFile(keyFile, 'utf8')\n } catch (error) {\n throw new Error(`apns: cannot read the auth key at ${keyFile}: ` + `${error instanceof Error ? error.message : String(error)}`, {\n cause: error,\n })\n }\n let key: KeyObject\n try {\n key = createPrivateKey(pem)\n } catch (error) {\n throw new Error(`apns: ${keyFile} is not a private key (${error instanceof Error ? error.message : String(error)})`, { cause: error })\n }\n if (key.asymmetricKeyType !== 'ec') {\n throw new Error(\n `apns: ${keyFile} is a ${key.asymmetricKeyType ?? 'unknown'} key, not EC — an APNs auth ` +\n 'key is the .p8 downloaded from Keys in the developer portal, not a certificate',\n )\n }\n return key\n}\n\nexport function createProviderToken(key: KeyObject, keyId: string, teamId: string): { get(now?: number): string; invalidate(): void } {\n let cached: { token: string; issuedAt: number } | null = null\n return {\n get(now = Date.now()) {\n if (cached !== null && now - cached.issuedAt < TOKEN_TTL_MS) {\n return cached.token\n }\n const header = base64url(JSON.stringify({ alg: 'ES256', kid: keyId }))\n const claims = base64url(JSON.stringify({ iss: teamId, iat: Math.floor(now / 1000) }))\n const signingInput = `${header}.${claims}`\n const signature = sign('sha256', Buffer.from(signingInput), {\n key,\n dsaEncoding: 'ieee-p1363',\n })\n const token = `${signingInput}.${base64url(signature)}`\n cached = { token, issuedAt: now }\n return token\n },\n invalidate() {\n cached = null\n },\n }\n}\n\nfunction createSessionPool(hosts: Record<ApnsEnvironment, string>): {\n get(environment: ApnsEnvironment): ClientHttp2Session\n lastFailure(environment: ApnsEnvironment): string | undefined\n // Only ever called on a *connecting* session: destroying a connected one cancels its siblings mid-flight.\n discard(environment: ApnsEnvironment, session: ClientHttp2Session): void\n close(): void\n} {\n const sessions = new Map<ApnsEnvironment, ClientHttp2Session>()\n const failures = new Map<ApnsEnvironment, string>()\n const drop = (environment: ApnsEnvironment, session: ClientHttp2Session): void => {\n if (sessions.get(environment) === session) {\n sessions.delete(environment)\n }\n }\n return {\n get(environment) {\n const existing = sessions.get(environment)\n if (existing !== undefined && !existing.closed && !existing.destroyed) {\n return existing\n }\n const session = connect(hosts[environment], {\n // The cast is @types/node not surfacing the net/tls options http2 forwards.\n autoSelectFamilyAttemptTimeout: DIAL_ATTEMPT_TIMEOUT_MS,\n } as SecureClientSessionOptions)\n sessions.set(environment, session)\n failures.delete(environment)\n // Without this listener a GOAWAY-then-error takes the whole process down, and push is a side channel.\n session.on('error', (error) => {\n failures.set(environment, `${(error as NodeJS.ErrnoException).code ?? 'error'}: ${error.message}`)\n drop(environment, session)\n })\n session.on('goaway', (code, _lastStreamId, data) => {\n const detail = data !== undefined && data.length > 0 ? `: ${data.toString('utf8').slice(0, 200)}` : ''\n failures.set(environment, `GOAWAY ${code}${detail}`)\n drop(environment, session)\n })\n session.on('close', () => drop(environment, session))\n session.setTimeout(5 * 60_000, () => session.close())\n return session\n },\n lastFailure: (environment) => failures.get(environment),\n discard(environment, session) {\n drop(environment, session)\n if (!session.destroyed) {\n session.destroy()\n }\n },\n close() {\n for (const session of sessions.values()) {\n session.close()\n }\n sessions.clear()\n },\n }\n}\n\nfunction describeStreamError(error: Error): string {\n const cause = (error as Error & { cause?: unknown }).cause\n if (!(cause instanceof AggregateError)) {\n return error.message\n }\n const parts = cause.errors.map((inner) => (inner instanceof Error ? inner.message : String(inner))).filter((message) => message !== '')\n if (parts.length === 0) {\n return error.message\n }\n return `${error.message.replace(' (caused by: )', '')} (caused by: ${parts.join('; ')})`\n}\n\ntype Retry = 'never' | 'now' | 'redial'\n\ntype Attempt = { result: ApnsResult; retry: Retry }\n\nexport function createApnsClient(\n config: ApnsConfig,\n key: KeyObject,\n // Test seam only. The real endpoints are deliberately not configurable: redirecting them would exfiltrate every push.\n options: { hosts?: Record<ApnsEnvironment, string>; retryDelayMs?: number } = {},\n): ApnsClient {\n const providerToken = createProviderToken(key, config.keyId, config.teamId)\n const pool = createSessionPool(options.hosts ?? HOSTS)\n const retryDelayMs = options.retryDelayMs ?? 1000\n\n const post = (request: ApnsRequest, jwt: string): Promise<Attempt> =>\n new Promise((resolve) => {\n const body = Buffer.from(JSON.stringify(request.payload))\n const session = pool.get(request.environment)\n let stream: ReturnType<ClientHttp2Session['request']>\n try {\n stream = session.request({\n ':method': 'POST',\n ':path': `/3/device/${request.deviceToken}`,\n authorization: `bearer ${jwt}`,\n 'apns-topic': config.topic,\n 'apns-push-type': 'alert',\n 'apns-priority': String(request.priority ?? 10),\n 'apns-expiration': String(request.expiration ?? 0),\n 'content-length': String(body.byteLength),\n ...(request.collapseId === undefined ? {} : { 'apns-collapse-id': request.collapseId }),\n })\n } catch (error) {\n resolve({\n result: {\n ok: false,\n status: 0,\n reason: error instanceof Error ? error.message : String(error),\n unregistered: false,\n },\n retry: 'now',\n })\n return\n }\n\n let status = 0\n let apnsId: string | undefined\n const chunks: Buffer[] = []\n let settled = false\n const settle = (result: ApnsResult, retry: Retry = 'never'): void => {\n if (settled) {\n return\n }\n settled = true\n resolve({ result, retry })\n }\n\n const transportRetry = (): Retry => {\n if (stream.pending) {\n return 'redial'\n }\n if (stream.rstCode === constants.NGHTTP2_REFUSED_STREAM) {\n return 'now'\n }\n return 'never'\n }\n const dropDoomedDial = (): void => {\n if (stream.pending && session.connecting) {\n pool.discard(request.environment, session)\n }\n }\n\n stream.setTimeout(REQUEST_TIMEOUT_MS, () => {\n const retry = transportRetry()\n dropDoomedDial()\n stream.close()\n settle({ ok: false, status: 0, reason: 'Timeout', unregistered: false }, retry)\n })\n stream.on('response', (headers) => {\n status = Number(headers[':status'] ?? 0)\n const id = headers['apns-id']\n apnsId = typeof id === 'string' ? id : undefined\n })\n stream.on('data', (chunk: Buffer) => chunks.push(chunk))\n stream.on('error', (error) => {\n const retry = transportRetry()\n dropDoomedDial()\n const cause = pool.lastFailure(request.environment)\n const message = describeStreamError(error)\n settle(\n {\n ok: false,\n status,\n reason: cause !== undefined ? `${cause} (stream: ${message})` : message,\n unregistered: false,\n },\n retry,\n )\n })\n stream.on('end', () => {\n if (status === 200) {\n settle({ ok: true, apnsId })\n return\n }\n let reason = `HTTP ${status}`\n try {\n const parsed = JSON.parse(Buffer.concat(chunks).toString('utf8')) as { reason?: string }\n if (typeof parsed.reason === 'string') {\n reason = parsed.reason\n }\n } catch {}\n settle({\n ok: false,\n status,\n reason,\n unregistered: reason === 'Unregistered' || reason === 'BadDeviceToken',\n })\n })\n // 'end' never fires on a destroyed readable, so without this last-resort settle the send hangs forever.\n stream.on('close', () => {\n const retry = transportRetry()\n settle(\n {\n ok: false,\n status,\n reason: pool.lastFailure(request.environment) ?? `connection closed (RST code ${stream.rstCode})`,\n unregistered: false,\n },\n retry,\n )\n })\n stream.end(body)\n })\n\n const wait = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms))\n\n return {\n async send(request) {\n let attempt = await post(request, providerToken.get())\n if (!attempt.result.ok && attempt.retry !== 'never') {\n if (attempt.retry === 'redial') {\n await wait(retryDelayMs)\n }\n attempt = await post(request, providerToken.get())\n }\n const first = attempt.result\n // The one *response* worth a retry: the cached JWT aged out against Apple's clock, so no duplicate is possible.\n if (!first.ok && first.reason === 'ExpiredProviderToken') {\n providerToken.invalidate()\n return (await post(request, providerToken.get())).result\n }\n return first\n },\n close: pool.close,\n }\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http'\n\n// Read a request body, bounded. Resolves `null` if the body exceeds `maxBytes` or the socket\n// errors — the caller answers 413 either way; it never resolves twice, and it never buffers\n// past the cap, which is the whole point of not using a body parser here.\nexport function readBody(req: IncomingMessage, maxBytes: number): Promise<string | null> {\n return new Promise((resolve) => {\n const chunks: Buffer[] = []\n let size = 0\n let settled = false\n const finish = (value: string | null): void => {\n if (!settled) {\n settled = true\n resolve(value)\n }\n }\n req.on('data', (chunk: Buffer) => {\n size += chunk.length\n if (size > maxBytes) {\n finish(null)\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => finish(Buffer.concat(chunks).toString('utf8')))\n req.on('error', () => finish(null))\n })\n}\n\n// A JSON answer that is never cached — these routes all carry auth state.\nexport function respondJson(res: ServerResponse, status: number, body: Record<string, unknown>, headers?: Record<string, string>): void {\n res.writeHead(status, { 'content-type': 'application/json', 'cache-control': 'no-store', ...headers }).end(JSON.stringify(body))\n}\n","import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { join } from 'node:path'\nimport type { ApnsEnvironment } from './client.ts'\nimport { readBody, respondJson } from '../lib/http.ts'\n\nexport type DeviceRecord = {\n token: string\n environment: ApnsEnvironment\n // The client's own id for this gateway, echoed back in every payload so an app with two gateways knows which woke it; never interpreted here.\n hostId?: string\n bundleId?: string\n platform?: string\n updatedAt: number\n}\n\nexport type DeviceRegistry = {\n list(): DeviceRecord[]\n register(record: Omit<DeviceRecord, 'updatedAt'>): Promise<void>\n remove(token: string): Promise<void>\n}\n\nconst FILENAME = 'apns-devices.json'\n// 64 hex chars today; the upper bound is loose because Apple reserved the right to grow the token.\nconst TOKEN_PATTERN = /^[0-9a-fA-F]{32,200}$/\nconst MAX_BODY_BYTES = 4096\n\nfunction isEnvironment(value: unknown): value is ApnsEnvironment {\n return value === 'development' || value === 'production'\n}\n\nexport async function createDeviceRegistry(options: {\n dir: string | null\n onError?: (error: unknown, context: { op: string; path: string }) => void\n}): Promise<DeviceRegistry> {\n const path = options.dir === null ? null : join(options.dir, FILENAME)\n const devices = new Map<string, DeviceRecord>()\n\n if (path !== null) {\n try {\n const parsed = JSON.parse(await readFile(path, 'utf8')) as { devices?: DeviceRecord[] }\n for (const record of parsed.devices ?? []) {\n if (typeof record?.token === 'string' && isEnvironment(record.environment)) {\n devices.set(record.token, record)\n }\n }\n } catch {\n // Missing, unreadable and corrupt all mean \"start empty\": push is a side channel and must never refuse a boot.\n }\n }\n\n const persist = async (): Promise<void> => {\n if (path === null) {\n return\n }\n try {\n await mkdir(options.dir!, { recursive: true, mode: 0o700 })\n await writeFile(path, `${JSON.stringify({ devices: [...devices.values()] }, null, 2)}\\n`, {\n mode: 0o600,\n })\n // writeFile's mode applies only on creation, so an existing file would keep its looser bits.\n await chmod(path, 0o600)\n } catch (error) {\n options.onError?.(error, { op: 'write', path })\n }\n }\n\n return {\n list: () => [...devices.values()],\n async register(record) {\n const existing = devices.get(record.token)\n devices.set(record.token, { ...record, updatedAt: Date.now() })\n if (existing !== undefined && existing.environment === record.environment && existing.hostId === record.hostId) {\n return\n }\n await persist()\n },\n async remove(token) {\n if (!devices.delete(token)) {\n return\n }\n await persist()\n },\n }\n}\n\nexport function createDeviceRoute(\n registry: DeviceRegistry,\n authenticate: (req: IncomingMessage) => unknown,\n): (req: IncomingMessage, res: ServerResponse) => Promise<boolean> {\n return async (req, res) => {\n let pathname: string\n try {\n pathname = new URL(req.url ?? '/', 'http://internal').pathname\n } catch {\n return false\n }\n if (pathname !== '/apns/devices') {\n return false\n }\n\n if (req.method !== 'POST' && req.method !== 'DELETE') {\n res.writeHead(405, { allow: 'POST, DELETE' }).end()\n return true\n }\n // A device token is an address this gateway can buzz, so an unauthenticated register lets anyone reachable aim it.\n if (authenticate(req) === null) {\n respondJson(res, 401, { error: 'unauthorized' })\n return true\n }\n\n const raw = await readBody(req, MAX_BODY_BYTES)\n if (raw === null) {\n respondJson(res, 413, { error: 'body too large' })\n res.once('finish', () => req.destroy())\n return true\n }\n let body: Record<string, unknown>\n try {\n body = JSON.parse(raw) as Record<string, unknown>\n } catch {\n respondJson(res, 400, { error: 'invalid JSON body' })\n return true\n }\n\n const token = body.token\n if (typeof token !== 'string' || !TOKEN_PATTERN.test(token)) {\n respondJson(res, 400, { error: 'token must be a hex APNs device token' })\n return true\n }\n\n if (req.method === 'DELETE') {\n await registry.remove(token)\n res.writeHead(204).end()\n return true\n }\n\n if (!isEnvironment(body.environment)) {\n respondJson(res, 400, { error: \"environment must be 'development' or 'production'\" })\n return true\n }\n const optionalString = (value: unknown): string | undefined =>\n typeof value === 'string' && value.length > 0 && value.length <= 200 ? value : undefined\n\n await registry.register({\n token,\n environment: body.environment,\n hostId: optionalString(body.hostId),\n bundleId: optionalString(body.bundleId),\n platform: optionalString(body.platform),\n })\n respondJson(res, 200, { registered: true, environment: body.environment })\n return true\n }\n}\n","import { createHash } from 'node:crypto'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { SessionInfo, SessionNotification } from '@workerdeck/protocol'\nimport { type ApnsClient, type ApnsConfig, createApnsClient, loadApnsKey, type ApnsRequest } from './client.ts'\nimport { createDeviceRegistry, createDeviceRoute, type DeviceRegistry } from './devices.ts'\n\n// Under APNs' 4 KB payload cap, with room for the alert dictionary to grow.\nconst MAX_PAYLOAD_BYTES = 3800\nconst BODY_LIMIT = 300\n\nexport type ApnsForwarder = {\n onNotification: (notification: SessionNotification) => void\n handleRequest: (req: IncomingMessage, res: ServerResponse) => Promise<boolean>\n deviceCount: () => number\n close: () => void\n}\n\n// Wire contract with the iOS app, which registers its Approve/Deny actions under these exact strings; a mismatch means a notification with no buttons.\nconst CATEGORY = {\n permission: 'PERMISSION_REQUEST',\n event: 'SESSION_EVENT',\n} as const\n\nfunction label(session: SessionInfo): string {\n const title = session.title?.trim()\n if (title !== undefined && title !== '') {\n return title\n }\n const leaf = session.cwd.split('/').filter(Boolean).at(-1)\n return leaf !== undefined && leaf !== '' ? leaf : session.id\n}\n\nfunction oneLine(text: string | undefined, limit = BODY_LIMIT): string {\n const flat = (text ?? '').replace(/\\s+/g, ' ').trim()\n return flat.length <= limit ? flat : `${flat.slice(0, limit - 1)}…`\n}\n\n// Hashed rather than truncated to 64 bytes: two session ids sharing a prefix must not collapse into each other.\nfunction collapseKey(sessionId: string): string {\n return createHash('sha256').update(sessionId).digest('base64url').slice(0, 32)\n}\n\nfunction titleFor(notification: SessionNotification, name: string): string {\n switch (notification.type) {\n case 'permission_requested': {\n return `Approval needed — ${name}`\n }\n case 'turn_completed': {\n return notification.result?.isError === true ? `Turn failed — ${name}` : name\n }\n case 'session_error': {\n return `Session error — ${name}`\n }\n case 'session_closed': {\n return `Session ended — ${name}`\n }\n default: {\n return name\n }\n }\n}\n\nfunction bodyFor(notification: SessionNotification): string {\n const preview = oneLine(notification.preview)\n if (preview !== '') {\n return preview\n }\n switch (notification.type) {\n case 'permission_requested': {\n return 'The agent is waiting for your approval.'\n }\n case 'turn_completed': {\n return 'The turn finished.'\n }\n case 'session_closed': {\n return `Closed by the ${notification.reason ?? 'server'}.`\n }\n default: {\n return 'Something needs your attention.'\n }\n }\n}\n\n// The payload carries routing only — never transcript text — and `requestId` is what a lock-screen Approve has to POST to.\nexport function buildPush(notification: SessionNotification, hostId: string | undefined): Omit<ApnsRequest, 'deviceToken' | 'environment'> {\n const permission = notification.type === 'permission_requested'\n const name = label(notification.session)\n let body = bodyFor(notification)\n\n const build = (text: string): Record<string, unknown> => ({\n aps: {\n alert: { title: titleFor(notification, name), body: text },\n sound: 'default',\n category: permission ? CATEGORY.permission : CATEGORY.event,\n 'thread-id': notification.sessionId,\n },\n type: notification.type,\n sessionId: notification.sessionId,\n seq: notification.seq,\n ...(hostId === undefined ? {} : { hostId }),\n ...(notification.request === undefined ? {} : { requestId: notification.request.id }),\n })\n\n let payload = build(body)\n while (Buffer.byteLength(JSON.stringify(payload)) > MAX_PAYLOAD_BYTES && body.length > 16) {\n body = oneLine(body, Math.floor(body.length / 2))\n payload = build(body)\n }\n\n return {\n payload,\n priority: permission || notification.type === 'session_error' ? 10 : 5,\n // Expire an approval push exactly when the server would have timed the request out; a dead approval on a lock screen is worse than none.\n expiration:\n permission && notification.request?.expiresAt !== undefined\n ? Math.floor(notification.request.expiresAt / 1000)\n : Math.floor(Date.now() / 1000) + 3600,\n ...(notification.type === 'turn_completed' ? { collapseId: `t:${collapseKey(notification.sessionId)}` } : {}),\n }\n}\n\nexport async function createApnsForwarder(options: {\n config: ApnsConfig\n stateDir: string | null\n authenticate: (req: IncomingMessage) => unknown\n warn?: (message: string) => void\n}): Promise<ApnsForwarder> {\n const warn = options.warn ?? ((message: string) => process.stderr.write(`[workerdeck] apns: ${message}\\n`))\n const key = await loadApnsKey(options.config.keyFile)\n const client: ApnsClient = createApnsClient(options.config, key)\n const registry: DeviceRegistry = await createDeviceRegistry({\n dir: options.stateDir,\n onError: (error, context) =>\n warn(`device registry ${context.op} failed for ${context.path}: ` + `${error instanceof Error ? error.message : String(error)}`),\n })\n const handleRequest = createDeviceRoute(registry, options.authenticate)\n const fallbackEnvironment = options.config.production === false ? 'development' : 'production'\n\n // Per-session delivery chain: `turn_completed` collapses, so an out-of-order pair leaves the older text on the lock screen.\n const chains = new Map<string, Promise<void>>()\n\n const deliver = async (notification: SessionNotification): Promise<void> => {\n const devices = registry.list()\n if (devices.length === 0) {\n return\n }\n await Promise.all(\n devices.map(async (device) => {\n const push = buildPush(notification, device.hostId)\n const result = await client.send({\n ...push,\n deviceToken: device.token,\n environment: device.environment ?? fallbackEnvironment,\n })\n if (result.ok) {\n return\n }\n if (result.unregistered) {\n await registry.remove(device.token)\n return\n }\n warn(`push to ${device.token.slice(0, 8)}… failed: ${result.reason} (${result.status})`)\n }),\n )\n }\n\n return {\n onNotification(notification) {\n const previous = chains.get(notification.sessionId) ?? Promise.resolve()\n const next = previous.then(() =>\n deliver(notification).catch((error: unknown) => {\n warn(error instanceof Error ? error.message : String(error))\n }),\n )\n chains.set(notification.sessionId, next)\n void next.then(() => {\n if (chains.get(notification.sessionId) === next) {\n chains.delete(notification.sessionId)\n }\n })\n },\n handleRequest,\n deviceCount: () => registry.list().length,\n close: client.close,\n }\n}\n","import { randomBytes } from 'node:crypto'\nimport { chmod, mkdir, readFile, stat, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\n\nexport type MaterializedAuthKey = {\n key: string\n source: 'stored' | 'created' | 'ephemeral'\n path: string | null\n}\n\nfunction generateKey(): string {\n return randomBytes(24).toString('hex')\n}\n\n// One printable-ASCII line of at least 12 chars, so a truncated or garbage file regenerates rather than half-working.\nfunction usableStoredKey(raw: string): string | null {\n const line = raw.split('\\n', 1)[0]?.trim() ?? ''\n return line.length >= 12 && /^[\\x21-\\x7e]+$/.test(line) ? line : null\n}\n\nexport async function materializeAuthKey(\n stateDir: string | null,\n options: { warn?: (message: string) => void } = {},\n): Promise<MaterializedAuthKey> {\n const warn = options.warn ?? ((message: string) => process.stderr.write(`[workerdeck] ${message}\\n`))\n if (stateDir === null) {\n return { key: generateKey(), source: 'ephemeral', path: null }\n }\n\n const path = join(stateDir, 'auth-key')\n let raw: string | null = null\n try {\n raw = await readFile(path, 'utf8')\n } catch {\n raw = null\n }\n if (raw !== null) {\n const key = usableStoredKey(raw)\n if (key !== null) {\n try {\n const { mode } = await stat(path)\n if ((mode & 0o077) !== 0) {\n warn(`auth key file ${path} is readable by other users ` + `(mode ${(mode & 0o777).toString(8)}) — run: chmod 600 ${path}`)\n }\n } catch {}\n return { key, source: 'stored', path }\n }\n }\n\n const key = generateKey()\n await mkdir(stateDir, { recursive: true, mode: 0o700 })\n await writeFile(path, `${key}\\n`, { mode: 0o600 })\n // writeFile's mode applies only on creation, so regenerating over a corrupt file would inherit its looser bits.\n await chmod(path, 0o600)\n return { key, source: 'created', path }\n}\n","import { chmod, mkdir, readFile, rename, stat, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport type { CliSessionStore, StoredSession } from './auth.ts'\n\nconst FORMAT_VERSION = 1\nconst FILE_NAME = 'auth-sessions.json'\n\nexport type AuthSessionStoreOptions = {\n stateDir: string\n warn?: (message: string) => void\n now?: () => number\n}\n\ntype FileShape = { version: number; sessions: [string, number][] }\n\nfunction parseSessions(raw: string, now: number): [string, StoredSession][] {\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n return []\n }\n const file = parsed as Partial<FileShape>\n if (file?.version !== FORMAT_VERSION || !Array.isArray(file.sessions)) {\n return []\n }\n const entries: [string, StoredSession][] = []\n for (const entry of file.sessions) {\n if (!Array.isArray(entry) || entry.length !== 2) {\n continue\n }\n const [key, expiresAt] = entry\n if (typeof key !== 'string' || key === '') {\n continue\n }\n if (typeof expiresAt !== 'number' || !Number.isFinite(expiresAt) || expiresAt <= now) {\n continue\n }\n entries.push([key, { expiresAt }])\n }\n return entries\n}\n\nexport async function createAuthSessionStore(options: AuthSessionStoreOptions): Promise<CliSessionStore> {\n const now = options.now ?? Date.now\n const warn = options.warn ?? ((message: string) => process.stderr.write(`[workerdeck] ${message}\\n`))\n const path = join(options.stateDir, FILE_NAME)\n\n let initial: [string, StoredSession][] = []\n try {\n const raw = await readFile(path, 'utf8')\n initial = parseSessions(raw, now())\n try {\n const { mode } = await stat(path)\n if ((mode & 0o077) !== 0) {\n warn(`session file ${path} is readable by other users ` + `(mode ${(mode & 0o777).toString(8)}) — run: chmod 600 ${path}`)\n }\n } catch {}\n } catch {\n initial = []\n }\n\n // One chain, so two mutations in the same tick cannot interleave their writes and land the older table last.\n let pending: Promise<void> = Promise.resolve()\n let warned = false\n\n const write = async (entries: [string, StoredSession][]): Promise<void> => {\n const body: FileShape = { version: FORMAT_VERSION, sessions: entries.map(([key, s]) => [key, s.expiresAt]) }\n const tmp = `${path}.${process.pid}.tmp`\n try {\n await mkdir(dirname(path), { recursive: true, mode: 0o700 })\n await writeFile(tmp, `${JSON.stringify(body)}\\n`, { mode: 0o600 })\n // `writeFile`'s mode applies only on creation, so a reused temp path would inherit whatever bits an earlier run left.\n await chmod(tmp, 0o600)\n await rename(tmp, path)\n warned = false\n } catch (error) {\n if (!warned) {\n warned = true\n warn(`could not persist browser logins to ${path}: ${error instanceof Error ? error.message : String(error)}`)\n }\n }\n }\n\n return {\n initial,\n save(entries) {\n pending = pending.then(() => write(entries))\n },\n flush: () => pending,\n }\n}\n","import { createHash, createHmac, randomBytes, timingSafeEqual } from 'node:crypto'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { Authenticator } from '@workerdeck/server'\nimport { readBody, respondJson } from '../lib/http.ts'\n\nexport type CliAuthOptions = {\n // Unset disables auth entirely; that the CLI then binds loopback only is enforced by the caller, not here.\n secret?: string\n // No `__Host-` prefix: it requires `Secure`, and plain-HTTP localhost is the primary deployment.\n cookieName?: string\n // Fixed, never sliding: the auth hooks only see the request, so a renewed cookie has nowhere to ride back on.\n ttlMs?: number\n // Attacker-writable on a directly exposed port, hence off by default — but behind TLS termination it must be on,\n // or `Secure` is skipped and the Origin check computes `http://` where the browser says `https://`.\n trustProxy?: boolean\n allowedOrigins?: string[]\n throttle?: { windowMs?: number; maxFailuresPerIp?: number; maxFailuresGlobal?: number }\n sessions?: CliSessionStore\n}\n\nexport type StoredSession = { expiresAt: number }\n\nexport type CliSessionStore = {\n initial?: Iterable<[string, StoredSession]>\n save(entries: [string, StoredSession][]): void\n flush?(): Promise<void>\n}\n\nexport type CliPrincipal = {\n via: 'header' | 'cookie' | 'open'\n // One secret, one trust level: whoever holds it is the operator (still bounded by `allowedConfigDirRoots`).\n canManageProfiles: true\n}\n\nexport type CliAuth = {\n enabled: boolean\n authenticate: Authenticator\n // Claims `/auth` and everything under it; the static host must call this before anything else.\n handleAuthRequest(req: IncomingMessage, res: ServerResponse): boolean | Promise<boolean>\n // Gating the SPA shell is UX, not security: every byte of data sits behind `authenticate`.\n hasValidSession(req: IncomingMessage): boolean\n loginPage(req: IncomingMessage): { action: string; field: string; error?: string }\n}\n\nconst MIN_SECRET_LENGTH = 12\nconst DEFAULT_COOKIE_NAME = 'workerdeck_session'\nconst DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1000\nconst DEFAULT_THROTTLE_WINDOW_MS = 15 * 60 * 1000\nconst DEFAULT_MAX_FAILURES_PER_IP = 10\nconst DEFAULT_MAX_FAILURES_GLOBAL = 100\n// Only successful logins insert, so this cap fences the secret-holder's own memory use and nobody else's.\nconst MAX_SESSIONS = 100\nconst MAX_LOGIN_BODY_BYTES = 4096\nconst SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS'])\n\nfunction sha256(value: string): Buffer {\n return createHash('sha256').update(value).digest()\n}\n\nexport function createCliAuth(options: CliAuthOptions = {}): CliAuth {\n const { secret } = options\n const enabled = secret !== undefined\n if (secret !== undefined && secret.length < MIN_SECRET_LENGTH) {\n throw new Error(\n `createCliAuth: secret must be at least ${MIN_SECRET_LENGTH} characters — ` +\n 'use a long random value, or leave it unset to run without auth on loopback',\n )\n }\n const cookieName = options.cookieName ?? DEFAULT_COOKIE_NAME\n const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS\n if (!(ttlMs > 0)) {\n throw new Error('createCliAuth: ttlMs must be positive')\n }\n const trustProxy = options.trustProxy === true\n const windowMs = options.throttle?.windowMs ?? DEFAULT_THROTTLE_WINDOW_MS\n const maxFailuresPerIp = options.throttle?.maxFailuresPerIp ?? DEFAULT_MAX_FAILURES_PER_IP\n const maxFailuresGlobal = options.throttle?.maxFailuresGlobal ?? DEFAULT_MAX_FAILURES_GLOBAL\n const allowedOrigins = new Set(\n (options.allowedOrigins ?? []).map((entry) => {\n try {\n return new URL(entry).origin\n } catch {\n throw new Error(`createCliAuth: allowedOrigins entry is not a valid origin: ${JSON.stringify(entry)}`)\n }\n }),\n )\n\n // Secret and session tokens are only ever compared as fixed-length digests, so no path compares secret material with an early exit.\n const secretDigest = secret === undefined ? undefined : sha256(secret)\n const secretMatches = (candidate: string): boolean => secretDigest !== undefined && timingSafeEqual(sha256(candidate), secretDigest)\n\n // Keys are `HMAC-SHA256(secret, token)`; never \"simplify\" that to a plain digest (`docs/GOTCHAS.md`).\n const sessions = new Map<string, StoredSession>()\n const store = options.sessions\n const tokenKey = (token: string): string =>\n secret === undefined ? sha256(token).toString('hex') : createHmac('sha256', secret).update(token).digest('hex')\n\n if (store?.initial !== undefined) {\n for (const [key, entry] of store.initial) {\n if (sessions.size >= MAX_SESSIONS) {\n break\n }\n sessions.set(key, { expiresAt: entry.expiresAt })\n }\n }\n\n const persist = (): void => store?.save([...sessions])\n\n const createSession = (): string => {\n if (sessions.size >= MAX_SESSIONS) {\n const oldest = sessions.keys().next().value\n if (oldest !== undefined) {\n sessions.delete(oldest)\n }\n }\n const token = randomBytes(32).toString('base64url')\n sessions.set(tokenKey(token), { expiresAt: Date.now() + ttlMs })\n persist()\n return token\n }\n\n const cookieToken = (req: IncomingMessage): string | undefined => {\n const header = req.headers.cookie\n if (typeof header !== 'string') {\n return undefined\n }\n for (const part of header.split(';')) {\n const eq = part.indexOf('=')\n if (eq === -1) {\n continue\n }\n if (part.slice(0, eq).trim() === cookieName) {\n return part.slice(eq + 1).trim()\n }\n }\n return undefined\n }\n\n const hasSession = (req: IncomingMessage): boolean => {\n const token = cookieToken(req)\n if (token === undefined || token === '') {\n return false\n }\n const key = tokenKey(token)\n const entry = sessions.get(key)\n if (entry === undefined) {\n return false\n }\n if (entry.expiresAt <= Date.now()) {\n sessions.delete(key)\n persist()\n return false\n }\n return true\n }\n\n // The last value is the one the trusted proxy appended; every earlier position is client-writable.\n const forwardedLast = (value: string | string[] | undefined): string | undefined => {\n if (value === undefined) {\n return undefined\n }\n const joined = Array.isArray(value) ? value.join(',') : value\n const last = joined.split(',').at(-1)?.trim()\n return last === '' ? undefined : last\n }\n\n const isSecure = (req: IncomingMessage): boolean => {\n if ((req.socket as { encrypted?: boolean }).encrypted === true) {\n return true\n }\n return trustProxy && forwardedLast(req.headers['x-forwarded-proto'])?.toLowerCase() === 'https'\n }\n\n const expectedOrigin = (req: IncomingMessage): string | null => {\n const host = (trustProxy ? forwardedLast(req.headers['x-forwarded-host']) : undefined) ?? req.headers.host\n if (host === undefined || host === '') {\n return null\n }\n try {\n return new URL(`${isSecure(req) ? 'https' : 'http'}://${host}`).origin\n } catch {\n return null\n }\n }\n\n // Tri-state on purpose: absence means a non-browser client, because a browser sends Origin on every cross-site POST\n // and every upgrade. Each call site decides what to make of that; `null` and unparseable are foreign.\n const originVerdict = (req: IncomingMessage): 'absent' | 'ok' | 'foreign' => {\n const raw = req.headers.origin\n if (raw === undefined) {\n return 'absent'\n }\n let origin: string\n try {\n origin = new URL(raw).origin\n } catch {\n return 'foreign'\n }\n if (allowedOrigins.has(origin)) {\n return 'ok'\n }\n const expected = expectedOrigin(req)\n return expected !== null && origin === expected ? 'ok' : 'foreign'\n }\n\n const headerSecret = (req: IncomingMessage): string | undefined => {\n const key = req.headers['x-workerdeck-key']\n if (typeof key === 'string' && key !== '') {\n return key\n }\n const authorization = req.headers.authorization\n if (typeof authorization === 'string') {\n const match = /^Bearer\\s+(.+)$/i.exec(authorization)\n if (match !== null) {\n return match[1]\n }\n }\n return undefined\n }\n\n const openPrincipal: CliPrincipal = { via: 'open', canManageProfiles: true }\n\n const isUpgradeRequest = (req: IncomingMessage): boolean => {\n const upgrade = req.headers.upgrade\n return typeof upgrade === 'string' && upgrade.toLowerCase().includes('websocket')\n }\n\n // Upgrades only. A query-string key lands in proxy access logs, so a REST call carrying `?key=` is not\n // authenticated by it and must stay that way: a leaked URL then buys one attach and nothing more.\n const querySecret = (req: IncomingMessage): string | undefined => {\n if (!isUpgradeRequest(req)) {\n return undefined\n }\n const key = new URL(req.url ?? '/', 'http://internal').searchParams.get('key')\n return key !== null && key !== '' ? key : undefined\n }\n\n const authenticate: Authenticator = (req) => {\n if (!enabled) {\n return openPrincipal\n }\n // The secret is not ambient — the sender chose to attach it — so no Origin check applies, and a\n // present-but-wrong header is a rejection rather than a fall-through to the cookie.\n const provided = headerSecret(req) ?? querySecret(req)\n if (provided !== undefined) {\n return secretMatches(provided) ? ({ via: 'header', canManageProfiles: true } satisfies CliPrincipal) : null\n }\n if (!hasSession(req)) {\n return null\n }\n const verdict = originVerdict(req)\n // A cookie-authenticated request must never carry a foreign Origin, whatever the method.\n if (verdict === 'foreign') {\n return null\n }\n // Unsafe methods and upgrades additionally require Origin *present*: browsers always send it\n // there, so absence means a non-browser client replaying the cookie.\n const isUpgrade = isUpgradeRequest(req)\n const unsafe = !SAFE_METHODS.has((req.method ?? 'GET').toUpperCase())\n if ((isUpgrade || unsafe) && verdict !== 'ok') {\n return null\n }\n return { via: 'cookie', canManageProfiles: true } satisfies CliPrincipal\n }\n\n // A global cap sits behind the per-IP one because rotating IPs is trivial over IPv6.\n const failures = new Map<string, { count: number; windowStart: number }>()\n const globalFailures = { count: 0, windowStart: 0 }\n\n const clientIp = (req: IncomingMessage): string =>\n (trustProxy ? forwardedLast(req.headers['x-forwarded-for']) : undefined) ?? req.socket.remoteAddress ?? 'unknown'\n\n const blockedMs = (entry: { count: number; windowStart: number } | undefined, max: number, now: number): number =>\n entry !== undefined && entry.count >= max && now - entry.windowStart < windowMs ? entry.windowStart + windowMs - now : 0\n\n const loginBlockedMs = (ip: string, now: number): number =>\n Math.max(blockedMs(failures.get(ip), maxFailuresPerIp, now), blockedMs(globalFailures, maxFailuresGlobal, now))\n\n const recordFailure = (ip: string, now: number): void => {\n if (failures.size > 256) {\n for (const [key, entry] of failures) {\n if (now - entry.windowStart >= windowMs) {\n failures.delete(key)\n }\n }\n }\n const entry = failures.get(ip)\n if (entry === undefined || now - entry.windowStart >= windowMs) {\n failures.set(ip, { count: 1, windowStart: now })\n } else {\n entry.count += 1\n }\n if (now - globalFailures.windowStart >= windowMs) {\n globalFailures.count = 1\n globalFailures.windowStart = now\n } else {\n globalFailures.count += 1\n }\n }\n\n const cookieAttributes = (req: IncomingMessage): string[] => {\n // Lax over Strict: Strict drops the cookie on an external top-level navigation, and buys nothing\n // the Origin check does not already cover.\n const attrs = ['Path=/', 'HttpOnly', 'SameSite=Lax']\n if (isSecure(req)) {\n attrs.push('Secure')\n }\n return attrs\n }\n\n const setCookieValue = (token: string, req: IncomingMessage): string =>\n [`${cookieName}=${token}`, `Max-Age=${Math.ceil(ttlMs / 1000)}`, ...cookieAttributes(req)].join('; ')\n\n const clearCookieValue = (req: IncomingMessage): string => [`${cookieName}=`, 'Max-Age=0', ...cookieAttributes(req)].join('; ')\n\n const respondRedirect = (res: ServerResponse, location: string, headers?: Record<string, string>): void => {\n res.writeHead(303, { location, 'cache-control': 'no-store', ...headers }).end()\n }\n\n const wantsJson = (req: IncomingMessage): boolean => (req.headers.accept ?? '').includes('application/json')\n\n const handleLogin = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const json = wantsJson(req)\n if (!enabled) {\n respondJson(res, 409, { error: 'auth is disabled: no secret is configured' })\n return\n }\n // Refused before touching the throttle, so a hostile page cannot burn a victim IP's budget.\n // Absent Origin stays allowed (curl-style provisioning): a browser forgery always carries one.\n if (originVerdict(req) === 'foreign') {\n respondJson(res, 403, { error: 'origin not allowed' })\n return\n }\n const ip = clientIp(req)\n const blocked = loginBlockedMs(ip, Date.now())\n if (blocked > 0) {\n const retryAfter = String(Math.ceil(blocked / 1000))\n if (json) {\n respondJson(res, 429, { error: 'too many failed attempts' }, { 'retry-after': retryAfter })\n } else {\n respondRedirect(res, '/?auth=throttled', { 'retry-after': retryAfter })\n }\n return\n }\n const body = await readBody(req, MAX_LOGIN_BODY_BYTES)\n if (body === null) {\n respondJson(res, 413, { error: 'body too large' })\n // The unread rest of an oversized request would desync the next keep-alive exchange on this socket.\n res.once('finish', () => req.destroy())\n return\n }\n const contentType = (req.headers['content-type'] ?? '').split(';')[0].trim().toLowerCase()\n let candidate: unknown\n if (contentType === 'application/x-www-form-urlencoded') {\n candidate = new URLSearchParams(body).get('secret')\n } else if (contentType === 'application/json') {\n try {\n candidate = (JSON.parse(body) as { secret?: unknown }).secret\n } catch {\n respondJson(res, 400, { error: 'invalid body' })\n return\n }\n } else {\n respondJson(res, 415, { error: 'expected application/x-www-form-urlencoded or application/json' })\n return\n }\n if (typeof candidate !== 'string' || candidate === '') {\n respondJson(res, 400, { error: 'missing secret' })\n return\n }\n if (!secretMatches(candidate)) {\n recordFailure(ip, Date.now())\n if (json) {\n respondJson(res, 401, { error: 'invalid secret' })\n } else {\n respondRedirect(res, '/?auth=failed')\n }\n return\n }\n failures.delete(ip)\n const cookie = setCookieValue(createSession(), req)\n if (json) {\n res.writeHead(204, { 'set-cookie': cookie, 'cache-control': 'no-store' }).end()\n } else {\n respondRedirect(res, '/', { 'set-cookie': cookie })\n }\n }\n\n const handleLogout = (req: IncomingMessage, res: ServerResponse): void => {\n const json = wantsJson(req)\n if (enabled) {\n // A forged logout is a nuisance, not a breach, so an absent Origin is allowed here.\n if (originVerdict(req) === 'foreign') {\n respondJson(res, 403, { error: 'origin not allowed' })\n return\n }\n const token = cookieToken(req)\n // Deleting the table row is the invalidation; clearing the cookie is only tidiness.\n if (token !== undefined && token !== '' && sessions.delete(tokenKey(token))) {\n persist()\n }\n }\n const cookie = clearCookieValue(req)\n if (json) {\n res.writeHead(204, { 'set-cookie': cookie, 'cache-control': 'no-store' }).end()\n } else {\n respondRedirect(res, '/', { 'set-cookie': cookie })\n }\n }\n\n const handleAuthRoute = async (pathname: string, req: IncomingMessage, res: ServerResponse): Promise<void> => {\n if (pathname === '/auth/status') {\n if (req.method !== 'GET') {\n respondJson(res, 405, { error: 'method not allowed' }, { allow: 'GET' })\n } else {\n respondJson(res, 200, { enabled, authenticated: enabled ? hasSession(req) : true })\n }\n return\n }\n if (pathname === '/auth/login') {\n if (req.method !== 'POST') {\n respondJson(res, 405, { error: 'method not allowed' }, { allow: 'POST' })\n } else {\n await handleLogin(req, res)\n }\n return\n }\n if (pathname === '/auth/logout') {\n if (req.method !== 'POST') {\n respondJson(res, 405, { error: 'method not allowed' }, { allow: 'POST' })\n } else {\n handleLogout(req, res)\n }\n return\n }\n respondJson(res, 404, { error: 'not found' })\n }\n\n const handleAuthRequest = (req: IncomingMessage, res: ServerResponse): boolean | Promise<boolean> => {\n let pathname: string\n try {\n pathname = new URL(req.url ?? '/', 'http://internal').pathname\n } catch {\n return false\n }\n // The whole prefix is claimed, unknown subpaths included, so nothing under it falls through to the SPA catch-all.\n if (pathname !== '/auth' && !pathname.startsWith('/auth/')) {\n return false\n }\n return handleAuthRoute(pathname, req, res).then(() => true)\n }\n\n const loginPage = (req: IncomingMessage): { action: string; field: string; error?: string } => {\n let reason: string | null = null\n try {\n reason = new URL(req.url ?? '/', 'http://internal').searchParams.get('auth')\n } catch {\n reason = null\n }\n const error =\n reason === 'failed'\n ? 'Invalid access key. Try again.'\n : reason === 'throttled'\n ? 'Too many failed attempts. Wait a few minutes, then try again.'\n : undefined\n return { action: '/auth/login', field: 'secret', error }\n }\n\n return {\n enabled,\n authenticate,\n handleAuthRequest,\n hasValidSession: (req) => (enabled ? hasSession(req) : true),\n loginPage,\n }\n}\n","import { existsSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { dirname, isAbsolute, join, resolve } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport type { ProfileInfo } from '@workerdeck/protocol'\nimport type { WorkerServerOptions } from '@workerdeck/server'\nimport type { ApnsConfig } from './apns/client.ts'\nimport type { CliAuthOptions } from './auth/auth.ts'\n\nconst CONFIG_BASENAMES = ['workerdeck.config.mjs', 'workerdeck.config.js', 'workerdeck.config.cjs']\n\nexport type WorkerDeckConfig = WorkerServerOptions & {\n port?: number\n host?: string\n // Ignored entirely when the file also supplies `authenticate`.\n auth?: CliAuthOptions\n stateDir?: string | null\n allowedHosts?: string[]\n // Binding a listed host waives the auth requirement, and while unauthenticated every entry is also\n // accepted as a Host header. Auth on widens nothing.\n insecureHosts?: string[]\n webRoot?: string\n web?: boolean\n // Hold the machine awake while a session is waiting on it. CLI-only by design: an embedding host owns its own\n // power policy and must not have it changed by a library it imported.\n keepAwake?: boolean\n // Refused unless auth is on: CORS on an open gateway lets any allowlisted page drive it with no credential.\n corsOrigins?: string[]\n apns?: ApnsConfig\n}\n\nexport type CliFlags = {\n config?: string\n port?: number\n host?: string\n authKey?: string\n profiles: ProfileInfo[]\n cwdRoots: string[]\n fsRoots: string[]\n fsWrite?: boolean\n shell?: boolean\n allowedOrigins: string[]\n allowedHosts: string[]\n insecureHosts: string[]\n trustProxy?: boolean\n approvalTimeoutMs?: number | null\n stateDir?: string\n parking?: boolean\n insecure?: boolean\n web?: boolean\n keepAwake?: boolean\n corsOrigins: string[]\n open?: boolean\n help?: boolean\n version?: boolean\n}\n\nexport class ConfigError extends Error {}\n\n// `none`, `0` and anything non-positive all mean the same thing: a prompt that never expires.\nfunction parseDuration(raw: string, source: string): number | null {\n if (raw === 'none' || raw === 'never') {\n return null\n }\n const match = /^(\\d+(?:\\.\\d+)?)(ms|s|m|h)?$/.exec(raw)\n if (!match) {\n throw new ConfigError(`${source}: expected a duration like 300000, 30s, 5m — or 'none'; got: ${raw}`)\n }\n const scale = { ms: 1, s: 1_000, m: 60_000, h: 3_600_000 }[match[2] ?? 'ms']!\n const ms = Number(match[1]) * scale\n return ms > 0 ? ms : null\n}\n\nfunction parsePort(raw: string, source: string): number {\n const port = Number(raw)\n if (!Number.isInteger(port) || port < 0 || port > 65535) {\n throw new ConfigError(`${source}: not a valid port: ${raw}`)\n }\n return port\n}\n\nexport function parseArgs(argv: string[]): CliFlags {\n const flags: CliFlags = {\n profiles: [],\n cwdRoots: [],\n fsRoots: [],\n allowedOrigins: [],\n allowedHosts: [],\n insecureHosts: [],\n corsOrigins: [],\n }\n const next = (i: number, name: string): string => {\n const value = argv[i + 1]\n if (value === undefined || value.startsWith('-')) {\n throw new ConfigError(`${name} requires a value`)\n }\n return value\n }\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i]!\n switch (arg) {\n case '-h':\n case '--help': {\n flags.help = true\n break\n }\n case '-v':\n case '--version': {\n flags.version = true\n break\n }\n case '-c':\n case '--config': {\n flags.config = next(i, arg)\n i++\n break\n }\n case '-p':\n case '--port': {\n flags.port = parsePort(next(i, arg), arg)\n i++\n break\n }\n case '--host': {\n flags.host = next(i, arg)\n i++\n break\n }\n case '--auth-key': {\n flags.authKey = next(i, arg)\n i++\n break\n }\n case '--profile': {\n // A config dir is a credential store, so naming one stays deliberate and is never inferred.\n const raw = next(i, arg)\n i++\n const eq = raw.indexOf('=')\n if (eq <= 0) {\n throw new ConfigError(`--profile expects name=dir, got: ${raw}`)\n }\n const name = raw.slice(0, eq)\n const dir = raw.slice(eq + 1)\n if (!dir) {\n throw new ConfigError(`--profile ${name}= is missing a directory`)\n }\n flags.profiles.push({ name, configDir: resolve(dir) })\n break\n }\n case '--cwd-root': {\n flags.cwdRoots.push(resolve(next(i, arg)))\n i++\n break\n }\n case '--fs-root': {\n flags.fsRoots.push(resolve(next(i, arg)))\n i++\n break\n }\n case '--fs-write': {\n flags.fsWrite = true\n break\n }\n case '--shell': {\n flags.shell = true\n break\n }\n case '--allowed-origin': {\n flags.allowedOrigins.push(next(i, arg))\n i++\n break\n }\n case '--allowed-host': {\n flags.allowedHosts.push(next(i, arg))\n i++\n break\n }\n case '--insecure-host': {\n flags.insecureHosts.push(next(i, arg))\n i++\n break\n }\n case '--trust-proxy': {\n flags.trustProxy = true\n break\n }\n case '--approval-timeout': {\n flags.approvalTimeoutMs = parseDuration(next(i, arg), arg)\n i++\n break\n }\n case '--state-dir': {\n flags.stateDir = resolve(next(i, arg))\n i++\n break\n }\n case '--no-parking-store': {\n flags.parking = false\n break\n }\n case '--insecure': {\n flags.insecure = true\n break\n }\n case '--no-web': {\n flags.web = false\n break\n }\n case '--no-keep-awake': {\n flags.keepAwake = false\n break\n }\n case '--cors-origin': {\n flags.corsOrigins.push(next(i, arg))\n i++\n break\n }\n case '--open': {\n flags.open = true\n break\n }\n default: {\n throw new ConfigError(`unknown option: ${arg}`)\n }\n }\n }\n return flags\n}\n\nexport type LoadedConfig = {\n path: string | null\n options: WorkerDeckConfig\n}\n\n// An implicit config is looked up in cwd only: walking parents would make what a command does depend on where it was run.\nexport async function loadConfigFile(explicit?: string, cwd = process.cwd()): Promise<LoadedConfig> {\n let path: string | null = null\n if (explicit) {\n path = isAbsolute(explicit) ? explicit : resolve(cwd, explicit)\n if (!existsSync(path)) {\n throw new ConfigError(`no config file at ${path}`)\n }\n } else {\n path = CONFIG_BASENAMES.map((name) => join(cwd, name)).find((p) => existsSync(p)) ?? null\n }\n if (!path) {\n return { path: null, options: {} }\n }\n\n let mod: { default?: unknown }\n try {\n // Deliberately non-literal so no bundler resolves it: this is the operator's code, not part of our module graph.\n mod = (await import(pathToFileURL(path).href)) as { default?: unknown }\n } catch (error) {\n throw new ConfigError(`failed to load ${path}: ${error instanceof Error ? error.message : String(error)}`)\n }\n const exported = mod.default\n if (exported === undefined) {\n throw new ConfigError(`${path} has no default export (expected WorkerServerOptions)`)\n }\n const options = typeof exported === 'function' ? await (exported as () => unknown)() : exported\n if (typeof options !== 'object' || options === null) {\n throw new ConfigError(`${path} default export is not an options object`)\n }\n return { path, options: options as WorkerDeckConfig }\n}\n\nconst LOOPBACK = new Set(['127.0.0.1', '::1', 'localhost', '::ffff:127.0.0.1'])\n\nexport function isLoopback(host: string): boolean {\n return LOOPBACK.has(host)\n}\n\nexport type ResolvedConfig = {\n port: number\n host: string\n authKey?: string\n auth: CliAuthOptions\n stateDir: string | null\n configPath: string | null\n hostAuthenticates: boolean\n // A promise rather than a key, because resolution stays I/O-free; `startInstance` must keep it.\n generateAuthKey: boolean\n // Host header values to accept, or null to accept any; non-null only for an unauthenticated instance.\n allowedHosts: Set<string> | null\n webRoot?: string\n web: boolean\n keepAwake: boolean\n corsOrigins: string[]\n apns?: ApnsConfig\n open: boolean\n options: WorkerServerOptions\n}\n\nexport function defaultStateDir(configPath: string | null): string {\n return configPath ? join(dirname(configPath), '.workerdeck') : join(homedir(), '.workerdeck')\n}\n\nexport function hostnameOf(hostHeader: string): string {\n try {\n return new URL(`http://${hostHeader}`).hostname.replace(/^\\[|\\]$/g, '').toLowerCase()\n } catch {\n return ''\n }\n}\n\nexport function isLoopbackHostname(hostname: string): boolean {\n if (LOOPBACK.has(hostname)) {\n return true\n }\n return /^127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(hostname)\n}\n\n// An entry with a port could never match what the guard compares, so it is rejected rather than normalized into a gate\n// that looks armed and is not. Bare IPv6 is bracketed first because WHATWG URL requires it.\nfunction normalizeInsecureHost(raw: string): string {\n const entry = raw.trim()\n const looksLikeNamePort = /^[^:]+:\\d+$/.test(entry)\n const candidate = entry.includes(':') && !entry.startsWith('[') && !looksLikeNamePort ? `[${entry}]` : entry\n let url: URL\n try {\n url = new URL(`http://${candidate}`)\n } catch {\n throw new ConfigError(`not a host name or address in insecureHosts: ${JSON.stringify(raw)}`)\n }\n if (url.port !== '') {\n throw new ConfigError(\n `insecureHosts entry ${JSON.stringify(raw)} carries a port — name the host alone; ` +\n `the bind host and the Host header are both compared portless`,\n )\n }\n return url.hostname.replace(/^\\[|\\]$/g, '').toLowerCase()\n}\n\nexport function resolveInstanceConfig(\n flags: CliFlags,\n loaded: LoadedConfig,\n env: NodeJS.ProcessEnv = process.env,\n cwd = process.cwd(),\n): ResolvedConfig {\n const envPort = env.WORKERDECK_PORT ? parsePort(env.WORKERDECK_PORT, 'WORKERDECK_PORT') : undefined\n const port = flags.port ?? envPort ?? loaded.options.port ?? 8787\n const host = flags.host ?? env.WORKERDECK_HOST ?? loaded.options.host ?? '127.0.0.1'\n // `||`, not `??`: an empty secret is not a secret — `WORKERDECK_AUTH_KEY=` means \"unset\".\n const authKey = flags.authKey || env.WORKERDECK_AUTH_KEY || loaded.options.auth?.secret || undefined\n const hostAuthenticates = typeof loaded.options.authenticate === 'function'\n\n const insecureHosts = new Set([...flags.insecureHosts, ...(loaded.options.insecureHosts ?? [])].map(normalizeInsecureHost))\n const bindHost = host\n .trim()\n .replace(/^\\[|\\]$/g, '')\n .toLowerCase()\n\n // An unauthenticated gateway on a routable interface is a shell for anyone who can reach the port, so serving one\n // takes an explicit opt-out; absent all three of them auth goes on and a key is generated.\n const generateAuthKey =\n !authKey &&\n !hostAuthenticates &&\n !loaded.options.allowUnauthenticated &&\n !isLoopback(host) &&\n !flags.insecure &&\n !insecureHosts.has(bindHost)\n\n const stateDir =\n flags.parking === false || loaded.options.stateDir === null\n ? null\n : (flags.stateDir ?? env.WORKERDECK_STATE_DIR ?? loaded.options.stateDir ?? defaultStateDir(loaded.path))\n\n const envCwdRoots = env.WORKERDECK_CWD_ROOTS?.split(':')\n .filter(Boolean)\n .map((p) => resolve(cwd, p))\n const cwdRoots = flags.cwdRoots.length ? flags.cwdRoots : envCwdRoots\n\n const envFsRoots = env.WORKERDECK_FS_ROOTS?.split(':')\n .filter(Boolean)\n .map((p) => resolve(cwd, p))\n const fsRoots = flags.fsRoots.length ? flags.fsRoots : envFsRoots\n\n const auth: CliAuthOptions = {\n ...loaded.options.auth,\n secret: authKey,\n trustProxy: flags.trustProxy ?? loaded.options.auth?.trustProxy,\n allowedOrigins: [...(loaded.options.auth?.allowedOrigins ?? []), ...flags.allowedOrigins],\n }\n\n // `generateAuthKey` counts as auth here on the promise that `startInstance` materializes the key, which it asserts.\n const authEnabled = Boolean(authKey) || hostAuthenticates || generateAuthKey\n const allowedHosts = authEnabled\n ? null\n : new Set([\n ...LOOPBACK,\n ...[...flags.allowedHosts, ...(loaded.options.allowedHosts ?? [])].map((name) => name.toLowerCase()),\n ...insecureHosts,\n ])\n\n const web = flags.web ?? loaded.options.web ?? true\n const keepAwake = flags.keepAwake ?? loaded.options.keepAwake ?? true\n\n const corsOrigins = flags.corsOrigins.length ? flags.corsOrigins : (loaded.options.corsOrigins ?? [])\n for (const origin of corsOrigins) {\n // Exactness is the guarantee: a wildcard hands the API to every page that can reach this port.\n if (origin === '*') {\n throw new ConfigError('--cors-origin does not accept \"*\"')\n }\n let parsed: URL\n try {\n parsed = new URL(origin)\n } catch {\n throw new ConfigError(`--cors-origin expects a full origin, got: ${origin}`)\n }\n if (parsed.origin !== origin) {\n throw new ConfigError(`--cors-origin expects scheme://host[:port] with no path, got: ${origin}`)\n }\n }\n if (!web && flags.open) {\n throw new ConfigError('--open cannot be used with --no-web: there is no dashboard to open')\n }\n\n const {\n port: _p,\n host: _h,\n auth: _a,\n stateDir: _s,\n allowedHosts: _ah,\n insecureHosts: _ih,\n webRoot: _w,\n web: _web,\n keepAwake: _ka,\n corsOrigins: _cors,\n apns: _ap,\n ...serverOptions\n } = loaded.options\n const options: WorkerServerOptions = { ...serverOptions }\n if (cwdRoots?.length) {\n options.allowedCwdRoots = cwdRoots\n }\n if (fsRoots?.length || flags.fsWrite) {\n options.hostFiles = {\n ...loaded.options.hostFiles,\n ...(fsRoots?.length ? { roots: fsRoots } : {}),\n ...(flags.fsWrite ? { write: true } : {}),\n }\n }\n if (flags.shell) {\n options.shell = { ...loaded.options.shell, enabled: true }\n }\n // Flags replace rather than merge: a half-declared profile set is a credential mix-up.\n if (flags.profiles.length) {\n options.profiles = flags.profiles\n }\n if (flags.approvalTimeoutMs !== undefined) {\n options.approvalTimeoutMs = flags.approvalTimeoutMs\n }\n\n return {\n port,\n host,\n authKey,\n auth,\n stateDir,\n configPath: loaded.path,\n hostAuthenticates,\n generateAuthKey,\n allowedHosts,\n webRoot: loaded.options.webRoot,\n web,\n keepAwake,\n corsOrigins,\n apns: resolveApns(loaded),\n open: flags.open ?? false,\n options,\n }\n}\n\n// A relative `keyFile` resolves against the config file's directory, never the cwd, so a deployment stays self-contained.\nfunction resolveApns(loaded: LoadedConfig): ApnsConfig | undefined {\n const apns = loaded.options.apns\n if (!apns) {\n return undefined\n }\n for (const field of ['keyFile', 'keyId', 'teamId', 'topic'] as const) {\n if (typeof apns[field] !== 'string' || apns[field] === '') {\n throw new ConfigError(`apns.${field} is required when apns is configured`)\n }\n }\n const base = loaded.path ? dirname(loaded.path) : process.cwd()\n return { ...apns, keyFile: resolve(base, apns.keyFile) }\n}\n","export type LoginPageOptions = {\n action: string\n field: string\n error?: string\n redirectTo?: string\n redirectField?: string\n}\n\nfunction escapeHtml(value: string): string {\n return value.replace(/[&<>\"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '\"': '"', \"'\": ''' })[c] ?? c)\n}\n\nexport function renderLoginPage(options: LoginPageOptions): string {\n const { action, field, error, redirectTo, redirectField } = options\n const hidden =\n redirectField && redirectTo ? `<input type=\"hidden\" name=\"${escapeHtml(redirectField)}\" value=\"${escapeHtml(redirectTo)}\">` : ''\n const alert = error ? `<p class=\"error\" role=\"alert\">${escapeHtml(error)}</p>` : ''\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<meta name=\"robots\" content=\"noindex\">\n<title>workerdeck</title>\n<style>\n :root {\n color-scheme: light dark;\n --bg: #fbfaf9; --fg: #1c1917; --muted: #78716c;\n --card: #ffffff; --border: #e7e5e4; --accent: #c2410c; --error: #b91c1c;\n }\n @media (prefers-color-scheme: dark) {\n :root {\n --bg: #1c1917; --fg: #fafaf9; --muted: #a8a29e;\n --card: #292524; --border: #44403c; --accent: #fb923c; --error: #fca5a5;\n }\n }\n * { box-sizing: border-box; }\n body {\n margin: 0; min-height: 100dvh; display: grid; place-items: center; padding: 1.5rem;\n background: var(--bg); color: var(--fg);\n font: 15px/1.5 ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", sans-serif;\n }\n form {\n width: 100%; max-width: 22rem; background: var(--card);\n border: 1px solid var(--border); border-radius: 12px; padding: 1.75rem;\n }\n h1 { margin: 0 0 .25rem; font-size: 1.05rem; letter-spacing: -0.01em; }\n p.sub { margin: 0 0 1.25rem; color: var(--muted); font-size: .875rem; }\n label { display: block; font-size: .8125rem; font-weight: 500; margin-bottom: .375rem; }\n input[type=password] {\n width: 100%; padding: .5rem .625rem; font: inherit; color: inherit;\n background: var(--bg); border: 1px solid var(--border); border-radius: 7px;\n }\n input[type=password]:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }\n button {\n width: 100%; margin-top: 1rem; padding: .5rem .75rem; font: inherit; font-weight: 500;\n color: var(--bg); background: var(--fg); border: 0; border-radius: 7px; cursor: pointer;\n }\n button:hover { opacity: .9; }\n .error { margin: 0 0 1rem; color: var(--error); font-size: .8125rem; }\n</style>\n</head>\n<body>\n <form method=\"post\" action=\"${escapeHtml(action)}\">\n <h1>workerdeck</h1>\n <p class=\"sub\">This instance is protected. Enter its access key to continue.</p>\n ${alert}\n <label for=\"key\">Access key</label>\n <input id=\"key\" name=\"${escapeHtml(field)}\" type=\"password\" autocomplete=\"current-password\"\n autofocus required spellcheck=\"false\">\n ${hidden}\n <button type=\"submit\">Sign in</button>\n </form>\n</body>\n</html>\n`\n}\n","import { spawn, type ChildProcess } from 'node:child_process'\nimport type { SessionInfo } from '@workerdeck/protocol'\n\n// The states that are waiting on this machine: a turn in flight, and a session blocked on the operator, who is\n// coming back to it and would find the socket gone. Deliberately the same set `workerdeck guard` calls busy.\nconst AWAKE_STATUSES = new Set<SessionInfo['status']>(['starting', 'running', 'awaiting_approval'])\n\nexport type WakeLock = {\n readonly held: boolean\n set: (wanted: boolean) => void\n release: () => void\n}\n\nexport function sessionsNeedTheMachine(sessions: readonly SessionInfo[]): boolean {\n return sessions.some((session) => AWAKE_STATUSES.has(session.status))\n}\n\n// Both platforms hold the lock for as long as a child process lives, and both tie that child's life to *this* pid\n// rather than to a kill we might never get to make: `caffeinate -w` waits on a pid, and the inhibited command polls\n// one. So a SIGKILLed gateway releases within seconds instead of pinning the machine awake until reboot.\nfunction lockCommand(pid: number): { command: string; args: string[] } | null {\n if (process.platform === 'darwin') {\n // -i is idle sleep only. Never -d: keeping the screen lit is not what was asked for.\n return { command: 'caffeinate', args: ['-i', '-w', String(pid)] }\n }\n if (process.platform === 'linux') {\n return {\n command: 'systemd-inhibit',\n args: [\n '--what=idle:sleep',\n '--mode=block',\n '--who=workerdeck',\n '--why=a turn is running',\n 'sh',\n '-c',\n `while kill -0 ${pid} 2>/dev/null; do sleep 5; done`,\n ],\n }\n }\n return null\n}\n\nexport type WakeSource = {\n list: () => SessionInfo[]\n observe: (\n listener: (runner: {\n subscribe: (listener: (event: { type: string }) => void, afterSeq?: number) => unknown\n info: () => SessionInfo\n }) => void,\n ) => unknown\n}\n\n// Recomputed from the registry rather than counted up and down. A refcount has to be decremented on every exit path\n// a session can take — closed, failed, parked, evicted, drained — and one missed path pins the machine awake until\n// the gateway stops. Derived state cannot leak that way, so the sweep below is insurance, not the mechanism.\nexport function driveWakeLock(source: WakeSource, lock: Pick<WakeLock, 'set'>): void {\n const sync = (): void => lock.set(sessionsNeedTheMachine(source.list()))\n source.observe((runner) => {\n runner.subscribe((event) => {\n if (event.type === 'status_changed' || event.type === 'session_closed' || event.type === 'session_error') {\n sync()\n }\n }, runner.info().lastSeq)\n })\n sync()\n}\n\nexport function createWakeLock(options: { pid?: number; onUnavailable?: (reason: string) => void } = {}): WakeLock {\n const plan = lockCommand(options.pid ?? process.pid)\n let child: ChildProcess | null = null\n let available = plan !== null\n\n const stop = (): void => {\n const running = child\n child = null\n running?.kill()\n }\n\n const start = (): void => {\n if (!plan || !available || child) {\n return\n }\n let spawned: ChildProcess\n try {\n spawned = spawn(plan.command, plan.args, { stdio: 'ignore' })\n } catch {\n available = false\n options.onUnavailable?.(`could not spawn ${plan.command}`)\n return\n }\n child = spawned\n // A missing binary is not an error — a box with neither tool must start and run normally — but stop trying.\n spawned.on('error', () => {\n if (child === spawned) {\n child = null\n }\n available = false\n options.onUnavailable?.(`${plan.command} is not available on this machine`)\n })\n spawned.on('exit', () => {\n if (child === spawned) {\n child = null\n }\n })\n // Never the reason the gateway cannot exit.\n spawned.unref()\n }\n\n return {\n get held(): boolean {\n return child !== null\n },\n set: (wanted) => (wanted ? start() : stop()),\n release: stop,\n }\n}\n","import { createReadStream } from 'node:fs'\nimport { stat } from 'node:fs/promises'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { join, resolve, sep } from 'node:path'\n\nconst CONTENT_TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.mjs': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.map': 'application/json; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.webp': 'image/webp',\n '.avif': 'image/avif',\n '.ico': 'image/x-icon',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.ttf': 'font/ttf',\n '.wasm': 'application/wasm',\n '.txt': 'text/plain; charset=utf-8',\n}\n\nexport function contentTypeFor(pathname: string): string {\n const dot = pathname.lastIndexOf('.')\n if (dot < 0) {\n return 'application/octet-stream'\n }\n return CONTENT_TYPES[pathname.slice(dot).toLowerCase()] ?? 'application/octet-stream'\n}\n\nexport function looksLikeAsset(pathname: string): boolean {\n const dot = pathname.lastIndexOf('.')\n if (dot < 0) {\n return false\n }\n return pathname.slice(dot).toLowerCase() in CONTENT_TYPES\n}\n\nexport function resolveWithinRoot(root: string, pathname: string): string | null {\n let decoded: string\n try {\n decoded = decodeURIComponent(pathname)\n } catch {\n return null\n }\n if (decoded.includes('\\0')) {\n return null\n }\n // Never normalise `decoded` first: that collapses an absolute pathname's leading `..` segments and rebases the\n // escape attempt inside the root, leaving the containment check below with nothing to catch.\n const candidate = resolve(join(root, decoded))\n const base = resolve(root)\n if (candidate !== base && !candidate.startsWith(base + sep)) {\n return null\n }\n return candidate\n}\n\nexport function sendHtml(req: IncomingMessage, res: ServerResponse, status: number, html: string, cache: string): void {\n const body = Buffer.from(html, 'utf8')\n res.writeHead(status, {\n 'content-type': 'text/html; charset=utf-8',\n 'content-length': body.byteLength,\n 'cache-control': cache,\n // The dashboard holds an ambient session cookie, so framing it elsewhere is only ever clickjacking.\n 'x-frame-options': 'DENY',\n 'x-content-type-options': 'nosniff',\n 'referrer-policy': 'same-origin',\n })\n res.end(req.method === 'HEAD' ? undefined : body)\n}\n\nexport type ServeResult = 'served' | 'not-found' | 'method-not-allowed'\n\nexport async function serveFile(\n req: IncomingMessage,\n res: ServerResponse,\n filePath: string,\n options: { immutable?: boolean } = {},\n): Promise<ServeResult> {\n if (req.method !== 'GET' && req.method !== 'HEAD') {\n return 'method-not-allowed'\n }\n\n let size: number\n try {\n const info = await stat(filePath)\n if (!info.isFile()) {\n return 'not-found'\n }\n size = info.size\n } catch {\n return 'not-found'\n }\n\n res.writeHead(200, {\n 'content-type': contentTypeFor(filePath),\n 'content-length': size,\n 'cache-control': options.immutable ? 'public, max-age=31536000, immutable' : 'no-cache, must-revalidate',\n 'x-content-type-options': 'nosniff',\n })\n if (req.method === 'HEAD') {\n res.end()\n return 'served'\n }\n await new Promise<void>((resolvePromise) => {\n const stream = createReadStream(filePath)\n stream.on('error', () => {\n res.destroy()\n resolvePromise()\n })\n stream.on('end', () => resolvePromise())\n stream.pipe(res)\n })\n return 'served'\n}\n","import { existsSync } from 'node:fs'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { join } from 'node:path'\nimport { createFileSessionStore, createWorkerServer, type WorkerServer } from '@workerdeck/server'\nimport { dashboardDir } from '@workerdeck/web'\nimport { createApnsForwarder } from '../apns/forwarder.ts'\nimport { materializeAuthKey, type MaterializedAuthKey } from '../auth/auth-key.ts'\nimport { createAuthSessionStore } from '../auth/auth-sessions.ts'\nimport { createCliAuth, type CliAuth } from '../auth/auth.ts'\nimport { hostnameOf, isLoopbackHostname, type ResolvedConfig } from '../config.ts'\nimport { renderLoginPage } from '../auth/login-page.ts'\nimport { createWakeLock, driveWakeLock, sessionsNeedTheMachine } from './keep-awake.ts'\nimport { looksLikeAsset, resolveWithinRoot, sendHtml, serveFile } from './static.ts'\n\n// The backstop, not the mechanism: the lock is driven by session transitions, and this only has to catch a release\n// that no event announced. Long, because holding a laptop awake for one extra minute is the cheap failure.\nconst WAKE_SWEEP_MS = 60_000\n\nexport type Instance = {\n server: WorkerServer\n url: string\n port: number\n closed: Promise<void>\n // Let running turns finish before `close()`. See `WorkerServer.drain`.\n drain: WorkerServer['drain']\n close: () => Promise<void>\n}\n\nexport function resolveWebRoot(): string {\n if (existsSync(join(dashboardDir, 'index.html'))) {\n return dashboardDir\n }\n throw new Error(`no dashboard build at ${dashboardDir}\\n` + ` in a checkout: pnpm --filter @workerdeck/web run build`)\n}\n\n// Loopback *names* are what is checked, never the socket: a DNS-rebinding attacker's connection really does arrive\n// on 127.0.0.1, and the browser's Host header is the one thing they cannot control.\nexport function createHostGuard(allowedHosts: Set<string> | null): (req: IncomingMessage) => boolean {\n if (allowedHosts === null) {\n return () => true\n }\n return (req) => {\n const header = req.headers.host\n // No Host at all is an HTTP/1.0 client or a script, never a browser being driven cross-origin.\n if (header === undefined) {\n return true\n }\n const hostname = hostnameOf(header)\n if (hostname === '') {\n return false\n }\n return isLoopbackHostname(hostname) || allowedHosts.has(hostname)\n }\n}\n\nfunction pathnameOf(req: IncomingMessage): string | null {\n try {\n return new URL(req.url ?? '/', 'http://internal').pathname\n } catch {\n return null\n }\n}\n\n// The order here is the contract: auth endpoints first (they are how a browser gets a session at all), then the APNs\n// route, then assets — ungated, being the app's own code — and documents last, the one place the auth decision is made.\nfunction createFallback(\n auth: CliAuth,\n webRoot: string | undefined,\n hostAllowed: (req: IncomingMessage) => boolean,\n apnsRoute?: (req: IncomingMessage, res: ServerResponse) => Promise<boolean>,\n): (req: IncomingMessage, res: ServerResponse) => Promise<void> {\n return async (req, res) => {\n if (!hostAllowed(req)) {\n res.writeHead(403, { 'content-type': 'text/plain; charset=utf-8' })\n res.end(\n 'unrecognised Host header.\\n\\nThis instance runs without auth, so it only answers to ' +\n 'loopback host names and the ones it was told to expect. Declare this name with ' +\n '--insecure-host <name> (which also lets it be bound without a key) or --allowed-host ' +\n '<name>, or set --auth-key.\\n',\n )\n return\n }\n if (await auth.handleAuthRequest(req, res)) {\n return\n }\n if (apnsRoute !== undefined && (await apnsRoute(req, res))) {\n return\n }\n if (apnsRoute === undefined && pathnameOf(req) === '/apns/devices') {\n res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' })\n res.end('this gateway runs without push\\n')\n return\n }\n\n // Below the auth and APNs routes, because turning the *dashboard* off must not turn off the gateway's own surfaces.\n if (webRoot === undefined) {\n res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' })\n res.end('the web dashboard is disabled on this gateway\\n')\n return\n }\n\n const pathname = new URL(req.url ?? '/', 'http://internal').pathname\n\n if (looksLikeAsset(pathname)) {\n const filePath = resolveWithinRoot(webRoot, pathname)\n if (!filePath) {\n res.writeHead(400, { 'content-type': 'text/plain; charset=utf-8' })\n res.end('bad request')\n return\n }\n const result = await serveFile(req, res, filePath, {\n immutable: pathname.startsWith('/assets/'),\n })\n if (result === 'served') {\n return\n }\n if (result === 'method-not-allowed') {\n res.writeHead(405, { allow: 'GET, HEAD' })\n res.end()\n return\n }\n res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' })\n res.end('not found')\n return\n }\n\n if (req.method !== 'GET' && req.method !== 'HEAD') {\n res.writeHead(405, { allow: 'GET, HEAD' })\n res.end()\n return\n }\n\n if (auth.enabled && !auth.hasValidSession(req)) {\n sendHtml(req, res, 401, renderLoginPage(auth.loginPage(req)), 'no-store')\n return\n }\n\n const entry = join(webRoot, 'index.html')\n const result = await serveFile(req, res, entry, { immutable: false })\n if (result !== 'served') {\n res.writeHead(500, { 'content-type': 'text/plain; charset=utf-8' })\n res.end('dashboard build is missing its entry document')\n }\n }\n}\n\nexport async function startInstance(config: ResolvedConfig, options: { quiet?: boolean } = {}): Promise<Instance> {\n const webRoot = config.web ? (config.webRoot ?? resolveWebRoot()) : undefined\n const generated: MaterializedAuthKey | null =\n config.generateAuthKey && !config.hostAuthenticates ? await materializeAuthKey(config.stateDir) : null\n const authOptions = config.hostAuthenticates\n ? { ...config.auth, secret: undefined }\n : generated\n ? { ...config.auth, secret: generated.key }\n : config.auth\n const sessions =\n authOptions.secret !== undefined && config.stateDir ? await createAuthSessionStore({ stateDir: config.stateDir }) : undefined\n const auth = createCliAuth(sessions ? { ...authOptions, sessions } : authOptions)\n // Keep this assert: it is the only thing between \"the config stood the Host-header guard down expecting auth\" and\n // \"createCliAuth got no secret\", which is an instance wide open while reporting itself authenticated.\n if (config.allowedHosts === null && !config.hostAuthenticates && !auth.enabled) {\n throw new Error(\n 'refusing to serve: the resolved config expects auth but no shared secret was ' +\n 'materialized — this instance would be open while believing itself authenticated',\n )\n }\n // Sibling of the assert above: CORS on an open gateway lets any allowlisted page drive it with no credential.\n if (config.corsOrigins.length > 0 && !config.hostAuthenticates && !auth.enabled) {\n throw new Error(\n 'refusing to serve: corsOrigins is set but this instance has no auth — ' +\n 'a cross-origin page could drive it with no credential. Set --auth-key.',\n )\n }\n const hostAllowed = createHostGuard(config.allowedHosts)\n\n // A bad key path throws before we listen: better a refusal at startup than a phone that never buzzes.\n const apns =\n config.apns === undefined\n ? undefined\n : await createApnsForwarder({\n config: config.apns,\n stateDir: config.stateDir,\n authenticate: (req) => (hostAllowed(req) ? auth.authenticate(req) : null),\n })\n\n const fallback = createFallback(auth, webRoot, hostAllowed, apns?.handleRequest)\n\n const parking = { ...config.options.parking }\n if (config.stateDir && !parking.store) {\n parking.store = createFileSessionStore({\n dir: join(config.stateDir, 'parked'),\n onError: (error, context) => {\n process.stderr.write(\n `[workerdeck] parked-session store ${context.op} failed for ${context.path}: ` +\n `${error instanceof Error ? error.message : String(error)}\\n`,\n )\n },\n })\n }\n\n const server = createWorkerServer({\n ...config.options,\n // On by default here, off in the library: a mispointed config dir should say so at startup.\n checkCredentials: config.options.checkCredentials ?? true,\n parking,\n // Composed, not replaced: turning push on must not unhook a config file's own observer.\n notifications:\n apns === undefined\n ? config.options.notifications\n : {\n ...config.options.notifications,\n onNotification: (notification) => {\n config.options.notifications?.onNotification?.(notification)\n apns.onNotification(notification)\n },\n },\n fallback,\n ...(config.corsOrigins.length ? { cors: { origins: config.corsOrigins } } : {}),\n // A config file's own `authenticate` wins outright: mixing two auth schemes on one hook is a bypass nobody meant to\n // write. The unauthenticated case still supplies one rather than `allowUnauthenticated`, so the Host check covers `/v1`.\n authenticate: config.hostAuthenticates\n ? (req) => (hostAllowed(req) ? config.options.authenticate!(req) : null)\n : (req) => (hostAllowed(req) ? auth.authenticate(req) : null),\n })\n\n const wake = config.keepAwake\n ? createWakeLock({\n onUnavailable: (reason) => {\n if (!options.quiet) {\n process.stderr.write(`[workerdeck] not holding the machine awake: ${reason}\\n`)\n }\n },\n })\n : undefined\n let sweep: ReturnType<typeof setInterval> | undefined\n if (wake) {\n driveWakeLock(server.registry, wake)\n sweep = setInterval(() => wake.set(sessionsNeedTheMachine(server.registry.list())), WAKE_SWEEP_MS)\n sweep.unref()\n }\n\n const { port } = await server.listen(config.port, config.host)\n const displayHost = config.host === '0.0.0.0' || config.host === '::' ? 'localhost' : config.host\n const url = `http://${displayHost.includes(':') ? `[${displayHost}]` : displayHost}:${port}`\n\n let resolveClosed: () => void = () => {}\n const closed = new Promise<void>((r) => {\n resolveClosed = r\n })\n\n if (!options.quiet) {\n const line = (text: string): void => void process.stdout.write(`${text}\\n`)\n line('')\n line(` workerdeck ${url}`)\n if (config.hostAuthenticates) {\n line(' auth: the config file supplies its own `authenticate`')\n } else if (generated?.source === 'created') {\n line(` auth: generated key ${generated.key}`)\n line(` stored in ${generated.path} — later starts reuse it without printing it`)\n } else if (generated?.source === 'ephemeral') {\n line(` auth: generated key ${generated.key}`)\n line(' ephemeral — no state dir to keep it, so the next start mints a new one')\n } else if (generated?.source === 'stored') {\n line(` auth: shared key from ${generated.path}`)\n } else if (auth.enabled) {\n line(' auth: shared key — browsers sign in, services send a header')\n } else {\n line(' NO AUTH — anyone who can reach this port gets a session')\n }\n if (!config.web) {\n line(' dashboard: off — bare gateway, /v1 and /auth only')\n }\n if (!config.keepAwake) {\n line(' keep-awake: off — this machine may sleep mid-turn')\n }\n if (config.corsOrigins.length) {\n line(` cors: ${config.corsOrigins.join(', ')} may call /v1 (still key-gated)`)\n }\n line(\n config.stateDir\n ? ` parked sessions persist in ${join(config.stateDir, 'parked')}`\n : ' parked sessions are in memory only — a restart drops them',\n )\n if (apns) {\n const count = apns.deviceCount()\n line(` push: APNs forwarder on ${config.apns?.topic} — ` + `${count === 0 ? 'no devices registered yet' : `${count} device(s)`}`)\n }\n if (config.configPath) {\n line(` config ${config.configPath}`)\n }\n line('')\n }\n\n return {\n server,\n url,\n port,\n closed,\n drain: (drainOptions) => server.drain(drainOptions),\n close: async () => {\n if (sweep) {\n clearInterval(sweep)\n }\n wake?.release()\n await server.close()\n // The session table's writes are queued rather than awaited by the request that caused them, so a last-moment\n // login is lost unless the queue drains here.\n await sessions?.flush?.()\n apns?.close()\n resolveClosed()\n },\n }\n}\n"],"mappings":"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"}
|