The close event will now include additional details to help debugging
if anything has gone wrong.
Example when a payload is over the maxHttpBufferSize value in HTTP
long-polling mode:
```js
socket.on("close", (reason, details) => {
console.log(reason); // "transport error"
// in that case, details is an error object
console.log(details.message); "xhr post error"
console.log(details.description); // 413 (the HTTP status of the response)
// details.context refers to the XMLHttpRequest object
console.log(details.context.status); // 413
console.log(details.context.responseText); // ""
});
```
Note: the error object was already included before this commit and is
kept for backward compatibility
Example when a payload is over the maxHttpBufferSize value with
WebSockets:
```js
socket.on("close", (reason, details) => {
console.log(reason); // "transport close"
// in that case, details is a plain object
console.log(details.description); // "websocket connection closed"
// details.context is a CloseEvent object
console.log(details.context.code); // 1009 (which means "Message Too Big")
console.log(details.context.reason); // ""
});
```
Example within a cluster without sticky sessions:
```js
socket.on("close", (reason, details) => {
console.log(details.context.status); // 400
console.log(details.context.responseText); // '{"code":1,"message":"Session ID unknown"}'
});
```
Note: we could also print some warnings in development for the "usual"
errors:
- CORS error
- HTTP 400 with multiple nodes
- HTTP 413 with maxHttpBufferSize
but that would require an additional step when going to production
(i.e. setting NODE_ENV variable to "production"). This is open to
discussion!
Related:
- https://github.com/socketio/socket.io/issues/3946
- https://github.com/socketio/socket.io/issues/1979
- https://github.com/socketio/socket.io-client/issues/1518
The server will now include a "maxPayload" field in the handshake
details, allowing the clients to decide how many packets they have to
send to stay under the maxHttpBufferSize value.
Related:
- https://github.com/socketio/socket.io-client/issues/1531
- 088dcb4dff
This restores the possibility to import the bundle directly, without
getting the following error:
> [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './dist/engine.io.min.js' is not defined by "exports"
On macOS the script was interpreted as the value of the `-i` option. It
will now use an empty extension for the `-i` option and the `-e` option
to specify the script.
Some bundlers (like vite) do not support having a "browser" field in a
nested package.json.
Note: the previous commit ([1]) fixed the resolution of the "browser"
field in the dev bundle, but the production bundle still failed.
Related: https://github.com/socketio/socket.io-client/issues/1504
[1]: 49719142f6
Removing the debug library and the debug calls from the final bundle is
unexpectedly quite hard.
Actually, there are several solutions, each with its own pro and cons:
> use webpack-remove-debug (our previous solution)
Pro: works well, even with ESM imports with a little hack
```js
import debugModule from "debug"; // debug()
const debug = debugModule("my-library"); // debug()
debug("hello world");
```
Cons: only for webpack
See also: https://github.com/johngodley/webpack-remove-debug
> NODE_ENV variable
```js
import debugModule from "debug";
let debug = () => {}
if (process.env.NODE_ENV !== "production") {
debug = debugModule("my-library");
}
```
Pro: the `debug()` calls are properly removed when bundling for
production
Cons: some bundlers leave the debug library in the bundle, even if it
is not called (for example, rollup needs an additional
"moduleSideEffects: true")
Reference: https://rollupjs.org/guide/en/#treeshake
> dynamic import
```js
let debug = () => {}
if (process.env.NODE_ENV !== "production") {
import("debug").then(debugModule => {
debug = debugModule.default("my-library");
});
}
```
Pro: the sanest solution, which allows to use debug in development
Cons: will likely break some bundlers due to the dynamic import (for
example, not supported for UMD bundles)
> browser field
```json
{
"browser": {
"debug": "./noop.js"
}
}
```
Pro: the safest solution from a compatibility point of view
Cons: some bundlers leave the noop debug calls, even after minification
> remove debug from the source code
We could also remove the debug calls, but the ability to turn them with
a single env variable on is quite appealing (at least in a Node.js
environment):
```
DEBUG=* node index.js
```
> dual packaging (our selected solution)
We provide two ESM builds, one with debug and one without.
Pros:
- no tricky configuration for bundlers
- debug logs are still available in Node.js
Cons:
- no more debug logs in the browser
We will go with the latest solution for now, until there is a better
alternative.
This change allows us to:
- reduce the size of the bundle
- provide an ESM bundle (for usage in <script type="module">)
BREAKING CHANGE: due to how default export works with ES modules, we
have removed the default export of the library, which means the
following code:
```js
require("engine.io-client")(...);
```
will not work anymore. The named export must be used instead:
```js
const { Socket } = require("engine.io-client);
// or import { Socket } from "engine.io-client";
const socket = new Socket(...);
```
Note: the UMD build still exposes a function though:
```html
<script src="/path/to/engine.io.js"></script>
<script>
const socket = eio(...);
</script>
```
Note: webpack is still used with zuul because of the custom builder
(zuul-builder-webpack)
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: https://github.com/socketio/socket.io-client/pull/1479
An immediate setTimeout was used to unblock the WebSocket write.
Unfortunately, this setTimeout can be throttled by browsers when the
tab is backgrounded.
This can lead to missed pong responses to server pings, which
eventually leads to disconnection.
Related: https://github.com/socketio/engine.io-client/issues/649
Since [1], the socket is now closed when receiving the "beforeunload"
event in the browser.
This change was meant to fix a discrepancy between Chrome and Firefox
when the user reloads/closes a browser tab: Firefox would close the
connection (and emit a "disconnect" event, at the Socket.IO level), but
not Chrome (see [2]).
But it also closes the connection when there is another "beforeunload"
handler, for example when the user is prompted "are you sure you want
to leave this page?".
Note: calling "stopImmediatePropagation()" was a possible workaround:
```js
window.addEventListener('beforeunload', (event) => {
event.preventDefault();
event.stopImmediatePropagation();
event.returnValue = 'are you sure you want to leave this page?';
});
```
This commit adds a "closeOnBeforeunload" option, which controls whether
a handler is registered for the "beforeunload" event.
Syntax:
```js
const socket = require('engine.io-client')('ws://localhost', {
closeOnBeforeunload: false // defaults to true
});
```
[1]: ed48b5dc34
[2]: https://github.com/socketio/socket.io/issues/3639
Related:
- https://github.com/socketio/engine.io-client/issues/661
- https://github.com/socketio/engine.io-client/issues/658
- https://github.com/socketio/socket.io-client/issues/1451
Reference: https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event