export class RTEngineClient { constructor(params = {}) { this.params = params } socket = null stateSubscribers = [] joinedTopics = new Set() handlers = new Set() async connect() { return new Promise((resolve, reject) => { if (this.socket) { this.disconnect() } let url = `${this.params.url}` if (this.params.token) { url += `?token=${this.params.token}` } this.socket = new WebSocket(url) this.socket.onopen = () => { resolve() this._emit("connect") } this.socket.onclose = () => { this._emit("disconnect") } this.socket.onerror = () => { reject() this._emit("error") } this.socket.onmessage = (event) => this.handleMessage(event) }) } async disconnect() { if (!this.socket) { return false } for await (const topic of this.joinedTopics) { this.leaveTopic(topic) } this.socket.close() this.socket = null } _emit(event, data) { for (const handler of this.handlers) { if (handler.event === event) { handler.handler(data) } } } on = (event, handler) => { this.handlers.add({ event, handler, }) } off = (event, handler) => { this.handlers.delete({ event, handler, }) } emit = (event, data) => { if (!this.socket) { throw new Error("Failed to send, socket not connected") } this.socket.send(JSON.stringify({ event, data })) } joinTopic = (topic) => { this.emit("topic:join", topic) this.joinedTopics.add(topic) } leaveTopic = (topic) => { this.emit("topic:leave", topic) this.joinedTopics.delete(topic) } updateState(state) { this.stateSubscribers.forEach((callback) => callback(state)) } //* HANDLERS handleMessage(event) { try { const payload = JSON.parse(event.data) if (typeof payload.event !== "string") { return false } if (payload.event === "error") { console.error(payload.data) return false } this._emit(payload.event, payload.data) } catch (error) { console.error("Error handling message:", error) } } // UPDATERS onStateChange(callback) { this.stateSubscribers.push(callback) return () => { this.stateSubscribers = this.stateSubscribers.filter( (cb) => cb !== callback, ) } } } export default RTEngineClient