import { WebSocketMessage, sendWebSocketMessage, subscriptionMap, } from "./websocket-handler"; import Crypto from "crypto"; import { WS } from "./index"; export interface Subscriber { send: (message: 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) return false; const today = new Date(); return this.expiryDate < today; } // init constructor(ws: WS) { this.ws = ws; this.sendId(); } sendId(): void { if (!this.ws) return; sendWebSocketMessage( { assignedMailboxId: this.id, }, this.ws ); } confirmConnection(): void { if (!this.ws) return; sendWebSocketMessage( { connectedMailboxId: this.id, }, this.ws ); } // storage messages = new Set(); // websocket private _ws: WS | undefined; get ws(): WS | undefined { return this._ws; } set ws(ws: WS) { this._ws = ws; this.dateLastUsed = new Date(); this.confirmConnection(); this.sendUnreadMessages(); const channels = subscriptionMap.getChannelList(ws); channels?.forEach((channel) => this.subscribe(channel)); subscriptionMap.deleteSubscriber(ws); } // subscribing subscribe(channelName: string): void { subscriptionMap.set(this, channelName); } // sending send(message: string): void { if (this._ws?.readyState == 1) { this._ws.send(message); this.dateLastUsed = new Date(); } else { this.messages.add(message); } } sendUnreadMessages(): void { this.messages.forEach((message) => { if (!this._ws) return; this._ws.send(message); this.messages.delete(message); }); } } 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); }); }); } }