diff --git a/README.md b/README.md index b9e7151..35302fe 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Object is meta information that describes data point and could content: max/min ### State State is the actual value of the data point and presented by javascript object: -``` +```js { val: VALUE, ack: ACKNOWLEDGED, @@ -69,6 +69,9 @@ It is suggested to use [socket class](https://github.com/ioBroker/socket-client) --> ## Changelog +### **WORK IN PROGRESS** +* (bluefox) Update ws-server library + ### 2.6.2 (2024-06-26) * (bluefox) Corrected call of getObjectView with null parameter diff --git a/gulpfile.js b/gulpfile.js deleted file mode 100644 index ab33d24..0000000 --- a/gulpfile.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -const gulp = require('gulp'); -const fs = require('fs'); - -gulp.task('copySocketIo', done => { - const socket = require.resolve('@iobroker/ws').replace(/\\/g, '/'); - fs.writeFileSync(__dirname + '/lib/socket.io.js', fs.readFileSync(socket)); - done(); -}); - -gulp.task('default', gulp.series('copySocketIo')); \ No newline at end of file diff --git a/lib/socket.io.js b/lib/socket.io.js index 6ee582b..7c46338 100644 --- a/lib/socket.io.js +++ b/lib/socket.io.js @@ -1,445 +1,413 @@ -/*! - * ioBroker WebSockets - * Copyright 2020-2022, bluefox - * Released under the MIT License. - * v 1.1.2 (2022_08_18) - */ -/* jshint -W097 */ -/* jshint strict: false */ -/* jslint node: true */ -/* jshint -W061 */ -'use strict'; - +"use strict"; +var __defProp = Object.defineProperty; +var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; +var __publicField = (obj, key, value) => { + __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); + return value; +}; const MESSAGE_TYPES = { - MESSAGE: 0, - PING: 1, - PONG: 2, - CALLBACK: 3 + MESSAGE: 0, + PING: 1, + PONG: 2, + CALLBACK: 3 }; - const DEBUG = true; - const ERRORS = { - 1000: 'CLOSE_NORMAL', // Successful operation / regular socket shutdown - 1001: 'CLOSE_GOING_AWAY', // Client is leaving (browser tab closing) - 1002: 'CLOSE_PROTOCOL_ERROR', // Endpoint received a malformed frame - 1003: 'CLOSE_UNSUPPORTED', // Endpoint received an unsupported frame (e.g. binary-only endpoint received text frame) - 1005: 'CLOSED_NO_STATUS', // Expected close status, received none - 1006: 'CLOSE_ABNORMAL', // No close code frame has been received - 1007: 'Unsupported payload', // Endpoint received inconsistent message (e.g. malformed UTF-8) - 1008: 'Policy violation', // Generic code used for situations other than 1003 and 1009 - 1009: 'CLOSE_TOO_LARGE', // Endpoint won't process large frame - 1010: 'Mandatory extension', // Client wanted an extension which server did not negotiate - 1011: 'Server error', // Internal server error while operating - 1012: 'Service restart', // Server/service is restarting - 1013: 'Try again later', // Temporary server condition forced blocking client's request - 1014: 'Bad gateway Server', // acting as gateway received an invalid response - 1015: 'TLS handshake fail' // Transport Layer Security handshake failure + 1e3: "CLOSE_NORMAL", + 1001: "CLOSE_GOING_AWAY", + 1002: "CLOSE_PROTOCOL_ERROR", + 1003: "CLOSE_UNSUPPORTED", + 1005: "CLOSED_NO_STATUS", + 1006: "CLOSE_ABNORMAL", + 1007: "Unsupported payload", + 1008: "Policy violation", + 1009: "CLOSE_TOO_LARGE", + 1010: "Mandatory extension", + 1011: "Server error", + 1012: "Service restart", + 1013: "Try again later", + 1014: "Bad gateway Server", + 1015: "TLS handshake fail" }; - -// every time create a new socket -function connect(url, options) { - const socket = new SocketClient(); - socket.connect(url, options); - return socket; -} - -// possible events: connect, disconnect, reconnect, error, connect_error -function SocketClient () { - const handlers = {}; - let wasConnected = false; - let connectTimer = null; - let connectingTimer = null; - let connectionCount = 0; - let callbacks = []; - this.pending = []; // pending requests till connection established - let id = 0; - let lastPong; - let socket; - let url; - let options; - let pingInterval; - let sessionID; - let authTimeout = null; - - this.log = { - debug: text => DEBUG && console.log(`[${new Date().toISOString()}] ${text}`), - warn: text => console.warn(`[${new Date().toISOString()}] ${text}`), - error: text => console.error(`[${new Date().toISOString()}] ${text}`) - }; - - this.getQuery = _url => { - const query = _url.split('?')[1] || ''; - const parts = query.split('&'); - const result = {}; - for (let p = 0; p < parts.length; p++) { - const parts1 = parts[p].split('='); - result[parts1[0]] = parts1[1]; - } - return result; - }; - - this.connect = (_url, _options) => { - this.log.debug('Try to connect'); - - // remove hash - if (_url) { - _url = _url.split('#')[0]; +class SocketClient { + constructor() { + __publicField(this, "connectHandlers", []); + __publicField(this, "reconnectHandlers", []); + __publicField(this, "disconnectHandlers", []); + __publicField(this, "errorHandlers", []); + __publicField(this, "handlers", {}); + __publicField(this, "wasConnected", false); + __publicField(this, "connectTimer", null); + __publicField(this, "connectingTimer", null); + __publicField(this, "connectionCount", 0); + __publicField(this, "callbacks", []); + __publicField(this, "pending", []); + __publicField(this, "id", 0); + __publicField(this, "lastPong", 0); + __publicField(this, "socket", null); + __publicField(this, "url", ""); + __publicField(this, "options", null); + __publicField(this, "pingInterval", null); + __publicField(this, "sessionID", 0); + __publicField(this, "authTimeout", null); + __publicField(this, "connected", false); + __publicField(this, "log"); + __publicField(this, "emit", (name, ...args) => { + if (!this.socket || !this.connected) { + if (!this.wasConnected) { + this.pending.push({ name, args }); + } else { + this.log.warn("Not connected"); } - - id = 0; - connectTimer && clearInterval(connectTimer); - connectTimer = null; - - // eslint-disable-next-line no-undef - url = url || _url || window.location.href; - options = options || JSON.parse(JSON.stringify(_options || {})); - - options.pongTimeout = parseInt(options.pongTimeout, 10) || 60000; // Timeout for answer for ping (pong) - options.pingInterval = parseInt(options.pingInterval, 10) || 5000; // Ping interval - options.connectTimeout = parseInt(options.connectTimeout, 10) || 3000; // connection request timeout - options.authTimeout = parseInt(options.authTimeout, 10) || 3000; // Authentication timeout - options.connectInterval = parseInt(options.connectInterval, 10) || 1000; // Interval between connection attempts - options.connectMaxAttempt = parseInt(options.connectMaxAttempt, 10) || 5; // Every connection attempt the interval increasing at options.connectInterval till max this number - - sessionID = Date.now(); - try { - if (url === '/') { - let parts = window.location.pathname.split('/'); - // remove filename - if (window.location.pathname.endsWith('.html') || window.location.pathname.endsWith('.htm')) { - parts.pop(); - } - - // eslint-disable-next-line no-undef - url = window.location.protocol + '//' + window.location.host + '/' + parts.join('/'); - } - - // extract all query attributes - const query = this.getQuery(url); - if (query.sid) { - delete query.sid; - } - - if (query.hasOwnProperty('')) { - delete query['']; - } - - let u = url.replace(/^http/, 'ws').split('?')[0] + '?sid=' + sessionID; - - // Apply query to new url - if (Object.keys(query).length) { - u += '&' + Object.keys(query).map(attr => query[attr] === undefined ? attr : attr + '=' + query[attr]).join('&'); - } - - if (options && options.name && !query.name) { - u += '&name=' + encodeURIComponent(options.name); - } - // "ws://www.example.com/socketserver" - // eslint-disable-next-line no-undef - socket = new WebSocket(u); - } catch (error) { - handlers.error && handlers.error.forEach(cb => cb.call(this, error)); - return this.close(); + return; + } + this.id++; + if (name === "writeFile" && args && typeof args[2] !== "string" && args[2]) { + let binary = ""; + const bytes = new Uint8Array(args[2]); + const len = bytes.byteLength; + for (let i = 0; i < len; i++) { + binary += String.fromCharCode(bytes[i]); } - - connectingTimer = setTimeout(() => { - connectingTimer = null; - this.log.warn('No READY flag received in 3 seconds. Re-init'); - this.close(); // re-init connection, because no ___ready___ received in 2000 ms - }, options.connectTimeout); - - socket.onopen = ()/*event*/ => { - lastPong = Date.now(); - connectionCount = 0; - - pingInterval = setInterval(() => { - if (Date.now() - lastPong > options.pingInterval - 10) { - try { - socket.send(JSON.stringify([MESSAGE_TYPES.PING])); - } catch (e) { - this.log.warn('Cannot send ping. Close connection: ' + e); - this.close(); - return this._garbageCollect(); - } - } - if (Date.now() - lastPong > options.pongTimeout) { - this.close(); - } - this._garbageCollect(); - }, options.pingInterval); - }; - - socket.onclose = event => { - if (event.code === 3001) { - this.log.warn('ws closed'); - } else { - this.log.error('ws connection error: ' + ERRORS[event.code]); - } - this.close(); - }; - - socket.onerror = error => { - if (this.connected) { - if (socket.readyState === 1) { - this.log.error('ws normal error: ' + error.type); - } - handlers.error && handlers.error.forEach(cb => cb.call(this, ERRORS[error.code] || 'UNKNOWN')); - } - this.close(); - }; - - socket.onmessage = message => { - lastPong = Date.now(); - if (!message || !message.data || typeof message.data !== 'string') { - return console.error('Received invalid message: ' + JSON.stringify(message)); - } - let data; - try { - data = JSON.parse(message.data); - } catch (e) { - return console.error('Received invalid message: ' + JSON.stringify(message.data)); - } - - const [type, id, name, args] = data; - - if (authTimeout) { - clearTimeout(authTimeout); - authTimeout = null; - } - - if (type === MESSAGE_TYPES.CALLBACK) { - this.findAnswer(id, args); - } else - if (type === MESSAGE_TYPES.MESSAGE) { - if (name === '___ready___') { - this.connected = true; - - if (wasConnected) { - handlers.reconnect && handlers.reconnect.forEach(cb => cb.call(this, true)); - } else { - handlers.connect && handlers.connect.forEach(cb => cb.call(this, true)); - wasConnected = true; - } - - connectingTimer && clearTimeout(connectingTimer); - connectingTimer = null; - - // resend all pending requests - if (this.pending.length) { - this.pending.forEach(([name, arg1, arg2, arg3, arg4, arg5]) => - this.emit(name, arg1, arg2, arg3, arg4, arg5)); - - this.pending = []; - } - - } else if (args) { - handlers[name] && handlers[name].forEach(cb => cb.call(this, args[0], args[1], args[2], args[3], args[4])); - } else { - handlers[name] && handlers[name].forEach(cb => cb.call(this)); - } - } else if (type === MESSAGE_TYPES.PING) { - if (socket) { - socket.send(JSON.stringify([MESSAGE_TYPES.PONG])); - } else { - this.log.warn('Cannot do pong: connection closed'); - } - } else if (type === MESSAGE_TYPES.PONG) { - // lastPong saved - } else { - this.log.warn('Received unknown message type: ' + type); - } - }; - - return this; - }; - - this._garbageCollect = () => { - const now = Date.now(); - let empty = 0; - if (!DEBUG) { - for (let i = 0; i < callbacks.length; i++) { - if (callbacks[i]) { - if (callbacks[i].ts > now) { - const cb = callbacks[i].cb; - setTimeout(cb, 0, 'timeout'); - callbacks[i] = null; - empty++; - } - } else { - empty++; - } - } - } - - // remove nulls - if (empty > callbacks.length / 2) { - const newCallback = []; - for (let i = 0; i < callbacks.length; i++) { - callbacks[i] && newCallback.push(callbacks[i]); - } - callbacks = newCallback; + args[2] = window.btoa(binary); + } + try { + if (args && typeof args[args.length - 1] === "function") { + const _args = [...args]; + const eventHandler = _args.pop(); + this.withCallback(name, this.id, _args, eventHandler); + } else if (!(args == null ? void 0 : args.length)) { + this.socket.send(JSON.stringify([MESSAGE_TYPES.MESSAGE, this.id, name])); + } else { + this.socket.send(JSON.stringify([MESSAGE_TYPES.MESSAGE, this.id, name, args])); } + } catch (e) { + console.error(`Cannot send: ${e}`); + this.close(); + } + }); + __publicField(this, "disconnect", this.close); + this.log = { + debug: (text) => DEBUG && console.log(`[${new Date().toISOString()}] ${text}`), + warn: (text) => console.warn(`[${new Date().toISOString()}] ${text}`), + error: (text) => console.error(`[${new Date().toISOString()}] ${text}`) }; - - this.withCallback = (name, id, args, cb) => { - if (name === 'authenticate') { - authTimeout = setTimeout(() => { - authTimeout = null; - if (this.connected) { - this.log.debug('Authenticate timeout'); - handlers.error && handlers.error.forEach(cb => cb.call(this, 'Authenticate timeout')); - } - this.close(); - }, options.authTimeout); + } + static getQuery(_url) { + const query = _url.split("?")[1] || ""; + const parts = query.split("&"); + const result = {}; + for (let p = 0; p < parts.length; p++) { + const parts1 = parts[p].split("="); + result[parts1[0]] = decodeURIComponent(parts[1]); + } + return result; + } + connect(url, options) { + var _a, _b; + this.log.debug("Try to connect"); + if (url) { + url = url.split("#")[0]; + } + this.id = 0; + this.connectTimer && clearInterval(this.connectTimer); + this.connectTimer = null; + this.url = this.url || url || window.location.href; + this.options = this.options || JSON.parse(JSON.stringify(options || {})); + if (!this.options) { + throw new Error("No options provided!"); + } + this.options.pongTimeout = parseInt(this.options.pongTimeout, 10) || 6e4; + this.options.pingInterval = parseInt(this.options.pingInterval, 10) || 5e3; + this.options.connectTimeout = parseInt(this.options.connectTimeout, 10) || 3e3; + this.options.authTimeout = parseInt(this.options.authTimeout, 10) || 3e3; + this.options.connectInterval = parseInt(this.options.connectInterval, 10) || 1e3; + this.options.connectMaxAttempt = parseInt(this.options.connectMaxAttempt, 10) || 5; + this.sessionID = Date.now(); + try { + if (this.url === "/") { + let parts = window.location.pathname.split("/"); + if (window.location.pathname.endsWith(".html") || window.location.pathname.endsWith(".htm")) { + parts.pop(); } - callbacks.push({id, cb, ts: DEBUG ? 0 : Date.now() + 30000}); - socket.send(JSON.stringify([MESSAGE_TYPES.CALLBACK, id, name, args])); - }; - - this.findAnswer = (id, args) => { - for (let i = 0; i < callbacks.length; i++) { - if (callbacks[i] && callbacks[i].id === id) { - const cb = callbacks[i].cb; - cb.apply(null, args); - callbacks[i] = null; - } + this.url = `${window.location.protocol}//${window.location.host}/${parts.join("/")}`; + } + const query = SocketClient.getQuery(this.url); + if (query.sid) { + delete query.sid; + } + if (query.hasOwnProperty("")) { + delete query[""]; + } + let u = `${this.url.replace(/^http/, "ws").split("?")[0]}?sid=${this.sessionID}`; + if (Object.keys(query).length) { + u += `&${Object.keys(query).map((attr) => query[attr] === void 0 ? attr : `${attr}=${query[attr]}`).join("&")}`; + } + if (((_a = this.options) == null ? void 0 : _a.name) && !query.name) { + u += `&name=${encodeURIComponent(this.options.name)}`; + } + this.socket = new WebSocket(u); + } catch (error) { + (_b = this.handlers.error) == null ? void 0 : _b.forEach((cb) => cb.call(this, error)); + this.close(); + return this; + } + this.connectingTimer = setTimeout(() => { + this.connectingTimer = null; + this.log.warn("No READY flag received in 3 seconds. Re-init"); + this.close(); + }, this.options.connectTimeout); + this.socket.onopen = () => { + var _a2; + this.lastPong = Date.now(); + this.connectionCount = 0; + this.pingInterval = setInterval(() => { + var _a3, _b2, _c; + if (!this.options) { + throw new Error("No options provided!"); } - }; - - this.emit = (name, arg1, arg2, arg3, arg4, arg5) => { - if (!socket || !this.connected) { - if (!wasConnected) { - // cache all calls till connected - this.pending.push([name, arg1, arg2, arg3, arg4, arg5]); - } else { - this.log.warn('Not connected'); - } + if (Date.now() - this.lastPong > (((_a3 = this.options) == null ? void 0 : _a3.pingInterval) || 5e3) - 10) { + try { + (_b2 = this.socket) == null ? void 0 : _b2.send(JSON.stringify([MESSAGE_TYPES.PING])); + } catch (e) { + this.log.warn(`Cannot send ping. Close connection: ${e}`); + this.close(); + this._garbageCollect(); return; + } } - - id++; - - if (name === 'writeFile' && typeof arg3 !== 'string' && arg3) { - // Arguments: arg1, arg2, arg3, arg4 - // Arguments: _adapter, filename, data, callback - let binary = ''; - const bytes = new Uint8Array(arg3); - const len = bytes.byteLength; - for (let i = 0; i < len; i++) { - binary += String.fromCharCode(bytes[i]); - } - arg3 = window.btoa(binary); - } - - try { - if (typeof arg5 === 'function') { - this.withCallback(name, id, [arg1, arg2, arg3, arg4], arg5); - } else if (typeof arg4 === 'function') { - this.withCallback(name, id, [arg1, arg2, arg3], arg4); - } else if (typeof arg3 === 'function') { - this.withCallback(name, id, [arg1, arg2], arg3); - } else if (typeof arg2 === 'function') { - this.withCallback(name, id, [arg1], arg2); - } else if (typeof arg1 === 'function') { - this.withCallback(name, id, [], arg1); - } else - if (arg1 === undefined && arg2 === undefined && arg3 === undefined && arg4 === undefined && arg5 === undefined) { - socket.send(JSON.stringify([MESSAGE_TYPES.MESSAGE, id, name])); - } else if (arg2 === undefined && arg3 === undefined && arg4 === undefined && arg5 === undefined) { - socket.send(JSON.stringify([MESSAGE_TYPES.MESSAGE, id, name, [arg1]])); - } else if (arg3 === undefined && arg4 === undefined && arg5 === undefined) { - socket.send(JSON.stringify([MESSAGE_TYPES.MESSAGE, id, name, [arg1, arg2]])); - } else if (arg4 === undefined && arg5 === undefined) { - socket.send(JSON.stringify([MESSAGE_TYPES.MESSAGE, id, name, [arg1, arg2, arg3]])); - } else if (arg5 === undefined) { - socket.send(JSON.stringify([MESSAGE_TYPES.MESSAGE, id, name, [arg1, arg2, arg3, arg4]])); - } else { - socket.send(JSON.stringify([MESSAGE_TYPES.MESSAGE, id, name, [arg1, arg2, arg3, arg4, arg5]])); - } - } catch (e) { - console.error('Cannot send: ' + e); - this.close(); + if (Date.now() - this.lastPong > (((_c = this.options) == null ? void 0 : _c.pongTimeout) || 6e4)) { + this.close(); } + this._garbageCollect(); + }, ((_a2 = this.options) == null ? void 0 : _a2.pingInterval) || 5e3); + }; + this.socket.onclose = (event) => { + if (event.code === 3001) { + this.log.warn("ws closed"); + } else { + this.log.error(`ws connection error: ${ERRORS[event.code]}`); + } + this.close(); }; - - this.on = (name, cb) => { - if (cb) { - handlers[name] = handlers[name] || []; - handlers[name].push(cb); + this.socket.onerror = (error) => { + if (this.connected && this.socket) { + if (this.socket.readyState === 1) { + this.log.error(`ws normal error: ${error.type}`); } + this.errorHandlers.forEach((cb) => cb.call(this, ERRORS[error.code] || "UNKNOWN")); + } + this.close(); }; - - this.off = (name, cb) => { - if (handlers[name]) { - const pos = handlers[name].indexOf(cb); - if (pos !== -1) { - handlers[name].splice(pos, 1); - if (!handlers[name].length) { - delete handlers[name]; - } - } + this.socket.onmessage = (message) => { + var _a2, _b2; + this.lastPong = Date.now(); + if (!(message == null ? void 0 : message.data) || typeof message.data !== "string") { + console.error(`Received invalid message: ${JSON.stringify(message)}`); + return; + } + let data; + try { + data = JSON.parse(message.data); + } catch (e) { + console.error(`Received invalid message: ${JSON.stringify(message.data)}`); + return; + } + const type = data[0]; + const id = data[1]; + const name = data[2]; + const args = data[3]; + if (this.authTimeout) { + clearTimeout(this.authTimeout); + this.authTimeout = null; + } + if (type === MESSAGE_TYPES.CALLBACK) { + this.findAnswer(id, args); + } else if (type === MESSAGE_TYPES.MESSAGE) { + if (name === "___ready___") { + this.connected = true; + if (this.wasConnected) { + this.reconnectHandlers.forEach((cb) => cb.call(this, true)); + } else { + this.connectHandlers.forEach((cb) => cb.call(this, true)); + this.wasConnected = true; + } + this.connectingTimer && clearTimeout(this.connectingTimer); + this.connectingTimer = null; + if (this.pending.length) { + this.pending.forEach(({ name: name2, args: args2 }) => this.emit(name2, ...args2)); + this.pending = []; + } + } else if (args) { + (_a2 = this.handlers[name]) == null ? void 0 : _a2.forEach((cb) => cb.apply(this, args)); + } else { + (_b2 = this.handlers[name]) == null ? void 0 : _b2.forEach((cb) => cb.call(this)); + } + } else if (type === MESSAGE_TYPES.PING) { + if (this.socket) { + this.socket.send(JSON.stringify([MESSAGE_TYPES.PONG])); + } else { + this.log.warn("Cannot do pong: connection closed"); } + } else if (type === MESSAGE_TYPES.PONG) { + } else { + this.log.warn(`Received unknown message type: ${type}`); + } }; - - this.close = function () { - pingInterval && clearInterval(pingInterval); - pingInterval = null; - - authTimeout && clearTimeout(authTimeout); - authTimeout = null; - - connectingTimer && clearTimeout(connectingTimer); - connectingTimer = null; - - if (socket) { - try { - socket.close(); - } catch (e) { - // ignore - } - socket = null; + return this; + } + _garbageCollect() { + const now = Date.now(); + let empty = 0; + if (!DEBUG) { + for (let i = 0; i < this.callbacks.length; i++) { + const callback = this.callbacks[i]; + if (callback) { + if (callback.ts > now) { + const cb = callback.cb; + setTimeout(cb, 0, "timeout"); + this.callbacks[i] = null; + empty++; + } + } else { + empty++; } - + } + } + if (empty > this.callbacks.length / 2) { + const newCallback = []; + for (let i = 0; i < this.callbacks.length; i++) { + this.callbacks[i] && newCallback.push(this.callbacks[i]); + } + this.callbacks = newCallback; + } + } + withCallback(name, id, args, cb) { + var _a, _b; + if (name === "authenticate") { + this.authTimeout = setTimeout(() => { + var _a2; + this.authTimeout = null; if (this.connected) { - handlers.disconnect && handlers.disconnect.forEach(cb => cb.call(this)); - this.connected = false; + this.log.debug("Authenticate timeout"); + (_a2 = this.handlers.error) == null ? void 0 : _a2.forEach((cb2) => cb2.call(this, "Authenticate timeout")); } - - callbacks = []; - - this._reconnect(); - return this; - }; - - // alias for back compatibility - this.disconnect = this.close; - - this.destroy = function () { this.close(); - connectTimer && clearTimeout(connectTimer); - connectTimer = null; - }; - - this._reconnect = function () { - if (!connectTimer) { - this.log.debug('Start reconnect ' + connectionCount); - connectTimer = setTimeout(() => { - connectTimer = null; - if (connectionCount < options.connectMaxAttempt) { - connectionCount++; - } - this.connect(url, options); - }, connectionCount * options.connectInterval); - } else { - this.log.debug('Reconnect is already running ' + connectionCount); + }, ((_a = this.options) == null ? void 0 : _a.authTimeout) || 3e3); + } + this.callbacks.push({ id, cb, ts: DEBUG ? 0 : Date.now() + 3e4 }); + (_b = this.socket) == null ? void 0 : _b.send(JSON.stringify([MESSAGE_TYPES.CALLBACK, id, name, args])); + } + findAnswer(id, args) { + for (let i = 0; i < this.callbacks.length; i++) { + const callback = this.callbacks[i]; + if ((callback == null ? void 0 : callback.id) === id) { + const cb = callback.cb; + cb.apply(null, args); + this.callbacks[i] = null; + } + } + } + on(name, cb) { + if (cb) { + if (name === "connect") { + this.connectHandlers.push(cb); + } else if (name === "disconnect") { + this.disconnectHandlers.push(cb); + } else if (name === "reconnect") { + this.reconnectHandlers.push(cb); + } else if (name === "error") { + this.errorHandlers.push(cb); + } else { + this.handlers[name] = this.handlers[name] || []; + this.handlers[name].push(cb); + } + } + } + off(name, cb) { + if (name === "connect") { + const pos = this.connectHandlers.indexOf(cb); + if (pos !== -1) { + this.connectHandlers.splice(pos, 1); + } + } else if (name === "disconnect") { + const pos = this.disconnectHandlers.indexOf(cb); + if (pos !== -1) { + this.disconnectHandlers.splice(pos, 1); + } + } else if (name === "reconnect") { + const pos = this.reconnectHandlers.indexOf(cb); + if (pos !== -1) { + this.reconnectHandlers.splice(pos, 1); + } + } else if (name === "error") { + const pos = this.errorHandlers.indexOf(cb); + if (pos !== -1) { + this.errorHandlers.splice(pos, 1); + } + } else if (this.handlers[name]) { + const pos = this.handlers[name].indexOf(cb); + if (pos !== -1) { + this.handlers[name].splice(pos, 1); + if (!this.handlers[name].length) { + delete this.handlers[name]; } - }; - - this.connected = false; // simulate socket.io interface + } + } + } + close() { + this.pingInterval && clearInterval(this.pingInterval); + this.pingInterval = null; + this.authTimeout && clearTimeout(this.authTimeout); + this.authTimeout = null; + this.connectingTimer && clearTimeout(this.connectingTimer); + this.connectingTimer = null; + if (this.socket) { + try { + this.socket.close(); + } catch (e) { + } + this.socket = null; + } + if (this.connected) { + this.disconnectHandlers.forEach((cb) => cb.call(this)); + this.connected = false; + } + this.callbacks = []; + this._reconnect(); + return this; + } + destroy() { + this.close(); + this.connectTimer && clearTimeout(this.connectTimer); + this.connectTimer = null; + } + _reconnect() { + var _a; + if (!this.connectTimer) { + this.log.debug(`Start reconnect ${this.connectionCount}`); + this.connectTimer = setTimeout(() => { + var _a2; + if (!this.options) { + throw new Error("No options provided!"); + } + this.connectTimer = null; + if (this.connectionCount < (((_a2 = this.options) == null ? void 0 : _a2.connectMaxAttempt) || 5)) { + this.connectionCount++; + } + this.connect(this.url, this.options); + }, this.connectionCount * (((_a = this.options) == null ? void 0 : _a.connectInterval) || 1e3)); + } else { + this.log.debug(`Reconnect is already running ${this.connectionCount}`); + } + } +} +function connect(url, options) { + const socketClient = new SocketClient(); + socketClient.connect(url, options); + return socketClient; } - -// eslint-disable-next-line no-undef window.io = { - connect: connect + connect }; +//# sourceMappingURL=socket.io.js.map diff --git a/package.json b/package.json index a1e3836..1105c47 100644 --- a/package.json +++ b/package.json @@ -19,11 +19,11 @@ "node": ">=18" }, "dependencies": { - "@iobroker/adapter-core": "^3.1.6", - "@iobroker/socket-classes": "1.5.6", - "@iobroker/webserver": "^1.0.3", - "@iobroker/ws-server": "^2.1.2", - "express-session": "^1.18.0" + "@iobroker/adapter-core": "^3.2.2", + "@iobroker/socket-classes": "1.6.1", + "@iobroker/webserver": "^1.0.6", + "@iobroker/ws-server": "^4.1.0", + "express-session": "^1.18.1" }, "devDependencies": { "@alcalzone/release-script": "^3.8.0", @@ -31,12 +31,11 @@ "@alcalzone/release-script-plugin-license": "^3.7.0", "@foxriver76/eslint-config": "^1.0.5", "@iobroker/adapter-dev": "^1.3.0", - "@iobroker/legacy-testing": "^1.0.13", - "@iobroker/testing": "^4.1.3", - "@iobroker/ws": "^1.1.2", + "@iobroker/legacy-testing": "^2.0.1", + "@iobroker/testing": "^5.0.0", + "@iobroker/ws": "^2.0.0", "chai": "^4.5.0", - "gulp": "^4.0.2", - "mocha": "^10.7.0" + "mocha": "^10.8.2" }, "bugs": { "url": "https://github.com/ioBroker/ioBroker.ws/issues" @@ -50,8 +49,8 @@ "main.js" ], "scripts": { - "test": "node node_modules/mocha/bin/mocha --exit", - "build": "node node_modules/gulp/bin/gulp.js", + "test": "mocha --exit", + "build": "node tasks", "release": "release-script", "release-patch": "release-script patch --yes", "release-minor": "release-script minor --yes", diff --git a/tasks.js b/tasks.js new file mode 100644 index 0000000..57b7d3b --- /dev/null +++ b/tasks.js @@ -0,0 +1,4 @@ +const { writeFileSync, readFileSync } = require('fs'); + +const socket = require.resolve('@iobroker/ws').replace(/\\/g, '/'); +writeFileSync(`${__dirname}/lib/socket.io.js`, readFileSync(socket));