import { IAuthSenderPayload, IChannel, IClient, IConnection, ITS5ConnectionHandler, ITS5DataHandler, ITS5MessageHandler } from "interfaces/teamspeak"; import { TS5DataHandler } from "handlers/teamspeak/dataHandler"; import { TS5MessageHandler } from "handlers/teamspeak/messageHandler"; import { ILogger } from "utils/logger"; import { ITSRemoteAppAuthPayloadOptions } from "interfaces/api"; // Establish connection to TS5 client // Main class export class TS5ConnectionHandler implements ITS5ConnectionHandler { ws: WebSocket; // Websocket connection to TS5 client authenticated = false; // Is the connection authenticated? remoteAppPort: number; // Port of TS5 client logger: ILogger; // Logger authPayload: ITSRemoteAppAuthPayloadOptions; // Authentication payload dataHandler: ITS5DataHandler; // Handles data/lists and states messageHandler: ITS5MessageHandler; // Handles messages received from TS5 client constructor( // Port of TS5 client remoteAppPort: number, authPayload: ITSRemoteAppAuthPayloadOptions, logger: ILogger, // State setters for dataHandler setConnections: React.Dispatch>, setChannels: React.Dispatch>, setClients: React.Dispatch>, setActiveConnectionStateId: React.Dispatch>, ) { // Create websocket connection to TS5 client this.remoteAppPort = remoteAppPort; this.authPayload = authPayload; this.logger = logger; this.ws = new WebSocket(`ws://localhost:${this.remoteAppPort}`); // Create dataHandler and messageHandler this.dataHandler = new TS5DataHandler(setConnections, setChannels, setClients, logger); this.messageHandler = new TS5MessageHandler(this.ws, this.dataHandler, setActiveConnectionStateId, logger); } reconnect() { this.logger.log("Reconnecting...") this.ws.close(); this.ws = new WebSocket(`ws://localhost:${this.remoteAppPort}`); this.dataHandler.clearAll(); this.authenticated = false; this.connect(); } // Connect to TS5 client connect() { this.logger.log('Connecting to TS5 client...'); // Create authentication payload const initialPayload: IAuthSenderPayload = { type: "auth", payload: { ...this.authPayload, content: { apiKey: localStorage.getItem("apiKey") ?? "", }, } }; this.ws.onopen = () => { // Send authentication payload to TS5 client this.ws.send(JSON.stringify(initialPayload)); this.logger.wsSent(initialPayload); }; this.ws.onclose = (event) => { this.logger.log("WebSocket connection closed", event); // If the connection was closed before authentication, remove the API key from local storage // OBS weirdly caches the localstorage and is very stubborn about clearing it (even when clicking "Clear Cache") if (!this.authenticated) { this.logger.log("WebSocket connection closed before authentication"); localStorage.removeItem("apiKey"); } setTimeout(() => { this.reconnect(); }, 2000); }; // Handle messages received from TS5 client // See TS5MessageHandler class this.ws.onmessage = (event) => { const data = JSON.parse(event.data); this.logger.wsReceived(data) switch (data.type) { case "auth": this.messageHandler.handleAuthMessage(data); this.authenticated = true; break; case "clientMoved": this.messageHandler.handleClientMovedMessage(data); break; case "clientPropertiesUpdated": this.messageHandler.handleClientPropertiesUpdatedMessage(data); break; case "talkStatusChanged": this.messageHandler.handleTalkStatusChangedMessage(data); break; case "serverPropertiesUpdated": this.messageHandler.handleServerPropertiesUpdatedMessage(data); break; case "connectStatusChanged": this.messageHandler.handleConnectStatusChangedMessage(data); break; case "clientSelfPropertyUpdated": this.messageHandler.handleClientSelfPropertyUpdatedMessage(data); break; case "channels": this.messageHandler.handleChannelsMessage(data); break; case "channelCreated": this.messageHandler.handleChannelCreatedMessage(data); break; default: this.logger.log(`No handler for event type: ${data.type}`); break; } }; } }