121 lines
3.7 KiB
TypeScript
121 lines
3.7 KiB
TypeScript
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<Defined<RouteOptions["wsHandler"]>>[0];
|
|
|
|
type URLString = string;
|
|
|
|
export type FirRouteInput<TPayloadSchema extends TSchema> = {
|
|
payload: Static<TPayloadSchema>,
|
|
}
|
|
|
|
export type FirWebsocketInput<TPayloadSchema extends TSchema> = {
|
|
socket: WebsocketConnection,
|
|
req: FastifyRequest,
|
|
payload: Static<TPayloadSchema>,
|
|
}
|
|
|
|
export type FirWebsocketHandler<TIn extends TSchema = TSchema> = {
|
|
onMessage?(input: FirWebsocketInput<TIn>): 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<TIn extends TSchema = TSchema, TOut extends TSchema = TSchema> = {
|
|
method: HTTPMethods,
|
|
url: URLString,
|
|
payloadT: TIn,
|
|
responseT?: TOut,
|
|
} & ({
|
|
handler: (input: FirRouteInput<TIn>) => Static<TOut> | Promise<Static<TOut>>,
|
|
} | {
|
|
websocket: FirWebsocketHandler<TIn>,
|
|
})
|
|
|
|
type Defined<T> = T extends undefined ? never : T;
|
|
|
|
export const attachRoute = <TIn extends TSchema, TOut extends TSchema>(server: FastifyInstance, routeOptions: FirRouteOptions<TIn, TOut>) => {
|
|
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<Defined<RouteOptions["wsHandler"]>>[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<RouteOptions["handler"]>[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,
|
|
});
|
|
} |