Spaces:
Paused
Paused
File size: 4,336 Bytes
2b43822 e3cb794 2b43822 58ad246 2b43822 58ad246 2b43822 58ad246 35fc662 58ad246 2b43822 d32b1fb 2b43822 e3cb794 2b43822 e3cb794 34a23a6 2b43822 9369ecd 2b43822 58ad246 2b43822 e3cb794 2b43822 58ad246 35fc662 58ad246 35fc662 58ad246 e3cb794 2b43822 58ad246 e3cb794 2b43822 307d8b2 2b43822 58ad246 d32b1fb 9dffdbf 35fc662 2b43822 9dffdbf 35fc662 2b43822 58ad246 e3cb794 2b43822 d32b1fb 2b43822 58ad246 d32b1fb 2b43822 58ad246 2b43822 58ad246 2b43822 58ad246 2b43822 58ad246 2b43822 35fc662 2b43822 e1a2bcd 58ad246 e1a2bcd 2b43822 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
import { ZodSchema } from "zod";
import { generateSessionToken } from "../helpers";
import { Room } from "./room";
import { WebSocketInterface } from "./WebSocketAdapter";
type RouteHandler<T> = (context: Context<T>) => void;
export interface Route<T> {
// Name of route
name: string;
// ZodSchema used to validate request body
schema: ZodSchema<T>;
// Handler Function. To be called after request body is validated
handler: RouteHandler<T>;
}
export class User {
static userIdLength: number = 32;
id: string;
username?: string;
socket: WebSocketInterface;
currentRoom?: Room;
constructor(socket: WebSocketInterface) {
this.id = generateSessionToken(32);
this.socket = socket;
this.username = "Unknown";
}
}
// Context holds all available information for any given request
export interface Context<T> {
ws: WebSocketInterface;
user: User;
payload: T;
server: HapticLinkServer;
}
export class HapticLinkServer {
// Router map
routes: { [key: string]: Route<any> };
// All rooms are stored in this map
rooms: { [key: string]: Room };
// All users are stored in this map and should be dropped when their connection does.
users: Map<WebSocketInterface, User>;
constructor() {
this.routes = {};
this.users = new Map();
this.rooms = {};
}
/**
* Removes user from registered users
* @param {WebSocketInterface} ws
* @returns {boolean} true/false whether the user was successfully removed
*/
removeUser(ws: WebSocketInterface): boolean {
const user = this.users.get(ws);
if (!user) return false;
if (user.currentRoom) {
user.currentRoom.removeUserById(user.id);
}
return true;
}
/**
* Registers a route with router
* @param {string} name The name of the route which be used by the client to identify the route.
* @param {ZodSchema<T>} schema A ZodSchema to match the body of the WS message
* @param {RouteHandler<T>} handler A handler function that should have a matching schema
* @returns
*/
addRoute<T>(name: string, schema: ZodSchema<T>, handler: RouteHandler<T>): boolean {
if (name in this.routes) {
return false;
}
this.routes[name] = {
name,
schema,
handler,
};
return true;
}
/**
* Compares message param with names of registered routes
* If route exists, validates the schema and then calls
* the handler function with the formatted payload
* @param {WebSocketInterface} ws
* @param {string} message WebSocket message
* @returns
*/
handleRoute(ws: WebSocketInterface, message: string) {
// Parse JSON
let payload: any;
try {
payload = JSON.parse(message);
} catch (e) {
return ws.send(JSON.stringify({ error: "message not in JSON format" }));
}
// Check if message includes route
if (typeof payload != "object" || !Object.keys(payload).includes("route")) {
return ws.send(JSON.stringify({ error: "missing route" }));
}
// Check if route is registered
if (!(payload.route in this.routes)) {
return ws.send(JSON.stringify({ error: "route not found" }));
}
const route = this.routes[payload.route];
// Removes route from body
delete payload.route;
let user: User;
// Get user or create new one
if (this.users.has(ws)) {
user = this.users.get(ws)!;
} else {
const newUser = new User(ws);
this.users.set(ws, newUser);
user = newUser;
}
// Create context
let context: Context<any> = {
ws,
payload,
server: this,
user,
};
if (!route) {
return ws.send(JSON.stringify({ error: "invalid route" }));
}
// Validates message body and calls handler
if (route.schema.safeParse(payload).success) {
route.handler(context);
} else {
return ws.send(JSON.stringify({ error: "invalid payload format" }));
}
}
}
|