45 lines
1.8 KiB
TypeScript
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 })
|
|
}
|
|
}
|