Files
UDN/websocket-handler.ts
T

227 lines
5.7 KiB
TypeScript
Raw Normal View History

2024-07-08 19:48:49 +02:00
import { Subscriber, SubscriptionMap } from "./subscriptionHandler";
2024-07-05 16:04:12 +02:00
import {
2024-07-07 18:16:28 +02:00
doIfIsString,
2024-07-05 16:04:12 +02:00
parseMessage,
stringifyMessage,
writeError,
writeSuccess,
} from "./utility";
import Crypto from "crypto";
2024-07-04 17:27:46 +02:00
import { WS } from "./index";
2024-07-05 16:04:12 +02:00
import { defaultConfig } from "./config-handler";
2024-07-04 17:27:46 +02:00
// requests
2024-07-05 16:04:12 +02:00
export interface WebSocketMessage {
uuid?: string;
// connecting to server
requestingServerConnection?: boolean;
2024-07-08 19:26:40 +02:00
// mailbox
requestingMailboxSetup?: boolean;
2024-07-08 19:48:49 +02:00
assignedMailboxId?: string; // sent by server
requestedMailbox?: string;
2024-07-08 19:26:40 +02:00
2024-07-04 17:27:38 +02:00
// subscribing to channel
2024-07-04 17:27:46 +02:00
subscribeChannel?: string;
unsubscribeChannel?: string;
2024-07-08 19:48:49 +02:00
subscribed?: string; // sent by server to confirm subscription
2024-07-04 17:27:38 +02:00
//sending message
messageChannel?: string;
messageBody?: string;
}
2024-07-04 17:27:46 +02:00
// TRACKING
2024-07-05 16:04:12 +02:00
const knownMessageIds = new Set<string>();
2024-07-07 18:16:28 +02:00
export const subscriptionMap = new SubscriptionMap();
2024-07-07 18:05:31 +02:00
export const clientsConnected = new Set<WS>();
2024-07-07 18:05:31 +02:00
export const serversConnectedAsClient = new Set<WS>();
export const serversConnected = new Set<WebSocket>();
2024-07-08 18:02:08 +02:00
export const serversDisconnected = new Set<string>();
2024-07-05 12:48:42 +02:00
2024-07-07 18:05:31 +02:00
export function getServerCount() {
return serversConnected.size + serversConnectedAsClient.size;
}
2024-07-07 18:16:28 +02:00
export function trackConnection(ws: WS) {
2024-07-07 18:05:31 +02:00
clientsConnected.add(ws);
2024-07-05 12:48:42 +02:00
}
2024-07-04 17:27:46 +02:00
2024-07-07 18:16:28 +02:00
export function forgetConnection(ws: WS): void {
clientsConnected.delete(ws);
2024-07-08 18:02:08 +02:00
serversConnectedAsClient.delete(ws);
2024-07-07 18:16:28 +02:00
const channels = subscriptionMap.getChannelList(ws);
channels?.forEach((channel) => {
subscriptionMap.delete(ws, channel);
});
2024-07-04 17:27:38 +02:00
}
export function trackSubscription(
subscriber: Subscriber,
channel: string
): void {
subscriptionMap.set(subscriber, channel);
confirmSubscription(subscriber, channel);
2024-07-04 17:27:46 +02:00
}
2024-07-04 17:27:38 +02:00
export function forgetSubscription(
subscriber: Subscriber,
channel: string
): void {
subscriptionMap.delete(subscriber, channel);
confirmSubscription(subscriber, channel);
2024-07-04 17:27:46 +02:00
}
2024-07-05 16:04:12 +02:00
// OTHER SERVERS
export function connectServers(config: typeof defaultConfig): void {
config.connectedServers.forEach((serverAddress) => {
2024-07-07 18:05:31 +02:00
connectServer(serverAddress, config);
2024-07-05 16:04:12 +02:00
});
}
2024-07-08 18:02:08 +02:00
export function reconnectServers(config: typeof defaultConfig): void {
[...serversDisconnected.values()].forEach((address) => {
connectServer(address, config);
});
}
2024-07-05 16:04:12 +02:00
export function connectServer(
address: string,
config: typeof defaultConfig
): void {
try {
2024-07-07 18:16:28 +02:00
// connect
2024-07-05 16:04:12 +02:00
const ws = new WebSocket(address);
2024-07-07 18:05:31 +02:00
serversConnected.add(ws);
2024-07-05 16:04:12 +02:00
2024-07-07 18:16:28 +02:00
// listen
2024-07-05 16:04:12 +02:00
ws.addEventListener("open", () => {
2024-07-08 18:02:08 +02:00
serversDisconnected.delete(address);
2024-07-05 16:04:12 +02:00
writeSuccess(`connected to server at "${address}".`);
subscribeChannels(ws, config);
requestServerConnection(ws);
});
ws.addEventListener("message", (message) => {
processMessage(ws, message.data.toString(), config);
});
ws.addEventListener("close", () => {
2024-07-07 18:05:31 +02:00
serversConnected.delete(ws);
2024-07-08 18:02:08 +02:00
serversDisconnected.add(address);
2024-07-05 16:04:12 +02:00
writeError(`server at "${address}" disconnected`);
});
} catch (error) {
writeError(`failed to connect to server at "${address}": ${error}`);
}
}
function subscribeChannels(ws: WS, config: typeof defaultConfig): void {
config.subscribedChannels.forEach((channel) => {
subscribeChannel(ws, channel);
});
}
export function subscribeChannel(server: WS, channel: string): void {
const message: WebSocketMessage = {
subscribeChannel: channel,
};
const messageString = stringifyMessage(message);
server.send(messageString);
}
2024-07-05 12:48:42 +02:00
// AUDIT
2024-07-07 18:05:31 +02:00
export function getWebSocketStats(): [string, number][] {
2024-07-05 12:48:42 +02:00
return [
["channels", subscriptionMap.subscribersPerChannel.size],
2024-07-07 18:05:31 +02:00
["clients connected", clientsConnected.size],
["servers connected", getServerCount()],
2024-07-08 18:02:08 +02:00
["servers disconnected", serversDisconnected.size],
2024-07-05 12:48:42 +02:00
];
}
2024-07-04 17:27:46 +02:00
// PROCESS MESSAGE
2024-07-05 16:04:12 +02:00
export function processMessage(
ws: WS,
messageString: string,
config: typeof defaultConfig
): void {
2024-07-08 19:27:49 +02:00
// MESSAGE ID
2024-07-07 18:16:28 +02:00
// check id
2024-07-05 16:04:12 +02:00
const messageObject: WebSocketMessage = parseMessage(messageString);
2024-07-08 18:02:08 +02:00
if (!messageObject.uuid) {
// assign if no id
2024-07-05 16:04:12 +02:00
messageObject.uuid = Crypto.randomUUID();
2024-07-08 18:02:08 +02:00
} else {
// ignore if already seen
2024-07-05 16:04:12 +02:00
if (knownMessageIds.has(messageObject.uuid)) return;
}
2024-07-07 18:16:28 +02:00
// remember message
2024-07-05 16:04:12 +02:00
knownMessageIds.add(messageObject.uuid);
2024-07-04 17:27:46 +02:00
2024-07-08 19:27:49 +02:00
// SUBSCRIPTIONS
2024-07-07 18:16:28 +02:00
doIfIsString(messageObject.subscribeChannel, (subscribeChannel) => {
2024-07-05 00:15:00 +02:00
trackSubscription(ws, subscribeChannel);
});
2024-07-07 18:16:28 +02:00
doIfIsString(messageObject.unsubscribeChannel, (unsubscribeChannel) => {
forgetSubscription(ws, unsubscribeChannel);
});
2024-07-08 19:27:49 +02:00
// MESSAGE
2024-07-07 18:16:28 +02:00
doIfIsString(messageObject.messageChannel, (messageChannel) => {
2024-07-05 16:04:12 +02:00
if (messageObject.messageBody == undefined) return;
sendMessage(messageChannel, messageObject.messageBody, messageObject.uuid!);
2024-07-05 12:48:42 +02:00
});
2024-07-07 18:16:28 +02:00
2024-07-08 19:27:49 +02:00
// OTHER SERVER
2024-07-05 16:04:12 +02:00
if (messageObject.requestingServerConnection == true) {
2024-07-07 18:05:31 +02:00
clientsConnected.delete(ws);
serversConnectedAsClient.add(ws);
2024-07-05 16:04:12 +02:00
subscribeChannels(ws, config);
}
2024-07-04 17:27:46 +02:00
}
// MESSAGING
2024-07-05 16:04:12 +02:00
export function sendMessage(
messageChannel: string,
messageBody: string,
uuid: string
): void {
const channels = messageChannel.split("/");
const messageObject = { uuid, messageChannel, messageBody };
const messageString = stringifyMessage(messageObject);
2024-07-04 17:27:38 +02:00
subscriptionMap.forwardMessage(channels, messageString);
2024-07-04 17:27:38 +02:00
}
2024-07-05 16:04:12 +02:00
function requestServerConnection(ws: WS): void {
const messageObject = {
requestingServerConnection: true,
};
2024-07-04 17:27:46 +02:00
const messageString = stringifyMessage(messageObject);
2024-07-05 16:04:12 +02:00
ws.send(messageString);
2024-07-04 17:27:38 +02:00
}
function confirmSubscription(
subscriber: Subscriber,
messageChannel: string
): void {
2024-07-08 19:48:49 +02:00
const messageObject: WebSocketMessage = {
2024-07-05 16:04:12 +02:00
messageChannel,
subscribed: subscriptionMap.getChannelList(subscriber)?.has(messageChannel)
? true
: false,
2024-07-04 17:27:46 +02:00
};
const messageString = stringifyMessage(messageObject);
subscriber.send(messageString);
2024-07-04 17:27:38 +02:00
}