import { Static, TSchema } from "@sinclair/typebox"; import { Value } from "@sinclair/typebox/value"; import { FastifyInstance, FastifyRequest, HTTPMethods } from "fastify" import { RouteOptions } from "fastify/types/route.js"; import { type WebSocket } from "@fastify/websocket"; type WebsocketConnection = Parameters>[0]; type URLString = string; export type FirRouteInput = { payload: Static, } export type FirWebsocketInput = { socket: WebsocketConnection, req: FastifyRequest, payload: Static, } export type FirWebsocketHandler = { onMessage?(input: FirWebsocketInput): void, onOpen?(input: {socket: WebSocket, req: FastifyRequest}): void, onClose?(input: {socket: WebSocket, req: FastifyRequest}): void, onError?(input: {socket: WebSocket, req: FastifyRequest, error: unknown}): void, }; export type FirRouteOptions = { method: HTTPMethods, url: URLString, payloadT: TIn, responseT?: TOut, } & ({ handler: (input: FirRouteInput) => Static | Promise>, } | { websocket: FirWebsocketHandler, }) type Defined = T extends undefined ? never : T; export const attachRoute = (server: FastifyInstance, routeOptions: FirRouteOptions) => { const { method, url, payloadT, } = routeOptions; if ("websocket" in routeOptions) { console.log('SETTING UP WS'); const {websocket} = routeOptions; server.register(async function(fastify: FastifyInstance) { fastify.get('/api/ws/room', { websocket: true }, (socket: WebSocket, req: FastifyRequest) => { websocket.onOpen && websocket.onOpen({socket, req}); socket.on('message', (message: any) => { const payload = JSON.parse(message.toString()); if (Value.Check(payloadT, payload)) { websocket.onMessage && websocket.onMessage({socket, payload, req}); } else { throw new Error("Payload wrong shape."); } }); socket.on('close', () => { websocket.onClose && websocket.onClose({socket, req}); }); socket.on('error', (error: any) => { websocket.onError && websocket.onError({socket, error, req}); }); }) }); return; // const {websocket} = routeOptions; // const augmentedWsHandler = (conn: Parameters>[0]) => { // console.log('HELLO'); // conn.on("message", (message) => { // const payload = JSON.parse(message.toString()); // if (Value.Check(payloadT, payload)) { // websocket({socket: conn, payload}); // } else { // throw new Error("Payload wrong shape."); // } // }); // } // return { // method: 'GET', // WebSocket upgrades are GET requests // url, // // websocket: true, // wsHandler: augmentedWsHandler, // handler: (...args) => { // console.log('socket!'); // const socket = args[0].socket.on("message", () => { // console.log("connected!"); // }) // }, // // handler: (request, reply) => { // // reply.code(405).send({ message: 'Method Not Allowed' }); // Handle non-WebSocket requests // // } // } } const {handler} = routeOptions; const augmentedHandler = (request: Parameters[0]) => { const { body, query, } = request; const payload = body ?? query; if (Value.Check(payloadT, payload)) { return handler({payload}); } else { throw new Error("Payload wrong shape."); } } server.route({ method, url, handler: augmentedHandler, }); }