import {Session} from "express-session"; import {IncomingMessage} from "http"; import WebSocket from "ws"; import Application from "../../Application.js"; import SessionWebSocketListener from "../../SessionWebSocketListener.js"; import MagicLink from "../models/MagicLink.js"; export default class MagicLinkWebSocketListener extends SessionWebSocketListener { private readonly connections: { [p: string]: (() => void)[] | undefined } = {}; public refreshMagicLink(sessionId: string): void { const fs = this.connections[sessionId]; if (fs) { fs.forEach(f => f()); } } public async handleSessionSocket(socket: WebSocket, request: IncomingMessage, session: Session): Promise { // Refuse any incoming data socket.on('message', () => { socket.close(1003); }); // Get magic link const magicLink = await MagicLink.select() .where('session_id', session.id) .sortBy('authorized') .first(); // Refresh if immediately applicable if (!magicLink || !await magicLink.isValid() || await magicLink.isAuthorized()) { socket.send('refresh'); const reason = magicLink ? 'Magic link state changed.' : 'Magic link not found for session ' + session.id; socket.close(1000, reason); return; } const validityTimeout = setTimeout(() => { socket.send('refresh'); socket.close(1000, 'Timed out'); }, magicLink.getExpirationDate().getTime() - new Date().getTime()); const f = () => { clearTimeout(validityTimeout); socket.send('refresh'); socket.close(1000, 'Closed by server'); }; socket.on('close', () => { const connections = this.connections[session.id]; if (connections) { this.connections[session.id] = connections.filter(f => f !== f); if (connections.length === 0) delete this.connections[session.id]; } }); let connections = this.connections[session.id]; if (!connections) connections = this.connections[session.id] = []; connections.push(f); } public path(): string { return '/magic-link'; } }