add CDN /data/ endpoint

This commit is contained in:
Devin Zuczek
2026-08-05 17:54:07 -04:00
parent 6b7acc9435
commit 8b804eaa33
2 changed files with 42 additions and 3 deletions
+26 -3
View File
@@ -195,6 +195,28 @@ const app = new Hono<App>()
(c) => serveAsset(c, `invention/${c.req.param('dataBlob')}`)
)
// Generic client data by name. Anything the client uploads as FileType 2 lands
// under `data/` (a Holotar recording is the one seen in the wild) and the client
// fetches it back from this prefix. Date-foldered like the room and invention
// blobs, so the rest of the path is matched as-is.
.get(
'/data/:id{.+}',
describeRoute({
tags: ['Assets'],
summary: 'Serve a client data blob',
description: [
'Streams the object stored under `data/<id>` — whatever the client uploaded as',
'`UploadFileType` 2 (see the `storage` worker), a Holotar recording being the case',
'observed. Like room and invention blobs the name is date-foldered by the upload,',
'e.g. `2026-02-03/<uuid>`, so it contains slashes. The worker does not interpret the',
'bytes — the prefix exists because the client expects to read these back from `/data/`.',
].join(' '),
parameters: [keyParam('id', 'The blob name.', true), ...CONDITIONAL_HEADERS],
responses: assetResponses('The data blob'),
}),
(c) => serveAsset(c, `data/${c.req.param('id')}`)
)
// The generated spec. Documentation only — no request is validated against it (see
// openapi.ts). `hide: true` keeps this route out of its own output.
app.get(
@@ -209,10 +231,11 @@ app.get(
description: [
'Binary asset delivery for recflare, a private-server reimplementation of the Rec',
'Room backend. Streams the blobs the client downloads while playing — anti-cheat',
'signatures, saved room scenes and invention data — out of the shared `recflare-cdn`',
'R2 bucket, plus the one bundled config file the loading screen reads.',
'signatures, saved room scenes, invention data and generic client uploads — out of',
'the shared `recflare-cdn` R2 bucket, plus the one bundled config file the loading',
'screen reads.',
'',
'Everything is keyed by prefix (`sigs/`, `room/`, `invention/`) and served as',
'Everything is keyed by prefix (`sigs/`, `room/`, `invention/`, `data/`) and served as',
'`application/octet-stream`; the worker never interprets what it hands back. Reads',
'are unauthenticated — a caller needs the exact key, which only comes from an',
'authenticated call to another worker.',
+16
View File
@@ -101,6 +101,21 @@ describe('cdn endpoints', () => {
expect(res.status).toBe(404)
})
test('GET /data/:id streams the data blob from R2', async () => {
// Date-foldered — the name the storage worker generates for a FileType 2 upload.
const name = '2026-08-05/3b9c1f0a-5d2e-4c1b-9a77-2e6f0b4d8c31'
await env.CDN_ASSETS.put(`data/${name}`, new Uint8Array([4, 5, 6]))
const res = await exports.default.fetch(`${ORIGIN}/data/${name}`)
expect(res.status).toBe(200)
expect(res.headers.get('content-type')).toBe('application/octet-stream')
expect(new Uint8Array(await res.arrayBuffer())).toEqual(new Uint8Array([4, 5, 6]))
})
test('GET /data/:id 404s when the blob is absent', async () => {
const res = await exports.default.fetch(`${ORIGIN}/data/missing`)
expect(res.status).toBe(404)
})
test('GET /openapi.json documents every route', async () => {
const res = await exports.default.fetch(`${ORIGIN}/openapi.json`)
expect(res.status).toBe(200)
@@ -124,6 +139,7 @@ describe('cdn endpoints', () => {
expect([...documented].sort()).toEqual([
'GET /',
'GET /config/LoadingScreenTipData',
'GET /data/{id}',
'GET /invention/{dataBlob}',
'GET /room/{dataBlob}',
'GET /sigs/{sigName}',