From 35448f386687030e7b68bd88f5f4852fbb833c9d Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Mon, 4 Dec 2023 17:30:05 +0100 Subject: [PATCH] fix(browser): use worker timers to prevent unexpected client close (#1753) --- examples/vite-example/src/App.vue | 7 +- package-lock.json | 151 ++++++++++++++++++++++++++---- package.json | 1 + src/lib/PingTimer.ts | 39 ++++++++ src/lib/client.ts | 23 ++--- src/lib/shared.ts | 5 + src/mqtt.ts | 1 - 7 files changed, 189 insertions(+), 38 deletions(-) create mode 100644 src/lib/PingTimer.ts diff --git a/examples/vite-example/src/App.vue b/examples/vite-example/src/App.vue index 668c34a70..98030924c 100644 --- a/examples/vite-example/src/App.vue +++ b/examples/vite-example/src/App.vue @@ -2,11 +2,12 @@ import { ref } from 'vue' import mqtt from 'mqtt' -console.log('mqtt', mqtt) - const connected = ref(false) -const client = mqtt.connect('wss://test.mosquitto.org:8081'); +const client = mqtt.connect('wss://test.mosquitto.org:8081', { + log: console.log.bind(console), + keepalive: 30, +}); const messages = ref([]) diff --git a/package-lock.json b/package-lock.json index 08f336b41..ca87427c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "reinterval": "^1.1.0", "rfdc": "^1.3.0", "split2": "^4.2.0", + "worker-timers": "^7.0.78", "ws": "^8.14.2" }, "bin": { @@ -1082,6 +1083,17 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/runtime": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz", + "integrity": "sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.22.15", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", @@ -4470,6 +4482,12 @@ "node": ">= 6" } }, + "node_modules/aedes/node_modules/retimer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/retimer/-/retimer-3.0.0.tgz", + "integrity": "sha512-WKE0j11Pa0ZJI5YIk0nflGI7SQsfl2ljihVy7ogh7DeQSeYAUi0ubZ/yEueGtDfUPk6GH5LRw1hBdLq4IwUBWA==", + "dev": true + }, "node_modules/aedes/node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -7858,6 +7876,18 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "dev": true }, + "node_modules/fast-unique-numbers": { + "version": "8.0.11", + "resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-8.0.11.tgz", + "integrity": "sha512-FmTi6tqMymufGgYEGKWez0I3Ji9ykhHjVzhqhPFOQ3j+IM6Y4PMo+Q17BVCKmCjK6QiFJ+YVINaYScOyFrkzew==", + "dependencies": { + "@babel/runtime": "^7.23.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.1.0" + } + }, "node_modules/fast-url-parser": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", @@ -13836,6 +13866,11 @@ "node": ">=4" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", @@ -14210,12 +14245,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/retimer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/retimer/-/retimer-3.0.0.tgz", - "integrity": "sha512-WKE0j11Pa0ZJI5YIk0nflGI7SQsfl2ljihVy7ogh7DeQSeYAUi0ubZ/yEueGtDfUPk6GH5LRw1hBdLq4IwUBWA==", - "dev": true - }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -15966,10 +15995,9 @@ } }, "node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tsscmp": { "version": "1.0.6", @@ -16681,6 +16709,37 @@ "node": ">=12.17" } }, + "node_modules/worker-timers": { + "version": "7.0.78", + "resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-7.0.78.tgz", + "integrity": "sha512-jATkwi6OFe2XwfvoPYl3hr8k19/JgCiFerJ4ZWv5AbMIjsUnayC1C8Dxau/sevqhzCrU4IqXDFDTjNINGNuibQ==", + "dependencies": { + "@babel/runtime": "^7.23.4", + "tslib": "^2.6.2", + "worker-timers-broker": "^6.0.98", + "worker-timers-worker": "^7.0.62" + } + }, + "node_modules/worker-timers-broker": { + "version": "6.0.98", + "resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-6.0.98.tgz", + "integrity": "sha512-Kq8dTnHBoBGXdMYHGYAtZ+gbxdsV085PF3QoxFZMDKvqPlyTuRFGQvc70k9fHeGOCFlUv6OUREvYF72aquBOFw==", + "dependencies": { + "@babel/runtime": "^7.23.4", + "fast-unique-numbers": "^8.0.11", + "tslib": "^2.6.2", + "worker-timers-worker": "^7.0.62" + } + }, + "node_modules/worker-timers-worker": { + "version": "7.0.62", + "resolved": "https://registry.npmjs.org/worker-timers-worker/-/worker-timers-worker-7.0.62.tgz", + "integrity": "sha512-WuVhWXWEqwcWD2Iy9tNQWQyfl984XF/hoblazHp4KOWiIN50B2PH0l61nBkJUndFhJ9qca0lEZ7SWSqdIMDWDQ==", + "dependencies": { + "@babel/runtime": "^7.23.4", + "tslib": "^2.6.2" + } + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -17786,6 +17845,14 @@ "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true }, + "@babel/runtime": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz", + "integrity": "sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==", + "requires": { + "regenerator-runtime": "^0.14.0" + } + }, "@babel/template": { "version": "7.22.15", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", @@ -20212,6 +20279,12 @@ "util-deprecate": "^1.0.1" } }, + "retimer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/retimer/-/retimer-3.0.0.tgz", + "integrity": "sha512-WKE0j11Pa0ZJI5YIk0nflGI7SQsfl2ljihVy7ogh7DeQSeYAUi0ubZ/yEueGtDfUPk6GH5LRw1hBdLq4IwUBWA==", + "dev": true + }, "uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -22875,6 +22948,15 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "dev": true }, + "fast-unique-numbers": { + "version": "8.0.11", + "resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-8.0.11.tgz", + "integrity": "sha512-FmTi6tqMymufGgYEGKWez0I3Ji9ykhHjVzhqhPFOQ3j+IM6Y4PMo+Q17BVCKmCjK6QiFJ+YVINaYScOyFrkzew==", + "requires": { + "@babel/runtime": "^7.23.4", + "tslib": "^2.6.2" + } + }, "fast-url-parser": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", @@ -27455,6 +27537,11 @@ "redis-errors": "^1.0.0" } }, + "regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + }, "regexp.prototype.flags": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", @@ -27726,12 +27813,6 @@ } } }, - "retimer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/retimer/-/retimer-3.0.0.tgz", - "integrity": "sha512-WKE0j11Pa0ZJI5YIk0nflGI7SQsfl2ljihVy7ogh7DeQSeYAUi0ubZ/yEueGtDfUPk6GH5LRw1hBdLq4IwUBWA==", - "dev": true - }, "retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -29090,10 +29171,9 @@ } }, "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "tsscmp": { "version": "1.0.6", @@ -29638,6 +29718,37 @@ "integrity": "sha512-JNjcULU2e4KJwUNv6CHgI46UvDGitb6dGryHajXTDiLgg1/RiGoPSDw4kZfYnwGtEXf2ZMeIewDQgFGzkCB2Sg==", "dev": true }, + "worker-timers": { + "version": "7.0.78", + "resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-7.0.78.tgz", + "integrity": "sha512-jATkwi6OFe2XwfvoPYl3hr8k19/JgCiFerJ4ZWv5AbMIjsUnayC1C8Dxau/sevqhzCrU4IqXDFDTjNINGNuibQ==", + "requires": { + "@babel/runtime": "^7.23.4", + "tslib": "^2.6.2", + "worker-timers-broker": "^6.0.98", + "worker-timers-worker": "^7.0.62" + } + }, + "worker-timers-broker": { + "version": "6.0.98", + "resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-6.0.98.tgz", + "integrity": "sha512-Kq8dTnHBoBGXdMYHGYAtZ+gbxdsV085PF3QoxFZMDKvqPlyTuRFGQvc70k9fHeGOCFlUv6OUREvYF72aquBOFw==", + "requires": { + "@babel/runtime": "^7.23.4", + "fast-unique-numbers": "^8.0.11", + "tslib": "^2.6.2", + "worker-timers-worker": "^7.0.62" + } + }, + "worker-timers-worker": { + "version": "7.0.62", + "resolved": "https://registry.npmjs.org/worker-timers-worker/-/worker-timers-worker-7.0.62.tgz", + "integrity": "sha512-WuVhWXWEqwcWD2Iy9tNQWQyfl984XF/hoblazHp4KOWiIN50B2PH0l61nBkJUndFhJ9qca0lEZ7SWSqdIMDWDQ==", + "requires": { + "@babel/runtime": "^7.23.4", + "tslib": "^2.6.2" + } + }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", diff --git a/package.json b/package.json index 18f96c820..ce2950612 100644 --- a/package.json +++ b/package.json @@ -123,6 +123,7 @@ "reinterval": "^1.1.0", "rfdc": "^1.3.0", "split2": "^4.2.0", + "worker-timers": "^7.0.78", "ws": "^8.14.2" }, "devDependencies": { diff --git a/src/lib/PingTimer.ts b/src/lib/PingTimer.ts new file mode 100644 index 000000000..ce88763bb --- /dev/null +++ b/src/lib/PingTimer.ts @@ -0,0 +1,39 @@ +import { clearTimeout as clearT, setTimeout as setT } from 'worker-timers' +import isBrowser from './is-browser' + +export default class PingTimer { + private keepalive: number + + private timer: any + + private checkPing: () => void + + private setTimeout = isBrowser ? setT : setTimeout + + private clearTimeout = isBrowser ? clearT : clearTimeout + + constructor(keepalive: number, checkPing: () => void) { + this.keepalive = keepalive * 1000 + this.checkPing = checkPing + this.setup() + } + + private setup() { + this.timer = this.setTimeout(() => { + this.checkPing() + this.reschedule() + }, this.keepalive) + } + + clear() { + if (this.timer) { + this.clearTimeout(this.timer) + this.timer = null + } + } + + reschedule() { + this.clear() + this.setup() + } +} diff --git a/src/lib/client.ts b/src/lib/client.ts index b3a962e4a..25320da2a 100644 --- a/src/lib/client.ts +++ b/src/lib/client.ts @@ -19,7 +19,6 @@ import DefaultMessageIdProvider, { IMessageIdProvider, } from './default-message-id-provider' import { DuplexOptions, Writable } from 'readable-stream' -import reInterval from 'reinterval' import clone from 'rfdc/default' import * as validations from './validations' import _debug from 'debug' @@ -34,24 +33,20 @@ import { IStream, StreamBuilder, VoidCallback, + nextTick, } from './shared' import TopicAliasSend from './topic-alias-send' import { TypedEventEmitter } from './TypedEmitter' - -const nextTick = process - ? process.nextTick - : (callback: () => void) => { - setTimeout(callback, 0) - } +import PingTimer from './PingTimer' const setImmediate = globalThis.setImmediate || - ((...args: any[]) => { + (((...args: any[]) => { const callback = args.shift() nextTick(() => { callback(...args) }) - }) + }) as typeof globalThis.setImmediate) const defaultConnectOptions = { keepalive: 60, @@ -359,7 +354,7 @@ export type OnConnectCallback = (packet: IConnackPacket) => void export type OnDisconnectCallback = (packet: IDisconnectPacket) => void export type ClientSubscribeCallback = ( err: Error | null, - granted: ISubscriptionGrant[], + granted?: ISubscriptionGrant[], ) => void export type OnMessageCallback = ( topic: string, @@ -430,7 +425,7 @@ export default class MqttClient extends TypedEventEmitter void - public pingTimer: any + public pingTimer: PingTimer /** * The connection to the Broker. In browsers env this also have `socket` property @@ -2068,9 +2063,9 @@ export default class MqttClient extends TypedEventEmitter { + this.pingTimer = new PingTimer(this.options.keepalive, () => { this._checkPing() - }, this.options.keepalive * 1000) + }) } } @@ -2085,7 +2080,7 @@ export default class MqttClient extends TypedEventEmitter void) => { + setTimeout(callback, 0) + } diff --git a/src/mqtt.ts b/src/mqtt.ts index d537c1d3c..eb84b8ed2 100644 --- a/src/mqtt.ts +++ b/src/mqtt.ts @@ -4,7 +4,6 @@ * * See LICENSE for more information */ - import MqttClient from './lib/client' import DefaultMessageIdProvider from './lib/default-message-id-provider' import UniqueMessageIdProvider from './lib/unique-message-id-provider'