2022-01-08 17:17:43 +01:00
|
|
|
import * as fs from "fs";
|
|
|
|
import * as https from "https";
|
2022-01-07 22:21:42 +01:00
|
|
|
import { WebSocketServer } from "ws";
|
2022-01-08 17:17:43 +01:00
|
|
|
|
|
|
|
import * as crypto from "crypto";
|
|
|
|
|
2022-01-08 20:23:03 +01:00
|
|
|
import type {
|
|
|
|
AckMessage,
|
|
|
|
IdResponseMessage,
|
|
|
|
TextMessage,
|
2022-01-17 16:20:31 +01:00
|
|
|
TypingMessage,
|
2022-01-21 14:51:26 +01:00
|
|
|
ConnectedUser,
|
|
|
|
ConnectedUsersMessage,
|
2022-01-27 16:22:15 +01:00
|
|
|
DesiredNameMessage,
|
2022-01-08 20:23:03 +01:00
|
|
|
} from "./lib/ServerMessage";
|
2022-01-08 17:17:43 +01:00
|
|
|
|
2022-01-07 17:36:19 +01:00
|
|
|
import {
|
2022-01-07 18:59:25 +01:00
|
|
|
isServerMessage,
|
|
|
|
isTextMessage,
|
2022-01-17 16:20:31 +01:00
|
|
|
isTypingMessage,
|
2022-01-21 14:51:26 +01:00
|
|
|
isDesiredNameMessage,
|
2022-01-07 18:59:25 +01:00
|
|
|
MessageType,
|
2022-01-08 17:17:43 +01:00
|
|
|
} from "./lib/ServerMessage";
|
|
|
|
|
|
|
|
const port = 8085;
|
2022-01-21 14:51:26 +01:00
|
|
|
const timeout = 5000;
|
2022-01-17 16:20:31 +01:00
|
|
|
const typingTimeout = 2000;
|
2022-01-21 14:51:26 +01:00
|
|
|
const serverId = "00000000-0000-0000-0000-000000000000";
|
2022-01-07 17:36:19 +01:00
|
|
|
|
2022-01-08 17:17:43 +01:00
|
|
|
const httpsServer = https.createServer({
|
|
|
|
key: fs.readFileSync("./key.pem"),
|
|
|
|
cert: fs.readFileSync("./cert.pem"),
|
|
|
|
});
|
|
|
|
const webSocketServer = new WebSocketServer({ server: httpsServer });
|
2022-01-07 17:36:19 +01:00
|
|
|
|
2022-01-17 16:20:31 +01:00
|
|
|
/* If not using SSL/TLS
|
|
|
|
const webSocketServer = new WebSocketServer({ port });
|
|
|
|
*/
|
|
|
|
|
2022-01-07 17:36:19 +01:00
|
|
|
console.log("listening on port: " + port);
|
|
|
|
|
2022-01-08 17:17:43 +01:00
|
|
|
async function handleTextMessage(message: TextMessage, from: string) {
|
|
|
|
for (const to of webSocketServer.clients) {
|
|
|
|
to.send(
|
|
|
|
JSON.stringify(
|
|
|
|
Object.assign({}, message, {
|
|
|
|
author: from,
|
|
|
|
})
|
|
|
|
)
|
|
|
|
);
|
2022-01-07 17:36:19 +01:00
|
|
|
}
|
2022-01-08 17:17:43 +01:00
|
|
|
}
|
2022-01-07 17:36:19 +01:00
|
|
|
|
2022-01-17 16:20:31 +01:00
|
|
|
const activeConnections = new Set<string>();
|
|
|
|
const currentlyTyping = new Set<string>();
|
|
|
|
const currentlyTypingTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
|
2022-01-21 14:51:26 +01:00
|
|
|
const desiredNames = new Map<string, string>();
|
|
|
|
|
|
|
|
activeConnections.add(serverId);
|
|
|
|
desiredNames.set(serverId, "[SYSTEM]");
|
2022-01-17 16:20:31 +01:00
|
|
|
|
|
|
|
async function handleTypingMessage(_message: TypingMessage, from: string) {
|
|
|
|
currentlyTyping.add(from);
|
|
|
|
|
|
|
|
if(currentlyTypingTimeouts.has(from)) {
|
|
|
|
clearTimeout(currentlyTypingTimeouts.get(from)!);
|
|
|
|
}
|
|
|
|
currentlyTypingTimeouts.set(from, setTimeout(() => {
|
|
|
|
currentlyTyping.delete(from);
|
|
|
|
currentlyTypingTimeouts.delete(from);
|
|
|
|
sendCurrentlyTypingMessage(from, true);
|
|
|
|
}, typingTimeout));
|
|
|
|
sendCurrentlyTypingMessage(from);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function sendCurrentlyTypingMessage(from?: string, stopped = false) {
|
|
|
|
for (const to of webSocketServer.clients) {
|
|
|
|
to.send(
|
|
|
|
JSON.stringify(
|
|
|
|
Object.assign({
|
|
|
|
type: MessageType.CURRENTLY_TYPING,
|
|
|
|
date: Date.now(),
|
|
|
|
}, {
|
|
|
|
currently: Array.from(currentlyTyping.values()),
|
|
|
|
}, from === undefined ? {
|
|
|
|
__ctx: "regular update",
|
|
|
|
} : {
|
|
|
|
__ctx: from + (stopped ? " stopped typing" : " started typing"),
|
|
|
|
})
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-08 20:23:03 +01:00
|
|
|
async function handleCloseConnection(id: string) {
|
|
|
|
activeConnections.delete(id);
|
2022-01-21 14:51:26 +01:00
|
|
|
if (desiredNames.has(id)) {
|
|
|
|
desiredNames.delete(id);
|
|
|
|
}
|
|
|
|
|
2022-01-27 16:22:15 +01:00
|
|
|
const connected =
|
|
|
|
Array.from(activeConnections,
|
|
|
|
(id) => {
|
|
|
|
return {
|
|
|
|
id,
|
|
|
|
desiredName: desiredNames.get(id),
|
|
|
|
} as ConnectedUser;
|
|
|
|
});
|
2022-01-21 14:51:26 +01:00
|
|
|
for (const to of webSocketServer.clients) {
|
|
|
|
to.send(
|
|
|
|
JSON.stringify({
|
|
|
|
type: MessageType.CONNECTED_USERS,
|
|
|
|
__ctx: `${id} left`,
|
|
|
|
date: Date.now(),
|
2022-01-27 16:22:15 +01:00
|
|
|
connected,
|
2022-01-21 14:51:26 +01:00
|
|
|
} as ConnectedUsersMessage)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleNewConnection(id: string) {
|
|
|
|
activeConnections.add(id);
|
|
|
|
|
2022-01-27 16:22:15 +01:00
|
|
|
const connected =
|
|
|
|
Array.from(activeConnections,
|
|
|
|
(id) => {
|
|
|
|
return {
|
|
|
|
id,
|
|
|
|
desiredName: desiredNames.get(id),
|
|
|
|
} as ConnectedUser;
|
|
|
|
});
|
|
|
|
|
2022-01-21 14:51:26 +01:00
|
|
|
for (const to of webSocketServer.clients) {
|
|
|
|
to.send(
|
|
|
|
JSON.stringify({
|
|
|
|
type: MessageType.CONNECTED_USERS,
|
|
|
|
__ctx: `${id} joined`,
|
|
|
|
date: Date.now(),
|
2022-01-27 16:22:15 +01:00
|
|
|
connected,
|
|
|
|
} as ConnectedUsersMessage)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function handleDesiredNameMessage(message: DesiredNameMessage, from: string) {
|
|
|
|
if (!activeConnections.has(from) && desiredNames.has(from)) {
|
|
|
|
desiredNames.delete(from);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
console.debug(`${from} set desiredName to ${message.desiredName}`);
|
|
|
|
if (message.desiredName === undefined) {
|
|
|
|
desiredNames.delete(from);
|
|
|
|
} else {
|
|
|
|
desiredNames.set(from, message.desiredName);
|
|
|
|
}
|
|
|
|
|
|
|
|
const connected =
|
|
|
|
Array.from(activeConnections,
|
|
|
|
(id) => {
|
|
|
|
return {
|
|
|
|
id,
|
|
|
|
desiredName: desiredNames.get(id),
|
|
|
|
} as ConnectedUser;
|
|
|
|
});
|
|
|
|
for (const to of webSocketServer.clients) {
|
|
|
|
to.send(
|
|
|
|
JSON.stringify({
|
|
|
|
type: MessageType.CONNECTED_USERS,
|
|
|
|
__ctx: `${from} changed desiredName to ${message.desiredName}`,
|
|
|
|
date: Date.now(),
|
|
|
|
connected,
|
2022-01-21 14:51:26 +01:00
|
|
|
} as ConnectedUsersMessage)
|
|
|
|
);
|
|
|
|
}
|
2022-01-08 20:23:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
webSocketServer.on("connection", function connection(socket) {
|
2022-01-08 17:17:43 +01:00
|
|
|
const close = (reason: string, code: number = 1000) => {
|
|
|
|
socket.send(
|
|
|
|
JSON.stringify({
|
|
|
|
type: MessageType.ACK,
|
|
|
|
date: Date.now(),
|
|
|
|
__ctx: `closing connection. reason: ${reason}`,
|
|
|
|
} as AckMessage)
|
|
|
|
);
|
|
|
|
socket.close(code, `closing connection. reason: ${reason}`);
|
2022-01-08 20:23:03 +01:00
|
|
|
handleCloseConnection(authorId);
|
2022-01-08 17:17:43 +01:00
|
|
|
};
|
2022-01-07 18:59:25 +01:00
|
|
|
|
2022-01-14 13:33:03 +01:00
|
|
|
const authorId: string = crypto.randomUUID();
|
2022-01-08 17:17:43 +01:00
|
|
|
|
|
|
|
socket.on("message", function (rawMessage: string) {
|
2022-01-07 18:59:25 +01:00
|
|
|
const message = JSON.parse(rawMessage);
|
|
|
|
if (!isServerMessage(message)) {
|
2022-01-14 13:33:03 +01:00
|
|
|
console.error(`Unexpected message received from client "${authorId}": \`${message}\``);
|
2022-01-08 20:23:03 +01:00
|
|
|
return;
|
2022-01-21 14:51:26 +01:00
|
|
|
} else {
|
|
|
|
clearTimeout(closeTimeout);
|
|
|
|
closeTimeout = setTimeout(close, timeout);
|
2022-01-07 18:59:25 +01:00
|
|
|
}
|
2022-01-08 20:23:03 +01:00
|
|
|
|
2022-01-07 18:59:25 +01:00
|
|
|
if (isTextMessage(message)) {
|
2022-01-08 20:23:03 +01:00
|
|
|
if (message.author === authorId) {
|
|
|
|
handleTextMessage(message, authorId);
|
|
|
|
}
|
2022-01-17 16:20:31 +01:00
|
|
|
} else if (isTypingMessage(message)) {
|
|
|
|
handleTypingMessage(message, authorId);
|
2022-01-21 14:51:26 +01:00
|
|
|
} else if (isDesiredNameMessage(message)) {
|
2022-01-27 16:22:15 +01:00
|
|
|
handleDesiredNameMessage(message, authorId);
|
2022-01-07 18:59:25 +01:00
|
|
|
}
|
2022-01-07 17:36:19 +01:00
|
|
|
});
|
2022-01-27 16:22:15 +01:00
|
|
|
|
2022-01-07 17:36:19 +01:00
|
|
|
socket.on("close", function close() {
|
|
|
|
console.log("closed a connection");
|
|
|
|
});
|
|
|
|
|
2022-01-08 20:23:03 +01:00
|
|
|
console.log("new client connected! ID:", authorId);
|
2022-01-08 17:17:43 +01:00
|
|
|
socket.send(
|
|
|
|
JSON.stringify({
|
2022-01-08 20:23:03 +01:00
|
|
|
type: MessageType.ID_RESPONSE,
|
2022-01-08 17:17:43 +01:00
|
|
|
__ctx: "connected successfully",
|
2022-01-08 20:23:03 +01:00
|
|
|
date: Date.now(),
|
|
|
|
authorId,
|
|
|
|
} as IdResponseMessage)
|
2022-01-08 17:17:43 +01:00
|
|
|
);
|
2022-01-21 14:51:26 +01:00
|
|
|
|
|
|
|
handleNewConnection(authorId);
|
|
|
|
|
|
|
|
socket.send(
|
|
|
|
JSON.stringify({
|
|
|
|
type: MessageType.TEXT,
|
|
|
|
__ctx: "server welcome message",
|
|
|
|
date: Date.now(),
|
|
|
|
author: serverId,
|
|
|
|
content: "Successfully connected. Welcome!\nSend a message to talk to other connected users.",
|
|
|
|
} as TextMessage)
|
|
|
|
);
|
2022-01-07 17:36:19 +01:00
|
|
|
let closeTimeout = setTimeout(close, timeout);
|
|
|
|
});
|
2022-01-08 17:17:43 +01:00
|
|
|
|
|
|
|
httpsServer.listen(port);
|