]> git.cworth.org Git - empires-server/blob - lmno.js
Add simple session tracking
[empires-server] / lmno.js
1 const express = require("express");
2 const cors = require("cors");
3 const body_parser = require("body-parser");
4 const session = require("express-session");
5
6 try {
7   var lmno_config = require("./lmno-config.json");
8 } catch (err) {
9   config_usage();
10   process.exit(1);
11 }
12
13 function config_usage() {
14   console.log(`Error: Refusing to run without configuration.
15
16 Please create a file named lmno-config.json that looks as follows:
17
18 {
19   "session_secret": "<this should be a long string of true-random characters>";
20 }
21
22 Note: Don't use the exact text above, but instead replace the string
23 with what it describes: a long string of random characters.`);
24 }
25
26 const app = express();
27 app.use(cors());
28 app.use(session({
29   secret: lmno_config.session_secret,
30   resave: false,
31   saveUninitialized: false
32 }));
33
34 /* Load each of our game mini-apps. */
35 var empires = require("./empires");
36
37 class LMNO {
38   constructor() {
39     this.ids = {};
40   }
41
42   generate_id() {
43     return [null,null,null,null].map(() => LMNO.letters.charAt(Math.floor(Math.random() * LMNO.letters.length))).join('');
44   }
45
46   create_game(engine) {
47     do {
48       var id = this.generate_id();
49     } while (id in this.ids);
50
51     const game = new empires.Game();
52
53     this.ids[id] = {
54         id: id,
55         engine: engine,
56         game: game
57     };
58
59     return id;
60   }
61 }
62
63 /* Some letters we don't use in our IDs:
64  *
65  * 1. Vowels (AEIOU) to avoid accidentally spelling an unfortunate word
66  * 2. Lowercase letters (replace with corresponding capital on input)
67  * 3. N (replace with M on input)
68  * 4. P (replace with B on input)
69  * 5. S (replace with F on input)
70  */
71 LMNO.letters = "BCDFGHJKLMQRTVWXYZ";
72
73 const lmno = new LMNO();
74
75 /* Force a game ID into a canonical form as described above. */
76 function lmno_canonize(id) {
77   /* Capitalize */
78   id = id.toUpperCase();
79
80   /* Replace unused letters with nearest phonetic match. */
81   id = id.replace(/N/g, 'M');
82   id = id.replace(/P/g, 'B');
83   id = id.replace(/S/g, 'F');
84
85   /* Replace unused numbers nearest visual match. */
86   id = id.replace(/0/g, 'O');
87   id = id.replace(/1/g, 'I');
88   id = id.replace(/5/g, 'S');
89
90   return id;
91 }
92
93 app.post('/new/:game_engine', (request, response) =>  {
94   const game_engine = request.params.game_engine;
95   const game_id = lmno.create_game(game_engine);
96   response.send(JSON.stringify(game_id));
97 });
98
99 /* Redirect any requests to a game ID at the top-level.
100  *
101  * Specifically, after obtaining the game ID (from the path) we simply
102  * lookup the game engine for the corresponding game and then redirect
103  * to the engine- and game-specific path.
104  */
105 app.get('/[a-zA-Z0-9]{4}', (request, response) => {
106   const game_id = request.path.replace(/\//g, "");
107   const canon_id = lmno_canonize(game_id);
108
109   /* Redirect user to page with the canonical ID in it. */
110   if (game_id !== canon_id) {
111     response.redirect(301, `/${canon_id}/`);
112     return;
113   }
114
115   const game = lmno.ids[game_id];
116   if (game === undefined) {
117       response.sendStatus(404);
118       return;
119   }
120   response.redirect(301, `/${game.engine}/${game.id}/`);
121 });
122
123 /* LMNO middleware to lookup the game. */
124 app.use('/empires/:game_id([a-zA-Z0-9]{4})', (request, response, next) => {
125   const game_id = request.params.game_id;
126   const canon_id = lmno_canonize(game_id);
127
128   /* Redirect user to page with the canonical ID in it. */
129   if (game_id !== canon_id) {
130     const new_url = request.originalUrl.replace("/empires/" + game_id,
131                                                 "/empires/" + canon_id);
132     response.redirect(301, new_url);
133     return;
134   }
135
136   request.game = lmno.ids[game_id].game;
137   if (request.game === undefined) {
138     response.sendStatus(404);
139     return;
140   }
141   next();
142 });
143
144 /* Mount sub apps. only _after_ we have done all the middleware we need. */
145 app.use('/empires/[a-zA-Z0-9]{4}/', empires.app);
146
147 app.listen(4000, function () {
148   console.log('LMNO server listening on localhost:4000');
149 });