Skip to content

Commit 35448f3

Browse files
authored
fix(browser): use worker timers to prevent unexpected client close (#1753)
1 parent a50e85c commit 35448f3

File tree

7 files changed

+189
-38
lines changed

7 files changed

+189
-38
lines changed

examples/vite-example/src/App.vue

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
import { ref } from 'vue'
33
import mqtt from 'mqtt'
44
5-
console.log('mqtt', mqtt)
6-
75
const connected = ref(false)
86
9-
const client = mqtt.connect('wss://test.mosquitto.org:8081');
7+
const client = mqtt.connect('wss://test.mosquitto.org:8081', {
8+
log: console.log.bind(console),
9+
keepalive: 30,
10+
});
1011
1112
const messages = ref([])
1213

package-lock.json

Lines changed: 131 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@
123123
"reinterval": "^1.1.0",
124124
"rfdc": "^1.3.0",
125125
"split2": "^4.2.0",
126+
"worker-timers": "^7.0.78",
126127
"ws": "^8.14.2"
127128
},
128129
"devDependencies": {

src/lib/PingTimer.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { clearTimeout as clearT, setTimeout as setT } from 'worker-timers'
2+
import isBrowser from './is-browser'
3+
4+
export default class PingTimer {
5+
private keepalive: number
6+
7+
private timer: any
8+
9+
private checkPing: () => void
10+
11+
private setTimeout = isBrowser ? setT : setTimeout
12+
13+
private clearTimeout = isBrowser ? clearT : clearTimeout
14+
15+
constructor(keepalive: number, checkPing: () => void) {
16+
this.keepalive = keepalive * 1000
17+
this.checkPing = checkPing
18+
this.setup()
19+
}
20+
21+
private setup() {
22+
this.timer = this.setTimeout(() => {
23+
this.checkPing()
24+
this.reschedule()
25+
}, this.keepalive)
26+
}
27+
28+
clear() {
29+
if (this.timer) {
30+
this.clearTimeout(this.timer)
31+
this.timer = null
32+
}
33+
}
34+
35+
reschedule() {
36+
this.clear()
37+
this.setup()
38+
}
39+
}

src/lib/client.ts

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import DefaultMessageIdProvider, {
1919
IMessageIdProvider,
2020
} from './default-message-id-provider'
2121
import { DuplexOptions, Writable } from 'readable-stream'
22-
import reInterval from 'reinterval'
2322
import clone from 'rfdc/default'
2423
import * as validations from './validations'
2524
import _debug from 'debug'
@@ -34,24 +33,20 @@ import {
3433
IStream,
3534
StreamBuilder,
3635
VoidCallback,
36+
nextTick,
3737
} from './shared'
3838
import TopicAliasSend from './topic-alias-send'
3939
import { TypedEventEmitter } from './TypedEmitter'
40-
41-
const nextTick = process
42-
? process.nextTick
43-
: (callback: () => void) => {
44-
setTimeout(callback, 0)
45-
}
40+
import PingTimer from './PingTimer'
4641

4742
const setImmediate =
4843
globalThis.setImmediate ||
49-
((...args: any[]) => {
44+
(((...args: any[]) => {
5045
const callback = args.shift()
5146
nextTick(() => {
5247
callback(...args)
5348
})
54-
})
49+
}) as typeof globalThis.setImmediate)
5550

5651
const defaultConnectOptions = {
5752
keepalive: 60,
@@ -359,7 +354,7 @@ export type OnConnectCallback = (packet: IConnackPacket) => void
359354
export type OnDisconnectCallback = (packet: IDisconnectPacket) => void
360355
export type ClientSubscribeCallback = (
361356
err: Error | null,
362-
granted: ISubscriptionGrant[],
357+
granted?: ISubscriptionGrant[],
363358
) => void
364359
export type OnMessageCallback = (
365360
topic: string,
@@ -430,7 +425,7 @@ export default class MqttClient extends TypedEventEmitter<MqttClientEventCallbac
430425

431426
public noop: (error?: any) => void
432427

433-
public pingTimer: any
428+
public pingTimer: PingTimer
434429

435430
/**
436431
* The connection to the Broker. In browsers env this also have `socket` property
@@ -2068,9 +2063,9 @@ export default class MqttClient extends TypedEventEmitter<MqttClientEventCallbac
20682063

20692064
if (!this.pingTimer && this.options.keepalive) {
20702065
this.pingResp = true
2071-
this.pingTimer = reInterval(() => {
2066+
this.pingTimer = new PingTimer(this.options.keepalive, () => {
20722067
this._checkPing()
2073-
}, this.options.keepalive * 1000)
2068+
})
20742069
}
20752070
}
20762071

@@ -2085,7 +2080,7 @@ export default class MqttClient extends TypedEventEmitter<MqttClientEventCallbac
20852080
this.options.keepalive &&
20862081
this.options.reschedulePings
20872082
) {
2088-
this.pingTimer.reschedule(this.options.keepalive * 1000)
2083+
this.pingTimer.reschedule()
20892084
}
20902085
}
20912086

0 commit comments

Comments
 (0)