Skip to content
File

Blob: worker/src/server/index.ts

typescript72 lines
1import { Hono } from 'hono';
2import { assertSameOrigin, authenticated } from './auth.ts';
3import { equal } from './crypto.ts';
4import { demand, OperationError } from './rpc.ts';
5import { json } from './http.ts';
6import type { HttpApp } from './http.ts';
7import access from './routes/access.ts';
8import device from './routes/device.ts';
9import viewers from './routes/viewers.ts';
10export { RobotRoom } from './robot-room.ts';
11 
12const app = new Hono<HttpApp>();
13 
14app.use('/api/*', async (c, next) => {
15 const requestId = crypto.randomUUID(),
16 began = Date.now();
17 c.set('requestId', requestId);
18 c.header('X-Request-Id', requestId);
19 c.header('Cache-Control', 'no-store');
20 demand(['GET', 'POST'].includes(c.req.method), 405, 'Method not allowed.');
21 assertSameOrigin(c.req.raw);
22 if (c.req.path !== '/api/login') {
23 if (c.req.path.startsWith('/api/device/')) {
24 demand(
25 await equal(c.req.header('Authorization') ?? '', `Bearer ${c.env.DEVICE_TOKEN}`),
26 401,
27 'Device authentication required.',
28 );
29 } else demand(await authenticated(c), 401, 'Enter the viewer password to listen.');
30 }
31 await next();
32 if (c.res.status >= 500 || (c.req.method === 'POST' && !c.req.path.endsWith('/heartbeat'))) {
33 console.log(
34 JSON.stringify({
35 event: 'request.complete',
36 requestId,
37 operation: c.req.path.replace(/[a-f0-9-]{36}/g, ':viewer'),
38 status: c.res.status,
39 durationMs: Date.now() - began,
40 }),
41 );
42 }
43});
44 
45app.route('/api', access);
46app.route('/api/device', device);
47app.route('/api/viewers', viewers);
48 
49app.all('/api/*', (c) =>
50 json(
51 { error: 'Unknown radio operation.', requestId: c.get('requestId') },
52 c.req.method === 'GET' ? 405 : 404,
53 ),
54);
55app.all('*', (c) => c.env.ASSETS.fetch(c.req.raw));
56app.onError((error, c) => {
57 const status = error instanceof OperationError ? error.status : 500;
58 if (status >= 500)
59 console.error(JSON.stringify({ phase: 'request', status, requestId: c.get('requestId') }));
60 return json(
61 {
62 error:
63 error instanceof OperationError
64 ? error.message
65 : 'The radio service could not complete this request.',
66 requestId: c.get('requestId'),
67 },
68 status,
69 );
70});
71export default app;