import { type } from "arktype"; import { readDropValidatedBody, throwingArktype } from "~/server/arktype"; import contextManager from "~/server/internal/downloads/coordinator"; import libraryManager from "~/server/internal/library"; import { logger } from "~/server/internal/logging"; const GetChunk = type({ context: "string", files: type({ filename: "string", chunkIndex: "number", }) .array() .atLeastLength(1) .atMostLength(256), }).configure(throwingArktype); export default defineEventHandler(async (h3) => { const body = await readDropValidatedBody(h3, GetChunk); const context = await contextManager.fetchContext(body.context); if (!context) throw createError({ statusCode: 400, statusMessage: "Invalid download context.", }); const streamFiles = []; for (const file of body.files) { const manifestFile = context.manifest[file.filename]; if (!manifestFile) throw createError({ statusCode: 400, statusMessage: `Unknown file: ${file.filename}`, }); const start = manifestFile.lengths .slice(0, file.chunkIndex) .reduce((a, b) => a + b, 0); const end = start + manifestFile.lengths[file.chunkIndex]; streamFiles.push({ filename: file.filename, start, end }); } setHeader( h3, "Content-Lengths", streamFiles.map((e) => e.end - e.start).join(","), ); // Non-standard header, but we're cool like that 😎 const streams = await Promise.all( streamFiles.map(async (file) => { const gameReadStream = await libraryManager.readFile( context.libraryId, context.libraryPath, context.versionName, file.filename, { start: file.start, end: file.end }, ); if (!gameReadStream) throw createError({ statusCode: 500, statusMessage: "Failed to create read stream", }); return { ...file, stream: gameReadStream }; }), ); for (const file of streams) { let length = 0; await file.stream.pipeTo( new WritableStream({ write(chunk) { h3.node.res.write(chunk); length += chunk.length; }, }), ); if (length != file.end - file.start) { logger.warn( `failed to read enough from ${file.filename}. read ${length}, required: ${file.end - file.start}`, ); throw createError({ statusCode: 500, statusMessage: "Failed to read enough from stream.", }); } } await h3.node.res.end(); return; });