-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.ts
162 lines (143 loc) · 4.68 KB
/
main.ts
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import readline from "node:readline";
import process from "node:process";
import { randomUUID } from "node:crypto";
// See: https://docs.deno.com/examples/os-signals/
const handleServerKillSignal = (
abort: AbortController,
clients: WebSocket[],
) => {
const sigIntHandler = () => {
console.log("Closing clients");
for (const client of clients) {
if (client.readyState === WebSocket.OPEN) {
client.send("shutdown");
}
}
console.log("Closing server with 2000 ms for clients to disconnect");
setTimeout(() => {
abort.abort();
Deno.exit(1);
}, 2000);
};
Deno.addSignalListener("SIGINT", sigIntHandler);
};
const clients: WebSocket[] = [];
// See: https://docs.deno.com/examples/http-server-websocket/
export const openWs = (): void => {
// See: https://developer.mozilla.org/en-US/docs/Web/API/AbortController
// And also: https://docs.deno.com/api/deno/~/Deno.serve
const abort = new AbortController();
handleServerKillSignal(abort, clients);
const server = Deno.serve({ signal: abort.signal }, (req) => {
if (req.headers.get("upgrade") != "websocket") {
return new Response(null, { status: 501 });
}
const { socket, response } = Deno.upgradeWebSocket(req);
socket.addEventListener("open", () => {
console.log("Adding new client to the pool");
clients.push(socket);
});
socket.addEventListener("message", (event) => {
if (event.data.match(/^join/)) {
socket.send("Welcome " + event.data.split(":")[1]);
return;
}
// Broadcast the message to all connected clients
for (const client of clients) {
if (client.readyState === WebSocket.OPEN) {
client.send(event.data);
}
}
});
socket.addEventListener("close", () => {
console.log("The connection has been closed successfully.");
console.log("Removing client from pool.");
clients.splice(clients.indexOf(socket), 1);
});
socket.addEventListener("error", (event) => {
console.log("WebSocket error: ", event);
});
return response;
});
server.finished.then(() => console.log("Server closed"));
};
// See: https://docs.deno.com/examples/os-signals/
const handleClientKillSignal = (socket: WebSocket) => {
const sigIntHandler = () => {
console.log("Closing websocket");
// See: https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
socket.close();
};
Deno.addSignalListener("SIGINT", sigIntHandler);
};
// See: https://docs.deno.com/examples/websocket/
export const connectToWs = (username: string): void => {
const socket = new WebSocket("ws://localhost:8000");
handleClientKillSignal(socket);
socket.addEventListener("open", () => {
if (socket.readyState !== WebSocket.OPEN) return;
socket.send(`join:${username}`);
});
socket.addEventListener("close", () => {
console.log("The connection has been closed successfully.");
rl.close();
Deno.exit();
});
socket.addEventListener("error", (event) => {
const connectionRefused = event instanceof ErrorEvent &&
event.message.startsWith("NetworkError");
if (connectionRefused) {
console.log("WebSocket error: ", event.error);
Deno.exit(1);
}
const poorlyClosedSocket = event instanceof ErrorEvent &&
event.message === "Unexpected EOF";
if (poorlyClosedSocket) {
console.log("WebSocket error: Server stopped without waiting");
Deno.exit(1);
}
console.log("WebSocket error: ", event);
Deno.exit(1);
});
// Request environment permission explicitly before readline takes control
Deno.permissions.request({ name: "env", variable: "TERM" });
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.on("line", (msg) => {
socket.send(`${username}: ` + msg);
readlineReset();
});
const readlineClear = () => {
readline.clearLine(process.stdout, 0);
readline.cursorTo(process.stdout, 0);
};
const readlineReset = () => {
readlineClear();
rl.setPrompt(`${username}: `);
rl.prompt();
};
socket.addEventListener("message", (event) => {
if (event.data.match(username + ":")?.length) return;
if (event.data === "shutdown") {
console.log("Server is shutting down.");
socket.close();
}
readlineClear();
console.log(event.data);
readlineReset();
});
};
// Learn more at https://docs.deno.com/runtime/manual/examples/module_metadata#concepts
if (import.meta.main) {
const action = Deno.args[0];
if (action === "start") {
console.log("...starting ws");
openWs();
} else {
console.log("...connecting to ws");
const username = Deno.args[1] || randomUUID();
connectToWs(username);
}
}