mirror of
https://github.com/Qbix/webserver.git
synced 2026-07-22 07:57:23 +02:00
143 lines
4.4 KiB
JavaScript
143 lines
4.4 KiB
JavaScript
/**
|
|
* QSocket — tiny WebSocket client for Qbix Server.
|
|
*
|
|
* Usage:
|
|
* var qs = new QSocket('ws://localhost:8080/ws/chat');
|
|
*
|
|
* qs.on('chat/message', function(data) {
|
|
* console.log(data.from + ': ' + data.text);
|
|
* });
|
|
*
|
|
* qs.emit('chat/message', {text: 'hello'}, function(ack) {
|
|
* console.log('Server confirmed:', ack);
|
|
* });
|
|
*
|
|
* qs.emit('chat/join', {room: 'lobby'});
|
|
*
|
|
* Protocol (JSON over WebSocket):
|
|
* Client → Server: {"event": "...", "data": {...}, "ack": N}
|
|
* Server → Client: {"event": "...", "data": {...}} (broadcast)
|
|
* Server → Client: {"ack": N, "data": {...}} (callback)
|
|
*/
|
|
(function (root) {
|
|
'use strict';
|
|
|
|
function QSocket(url, options) {
|
|
var self = this;
|
|
options = options || {};
|
|
self._handlers = {};
|
|
self._ackId = 0;
|
|
self._acks = {};
|
|
self._queue = [];
|
|
self._reconnect = options.reconnect !== false;
|
|
self._reconnectDelay = options.reconnectDelay || 1000;
|
|
self._maxDelay = options.maxReconnectDelay || 30000;
|
|
self._url = url;
|
|
|
|
self._connect = function () {
|
|
self.ws = new WebSocket(url);
|
|
|
|
self.ws.onopen = function () {
|
|
self._delay = self._reconnectDelay;
|
|
// Flush queued messages
|
|
while (self._queue.length) {
|
|
self.ws.send(self._queue.shift());
|
|
}
|
|
if (self._handlers['connect']) {
|
|
self._handlers['connect'].forEach(function (fn) { fn(); });
|
|
}
|
|
};
|
|
|
|
self.ws.onmessage = function (e) {
|
|
var msg;
|
|
try { msg = JSON.parse(e.data); } catch (err) { return; }
|
|
|
|
// Ack response (callback from server)
|
|
if (msg.ack !== undefined && self._acks[msg.ack]) {
|
|
self._acks[msg.ack](msg.data);
|
|
delete self._acks[msg.ack];
|
|
return;
|
|
}
|
|
|
|
// Event broadcast
|
|
if (msg.event && self._handlers[msg.event]) {
|
|
self._handlers[msg.event].forEach(function (fn) {
|
|
fn(msg.data);
|
|
});
|
|
}
|
|
};
|
|
|
|
self.ws.onclose = function () {
|
|
if (self._handlers['disconnect']) {
|
|
self._handlers['disconnect'].forEach(function (fn) { fn(); });
|
|
}
|
|
if (self._reconnect) {
|
|
self._delay = Math.min(self._delay * 1.5, self._maxDelay);
|
|
setTimeout(self._connect, self._delay);
|
|
}
|
|
};
|
|
|
|
self.ws.onerror = function () {
|
|
self.ws.close();
|
|
};
|
|
};
|
|
|
|
self._delay = self._reconnectDelay;
|
|
self._connect();
|
|
}
|
|
|
|
/**
|
|
* Listen for an event from the server.
|
|
* Special events: 'connect', 'disconnect'
|
|
*/
|
|
QSocket.prototype.on = function (event, fn) {
|
|
if (!this._handlers[event]) this._handlers[event] = [];
|
|
this._handlers[event].push(fn);
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Remove a listener.
|
|
*/
|
|
QSocket.prototype.off = function (event, fn) {
|
|
if (!this._handlers[event]) return this;
|
|
if (!fn) { delete this._handlers[event]; return this; }
|
|
this._handlers[event] = this._handlers[event].filter(function (f) { return f !== fn; });
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Send an event to the server.
|
|
* Optional callback is invoked when the server acks.
|
|
*/
|
|
QSocket.prototype.emit = function (event, data, callback) {
|
|
var msg = { event: event, data: data || {} };
|
|
if (typeof callback === 'function') {
|
|
msg.ack = ++this._ackId;
|
|
this._acks[msg.ack] = callback;
|
|
}
|
|
var json = JSON.stringify(msg);
|
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
this.ws.send(json);
|
|
} else {
|
|
this._queue.push(json);
|
|
}
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Close the connection (disables auto-reconnect).
|
|
*/
|
|
QSocket.prototype.close = function () {
|
|
this._reconnect = false;
|
|
if (this.ws) this.ws.close();
|
|
};
|
|
|
|
// Export
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = QSocket;
|
|
} else {
|
|
root.QSocket = QSocket;
|
|
}
|
|
})(typeof window !== 'undefined' ? window : this);
|