With the `websocket` transport, the callbacks which indicate that the
packets are actually written were not properly called.
Example:
```js
socket.send("hello", () => {
// the message has been written to the underlying transport
});
```
The bug was caused by the `websocket` transport (and `webtransport` as
well) having its `supportsFraming` property set to `true`, despite
having been changed in [1] to emit a single `drain` event for each
batch of messages written to the transport like the `polling` transport
always did. Note that although [1] is partially reverted in [2], the
new `drain` event behavior is preserved as called out in that commit's
message.
The `supportsFraming` attribute was introduced in [3] (amended by [4])
as a way to distinguish transports that emit one `drain` per message
from those that emit one `drain` per batch. Since the delivery of
`send` callbacks depends on matching `drain` events with
`transport.send` calls, that distinction is vital to correct behavior.
However, now that all transports have converged to "one `drain` per
batch" behavior, this `supportsFraming` property can be retired (and
the code for calling callbacks simplified).
[1]: https://github.com/socketio/engine.io/pull/618
[2]: a65a047526
[3]: https://github.com/socketio/engine.io/pull/130
[4]: https://github.com/socketio/engine.io/pull/132
Related: https://github.com/socketio/engine.io/issues/698
The yeast() method could generate the same string twice when used in
two different iframes, which can cause Safari to only send one HTTP
request (deduplication) and trigger an HTTP 400 error afterwards since
the two iframes share the same session ID.
This new method, combining 5 chars from the timestamp and 3 chars from
Math.random() should be sufficient for our use case.
Related: https://github.com/socketio/engine.io/issues/690
See also: 874484cc1e
The debug package was not included anymore in the dev bundle since the
migration from webpack to rollup ([1]) in version 6.0.0.
[1]: 27de300de4
See also: 4683a954d4
By default, Babel uses `Object.defineProperty()` when transpiling
classes. We'll now use the loose mode which creates a more terse
output.
| | before | after |
|----------|--------|--------|
| min+gzip | 8.8 KB | 8.6 KB |
| min+br | 8.0 KB | 7.7 KB |
Reference: https://babeljs.io/docs/babel-plugin-transform-classes
This commit adds the ability to provide a list of transport
implementations to use when connecting to an Engine.IO server.
This can be used to use HTTP long-polling based on `fetch()`, instead
of the default implementation based on the `XMLHttpRequest` object.
```
import { Socket, Fetch, WebSocket } from "engine.io-client";
const socket = new Socket({
transports: [Fetch, WebSocket]
});
```
This is useful in some environments that do not provide a
`XMLHttpRequest` object, like Chrome extension background scripts.
> XMLHttpRequest() can't be called from a service worker, extension or
otherwise. Replace calls from your background script to
XMLHttpRequest() with calls to global fetch().
Source: https://developer.chrome.com/docs/extensions/develop/migrate/to-service-workers#replace-xmlhttprequest
Related:
- https://github.com/socketio/engine.io-client/issues/716
- https://github.com/socketio/socket.io/issues/4980
This is also useful when running the client with Deno or Bun, as it
allows to use the built-in `fetch()` method and `WebSocket` object,
instead of using the `xmlhttprequest-ssl` and `ws` Node.js packages.
Related: https://github.com/socketio/socket.io-deno/issues/12
This feature also comes with the ability to exclude the code related to
unused transports (a.k.a. "tree-shaking"):
```js
import { SocketWithoutUpgrade, WebSocket } from "engine.io-client";
const socket = new SocketWithoutUpgrade({
transports: [WebSocket]
});
```
In that case, the code related to HTTP long-polling and WebTransport
will be excluded from the final bundle.
Related: https://github.com/socketio/socket.io/discussions/4393
When setting the `tryAllTransports` option to `true`, if the first
transport (usually, HTTP long-polling) fails, then the other transports
will be tested too.
This is useful in two cases:
> when HTTP long-polling is disabled on the server, or if CORS fails
Related:
- https://github.com/socketio/engine.io-client/issues/575
- https://github.com/socketio/socket.io-client/issues/1448
> when WebSocket is tested first (`transports: ["websocket", "polling"])
Related:
- https://github.com/socketio/engine.io-client/issues/714
- https://github.com/socketio/socket.io-client/issues/1599
The only potential downside is that the connection attempt could take
more time in case of failure, as there have been reports of WebSocket
connection errors taking several seconds before being detected (that's
one reason for using HTTP long-polling first). That's why the option
defaults to `false` for now.
Following [1], emitting from a dynamic namespace to a room would throw
this error:
> node_modules/socket.io/dist/parent-namespace.js:88
> this.children.forEach((nsp) => {
> ^
>
> TypeError: Cannot read properties of undefined (reading 'forEach')
> at ParentBroadcastAdapter.broadcast (node_modules/socket.io/dist/parent-namespace.js:88:23)
> at BroadcastOperator.emit (node_modules/socket.io/dist/broadcast-operator.js:169:26)
> at Socket.<anonymous> (server.js:60:33)
> at Socket.emit (node:events:520:28)
> at Socket.emitReserved (node_modules/socket.io/dist/typed-events.js:56:22)
> at Socket._onclose (node_modules/socket.io/dist/socket.js:547:14)
> at Client.onclose (node_modules/socket.io/dist/client.js:247:20)
> at Socket.emit (node:events:532:35)
> at Socket.onClose (node_modules/engine.io/build/socket.js:304:18)
> at Object.onceWrapper (node:events:639:28)
Previous output code:
```js
class ParentNamespace extends namespace_1.Namespace {
constructor(server) {
super(server, "/_" + ParentNamespace.count++);
this.children = new Set();
}
_initAdapter() {
this.adapter = new ParentBroadcastAdapter(this, this.children);
}
}
```
Here, `super()` calls `Namespace._initAdapter()`, but `this.children`
is not defined yet, hence the problem.
[1]: b9ce6a25d1
Related: https://github.com/socketio/socket.io/issues/4985
In order to fix the following issue:
> error TS1479: The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Consider writing a dynamic 'import("@socket.io/component-emitter")' call instead.
>
> 1 import { Emitter } from "@socket.io/component-emitter";
This problem was introduced in [1], when reworking the dual packaging.
Related: https://github.com/socketio/socket.io-parser/issues/132
[1]: ba6b56de2c
Previously, getting disconnected while waiting for an acknowledgement
would create a memory leak, as the acknowledgement was never received
and the handler would stay in memory forever.
This commit fixes the issue:
- handlers that do accept an error as first argument, such as:
* `socket.emit("test", (err, value) => { ... })` with `ackTimeout` option
* `socket.timeout(5000).emit("test", (err, value) => { ... })`
* `const value = await socket.emitWithAck("test")`
will now properly resolve with an error and get discarded.
- handlers that don't like `socket.emit("test", (value) => { ... });`
will simply be discarded upon disconnection
Note: the structure of the 'acks' attribute has been left untouched, in
order to prevent any breaking change.
Related:
- https://github.com/socketio/socket.io-client/issues/1546
- https://github.com/socketio/socket.io/issues/4964
Before this change, the broadcast() method would send the BROADCAST
command and then apply it locally (which is required to retrieve the
offset of the message, when connection state recovery is enabled),
while the other commands like disconnectSockets() would first apply it
locally and then send the command to the other nodes.
So, for example:
```js
io.emit("bye");
io.disconnectSockets();
```
In that case, the clients connected to the io instance would not receive
the "bye" event, while the clients connected to the other server
instances would receive it before being disconnected.
Related:
- https://github.com/socketio/socket.io-redis-streams-adapter/issues/13
- https://github.com/socketio/socket.io-postgres-adapter/issues/12