From 824b4e708bbe63efce34e85cdacb52db8a0297df Mon Sep 17 00:00:00 2001 From: DecDuck Date: Sun, 10 Aug 2025 11:36:10 +1000 Subject: [PATCH] fix: client route type hacking --- server/api/v2/client/chunk.post.ts | 1 + server/api/v2/client/context.post.ts | 24 ++++++------- server/internal/clients/event-handler.ts | 45 +++++++++++++++--------- 3 files changed, 40 insertions(+), 30 deletions(-) diff --git a/server/api/v2/client/chunk.post.ts b/server/api/v2/client/chunk.post.ts index 7589367..0fa11df 100644 --- a/server/api/v2/client/chunk.post.ts +++ b/server/api/v2/client/chunk.post.ts @@ -15,6 +15,7 @@ const GetChunk = type({ * Part of v2 download API. Intended to be client-only. * * Returns raw stream of all files requested, in order. + * @response `application/octet-stream` stream of all files concatenated */ export default defineEventHandler<{ body: typeof GetChunk.infer }>( async (h3) => { diff --git a/server/api/v2/client/context.post.ts b/server/api/v2/client/context.post.ts index c1f5c84..25473c5 100644 --- a/server/api/v2/client/context.post.ts +++ b/server/api/v2/client/context.post.ts @@ -1,5 +1,5 @@ import { type } from "arktype"; -import { readDropValidatedBody, throwingArktype } from "~/server/arktype"; +import { throwingArktype } from "~/server/arktype"; import { defineClientEventHandler } from "~/server/internal/clients/event-handler"; import contextManager from "~/server/internal/downloads/coordinator"; @@ -11,17 +11,13 @@ const CreateContext = type({ /** * Part of v2 download API. Create a download context for use with `/api/v2/client/chunk`. */ -export default defineClientEventHandler<{ body: typeof CreateContext.infer }>( - async (h3) => { - const body = await readDropValidatedBody(h3, CreateContext); +export default defineClientEventHandler(async (h3, { body }) => { + const context = await contextManager.createContext(body.game, body.version); + if (!context) + throw createError({ + statusCode: 400, + statusMessage: "Invalid game or version", + }); - const context = await contextManager.createContext(body.game, body.version); - if (!context) - throw createError({ - statusCode: 400, - statusMessage: "Invalid game or version", - }); - - return { context }; - }, -); + return { context }; +}, CreateContext); diff --git a/server/internal/clients/event-handler.ts b/server/internal/clients/event-handler.ts index f9a8800..896c2ec 100644 --- a/server/internal/clients/event-handler.ts +++ b/server/internal/clients/event-handler.ts @@ -1,15 +1,14 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import type { ClientModel, UserModel } from "~/prisma/client/models"; -import type { EventHandlerRequest, EventHandlerResponse, H3Event } from "h3"; +import type { EventHandlerResolver, EventHandlerResponse, H3Event } from "h3"; import droplet from "@drop-oss/droplet"; import prisma from "../db/database"; import { useCertificateAuthority } from "~/server/plugins/ca"; +import type { Type } from "arktype"; +import { readDropValidatedBody } from "~/server/arktype"; -export type EventHandlerFunction = ( - h3: H3Event, - utils: ClientUtils, -) => Promise | T; - -type ClientUtils = { +type ClientUtils = { + body: R; clientId: string; fetchClient: () => Promise; fetchUser: () => Promise; @@ -17,17 +16,26 @@ type ClientUtils = { const NONCE_LENIENCE = 30_000; -interface ClientHandler< - R extends EventHandlerRequest = EventHandlerRequest, - K extends EventHandlerResponse = EventHandlerResponse, +type ClientEventHandlerRequest = { + body: T; + query: { [key: string]: string | string[] }; +}; + +interface ClientEventHandler< + R = any, + Request extends ClientEventHandlerRequest = ClientEventHandlerRequest, + Response extends EventHandlerResponse = EventHandlerResponse, > { - (event: H3Event, utils: ClientUtils): K; + __is_handler__?: true; + __resolve__?: EventHandlerResolver; + (event: H3Event, utils: ClientUtils): Response; } export function defineClientEventHandler< - R extends EventHandlerRequest = EventHandlerRequest, - K = EventHandlerResponse, ->(handler: ClientHandler) { + R = any, + T extends ClientEventHandlerRequest = ClientEventHandlerRequest, + K extends EventHandlerResponse = EventHandlerResponse, +>(handler: ClientEventHandler, validator?: Type) { return defineEventHandler(async (h3) => { const header = getHeader(h3, "Authorization"); if (!header) throw createError({ statusCode: 403 }); @@ -128,10 +136,11 @@ export function defineClientEventHandler< return client.user; } - const utils: ClientUtils = { + const utils: ClientUtils = { clientId, fetchClient, fetchUser, + body: undefined, }; await prisma.client.update({ @@ -139,6 +148,10 @@ export function defineClientEventHandler< data: { lastConnected: new Date() }, }); - return await handler(h3, utils); + const body = validator + ? await readDropValidatedBody(h3, validator) + : undefined; + + return await handler(h3, { ...utils, body: body as R }); }); }