UNPKG

iobroker.cloudless-homeconnect

Version:
231 lines (190 loc) 6.72 kB
(function (g) { // @ts-ignore // eslint-disable-next-line no-undef typeof exports === "object" && typeof module !== "undefined" ? (module.exports = pws) : ((g ? g : self).pws = pws); function pws(url, protocols, WebSocket, options) { if (typeof protocols === "function") { if (typeof WebSocket === "object") options = WebSocket; WebSocket = protocols; protocols = undefined; } if (!Array.isArray(protocols) && typeof protocols === "object") { options = protocols; protocols = undefined; } if (typeof WebSocket === "object") { options = WebSocket; WebSocket = undefined; } if (!WebSocket) throw new Error("Please supply a websocket library to use"); if (!options) options = {}; let connection = null, reconnecting = 0, reconnectTimer = null, heartbeatTimer = null, openTimer = null, binaryType = null, closed = false, reconnectDelay = 0, attempts = 0; const listeners = {}; const listenerHandlers = {}; const ons = {}; const onHandlers = {}; const pws = { CONNECTING: 0, OPEN: 1, CLOSING: 2, CLOSED: 3, get readyState() { return connection ? connection.readyState : 0; }, get protocol() { return connection ? connection.protocol : ""; }, get extensions() { return connection ? connection.extensions : ""; }, get bufferedAmount() { return connection ? connection.bufferedAmount : 0; }, get binaryType() { return connection ? connection.binaryType : "blob"; }, set binaryType(type) { binaryType = type; connection && (connection.binaryType = type); }, connect, url, retries: 0, pingTimeout: "pingTimeout" in options ? options.pingTimeout : false, maxTimeout: options.maxTimeout || 5 * 60 * 1000, maxRetries: options.maxRetries || 0, nextReconnectDelay: options.nextReconnectDelay || function reconnectTimeout(retries) { return Math.min((1 + Math.random()) * Math.pow(1.5, retries) * 1000, pws.maxTimeout); }, send: function () { if (!connection) throw new Error("InvalidAccessError"); connection.send.apply(connection, arguments); }, close: function () { clearTimeout(reconnectTimer); closed = true; connection && connection.close.apply(connection, arguments); }, onopen: options.onopen, onmessage: options.onmessage, onclose: options.onclose, onerror: options.onerror, options, }; const on = (method, events, handlers) => (event, fn, options) => { function handler(e) { options && options.once && connection[method === "on" ? "off" : "removeEventListener"](event, handler); e && typeof e === "object" && reconnectDelay && (e.reconnectDelay = reconnectDelay); fn.apply(pws, arguments); } event in events ? events[event].push(fn) : (events[event] = [fn]); event in handlers ? handlers[event].push(handler) : (handlers[event] = [handler]); connection && connection[method](event, handler); }; const off = (method, events, handlers) => (event, fn) => { const index = events[event].indexOf(fn); if (index === -1) return; connection && connection[method](event, handlers[event][index]); events[event].splice(index, 1); handlers[event].splice(index, 1); }; pws.addEventListener = on("addEventListener", listeners, listenerHandlers); pws.removeEventListener = off("removeEventListener", listeners, listenerHandlers); pws.on = on("on", ons, onHandlers); pws.off = off("off", ons, onHandlers); pws.once = (event, fn) => pws.on(event, fn, { once: true }); url && Promise.resolve().then(connect); return pws; async function connect(url) { const attempt = ++attempts; closed = false; reconnecting = 0; clearTimeout(reconnectTimer); if (connection && connection.readyState !== pws.CLOSED) { close(4665, "Manual connect initiated"); return connect(url); } url && (pws.url = url); url = typeof pws.url === "function" ? await pws.url(pws) : pws.url; if (attempt !== attempts) return; connection = new WebSocket(url, protocols, options); typeof connection.on === "function" ? connection.on("error", onerror) : (connection.onerror = onerror); connection.onclose = onclose; connection.onopen = onopen; connection.onmessage = onmessage; Object.keys(listenerHandlers).forEach((event) => { listenerHandlers[event].forEach((handler) => connection.addEventListener(event, handler)); }); Object.keys(onHandlers).forEach((event) => { onHandlers[event].forEach((handler) => connection.on(event, handler)); }); if (binaryType) connection.binaryType = binaryType; } function onclose(event) { event.reconnectDelay = reconnect(); pws.onclose && pws.onclose.apply(pws, arguments); clearTimeout(heartbeatTimer); clearTimeout(openTimer); } function onerror() { pws.onerror && pws.onerror.apply(pws, arguments); } function onopen() { pws.onopen && pws.onopen.apply(pws, arguments); heartbeat(); openTimer = setTimeout(() => (pws.retries = 0), reconnectDelay || 0); } function onmessage() { pws.onmessage && pws.onmessage.apply(pws, arguments); heartbeat(); } function heartbeat() { if (!pws.pingTimeout) return; clearTimeout(heartbeatTimer); heartbeatTimer = setTimeout(timedOut, pws.pingTimeout); } function timedOut() { close(4663, "No heartbeat received within " + pws.pingTimeout + "ms"); } function reconnect() { if (closed) return; if (reconnecting) return reconnectDelay - (Date.now() - reconnecting); if (pws.maxRetries && pws.retries >= pws.maxRetries) return; reconnecting = Date.now(); reconnectDelay = Math.ceil(pws.nextReconnectDelay(pws.retries++)); reconnectTimer = setTimeout(connect, reconnectDelay); return reconnectDelay; } function close(code, reason) { connection.onclose = connection.onopen = connection.onerror = connection.onmessage = null; Object.keys(listenerHandlers).forEach((event) => { listenerHandlers[event].forEach((handler) => connection.removeEventListener(event, handler)); }); Object.keys(onHandlers).forEach((event) => { onHandlers[event].forEach((handler) => connection.removeListener(event, handler)); }); connection.close(); connection = null; const event = closeEvent(code, reason); onclose(event); listenerHandlers.close && listenerHandlers.close.forEach((handler) => handler(event)); onHandlers.close && onHandlers.close.forEach((handler) => handler(code, reason, reconnectDelay)); } function closeEvent(code, reason) { const event = new Error("HeartbeatTimeout"); event["code"] = code; event["reason"] = reason; return event; } } })(this);