UNPKG

@hathora/server-sdk

Version:

![Hathora Logo](https://user-images.githubusercontent.com/7004280/223056895-c16419d2-2b91-4013-82f0-7616c84d31b7.svg)

102 lines (93 loc) 3.42 kB
import uWS from "uWebSockets.js"; import jwt from "jsonwebtoken"; export type RoomId = string; export type UserId = string; export interface Application { verifyToken(token: string, roomId: RoomId): Promise<UserId | undefined>; subscribeUser(roomId: RoomId, userId: UserId): Promise<void>; unsubscribeUser(roomId: RoomId, userId: UserId): Promise<void>; onMessage(roomId: RoomId, userId: UserId, data: ArrayBuffer): Promise<void>; } export interface Server { broadcastMessage(roomId: RoomId, data: ArrayBuffer): void; sendMessage(roomId: RoomId, userId: UserId, data: ArrayBuffer): void; closeConnection(roomId: RoomId, userId: UserId, error: string): void; } type ConnectionData = { roomId: RoomId; userId: UserId; }; const socketsMap: Map<string, uWS.WebSocket<ConnectionData>> = new Map(); export function startServer(app: Application, port: number): Promise<Server> { return new Promise((resolve, reject) => { const server = uWS .App() .ws<ConnectionData>("/:roomId", { upgrade: async (res, req, context) => { const roomId = req.getParameter(0); const queryParts = req.getQuery().split("token="); if (queryParts.length !== 2) { res.writeStatus("401").end(); return; } const token = queryParts[1]; const userId = await app.verifyToken(token, roomId); if (userId === undefined) { res.writeStatus("401").end(); return; } if (socketsMap.has(roomId + userId)) { res.writeStatus("400").end(); return; } res.upgrade( { roomId, userId }, req.getHeader("sec-websocket-key"), req.getHeader("sec-websocket-protocol"), req.getHeader("sec-websocket-extensions"), context ); }, open: (ws) => { const { roomId, userId } = ws.getUserData(); ws.subscribe(roomId); socketsMap.set(roomId + userId, ws); app.subscribeUser(roomId, userId); }, message: (ws, message) => { const { roomId, userId } = ws.getUserData(); app.onMessage(roomId, userId, message); }, close: (ws) => { const { roomId, userId } = ws.getUserData(); socketsMap.delete(roomId + userId); app.unsubscribeUser(roomId, userId); }, }) .listen(port, (listenSocket) => { if (listenSocket) { resolve({ sendMessage: (roomId: RoomId, userId: UserId, data: ArrayBuffer) => { socketsMap.get(roomId + userId)?.send(data, true); }, broadcastMessage: (roomId: RoomId, data: ArrayBuffer) => { server.publish(roomId, data, true); }, closeConnection: (roomId: RoomId, userId: UserId, error: string) => { socketsMap.get(roomId + userId)?.end(4000, error); }, }); } else { reject(`Server failed to start, is the port ${port} already in use?`); } }); }); } export function verifyJwt(token: string, secret: string, userIdField: string = "id"): UserId | undefined { try { const payload = jwt.verify(token, secret); if (typeof payload === "object" && typeof payload.id === "string") { return payload[userIdField]; } } catch (e) {} }