Files
evening_detective_screen/electron/package-media.ts
T
2026-10-17 19:08:09 +07:00

45 lines
1.8 KiB
TypeScript

import { createReadStream } from 'node:fs'
import { Readable } from 'node:stream'
import type { PackageStore } from './package-store.js'
import { parseRange } from './range.js'
export async function packageMediaResponse(
store: PackageStore,
request: Request,
): Promise<Response> {
const url = new URL(request.url)
const match = /^\/packages\/([a-f0-9]{64})\/([a-f0-9]{64})$/.exec(url.pathname)
if (url.host !== 'app' || request.method !== 'GET' || !match)
return new Response(null, { status: 404 })
try {
const file = await store.localFile(match[1]!, match[2]!)
const rangeHeader = request.headers.get('range')
const range = parseRange(rangeHeader, file.size)
const headers: Record<string, string> = {
'Content-Type': file.mime,
'Accept-Ranges': 'bytes',
'Cache-Control': 'no-store',
'X-Content-Type-Options': 'nosniff',
'Content-Security-Policy': "default-src 'none'; frame-ancestors 'none'; sandbox",
}
if (!range)
return new Response(null, {
status: 416,
headers: { ...headers, 'Content-Range': `bytes */${file.size}` },
})
headers['Content-Length'] = String(range.end - range.start + 1)
if (rangeHeader) headers['Content-Range'] = `bytes ${range.start}-${range.end}/${file.size}`
const stream = createReadStream(file.path, range)
const abort = () => stream.destroy()
request.signal.addEventListener('abort', abort, { once: true })
stream.on('close', () => request.signal.removeEventListener('abort', abort))
if (request.signal.aborted) stream.destroy()
return new Response(Readable.toWeb(stream) as ReadableStream<Uint8Array>, {
status: rangeHeader ? 206 : 200,
headers,
})
} catch {
return new Response(null, { status: 404 })
}
}