/** * User websocket controller * * @author Pierre Hubert */ import * as ws from 'ws'; import { Request } from 'express'; import { RequestHandler } from '../entities/RequestHandler'; import { time } from '../utils/DateUtils'; import { randomStr } from '../utils/CryptUtils'; import { EventsHelper } from '../helpers/EventsHelper'; interface PendingRequests { time: number, clientID: number, userID: number, token: string } interface ActiveClient { clientID: number, userID: number, ws: ws } // Tokens are valid only 10 seconds after they are generated const TOKENS_DURATION = 10 const TOKEN_LENGTH = 20 export class UserWebSocketController { /** * The list of pending connections */ static pending_list: PendingRequests[] = [] /** * The list of active clients */ static active_clients: ActiveClient[] = [] /** * Clean the list of tokens */ private static CleanList() { // Clean the list this.pending_list = this.pending_list .filter((l) => l.time + TOKENS_DURATION + 1 > time()) } /** * Get a websocket access token * * @param h Request handler */ public static async GetToken(h: RequestHandler) { this.CleanList(); // Generate a temporary token const token = randomStr(TOKEN_LENGTH); // Add the token to the list this.pending_list.push({ time: time(), clientID: h.getClientInfo().id, userID: h.getUserId(), token: token }); h.send({ token: token }); } /** * Handler user websocket request * * @param req Associated request * @param ws The socket */ public static async UserWS(req: Request, ws: ws) { this.CleanList(); // First, check for token if(!req.query.hasOwnProperty("token") || String(req.query.token).length != TOKEN_LENGTH) { ws.send("Missing token!"); ws.close(); return; } // Search appropriate connection const token = req.query.token; const entryIndex = this.pending_list.findIndex((el) => el.token == token); if(entryIndex == -1) { ws.send("Invalid token!"); ws.close(); return; } // Remove the entry from the array const entry = this.pending_list[entryIndex]; this.pending_list.splice(entryIndex, 1); // Add the client to the list of active clients const client: ActiveClient = { clientID: entry.clientID, userID: entry.userID, ws: ws } this.active_clients.push(client); // Remove the client for the list as soon as the // socket is closed ws.addEventListener("close", () => { this.active_clients.splice(this.active_clients.indexOf(client), 1); }) // Handles error ws.addEventListener("error", (e) => { if(ws.readyState == ws.OPEN) ws.close(); console.log("WebSocket error", e) }) // Handles incoming messages ws.addEventListener("message", (msg) => { // Only accept text messages if(msg.type != "message") { console.error("Received a non-text messsage through a WebSocket !") ws.close(); return; } }) } /** * Close a specific user websocket * * @param clientID Target client ID * @param userID Target user ID */ public static async CloseClientSockets(clientID: number, userID: number) { for(const entry of this.active_clients.filter((f) => f.clientID == clientID && f.userID == userID)) entry.ws.close(); } } // When user sign out EventsHelper.Listen("destroyed_login_tokens", (e) => UserWebSocketController.CloseClientSockets(e.client.id, e.userID));