import type { IncomingMessage, ServerResponse } from 'node:http'; import { DevicePackageError } from '../../coding-packages/device-package-manager'; import type { HostApiContext } from '../context'; import { parseJsonBody, sendJson } from '../route-utils'; const ROOT = '/api/coding/device-packages'; const PACKAGE = /^\/api\/coding\/device-packages\/([^/]+)$/u; const PACKAGE_ID = /^[a-z0-9][a-z0-9._-]{0,127}$/u; class DevicePackageRouteError extends Error { constructor(readonly status: 400 | 404, message: string) { super(message); } } function packageId(value: string): string { try { const decoded = decodeURIComponent(value); if (PACKAGE_ID.test(decoded)) return decoded; } catch { // Project a single bounded request error below. } throw new DevicePackageRouteError(400, 'Device package id is invalid'); } function noQuery(url: URL): void { if (url.search) throw new DevicePackageRouteError(400, 'Query parameters are not supported'); } async function exactBody(req: IncomingMessage, keys: readonly string[]): Promise> { const body = await parseJsonBody(req); if (!body || typeof body !== 'object' || Array.isArray(body)) { throw new DevicePackageRouteError(400, 'Request body is invalid'); } const record = body as Record; const actual = Object.keys(record).sort(); const expected = [...keys].sort(); if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) { throw new DevicePackageRouteError(400, 'Request body has unexpected fields'); } return record; } function sendError(res: ServerResponse, error: unknown): void { if (error instanceof DevicePackageRouteError) { sendJson(res, error.status, { success: false, code: 'local_package_request_invalid', error: error.message, }); return; } if (error instanceof DevicePackageError) { const status = error.code === 'local_package_not_installed' || error.code === 'local_package_not_found' ? 404 : error.code === 'local_package_in_use' || error.code === 'local_package_confirmation_required' ? 409 : error.code === 'local_package_dependency_failed' || error.code === 'local_package_install_failed' ? 503 : 422; sendJson(res, status, { success: false, code: error.code, error: error.message }); return; } if (error instanceof SyntaxError) { sendJson(res, 400, { success: false, code: 'local_package_request_invalid', error: 'Request body is invalid', }); return; } sendJson(res, 503, { success: false, code: 'local_package_install_failed', error: 'Device package service is unavailable', }); } export async function handleDevicePackageRoutes( req: IncomingMessage, res: ServerResponse, url: URL, ctx: HostApiContext, ): Promise { const match = PACKAGE.exec(url.pathname); const known = (url.pathname === ROOT && req.method === 'GET') || Boolean(match && (req.method === 'PATCH' || req.method === 'DELETE')); if (!known) return false; const manager = ctx.codingProducts?.devicePackages; if (!manager) { sendJson(res, 503, { success: false, code: 'local_package_install_failed', error: 'Device package service is unavailable', }); return true; } try { noQuery(url); if (url.pathname === ROOT) { sendJson(res, 200, await manager.list()); return true; } const id = packageId(match?.[1] ?? ''); if (req.method === 'PATCH') { const body = await exactBody(req, ['enabled']); if (typeof body.enabled !== 'boolean') { throw new DevicePackageRouteError(400, 'enabled is invalid'); } sendJson(res, 200, await manager.setEnabled(id, body.enabled)); return true; } await exactBody(req, []); sendJson(res, 200, await manager.uninstall(id)); return true; } catch (error) { sendError(res, error); return true; } }