// WebSocket lifecycle management. // import.meta.url is used for the 'auto' endpoint mode so the SDK always // connects back to the same server it was downloaded from. export class Connection { constructor(mwse, options) { this.mwse = mwse; this.connected = false; this.autoPair = false; this.autoReconnect = true; this.autoReconnectTimeout = 3000; this.autoReconnectTimer = undefined; this._activeCallbacks = []; this._passiveCallbacks = []; this._packCallbacks = []; if (options.endpoint === 'auto') { // In ES modules document.currentScript is null; use import.meta.url // instead — it resolves to the URL the SDK was actually loaded from. const scriptURL = new URL(import.meta.url); const isSecure = scriptURL.protocol === 'https:'; scriptURL.protocol = isSecure ? 'wss:' : 'ws:'; // Strip /index.js (or any filename) so we connect to the server root. scriptURL.pathname = scriptURL.pathname.replace(/\/[^/]+$/, '/'); this.endpoint = scriptURL; } else { try { this.endpoint = new URL(options.endpoint); } catch { throw new Error('MWSE: endpoint is required and must be a valid URL'); } } if (typeof options.autoReconnect === 'boolean') { this.autoReconnect = options.autoReconnect; } else if (options.autoReconnect) { this.autoReconnect = true; this.autoReconnectTimeout = options.autoReconnect.timeout; } } connect() { if (this.autoReconnectTimer) clearTimeout(this.autoReconnectTimer); this.ws = new WebSocket(this.endpoint.href); this._attachEvents(); } disconnect() { // Prevent auto-reconnect when the caller explicitly closes. this.autoReconnect = false; this.ws.close(); } _attachEvents() { this.ws.addEventListener('open', () => this._onOpen()); this.ws.addEventListener('close', () => this._onClose()); this.ws.addEventListener('error', () => this._onError()); this.ws.addEventListener('message', ({ data }) => this._onMessage(data)); } _onOpen() { this.connected = true; for (const cb of this._activeCallbacks) cb(); } _onClose() { for (const cb of this._passiveCallbacks) cb(); this.connected = false; if (this.autoReconnect) { this.autoReconnectTimer = setTimeout( () => this.connect(), this.autoReconnectTimeout ); } } _onError() { this.connected = false; } _onMessage(data) { // Pass raw wire data to WSTSProtocol; codec.decode() handles parsing. // Pre-parsing here would hand an object to codec.decode which only accepts string/ArrayBuffer. if (typeof data === 'string' || data instanceof ArrayBuffer) { for (const cb of this._packCallbacks) cb(data); } } onRecaivePack(fn) { this._packCallbacks.push(fn); } onActive(fn) { if (this.connected) fn(); else this._activeCallbacks.push(fn); } onPassive(fn) { if (!this.connected) fn(); else this._passiveCallbacks.push(fn); } tranferToServer(data) { if (this.connected) this.ws.send(data); } }