Retrieve a socket by their token/account_id
[browser-rts.git] / src / lib / server.ts
1 import express, { Request, Response } from 'express';
2 import { join } from 'path';
3 import { isString } from 'lodash';
4 import { merge } from 'lodash';
5 import bodyParser from 'body-parser';
6 import { ExpressAdapter } from '@bull-board/express';
7 import http from 'http';
8 import { Server, Socket } from 'socket.io';
9
10 type AuthInfo = {
11         authInfo: { 
12                 accountId: string;
13                 token: string;
14         }
15 };
16
17 export type HttpHandler<I, O> = (params: I & AuthInfo, rawReq: Request, rawRes: Response) => Promise<O>;
18
19 export class HttpServer {
20         server: express.Application;
21   http: http.Server;
22   ws: Server;
23         port: string | number;
24         bullAdapter: ExpressAdapter;
25         constructor(port: string | number) {
26                 this.port = port;
27     this.server = express();
28                 this.bullAdapter = new ExpressAdapter()
29                 this.configureMiddleWare();
30     this.http = http.createServer(this.server);
31     this.ws = new Server(this.http);
32         }
33
34         configureMiddleWare() {
35                 this.server.use(express.json());
36                 this.server.use(bodyParser());
37                 this.server.use(express.static(join(__dirname, '..', '..', 'public')));
38                 
39                 this.bullAdapter.setBasePath('/admin/queues');
40                 this.server.use('/admin/queues', this.bullAdapter.getRouter());
41         }
42
43   authFromUrl(raw: string): {authInfo: {token: string, accountId: string}} {
44     let url = new URL('http://localhost.com?id=null&token=null');
45     try {
46       url = new URL(raw);
47       const authInfo = {
48         authInfo: {
49           token: url.searchParams.get('token'),
50           accountId: url.searchParams.get('id')
51         }
52       };
53
54       return authInfo;
55     }
56     catch(e) {
57       console.log(e);
58     }
59   }
60
61   getSocketFromAuthenticatedUser(authInfo: {token: string, accountId: string}): Socket | null {
62     let socket: Socket;
63
64     this.ws.of('/').sockets.forEach(s => {
65       const auth = this.authFromUrl(s.request.headers['referer']);
66       if (auth.authInfo.accountId === authInfo.accountId && auth.authInfo.token === authInfo.token) {
67         socket = s;
68       }
69     });
70
71     return socket;
72   }
73
74         wrap<I, O>(handler: HttpHandler<I, O>, hxEvents: string) {
75     const self = this;
76                 return async function (req: Request, res: Response) {
77                         try {
78                                 const start = Date.now();
79                                 console.log(`Req: ${req.method.toUpperCase()} ${req.path}`);
80
81                                 // extract hx game vars (token, id);
82         const headerData = self.authFromUrl(req.headers['hx-current-url'].toString());
83                                 const output: O = await handler(merge(req, headerData) as unknown as (I & AuthInfo), req, res);
84                                 console.log(`Runtime: ${Date.now() - start}ms`);
85
86                                 res.setHeader('hx-trigger', hxEvents);
87                                 if(output === undefined) {
88                                         res.statusCode = 204;
89                                 }
90                                 else if(isString(output)) {
91                                         res.send(output);
92                                 }
93                                 else {
94                                         res.json(output);
95                                 }
96                         }
97                         catch(e) {
98                                 console.log(e);
99                                 res.send(`
100                                 <div class="alert danger autofade">${e.message}</div>
101                                 `);
102                         }
103                         finally {
104                                 res.end();
105                         }
106                 }
107         }
108
109         get<I, O>(endpoint: string, handler: HttpHandler<I, O>, hxEvents: string = ''): void {
110                 console.log(`Mapped GET ${endpoint}`);
111                 this.server.get(endpoint, this.wrap(handler, hxEvents));
112         }
113
114         post<I, O>(endpoint: string, handler: HttpHandler<I, O>, hxEvents: string = ''): void {
115                 console.log(`Mapped POST ${endpoint}`);
116                 this.server.post(endpoint, this.wrap(handler, hxEvents));
117         }
118
119         start(fn?: any): void {
120                 console.log(`Listening on port ${this.port}`);
121                 this.http.listen(this.port, fn?.bind(this));
122         }
123 }