Spaces:
Runtime error
Runtime error
Added websocket to wrapper for easier testing
Browse files
server/src/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import * as http from "http";
|
|
| 3 |
import * as WebSocket from "ws";
|
| 4 |
import { HapticLinkServer } from "./socket/hapticLinkServer";
|
| 5 |
import { registerRoutes } from "./socket/routes";
|
|
|
|
| 6 |
|
| 7 |
const port: number = parseInt(process.env.PORT as string, 10) || 3000;
|
| 8 |
|
|
@@ -24,18 +25,19 @@ registerRoutes(hapticLink);
|
|
| 24 |
// data for later requests such as an ID
|
| 25 |
wss.on("connection", (ws: WebSocket) => {
|
| 26 |
console.log("Client Connected");
|
|
|
|
| 27 |
|
| 28 |
ws.on("message", (message: string) => {
|
| 29 |
console.log(`Received Message: ${message}`);
|
| 30 |
-
hapticLink.handleRoute(
|
| 31 |
});
|
| 32 |
|
| 33 |
// When a user disconnects, their account is removed, and they are removed from all groups
|
| 34 |
ws.on("close", () => {
|
| 35 |
-
hapticLink.removeUser(
|
| 36 |
})
|
| 37 |
ws.on("error", () => {
|
| 38 |
-
hapticLink.removeUser(
|
| 39 |
})
|
| 40 |
|
| 41 |
ws.send("Welcome");
|
|
|
|
| 3 |
import * as WebSocket from "ws";
|
| 4 |
import { HapticLinkServer } from "./socket/hapticLinkServer";
|
| 5 |
import { registerRoutes } from "./socket/routes";
|
| 6 |
+
import WebSocketWrapper from "./socket/WebSocketAdapter";
|
| 7 |
|
| 8 |
const port: number = parseInt(process.env.PORT as string, 10) || 3000;
|
| 9 |
|
|
|
|
| 25 |
// data for later requests such as an ID
|
| 26 |
wss.on("connection", (ws: WebSocket) => {
|
| 27 |
console.log("Client Connected");
|
| 28 |
+
const wsw = new WebSocketWrapper(ws);
|
| 29 |
|
| 30 |
ws.on("message", (message: string) => {
|
| 31 |
console.log(`Received Message: ${message}`);
|
| 32 |
+
hapticLink.handleRoute(wsw, message);
|
| 33 |
});
|
| 34 |
|
| 35 |
// When a user disconnects, their account is removed, and they are removed from all groups
|
| 36 |
ws.on("close", () => {
|
| 37 |
+
hapticLink.removeUser(wsw);
|
| 38 |
})
|
| 39 |
ws.on("error", () => {
|
| 40 |
+
hapticLink.removeUser(wsw);
|
| 41 |
})
|
| 42 |
|
| 43 |
ws.send("Welcome");
|
server/src/socket/WebSocketAdapter.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { WebSocket } from "ws";
|
| 2 |
+
|
| 3 |
+
export default class WebSocketWrapper {
|
| 4 |
+
ws: WebSocket
|
| 5 |
+
|
| 6 |
+
constructor(ws: WebSocket) {
|
| 7 |
+
this.ws = ws;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
send(data: string | Buffer) {
|
| 11 |
+
this.ws.send(data);
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
get OPEN() {
|
| 15 |
+
return this.ws.OPEN;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
get CLOSED() {
|
| 19 |
+
return this.ws.CLOSED;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
get CLOSING() {
|
| 23 |
+
return this.ws.CLOSING;
|
| 24 |
+
}
|
| 25 |
+
}
|
server/src/socket/hapticLinkServer.ts
CHANGED
|
@@ -1,39 +1,39 @@
|
|
| 1 |
-
import * as WebSocket from "ws";
|
| 2 |
import { ZodSchema } from "zod";
|
| 3 |
import { generateSessionToken } from "../helpers";
|
| 4 |
import { Room } from "./room";
|
|
|
|
| 5 |
|
| 6 |
type RouteHandler<T> = (context: Context<T>) => void;
|
| 7 |
|
| 8 |
export interface Route<T> {
|
| 9 |
name: string;
|
| 10 |
-
schema: ZodSchema<T
|
| 11 |
handler: RouteHandler<T>;
|
| 12 |
}
|
| 13 |
|
| 14 |
export class User {
|
| 15 |
id: string;
|
| 16 |
username?: string;
|
| 17 |
-
socket:
|
| 18 |
currentRoom?: Room;
|
| 19 |
|
| 20 |
-
constructor(socket:
|
| 21 |
this.id = generateSessionToken(16);
|
| 22 |
this.socket = socket;
|
| 23 |
}
|
| 24 |
}
|
| 25 |
|
| 26 |
export interface Context<T> {
|
| 27 |
-
ws:
|
| 28 |
user: User;
|
| 29 |
payload: T;
|
| 30 |
server: HapticLinkServer;
|
| 31 |
}
|
| 32 |
|
| 33 |
export class HapticLinkServer {
|
| 34 |
-
routes: { [key: string]: Route<any> }
|
| 35 |
-
rooms: { [key: string]: Room }
|
| 36 |
-
users: Map<
|
| 37 |
|
| 38 |
constructor() {
|
| 39 |
this.routes = {};
|
|
@@ -41,7 +41,7 @@ export class HapticLinkServer {
|
|
| 41 |
this.rooms = {};
|
| 42 |
}
|
| 43 |
|
| 44 |
-
removeUser(ws:
|
| 45 |
const user = this.users.get(ws);
|
| 46 |
if (!user) return false;
|
| 47 |
if (user.currentRoom) {
|
|
@@ -50,30 +50,39 @@ export class HapticLinkServer {
|
|
| 50 |
return true;
|
| 51 |
}
|
| 52 |
|
| 53 |
-
addRoute<T>(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
if (name in this.routes) {
|
| 55 |
-
return false
|
| 56 |
}
|
| 57 |
|
| 58 |
this.routes[name] = {
|
| 59 |
name,
|
| 60 |
schema,
|
| 61 |
-
handler
|
| 62 |
-
}
|
| 63 |
|
| 64 |
return true;
|
| 65 |
}
|
| 66 |
|
| 67 |
-
handleRoute(ws:
|
| 68 |
// Parse JSON
|
| 69 |
let payload: any;
|
| 70 |
try {
|
| 71 |
payload = JSON.parse(message);
|
| 72 |
} catch (e) {
|
| 73 |
-
return ws.send(
|
|
|
|
|
|
|
| 74 |
}
|
| 75 |
|
| 76 |
-
if (
|
|
|
|
|
|
|
|
|
|
| 77 |
return ws.send(JSON.stringify({ error: "missing route" }));
|
| 78 |
}
|
| 79 |
|
|
@@ -81,7 +90,6 @@ export class HapticLinkServer {
|
|
| 81 |
return ws.send(JSON.stringify({ error: "route not found" }));
|
| 82 |
}
|
| 83 |
|
| 84 |
-
|
| 85 |
const route = this.routes[payload.route];
|
| 86 |
delete payload.route;
|
| 87 |
|
|
@@ -99,8 +107,8 @@ export class HapticLinkServer {
|
|
| 99 |
ws,
|
| 100 |
payload,
|
| 101 |
server: this,
|
| 102 |
-
user
|
| 103 |
-
}
|
| 104 |
|
| 105 |
if (route && route.schema.safeParse(payload).success) {
|
| 106 |
route.handler(context);
|
|
|
|
|
|
|
| 1 |
import { ZodSchema } from "zod";
|
| 2 |
import { generateSessionToken } from "../helpers";
|
| 3 |
import { Room } from "./room";
|
| 4 |
+
import WebSocketWrapper from "./WebSocketAdapter";
|
| 5 |
|
| 6 |
type RouteHandler<T> = (context: Context<T>) => void;
|
| 7 |
|
| 8 |
export interface Route<T> {
|
| 9 |
name: string;
|
| 10 |
+
schema: ZodSchema<T>;
|
| 11 |
handler: RouteHandler<T>;
|
| 12 |
}
|
| 13 |
|
| 14 |
export class User {
|
| 15 |
id: string;
|
| 16 |
username?: string;
|
| 17 |
+
socket: WebSocketWrapper;
|
| 18 |
currentRoom?: Room;
|
| 19 |
|
| 20 |
+
constructor(socket: WebSocketWrapper) {
|
| 21 |
this.id = generateSessionToken(16);
|
| 22 |
this.socket = socket;
|
| 23 |
}
|
| 24 |
}
|
| 25 |
|
| 26 |
export interface Context<T> {
|
| 27 |
+
ws: WebSocketWrapper;
|
| 28 |
user: User;
|
| 29 |
payload: T;
|
| 30 |
server: HapticLinkServer;
|
| 31 |
}
|
| 32 |
|
| 33 |
export class HapticLinkServer {
|
| 34 |
+
routes: { [key: string]: Route<any> };
|
| 35 |
+
rooms: { [key: string]: Room };
|
| 36 |
+
users: Map<WebSocketWrapper, User>;
|
| 37 |
|
| 38 |
constructor() {
|
| 39 |
this.routes = {};
|
|
|
|
| 41 |
this.rooms = {};
|
| 42 |
}
|
| 43 |
|
| 44 |
+
removeUser(ws: WebSocketWrapper): boolean {
|
| 45 |
const user = this.users.get(ws);
|
| 46 |
if (!user) return false;
|
| 47 |
if (user.currentRoom) {
|
|
|
|
| 50 |
return true;
|
| 51 |
}
|
| 52 |
|
| 53 |
+
addRoute<T>(
|
| 54 |
+
name: string,
|
| 55 |
+
schema: ZodSchema<T>,
|
| 56 |
+
handler: RouteHandler<T>
|
| 57 |
+
): boolean {
|
| 58 |
if (name in this.routes) {
|
| 59 |
+
return false;
|
| 60 |
}
|
| 61 |
|
| 62 |
this.routes[name] = {
|
| 63 |
name,
|
| 64 |
schema,
|
| 65 |
+
handler,
|
| 66 |
+
};
|
| 67 |
|
| 68 |
return true;
|
| 69 |
}
|
| 70 |
|
| 71 |
+
handleRoute(ws: WebSocketWrapper, message: string) {
|
| 72 |
// Parse JSON
|
| 73 |
let payload: any;
|
| 74 |
try {
|
| 75 |
payload = JSON.parse(message);
|
| 76 |
} catch (e) {
|
| 77 |
+
return ws.send(
|
| 78 |
+
JSON.stringify({ error: "message not in JSON format" })
|
| 79 |
+
);
|
| 80 |
}
|
| 81 |
|
| 82 |
+
if (
|
| 83 |
+
typeof payload != "object" ||
|
| 84 |
+
!Object.keys(payload).includes("route")
|
| 85 |
+
) {
|
| 86 |
return ws.send(JSON.stringify({ error: "missing route" }));
|
| 87 |
}
|
| 88 |
|
|
|
|
| 90 |
return ws.send(JSON.stringify({ error: "route not found" }));
|
| 91 |
}
|
| 92 |
|
|
|
|
| 93 |
const route = this.routes[payload.route];
|
| 94 |
delete payload.route;
|
| 95 |
|
|
|
|
| 107 |
ws,
|
| 108 |
payload,
|
| 109 |
server: this,
|
| 110 |
+
user,
|
| 111 |
+
};
|
| 112 |
|
| 113 |
if (route && route.schema.safeParse(payload).success) {
|
| 114 |
route.handler(context);
|