import { WebSocketMessage, sendWebSocketMessage, subscriptionMap, } from "./websocket-handler"; import Crypto from "crypto"; import { WS } from "./index"; export interface Subscriber { send: (message: string, compress?: boolean, id?: string) => void; } export class Mailbox implements Subscriber { // basic data id = Crypto.randomUUID(); dateLastUsed = new Date(); get expiryDate() { const expiryDate = new Date(this.dateLastUsed); expiryDate.setDate(expiryDate.getDate() + 7); return expiryDate; } get isExpired() { if (this.ws && this.ws.readyState == 1) return false; const today = new Date(); return this.expiryDate < today; } // manage constructor(ws: WS) { sendWebSocketMessage( { assignedMailboxId: this.id, }, ws, ); } confirmConnection(): void { if (!this.ws) return; sendWebSocketMessage( { connectedMailboxId: this.id, }, this.ws, ); } delete(): void { subscriptionMap.deleteSubscriber(this); if (!this.ws) return; sendWebSocketMessage({ deletedMailbox: this.id }, this.ws); } // storage messages = new Map(); // websocket private _ws: WS | undefined; get ws(): WS | undefined { return this._ws; } set ws(ws: WS) { this._ws = ws; this.update(); } update() { if (!this._ws) return; this.dateLastUsed = new Date(); this.confirmConnection(); this.sendUnreadMessages(); const channels = subscriptionMap.getChannelList(this._ws); channels?.forEach((channel) => this.subscribe(channel)); subscriptionMap.deleteSubscriber(this._ws); } // subscribing subscribe(channelName: string): void { subscriptionMap.set(this, channelName); } // sending send(message: string, _?: boolean, uuid?: string): void { this._ws?.send(message); console.log(this.id, uuid, message, "sending"); if (!uuid) return; this.messages.set(uuid, message); } sendUnreadMessages(): void { this.messages.forEach((message) => { if (this._ws?.readyState != 1) return; this._ws.send(message); this.messages.delete(message); }); } checkMessageReceivedConfirmation(uuid: string): void { if (!this.messages.has(uuid)) return; this.dateLastUsed = new Date(); this.messages.delete(uuid); } } export class SubscriptionMap { channelsPerSubscriber = new Map>(); subscribersPerChannel = new Map>(); // prepare private prepareChannelSet(subscriber: Subscriber): void { if (this.channelsPerSubscriber.has(subscriber)) return; this.channelsPerSubscriber.set(subscriber, new Set()); } private prepareSubscriberSet(channel: string): void { if (this.subscribersPerChannel.has(channel)) return; this.subscribersPerChannel.set(channel, new Set()); } // utility getChannelList(subscriber: Subscriber): Set | undefined { return this.channelsPerSubscriber.get(subscriber); } getClientList(channel: string): Set | undefined { return this.subscribersPerChannel.get(channel); } // modifications set(subscriber: Subscriber, channel: string): void { this.prepareChannelSet(subscriber); this.prepareSubscriberSet(channel); this.getChannelList(subscriber)?.add(channel); this.getClientList(channel)?.add(subscriber); } delete(subscriber: Subscriber, channel: string): void { const channelList = this.getChannelList(subscriber); const clientList = this.getClientList(channel); channelList?.delete(channel); if (channelList?.size == 0) { this.channelsPerSubscriber.delete(subscriber); } clientList?.delete(subscriber); if (clientList?.size == 0) { this.subscribersPerChannel.delete(channel); } } deleteSubscriber(subscriber: Subscriber): void { this.getChannelList(subscriber)?.forEach((channel) => { this.delete(subscriber, channel); }); } // messages forwardMessage(channels: string[], messageObject: WebSocketMessage): void { channels.forEach((channel) => { const subscribers = this.getClientList(channel); subscribers?.forEach((subscriber) => { sendWebSocketMessage(messageObject, subscriber); }); }); } }