108 lines
3.4 KiB
JavaScript
108 lines
3.4 KiB
JavaScript
// 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) {
|
|
if (typeof data === 'string') {
|
|
const parsed = JSON.parse(data);
|
|
for (const cb of this._packCallbacks) cb(parsed);
|
|
} else if (data instanceof ArrayBuffer) {
|
|
// Binary frame — passed raw to WSTSProtocol for codec decoding.
|
|
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);
|
|
}
|
|
}
|