Skip to content

Commit 5d1d5be

Browse files
vartandarrachequesne
authored andcommitted
feat: add an option to use native timer functions (#672)
This allows to control the behavior of mocked timers (@sinonjs/fake-timers), depending on the value of the "useNativeTimers" option: - true: use native setTimeout function - false (default): use classic timers, that may be mocked The "installTimerFunctions" method will also be used in the `socket.io-client` package: ``` import { installTimerFunctions } from "engine.io-client/lib/util"; ``` Note: we could also have put the method in its own library, but that sounded a bit overkill Related: socketio/socket.io-client#1479
1 parent 08db7f6 commit 5d1d5be

12 files changed

+211
-147
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ Exposed as `eio` in the browser standalone build.
263263
- `forceNode` (`Boolean`): Uses NodeJS implementation for websockets - even if there is a native Browser-Websocket available, which is preferred by default over the NodeJS implementation. (This is useful when using hybrid platforms like nw.js or electron) (`false`, NodeJS only)
264264
- `localAddress` (`String`): the local IP address to connect to
265265
- `autoUnref` (`Boolean`): whether the transport should be `unref`'d upon creation. This calls `unref` on the underlying timers and sockets so that the program is allowed to exit if they are the only timers/sockets in the event system (Node.js only)
266+
- `useNativeTimers` (`Boolean`): Whether to always use the native timeouts. This allows the client to reconnect when the native timeout functions are overridden, such as when mock clocks are installed with [`@sinonjs/fake-timers`](https://github.com/sinonjs/fake-timers).
266267
- **Polling-only options**
267268
- `requestTimeout` (`Number`): Timeout for xhr-polling requests in milliseconds (`0`)
268269
- **Websocket-only options**

lib/socket.js

+8-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const debug = require("debug")("engine.io-client:socket");
44
const parser = require("engine.io-parser");
55
const parseuri = require("parseuri");
66
const parseqs = require("parseqs");
7+
const { installTimerFunctions } = require("./util");
78

89
class Socket extends Emitter {
910
/**
@@ -31,6 +32,8 @@ class Socket extends Emitter {
3132
opts.hostname = parseuri(opts.host).host;
3233
}
3334

35+
installTimerFunctions(this, opts);
36+
3437
this.secure =
3538
null != opts.secure
3639
? opts.secure
@@ -172,7 +175,7 @@ class Socket extends Emitter {
172175
transport = "websocket";
173176
} else if (0 === this.transports.length) {
174177
// Emit error on next tick so it can be listened to
175-
setTimeout(() => {
178+
this.setTimeoutFn(() => {
176179
this.emit("error", "No transports available");
177180
}, 0);
178181
return;
@@ -431,8 +434,8 @@ class Socket extends Emitter {
431434
* @api private
432435
*/
433436
resetPingTimeout() {
434-
clearTimeout(this.pingTimeoutTimer);
435-
this.pingTimeoutTimer = setTimeout(() => {
437+
this.clearTimeoutFn(this.pingTimeoutTimer);
438+
this.pingTimeoutTimer = this.setTimeoutFn(() => {
436439
this.onClose("ping timeout");
437440
}, this.pingInterval + this.pingTimeout);
438441
if (this.opts.autoUnref) {
@@ -609,8 +612,8 @@ class Socket extends Emitter {
609612
debug('socket close with reason: "%s"', reason);
610613

611614
// clear timers
612-
clearTimeout(this.pingIntervalTimer);
613-
clearTimeout(this.pingTimeoutTimer);
615+
this.clearTimeoutFn(this.pingIntervalTimer);
616+
this.clearTimeoutFn(this.pingTimeoutTimer);
614617

615618
// stop event from firing again for transport
616619
this.transport.removeAllListeners("close");

lib/transport.js

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const parser = require("engine.io-parser");
22
const Emitter = require("component-emitter");
3+
const { installTimerFunctions } = require("./util");
34
const debug = require("debug")("engine.io-client:transport");
45

56
class Transport extends Emitter {
@@ -11,6 +12,7 @@ class Transport extends Emitter {
1112
*/
1213
constructor(opts) {
1314
super();
15+
installTimerFunctions(this, opts);
1416

1517
this.opts = opts;
1618
this.query = opts.query;

lib/transports/polling-jsonp.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ class JSONPPolling extends Polling {
9999
"undefined" !== typeof navigator && /gecko/i.test(navigator.userAgent);
100100

101101
if (isUAgecko) {
102-
setTimeout(function() {
102+
this.setTimeoutFn(function() {
103103
const iframe = document.createElement("iframe");
104104
document.body.appendChild(iframe);
105105
document.body.removeChild(iframe);

lib/transports/polling-xhr.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
const XMLHttpRequest = require("xmlhttprequest-ssl");
44
const Polling = require("./polling");
55
const Emitter = require("component-emitter");
6-
const { pick } = require("../util");
6+
const { pick, installTimerFunctions } = require("../util");
77
const globalThis = require("../globalThis");
88

99
const debug = require("debug")("engine.io-client:polling-xhr");
@@ -105,6 +105,7 @@ class Request extends Emitter {
105105
*/
106106
constructor(uri, opts) {
107107
super();
108+
installTimerFunctions(this, opts);
108109
this.opts = opts;
109110

110111
this.method = opts.method || "GET";
@@ -187,7 +188,7 @@ class Request extends Emitter {
187188
} else {
188189
// make sure the `error` event handler that's user-set
189190
// does not throw in the same tick and gets caught here
190-
setTimeout(() => {
191+
this.setTimeoutFn(() => {
191192
this.onError(typeof xhr.status === "number" ? xhr.status : 0);
192193
}, 0);
193194
}
@@ -200,7 +201,7 @@ class Request extends Emitter {
200201
// Need to defer since .create() is called directly from the constructor
201202
// and thus the 'error' event can only be only bound *after* this exception
202203
// occurs. Therefore, also, we cannot throw here at all.
203-
setTimeout(() => {
204+
this.setTimeoutFn(() => {
204205
this.onError(e);
205206
}, 0);
206207
return;

lib/transports/websocket-constructor.browser.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const nextTick = (() => {
55
if (isPromiseAvailable) {
66
return cb => Promise.resolve().then(cb);
77
} else {
8-
return cb => setTimeout(cb, 0);
8+
return (cb, setTimeoutFn) => setTimeoutFn(cb, 0);
99
}
1010
})();
1111

lib/transports/websocket.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ class WS extends Transport {
165165
nextTick(() => {
166166
this.writable = true;
167167
this.emit("drain");
168-
});
168+
}, this.setTimeoutFn);
169169
}
170170
});
171171
}

lib/util.js

+16
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
const globalThis = require("./globalThis");
2+
13
module.exports.pick = (obj, ...attr) => {
24
return attr.reduce((acc, k) => {
35
if (obj.hasOwnProperty(k)) {
@@ -6,3 +8,17 @@ module.exports.pick = (obj, ...attr) => {
68
return acc;
79
}, {});
810
};
11+
12+
// Keep a reference to the real timeout functions so they can be used when overridden
13+
const NATIVE_SET_TIMEOUT = setTimeout;
14+
const NATIVE_CLEAR_TIMEOUT = clearTimeout;
15+
16+
module.exports.installTimerFunctions = (obj, opts) => {
17+
if (opts.useNativeTimers) {
18+
obj.setTimeoutFn = NATIVE_SET_TIMEOUT.bind(globalThis);
19+
obj.clearTimeoutFn = NATIVE_CLEAR_TIMEOUT.bind(globalThis);
20+
} else {
21+
obj.setTimeoutFn = setTimeout.bind(globalThis);
22+
obj.clearTimeoutFn = clearTimeout.bind(globalThis);
23+
}
24+
};

0 commit comments

Comments
 (0)