-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
120 lines (99 loc) · 3.79 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/**
* @typedef {{
* username: string
* password: string // - hashed + salted
* }} User
*
* @typedef {{
* __count__: (size: number) => void
* __disconnect__: (args: {id: string}) => void
* __connect__: () => void
* }} ServerToClientEvents
*
* @typedef {{
* type?: "controller" | "view"
* }} SocketData
*/
import cors from "cors";
import mime from "mime";
import dotenv from "dotenv";
import * as path from "path";
import express from "express";
import {readFileSync} from "fs";
import {Server} from "socket.io";
import {fileURLToPath} from "url";
import {instrument} from "@socket.io/admin-ui";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
/** @type {(sockets: Map<string, import("socket.io").Socket<any, any, any, SocketData>>) => number} */
const getViewCount = sockets => {
let count = 0;
for (let [_k, v] of sockets) {
if (v.data.type !== "view") continue
count += 1;
}
return count;
};
dotenv.config();
const config = JSON.parse(readFileSync("config.json").toString());
/** @type {User[]} */
const credentials = JSON.parse(readFileSync("credentials.json").toString());
/** @type {(namespace: import("socket.io").Namespace<{}, ServerToClientEvents, {}, SocketData>, authenticate: boolean) => void} */
const configureNamespace = (namespace, authenticate = true) => {
namespace.use((socket, next) => {
if (!authenticate) {
next();
return;
}
const {username, password} = socket.handshake.auth;
const user = credentials.find(user => user.username === username);
if (user === undefined || user.password !== password) {
next(new Error("UNAUTHORIZED"));
} else {
next();
}
});
namespace.on("connection", client => {
console.log(`Client ${client.id} with type ${client.data.type ?? "default"} connected via namespace ${client.nsp.name} from ${client.handshake.address}`);
if (client.data.type === "view") {
namespace.emit(/** @type {"__connect__"} */ "__connect__");
}
client.onAny((eventName, ...args) => {
if (eventName === "__count__" && client.data.type === "controller") {
client.emit(/** @type {"__count__"} */ "__count__", getViewCount(namespace.sockets));
}
namespace.emit(eventName, ...args);
});
client.on("disconnect", () => {
if (client.data.type === "controller") return;
namespace.emit(/** @type {"__disconnect__"} */"__disconnect__", {id: client.id});
});
});
};
const app = express();
app.use(cors(config["cors"] ?? {origin: "*"}));
app.use(express.static("public"));
app.use("/admin", express.static(path.join("node_modules", "@socket.io", "admin-ui", "ui", "dist")));
app.get("/socket.io.js", (_req, res) => {
res.setHeader("Content-Type", mime.lookup('.js'));
if (process.env.NODE_ENVIRONMENT === "production") {
res.sendFile(path.join(__dirname, "..", "node_modules", "socket.io-client", "dist", "socket.io.js"));
} else {
res.sendFile(path.join(__dirname, "socket.io.js"));
}
});
const server = app.listen(parseInt(process.env.PORT ?? "8080"), process.env.HOST ?? "127.0.0.1", () => console.log(`Listening on ${process.env.HOST}:${process.env.PORT}`));
const io = new Server(server, /** @type {import("socket.io").Partial<import("socket.io").ServerOptions>} */ config);
io.of("/").use((_socket, next) => {
next(new Error("UNAUTHORIZED"));
});
const auth = !process.env.USERNAME || !process.env.PASSWORD ? false : {
type: /** @type {"basic"} */ "basic",
username: process.env.USERNAME,
password: process.env.PASSWORD
};
instrument(io, {auth, mode: process.env.NODE_ENV === "production" ? "production" : "development"});
const defaultNamespace = io.of("/sharedsocket");
const dynamicNamespace = io.of(/^\/do-.+$/);
configureNamespace(defaultNamespace, false);
configureNamespace(dynamicNamespace);