mirror of
https://github.com/socketio/socket.io.git
synced 2026-01-11 07:58:13 -05:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2e5d1f77f | ||
|
|
d8143cc067 | ||
|
|
b2dd7cf660 | ||
|
|
3734b74b45 | ||
|
|
8aa94991ce | ||
|
|
4e64123862 | ||
|
|
115a9819fd | ||
|
|
0c0eb00163 | ||
|
|
f8640d9451 | ||
|
|
93d446a545 | ||
|
|
184f3cf7af | ||
|
|
5d9220b69a | ||
|
|
129883958a | ||
|
|
6c27b8b0a6 | ||
|
|
f3ada7d8cc | ||
|
|
a21ad88828 | ||
|
|
54d5ee05a6 | ||
|
|
da2b542797 | ||
|
|
b7d54dbe8d | ||
|
|
d4a9b2cdcb | ||
|
|
547c541fb9 |
195
CHANGELOG.md
195
CHANGELOG.md
@@ -1,5 +1,9 @@
|
||||
# History
|
||||
|
||||
## 2023
|
||||
|
||||
- [4.6.0](#460-2023-02-07) (Feb 2023)
|
||||
|
||||
## 2022
|
||||
|
||||
- [4.5.4](#454-2022-11-22) (Nov 2022)
|
||||
@@ -52,6 +56,187 @@
|
||||
|
||||
# Release notes
|
||||
|
||||
# [4.6.0](https://github.com/socketio/socket.io/compare/4.5.4...4.6.0) (2023-02-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add timeout method to remote socket ([#4558](https://github.com/socketio/socket.io/issues/4558)) ([0c0eb00](https://github.com/socketio/socket.io/commit/0c0eb0016317218c2be3641e706cfaa9bea39a2d))
|
||||
* **typings:** properly type emits with timeout ([f3ada7d](https://github.com/socketio/socket.io/commit/f3ada7d8ccc02eeced2b9b9ac8e4bc921eb630d2))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
#### Promise-based acknowledgements
|
||||
|
||||
This commit adds some syntactic sugar around acknowledgements:
|
||||
|
||||
- `emitWithAck()`
|
||||
|
||||
```js
|
||||
try {
|
||||
const responses = await io.timeout(1000).emitWithAck("some-event");
|
||||
console.log(responses); // one response per client
|
||||
} catch (e) {
|
||||
// some clients did not acknowledge the event in the given delay
|
||||
}
|
||||
|
||||
io.on("connection", async (socket) => {
|
||||
// without timeout
|
||||
const response = await socket.emitWithAck("hello", "world");
|
||||
|
||||
// with a specific timeout
|
||||
try {
|
||||
const response = await socket.timeout(1000).emitWithAck("hello", "world");
|
||||
} catch (err) {
|
||||
// the client did not acknowledge the event in the given delay
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
- `serverSideEmitWithAck()`
|
||||
|
||||
```js
|
||||
try {
|
||||
const responses = await io.timeout(1000).serverSideEmitWithAck("some-event");
|
||||
console.log(responses); // one response per server (except itself)
|
||||
} catch (e) {
|
||||
// some servers did not acknowledge the event in the given delay
|
||||
}
|
||||
```
|
||||
|
||||
Added in [184f3cf](https://github.com/socketio/socket.io/commit/184f3cf7af57acc4b0948eee307f25f8536eb6c8).
|
||||
|
||||
#### Connection state recovery
|
||||
|
||||
This feature allows a client to reconnect after a temporary disconnection and restore its state:
|
||||
|
||||
- id
|
||||
- rooms
|
||||
- data
|
||||
- missed packets
|
||||
|
||||
Usage:
|
||||
|
||||
```js
|
||||
import { Server } from "socket.io";
|
||||
|
||||
const io = new Server({
|
||||
connectionStateRecovery: {
|
||||
// default values
|
||||
maxDisconnectionDuration: 2 * 60 * 1000,
|
||||
skipMiddlewares: true,
|
||||
},
|
||||
});
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
console.log(socket.recovered); // whether the state was recovered or not
|
||||
});
|
||||
```
|
||||
|
||||
Here's how it works:
|
||||
|
||||
- the server sends a session ID during the handshake (which is different from the current `id` attribute, which is public and can be freely shared)
|
||||
- the server also includes an offset in each packet (added at the end of the data array, for backward compatibility)
|
||||
- upon temporary disconnection, the server stores the client state for a given delay (implemented at the adapter level)
|
||||
- upon reconnection, the client sends both the session ID and the last offset it has processed, and the server tries to restore the state
|
||||
|
||||
The in-memory adapter already supports this feature, and we will soon update the Postgres and MongoDB adapters. We will also create a new adapter based on [Redis Streams](https://redis.io/docs/data-types/streams/), which will support this feature.
|
||||
|
||||
Added in [54d5ee0](https://github.com/socketio/socket.io/commit/54d5ee05a684371191e207b8089f09fc24eb5107).
|
||||
|
||||
#### Compatibility (for real) with Express middlewares
|
||||
|
||||
This feature implements middlewares at the Engine.IO level, because Socket.IO middlewares are meant for namespace authorization and are not executed during a classic HTTP request/response cycle.
|
||||
|
||||
Syntax:
|
||||
|
||||
```js
|
||||
io.engine.use((req, res, next) => {
|
||||
// do something
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
// with express-session
|
||||
import session from "express-session";
|
||||
|
||||
io.engine.use(session({
|
||||
secret: "keyboard cat",
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
cookie: { secure: true }
|
||||
}));
|
||||
|
||||
// with helmet
|
||||
import helmet from "helmet";
|
||||
|
||||
io.engine.use(helmet());
|
||||
```
|
||||
|
||||
A workaround was possible by using the allowRequest option and the "headers" event, but this feels way cleaner and works with upgrade requests too.
|
||||
|
||||
Added in [24786e7](https://github.com/socketio/engine.io/commit/24786e77c5403b1c4b5a2bc84e2af06f9187f74a).
|
||||
|
||||
#### Error details in the disconnecting and disconnect events
|
||||
|
||||
The `disconnect` event will now contain additional details about the disconnection reason.
|
||||
|
||||
```js
|
||||
io.on("connection", (socket) => {
|
||||
socket.on("disconnect", (reason, description) => {
|
||||
console.log(description);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Added in [8aa9499](https://github.com/socketio/socket.io/commit/8aa94991cee5518567d6254eec04b23f81510257).
|
||||
|
||||
#### Automatic removal of empty child namespaces
|
||||
|
||||
This commit adds a new option, "cleanupEmptyChildNamespaces". With this option enabled (disabled by default), when a socket disconnects from a dynamic namespace and if there are no other sockets connected to it then the namespace will be cleaned up and its adapter will be closed.
|
||||
|
||||
```js
|
||||
import { createServer } from "node:http";
|
||||
import { Server } from "socket.io";
|
||||
|
||||
const httpServer = createServer();
|
||||
const io = new Server(httpServer, {
|
||||
cleanupEmptyChildNamespaces: true
|
||||
});
|
||||
```
|
||||
|
||||
Added in [5d9220b](https://github.com/socketio/socket.io/commit/5d9220b69adf73e086c27bbb63a4976b348f7c4c).
|
||||
|
||||
#### A new "addTrailingSlash" option
|
||||
|
||||
The trailing slash which was added by default can now be disabled:
|
||||
|
||||
```js
|
||||
import { createServer } from "node:http";
|
||||
import { Server } from "socket.io";
|
||||
|
||||
const httpServer = createServer();
|
||||
const io = new Server(httpServer, {
|
||||
addTrailingSlash: false
|
||||
});
|
||||
```
|
||||
|
||||
In the example above, the clients can omit the trailing slash and use `/socket.io` instead of `/socket.io/`.
|
||||
|
||||
Added in [d0fd474](https://github.com/socketio/engine.io/commit/d0fd4746afa396297f07bb62e539b0c1c4018d7c).
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* precompute the WebSocket frames when broadcasting ([da2b542](https://github.com/socketio/socket.io/commit/da2b54279749adc5279c9ac4742b01b36c01cff0))
|
||||
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`engine.io@~6.4.0`](https://github.com/socketio/engine.io/releases/tag/6.4.0) ([diff](https://github.com/socketio/engine.io/compare/6.2.0...6.2.1))
|
||||
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) ([diff](https://github.com/websockets/ws/compare/8.2.3...8.11.0))
|
||||
|
||||
|
||||
## [4.5.4](https://github.com/socketio/socket.io/compare/4.5.3...4.5.4) (2022-11-22)
|
||||
|
||||
This release contains a bump of:
|
||||
@@ -61,8 +246,8 @@ This release contains a bump of:
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`engine.io@~6.2.1`](https://github.com/socketio/engine.io-client/tree/6.2.1) ([diff](https://github.com/socketio/engine.io/compare/6.2.0...6.2.1))
|
||||
- [`ws@~8.2.3`](https://github.com/websockets/ws/releases/tag/8.2.3)
|
||||
- [`engine.io@~6.2.1`](https://github.com/socketio/engine.io/releases/tag/6.2.1) ([diff](https://github.com/socketio/engine.io/compare/6.2.0...6.2.1))
|
||||
- [`ws@~8.2.3`](https://github.com/websockets/ws/releases/tag/8.2.3) (no change)
|
||||
|
||||
|
||||
|
||||
@@ -88,6 +273,12 @@ This release contains a bump of:
|
||||
|
||||
# [2.5.0](https://github.com/socketio/socket.io/compare/2.4.1...2.5.0) (2022-06-26)
|
||||
|
||||
⚠️ WARNING ⚠️
|
||||
|
||||
The default value of the maxHttpBufferSize option has been decreased from 100 MB to 1 MB, in order to prevent attacks by denial of service.
|
||||
|
||||
Security advisory: [GHSA-j4f2-536g-r55m](https://github.com/advisories/GHSA-j4f2-536g-r55m)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ Some implementations in other languages are also available:
|
||||
- [Dart](https://github.com/rikulo/socket.io-client-dart)
|
||||
- [Python](https://github.com/miguelgrinberg/python-socketio)
|
||||
- [.NET](https://github.com/doghappy/socket.io-client-csharp)
|
||||
- [Rust](https://github.com/1c3t3a/rust-socketio)
|
||||
|
||||
Its main features are:
|
||||
|
||||
|
||||
22
SECURITY.md
Normal file
22
SECURITY.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
|---------|--------------------|
|
||||
| 4.x | :white_check_mark: |
|
||||
| 3.x | :white_check_mark: |
|
||||
| 2.4.x | :white_check_mark: |
|
||||
| < 2.4.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
To report a security vulnerability in this package, please send an email to [@darrachequesne](https://github.com/darrachequesne) (see address in profile) describing the vulnerability and how to reproduce it.
|
||||
|
||||
We will get back to you as soon as possible and publish a fix if necessary.
|
||||
|
||||
:warning: IMPORTANT :warning: please do not create an issue in this repository, as attackers might take advantage of it. Thank you in advance for your responsible disclosure.
|
||||
|
||||
## History
|
||||
|
||||
No security vulnerability were reported yet.
|
||||
6
client-dist/socket.io.esm.min.js
vendored
6
client-dist/socket.io.esm.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
/*!
|
||||
* Socket.IO v4.5.4
|
||||
* (c) 2014-2022 Guillermo Rauch
|
||||
* Socket.IO v4.6.0
|
||||
* (c) 2014-2023 Guillermo Rauch
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
(function (global, factory) {
|
||||
@@ -683,15 +683,15 @@
|
||||
}, {});
|
||||
} // Keep a reference to the real timeout functions so they can be used when overridden
|
||||
|
||||
var NATIVE_SET_TIMEOUT = setTimeout;
|
||||
var NATIVE_CLEAR_TIMEOUT = clearTimeout;
|
||||
var NATIVE_SET_TIMEOUT = globalThisShim.setTimeout;
|
||||
var NATIVE_CLEAR_TIMEOUT = globalThisShim.clearTimeout;
|
||||
function installTimerFunctions(obj, opts) {
|
||||
if (opts.useNativeTimers) {
|
||||
obj.setTimeoutFn = NATIVE_SET_TIMEOUT.bind(globalThisShim);
|
||||
obj.clearTimeoutFn = NATIVE_CLEAR_TIMEOUT.bind(globalThisShim);
|
||||
} else {
|
||||
obj.setTimeoutFn = setTimeout.bind(globalThisShim);
|
||||
obj.clearTimeoutFn = clearTimeout.bind(globalThisShim);
|
||||
obj.setTimeoutFn = globalThisShim.setTimeout.bind(globalThisShim);
|
||||
obj.clearTimeoutFn = globalThisShim.clearTimeout.bind(globalThisShim);
|
||||
}
|
||||
} // base64 encoded buffers are about 33% bigger (https://en.wikipedia.org/wiki/Base64)
|
||||
|
||||
@@ -756,8 +756,8 @@
|
||||
/**
|
||||
* Transport abstract constructor.
|
||||
*
|
||||
* @param {Object} options.
|
||||
* @api private
|
||||
* @param {Object} opts - options
|
||||
* @protected
|
||||
*/
|
||||
function Transport(opts) {
|
||||
var _this2;
|
||||
@@ -769,7 +769,6 @@
|
||||
installTimerFunctions(_assertThisInitialized(_this2), opts);
|
||||
_this2.opts = opts;
|
||||
_this2.query = opts.query;
|
||||
_this2.readyState = "";
|
||||
_this2.socket = opts.socket;
|
||||
return _this2;
|
||||
}
|
||||
@@ -780,7 +779,7 @@
|
||||
* @param description
|
||||
* @param context - the error context
|
||||
* @return {Transport} for chaining
|
||||
* @api protected
|
||||
* @protected
|
||||
*/
|
||||
|
||||
|
||||
@@ -793,30 +792,23 @@
|
||||
}
|
||||
/**
|
||||
* Opens the transport.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: "open",
|
||||
value: function open() {
|
||||
if ("closed" === this.readyState || "" === this.readyState) {
|
||||
this.readyState = "opening";
|
||||
this.doOpen();
|
||||
}
|
||||
|
||||
this.readyState = "opening";
|
||||
this.doOpen();
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Closes the transport.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: "close",
|
||||
value: function close() {
|
||||
if ("opening" === this.readyState || "open" === this.readyState) {
|
||||
if (this.readyState === "opening" || this.readyState === "open") {
|
||||
this.doClose();
|
||||
this.onClose();
|
||||
}
|
||||
@@ -827,20 +819,19 @@
|
||||
* Sends multiple packets.
|
||||
*
|
||||
* @param {Array} packets
|
||||
* @api public
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: "send",
|
||||
value: function send(packets) {
|
||||
if ("open" === this.readyState) {
|
||||
if (this.readyState === "open") {
|
||||
this.write(packets);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Called upon open
|
||||
*
|
||||
* @api protected
|
||||
* @protected
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -855,7 +846,7 @@
|
||||
* Called with data.
|
||||
*
|
||||
* @param {String} data
|
||||
* @api protected
|
||||
* @protected
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -867,7 +858,7 @@
|
||||
/**
|
||||
* Called with a decoded packet.
|
||||
*
|
||||
* @api protected
|
||||
* @protected
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -878,7 +869,7 @@
|
||||
/**
|
||||
* Called upon close.
|
||||
*
|
||||
* @api protected
|
||||
* @protected
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -888,6 +879,15 @@
|
||||
|
||||
_get(_getPrototypeOf(Transport.prototype), "emitReserved", this).call(this, "close", details);
|
||||
}
|
||||
/**
|
||||
* Pauses the transport, in order not to lose packets during an upgrade.
|
||||
*
|
||||
* @param onPause
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: "pause",
|
||||
value: function pause(onPause) {}
|
||||
}]);
|
||||
|
||||
return Transport;
|
||||
@@ -1024,7 +1024,7 @@
|
||||
* XHR Polling constructor.
|
||||
*
|
||||
* @param {Object} opts
|
||||
* @api public
|
||||
* @package
|
||||
*/
|
||||
function Polling(opts) {
|
||||
var _this;
|
||||
@@ -1054,10 +1054,6 @@
|
||||
_this.supportsBinary = hasXHR2 && !forceBase64;
|
||||
return _this;
|
||||
}
|
||||
/**
|
||||
* Transport name.
|
||||
*/
|
||||
|
||||
|
||||
_createClass(Polling, [{
|
||||
key: "name",
|
||||
@@ -1068,7 +1064,7 @@
|
||||
* Opens the socket (triggers polling). We write a PING message to determine
|
||||
* when the transport is open.
|
||||
*
|
||||
* @api private
|
||||
* @protected
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -1079,8 +1075,8 @@
|
||||
/**
|
||||
* Pauses polling.
|
||||
*
|
||||
* @param {Function} callback upon buffers are flushed and transport is paused
|
||||
* @api private
|
||||
* @param {Function} onPause - callback upon buffers are flushed and transport is paused
|
||||
* @package
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -1118,7 +1114,7 @@
|
||||
/**
|
||||
* Starts polling cycle.
|
||||
*
|
||||
* @api public
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -1131,7 +1127,7 @@
|
||||
/**
|
||||
* Overloads onData to detect payloads.
|
||||
*
|
||||
* @api private
|
||||
* @protected
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -1174,7 +1170,7 @@
|
||||
/**
|
||||
* For polling, send a close packet.
|
||||
*
|
||||
* @api private
|
||||
* @protected
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -1199,9 +1195,8 @@
|
||||
/**
|
||||
* Writes a packets payload.
|
||||
*
|
||||
* @param {Array} data packets
|
||||
* @param {Function} drain callback
|
||||
* @api private
|
||||
* @param {Array} packets - data packets
|
||||
* @protected
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -1221,7 +1216,7 @@
|
||||
/**
|
||||
* Generates uri for connection.
|
||||
*
|
||||
* @api private
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -1252,7 +1247,7 @@
|
||||
* Creates a request.
|
||||
*
|
||||
* @param {String} method
|
||||
* @api private
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -1272,7 +1267,7 @@
|
||||
*
|
||||
* @param {String} data to send.
|
||||
* @param {Function} called upon flush.
|
||||
* @api private
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -1292,7 +1287,7 @@
|
||||
/**
|
||||
* Starts a poll cycle.
|
||||
*
|
||||
* @api private
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -1320,7 +1315,7 @@
|
||||
* Request constructor
|
||||
*
|
||||
* @param {Object} options
|
||||
* @api public
|
||||
* @package
|
||||
*/
|
||||
function Request(uri, opts) {
|
||||
var _this8;
|
||||
@@ -1342,7 +1337,7 @@
|
||||
/**
|
||||
* Creates the XHR object and sends the request.
|
||||
*
|
||||
* @api private
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
@@ -1423,7 +1418,7 @@
|
||||
/**
|
||||
* Called upon error.
|
||||
*
|
||||
* @api private
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -1435,7 +1430,7 @@
|
||||
/**
|
||||
* Cleans up house.
|
||||
*
|
||||
* @api private
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -1462,7 +1457,7 @@
|
||||
/**
|
||||
* Called upon load.
|
||||
*
|
||||
* @api private
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -1479,7 +1474,7 @@
|
||||
/**
|
||||
* Aborts the request.
|
||||
*
|
||||
* @api public
|
||||
* @package
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -1544,8 +1539,8 @@
|
||||
/**
|
||||
* WebSocket transport constructor.
|
||||
*
|
||||
* @api {Object} connection options
|
||||
* @api public
|
||||
* @param {Object} opts - connection options
|
||||
* @protected
|
||||
*/
|
||||
function WS(opts) {
|
||||
var _this;
|
||||
@@ -1556,24 +1551,12 @@
|
||||
_this.supportsBinary = !opts.forceBase64;
|
||||
return _this;
|
||||
}
|
||||
/**
|
||||
* Transport name.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
|
||||
_createClass(WS, [{
|
||||
key: "name",
|
||||
get: function get() {
|
||||
return "websocket";
|
||||
}
|
||||
/**
|
||||
* Opens socket.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: "doOpen",
|
||||
value: function doOpen() {
|
||||
@@ -1603,7 +1586,7 @@
|
||||
/**
|
||||
* Adds event listeners to the socket
|
||||
*
|
||||
* @api private
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -1634,13 +1617,6 @@
|
||||
return _this2.onError("websocket error", e);
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Writes data to socket.
|
||||
*
|
||||
* @param {Array} array of packets.
|
||||
* @api private
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: "write",
|
||||
value: function write(packets) {
|
||||
@@ -1682,12 +1658,6 @@
|
||||
_loop(i);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Closes socket.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: "doClose",
|
||||
value: function doClose() {
|
||||
@@ -1699,7 +1669,7 @@
|
||||
/**
|
||||
* Generates uri for connection.
|
||||
*
|
||||
* @api private
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -1731,7 +1701,7 @@
|
||||
* Feature detection for WebSocket.
|
||||
*
|
||||
* @return {Boolean} whether this transport is available.
|
||||
* @api public
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -1752,12 +1722,24 @@
|
||||
// imported from https://github.com/galkn/parseuri
|
||||
|
||||
/**
|
||||
* Parses an URI
|
||||
* Parses a URI
|
||||
*
|
||||
* Note: we could also have used the built-in URL object, but it isn't supported on all platforms.
|
||||
*
|
||||
* See:
|
||||
* - https://developer.mozilla.org/en-US/docs/Web/API/URL
|
||||
* - https://caniuse.com/url
|
||||
* - https://www.rfc-editor.org/rfc/rfc3986#appendix-B
|
||||
*
|
||||
* History of the parse() method:
|
||||
* - first commit: https://github.com/socketio/socket.io-client/commit/4ee1d5d94b3906a9c052b459f1a818b15f38f91c
|
||||
* - export into its own module: https://github.com/socketio/engine.io-client/commit/de2c561e4564efeb78f1bdb1ba39ef81b2822cb3
|
||||
* - reimport: https://github.com/socketio/engine.io-client/commit/df32277c3f6d622eec5ed09f493cae3f3391d242
|
||||
*
|
||||
* @author Steven Levithan <stevenlevithan.com> (MIT license)
|
||||
* @api private
|
||||
*/
|
||||
var re = /^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
|
||||
var re = /^(?:(?![^:@\/?#]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@\/?#]*)(?::([^:@\/?#]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
|
||||
var parts = ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor'];
|
||||
function parse(str) {
|
||||
var src = str,
|
||||
@@ -1821,9 +1803,8 @@
|
||||
/**
|
||||
* Socket constructor.
|
||||
*
|
||||
* @param {String|Object} uri or options
|
||||
* @param {String|Object} uri - uri or options
|
||||
* @param {Object} opts - options
|
||||
* @api public
|
||||
*/
|
||||
function Socket(uri) {
|
||||
var _this;
|
||||
@@ -1833,6 +1814,7 @@
|
||||
_classCallCheck(this, Socket);
|
||||
|
||||
_this = _super.call(this);
|
||||
_this.writeBuffer = [];
|
||||
|
||||
if (uri && "object" === _typeof(uri)) {
|
||||
opts = uri;
|
||||
@@ -1860,7 +1842,6 @@
|
||||
_this.hostname = opts.hostname || (typeof location !== "undefined" ? location.hostname : "localhost");
|
||||
_this.port = opts.port || (typeof location !== "undefined" && location.port ? location.port : _this.secure ? "443" : "80");
|
||||
_this.transports = opts.transports || ["polling", "websocket"];
|
||||
_this.readyState = "";
|
||||
_this.writeBuffer = [];
|
||||
_this.prevBufferLen = 0;
|
||||
_this.opts = _extends({
|
||||
@@ -1870,6 +1851,7 @@
|
||||
upgrade: true,
|
||||
timestampParam: "t",
|
||||
rememberUpgrade: false,
|
||||
addTrailingSlash: true,
|
||||
rejectUnauthorized: true,
|
||||
perMessageDeflate: {
|
||||
threshold: 1024
|
||||
@@ -1877,7 +1859,7 @@
|
||||
transportOptions: {},
|
||||
closeOnBeforeunload: true
|
||||
}, opts);
|
||||
_this.opts.path = _this.opts.path.replace(/\/$/, "") + "/";
|
||||
_this.opts.path = _this.opts.path.replace(/\/$/, "") + (_this.opts.addTrailingSlash ? "/" : "");
|
||||
|
||||
if (typeof _this.opts.query === "string") {
|
||||
_this.opts.query = decode(_this.opts.query);
|
||||
@@ -1926,9 +1908,9 @@
|
||||
/**
|
||||
* Creates transport of the given type.
|
||||
*
|
||||
* @param {String} transport name
|
||||
* @param {String} name - transport name
|
||||
* @return {Transport}
|
||||
* @api private
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
@@ -1957,7 +1939,7 @@
|
||||
/**
|
||||
* Initializes transport to use and starts probe.
|
||||
*
|
||||
* @api private
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -1995,7 +1977,7 @@
|
||||
/**
|
||||
* Sets the current transport. Disables the existing one (if any).
|
||||
*
|
||||
* @api private
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -2017,8 +1999,8 @@
|
||||
/**
|
||||
* Probes a transport.
|
||||
*
|
||||
* @param {String} transport name
|
||||
* @api private
|
||||
* @param {String} name - transport name
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -2131,7 +2113,7 @@
|
||||
/**
|
||||
* Called when connection is deemed open.
|
||||
*
|
||||
* @api private
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -2143,7 +2125,7 @@
|
||||
this.flush(); // we check for `readyState` in case an `open`
|
||||
// listener already closed the socket
|
||||
|
||||
if ("open" === this.readyState && this.opts.upgrade && this.transport.pause) {
|
||||
if ("open" === this.readyState && this.opts.upgrade) {
|
||||
var i = 0;
|
||||
var l = this.upgrades.length;
|
||||
|
||||
@@ -2155,7 +2137,7 @@
|
||||
/**
|
||||
* Handles a packet.
|
||||
*
|
||||
* @api private
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -2196,7 +2178,7 @@
|
||||
* Called upon handshake completion.
|
||||
*
|
||||
* @param {Object} data - handshake obj
|
||||
* @api private
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -2217,7 +2199,7 @@
|
||||
/**
|
||||
* Sets and resets ping timeout timer based on server pings.
|
||||
*
|
||||
* @api private
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -2237,7 +2219,7 @@
|
||||
/**
|
||||
* Called on `drain` event
|
||||
*
|
||||
* @api private
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -2258,7 +2240,7 @@
|
||||
/**
|
||||
* Flush write buffers.
|
||||
*
|
||||
* @api private
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -2310,11 +2292,10 @@
|
||||
/**
|
||||
* Sends a message.
|
||||
*
|
||||
* @param {String} message.
|
||||
* @param {Function} callback function.
|
||||
* @param {String} msg - message.
|
||||
* @param {Object} options.
|
||||
* @param {Function} callback function.
|
||||
* @return {Socket} for chaining.
|
||||
* @api public
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -2332,11 +2313,11 @@
|
||||
/**
|
||||
* Sends a packet.
|
||||
*
|
||||
* @param {String} packet type.
|
||||
* @param {String} type: packet type.
|
||||
* @param {String} data.
|
||||
* @param {Object} options.
|
||||
* @param {Function} callback function.
|
||||
* @api private
|
||||
* @param {Function} fn - callback function.
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -2370,8 +2351,6 @@
|
||||
}
|
||||
/**
|
||||
* Closes the connection.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -2423,7 +2402,7 @@
|
||||
/**
|
||||
* Called upon transport error
|
||||
*
|
||||
* @api private
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -2436,7 +2415,7 @@
|
||||
/**
|
||||
* Called upon transport close.
|
||||
*
|
||||
* @api private
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -2472,9 +2451,8 @@
|
||||
/**
|
||||
* Filters upgrades, returning only those matching client transports.
|
||||
*
|
||||
* @param {Array} server upgrades
|
||||
* @api private
|
||||
*
|
||||
* @param {Array} upgrades - server upgrades
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
@@ -2671,7 +2649,7 @@
|
||||
|
||||
function reconstructPacket(packet, buffers) {
|
||||
packet.data = _reconstructPacket(packet.data, buffers);
|
||||
packet.attachments = undefined; // no longer useful
|
||||
delete packet.attachments; // no longer useful
|
||||
|
||||
return packet;
|
||||
}
|
||||
@@ -2749,8 +2727,12 @@
|
||||
value: function encode(obj) {
|
||||
if (obj.type === PacketType.EVENT || obj.type === PacketType.ACK) {
|
||||
if (hasBinary(obj)) {
|
||||
obj.type = obj.type === PacketType.EVENT ? PacketType.BINARY_EVENT : PacketType.BINARY_ACK;
|
||||
return this.encodeAsBinary(obj);
|
||||
return this.encodeAsBinary({
|
||||
type: obj.type === PacketType.EVENT ? PacketType.BINARY_EVENT : PacketType.BINARY_ACK,
|
||||
nsp: obj.nsp,
|
||||
data: obj.data,
|
||||
id: obj.id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2851,9 +2833,11 @@
|
||||
}
|
||||
|
||||
packet = this.decodeString(obj);
|
||||
var isBinaryEvent = packet.type === PacketType.BINARY_EVENT;
|
||||
|
||||
if (isBinaryEvent || packet.type === PacketType.BINARY_ACK) {
|
||||
packet.type = isBinaryEvent ? PacketType.EVENT : PacketType.ACK; // binary packet's json
|
||||
|
||||
if (packet.type === PacketType.BINARY_EVENT || packet.type === PacketType.BINARY_ACK) {
|
||||
// binary packet's json
|
||||
this.reconstructor = new BinaryReconstructor(packet); // no attachments, labeled binary but no binary data to follow
|
||||
|
||||
if (packet.attachments === 0) {
|
||||
@@ -2982,6 +2966,7 @@
|
||||
function destroy() {
|
||||
if (this.reconstructor) {
|
||||
this.reconstructor.finishedReconstruction();
|
||||
this.reconstructor = null;
|
||||
}
|
||||
}
|
||||
}], [{
|
||||
@@ -3150,6 +3135,12 @@
|
||||
*/
|
||||
|
||||
_this.connected = false;
|
||||
/**
|
||||
* Whether the connection state was recovered after a temporary disconnection. In that case, any missed packets will
|
||||
* be transmitted by the server.
|
||||
*/
|
||||
|
||||
_this.recovered = false;
|
||||
/**
|
||||
* Buffer for packets received before the CONNECT packet
|
||||
*/
|
||||
@@ -3160,6 +3151,14 @@
|
||||
*/
|
||||
|
||||
_this.sendBuffer = [];
|
||||
/**
|
||||
* The queue of packets to be sent with retry in case of failure.
|
||||
*
|
||||
* Packets are sent one by one, each waiting for the server acknowledgement, in order to guarantee the delivery order.
|
||||
* @private
|
||||
*/
|
||||
|
||||
_this._queue = [];
|
||||
_this.ids = 0;
|
||||
_this.acks = {};
|
||||
_this.flags = {};
|
||||
@@ -3170,6 +3169,7 @@
|
||||
_this.auth = opts.auth;
|
||||
}
|
||||
|
||||
_this._opts = _extends({}, opts);
|
||||
if (_this.io._autoConnect) _this.open();
|
||||
return _this;
|
||||
}
|
||||
@@ -3317,6 +3317,13 @@
|
||||
}
|
||||
|
||||
args.unshift(ev);
|
||||
|
||||
if (this._opts.retries && !this.flags.fromQueue && !this.flags["volatile"]) {
|
||||
this._addToQueue(args);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
var packet = {
|
||||
type: PacketType.EVENT,
|
||||
data: args
|
||||
@@ -3355,7 +3362,9 @@
|
||||
value: function _registerAckCallback(id, ack) {
|
||||
var _this2 = this;
|
||||
|
||||
var timeout = this.flags.timeout;
|
||||
var _a;
|
||||
|
||||
var timeout = (_a = this.flags.timeout) !== null && _a !== void 0 ? _a : this._opts.ackTimeout;
|
||||
|
||||
if (timeout === undefined) {
|
||||
this.acks[id] = ack;
|
||||
@@ -3386,6 +3395,135 @@
|
||||
ack.apply(_this2, [null].concat(args));
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Emits an event and waits for an acknowledgement
|
||||
*
|
||||
* @example
|
||||
* // without timeout
|
||||
* const response = await socket.emitWithAck("hello", "world");
|
||||
*
|
||||
* // with a specific timeout
|
||||
* try {
|
||||
* const response = await socket.timeout(1000).emitWithAck("hello", "world");
|
||||
* } catch (err) {
|
||||
* // the server did not acknowledge the event in the given delay
|
||||
* }
|
||||
*
|
||||
* @return a Promise that will be fulfilled when the server acknowledges the event
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: "emitWithAck",
|
||||
value: function emitWithAck(ev) {
|
||||
var _this3 = this;
|
||||
|
||||
for (var _len4 = arguments.length, args = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) {
|
||||
args[_key4 - 1] = arguments[_key4];
|
||||
}
|
||||
|
||||
// the timeout flag is optional
|
||||
var withErr = this.flags.timeout !== undefined || this._opts.ackTimeout !== undefined;
|
||||
return new Promise(function (resolve, reject) {
|
||||
args.push(function (arg1, arg2) {
|
||||
if (withErr) {
|
||||
return arg1 ? reject(arg1) : resolve(arg2);
|
||||
} else {
|
||||
return resolve(arg1);
|
||||
}
|
||||
});
|
||||
|
||||
_this3.emit.apply(_this3, [ev].concat(args));
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Add the packet to the queue.
|
||||
* @param args
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: "_addToQueue",
|
||||
value: function _addToQueue(args) {
|
||||
var _this4 = this;
|
||||
|
||||
var ack;
|
||||
|
||||
if (typeof args[args.length - 1] === "function") {
|
||||
ack = args.pop();
|
||||
}
|
||||
|
||||
var packet = {
|
||||
id: this.ids++,
|
||||
tryCount: 0,
|
||||
pending: false,
|
||||
args: args,
|
||||
flags: _extends({
|
||||
fromQueue: true
|
||||
}, this.flags)
|
||||
};
|
||||
args.push(function (err) {
|
||||
if (packet !== _this4._queue[0]) {
|
||||
// the packet has already been acknowledged
|
||||
return;
|
||||
}
|
||||
|
||||
var hasError = err !== null;
|
||||
|
||||
if (hasError) {
|
||||
if (packet.tryCount > _this4._opts.retries) {
|
||||
_this4._queue.shift();
|
||||
|
||||
if (ack) {
|
||||
ack(err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_this4._queue.shift();
|
||||
|
||||
if (ack) {
|
||||
for (var _len5 = arguments.length, responseArgs = new Array(_len5 > 1 ? _len5 - 1 : 0), _key5 = 1; _key5 < _len5; _key5++) {
|
||||
responseArgs[_key5 - 1] = arguments[_key5];
|
||||
}
|
||||
|
||||
ack.apply(void 0, [null].concat(responseArgs));
|
||||
}
|
||||
}
|
||||
|
||||
packet.pending = false;
|
||||
return _this4._drainQueue();
|
||||
});
|
||||
|
||||
this._queue.push(packet);
|
||||
|
||||
this._drainQueue();
|
||||
}
|
||||
/**
|
||||
* Send the first packet of the queue, and wait for an acknowledgement from the server.
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: "_drainQueue",
|
||||
value: function _drainQueue() {
|
||||
if (this._queue.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var packet = this._queue[0];
|
||||
|
||||
if (packet.pending) {
|
||||
return;
|
||||
}
|
||||
|
||||
packet.pending = true;
|
||||
packet.tryCount++;
|
||||
var currentId = this.ids;
|
||||
this.ids = packet.id; // the same id is reused for consecutive retries, in order to allow deduplication on the server side
|
||||
|
||||
this.flags = packet.flags;
|
||||
this.emit.apply(this, packet.args);
|
||||
this.ids = currentId; // restore offset
|
||||
}
|
||||
/**
|
||||
* Sends a packet.
|
||||
*
|
||||
@@ -3409,22 +3547,34 @@
|
||||
}, {
|
||||
key: "onopen",
|
||||
value: function onopen() {
|
||||
var _this3 = this;
|
||||
var _this5 = this;
|
||||
|
||||
if (typeof this.auth == "function") {
|
||||
this.auth(function (data) {
|
||||
_this3.packet({
|
||||
type: PacketType.CONNECT,
|
||||
data: data
|
||||
});
|
||||
_this5._sendConnectPacket(data);
|
||||
});
|
||||
} else {
|
||||
this.packet({
|
||||
type: PacketType.CONNECT,
|
||||
data: this.auth
|
||||
});
|
||||
this._sendConnectPacket(this.auth);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Sends a CONNECT packet to initiate the Socket.IO session.
|
||||
*
|
||||
* @param data
|
||||
* @private
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: "_sendConnectPacket",
|
||||
value: function _sendConnectPacket(data) {
|
||||
this.packet({
|
||||
type: PacketType.CONNECT,
|
||||
data: this._pid ? _extends({
|
||||
pid: this._pid,
|
||||
offset: this._lastOffset
|
||||
}, data) : data
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Called upon engine or manager `error`.
|
||||
*
|
||||
@@ -3470,8 +3620,7 @@
|
||||
switch (packet.type) {
|
||||
case PacketType.CONNECT:
|
||||
if (packet.data && packet.data.sid) {
|
||||
var id = packet.data.sid;
|
||||
this.onconnect(id);
|
||||
this.onconnect(packet.data.sid, packet.data.pid);
|
||||
} else {
|
||||
this.emitReserved("connect_error", new Error("It seems you are trying to reach a Socket.IO server in v2.x with a v3.x client, but they are not compatible (more information here: https://socket.io/docs/v3/migrating-from-2-x-to-3-0/)"));
|
||||
}
|
||||
@@ -3545,6 +3694,10 @@
|
||||
}
|
||||
|
||||
_get(_getPrototypeOf(Socket.prototype), "emit", this).apply(this, args);
|
||||
|
||||
if (this._pid && args.length && typeof args[args.length - 1] === "string") {
|
||||
this._lastOffset = args[args.length - 1];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Produces an ack callback to emit with an event.
|
||||
@@ -3562,8 +3715,8 @@
|
||||
if (sent) return;
|
||||
sent = true;
|
||||
|
||||
for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
|
||||
args[_key4] = arguments[_key4];
|
||||
for (var _len6 = arguments.length, args = new Array(_len6), _key6 = 0; _key6 < _len6; _key6++) {
|
||||
args[_key6] = arguments[_key6];
|
||||
}
|
||||
|
||||
self.packet({
|
||||
@@ -3598,8 +3751,11 @@
|
||||
|
||||
}, {
|
||||
key: "onconnect",
|
||||
value: function onconnect(id) {
|
||||
value: function onconnect(id, pid) {
|
||||
this.id = id;
|
||||
this.recovered = pid && this._pid === pid;
|
||||
this._pid = pid; // defined only if connection state recovery is enabled
|
||||
|
||||
this.connected = true;
|
||||
this.emitBuffered();
|
||||
this.emitReserved("connect");
|
||||
@@ -3613,16 +3769,16 @@
|
||||
}, {
|
||||
key: "emitBuffered",
|
||||
value: function emitBuffered() {
|
||||
var _this4 = this;
|
||||
var _this6 = this;
|
||||
|
||||
this.receiveBuffer.forEach(function (args) {
|
||||
return _this4.emitEvent(args);
|
||||
return _this6.emitEvent(args);
|
||||
});
|
||||
this.receiveBuffer = [];
|
||||
this.sendBuffer.forEach(function (packet) {
|
||||
_this4.notifyOutgoingListeners(packet);
|
||||
_this6.notifyOutgoingListeners(packet);
|
||||
|
||||
_this4.packet(packet);
|
||||
_this6.packet(packet);
|
||||
});
|
||||
this.sendBuffer = [];
|
||||
}
|
||||
@@ -4350,6 +4506,10 @@
|
||||
this.nsps[nsp] = socket;
|
||||
}
|
||||
|
||||
if (this._autoConnect) {
|
||||
socket.connect();
|
||||
}
|
||||
|
||||
return socket;
|
||||
}
|
||||
/**
|
||||
|
||||
File diff suppressed because one or more lines are too long
6
client-dist/socket.io.min.js
vendored
6
client-dist/socket.io.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
6
client-dist/socket.io.msgpack.min.js
vendored
6
client-dist/socket.io.msgpack.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -7,6 +7,11 @@ import type {
|
||||
EventNames,
|
||||
EventsMap,
|
||||
TypedEventBroadcaster,
|
||||
DecorateAcknowledgements,
|
||||
DecorateAcknowledgementsWithTimeoutAndMultipleResponses,
|
||||
AllButLast,
|
||||
Last,
|
||||
SecondArg,
|
||||
} from "./typed-events";
|
||||
|
||||
export class BroadcastOperator<EmitEvents extends EventsMap, SocketData>
|
||||
@@ -16,7 +21,9 @@ export class BroadcastOperator<EmitEvents extends EventsMap, SocketData>
|
||||
private readonly adapter: Adapter,
|
||||
private readonly rooms: Set<Room> = new Set<Room>(),
|
||||
private readonly exceptRooms: Set<Room> = new Set<Room>(),
|
||||
private readonly flags: BroadcastFlags = {}
|
||||
private readonly flags: BroadcastFlags & {
|
||||
expectSingleResponse?: boolean;
|
||||
} = {}
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -169,12 +176,10 @@ export class BroadcastOperator<EmitEvents extends EventsMap, SocketData>
|
||||
*/
|
||||
public timeout(timeout: number) {
|
||||
const flags = Object.assign({}, this.flags, { timeout });
|
||||
return new BroadcastOperator<EmitEvents, SocketData>(
|
||||
this.adapter,
|
||||
this.rooms,
|
||||
this.exceptRooms,
|
||||
flags
|
||||
);
|
||||
return new BroadcastOperator<
|
||||
DecorateAcknowledgementsWithTimeoutAndMultipleResponses<EmitEvents>,
|
||||
SocketData
|
||||
>(this.adapter, this.rooms, this.exceptRooms, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -230,7 +235,10 @@ export class BroadcastOperator<EmitEvents extends EventsMap, SocketData>
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
timedOut = true;
|
||||
ack.apply(this, [new Error("operation has timed out"), responses]);
|
||||
ack.apply(this, [
|
||||
new Error("operation has timed out"),
|
||||
this.flags.expectSingleResponse ? null : responses,
|
||||
]);
|
||||
}, this.flags.timeout);
|
||||
|
||||
let expectedServerCount = -1;
|
||||
@@ -244,7 +252,10 @@ export class BroadcastOperator<EmitEvents extends EventsMap, SocketData>
|
||||
responses.length === expectedClientCount
|
||||
) {
|
||||
clearTimeout(timer);
|
||||
ack.apply(this, [null, responses]);
|
||||
ack.apply(this, [
|
||||
null,
|
||||
this.flags.expectSingleResponse ? null : responses,
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -276,6 +287,36 @@ export class BroadcastOperator<EmitEvents extends EventsMap, SocketData>
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event and waits for an acknowledgement from all clients.
|
||||
*
|
||||
* @example
|
||||
* try {
|
||||
* const responses = await io.timeout(1000).emitWithAck("some-event");
|
||||
* console.log(responses); // one response per client
|
||||
* } catch (e) {
|
||||
* // some clients did not acknowledge the event in the given delay
|
||||
* }
|
||||
*
|
||||
* @return a Promise that will be fulfilled when all clients have acknowledged the event
|
||||
*/
|
||||
public emitWithAck<Ev extends EventNames<EmitEvents>>(
|
||||
ev: Ev,
|
||||
...args: AllButLast<EventParams<EmitEvents, Ev>>
|
||||
): Promise<SecondArg<Last<EventParams<EmitEvents, Ev>>>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
args.push((err, responses) => {
|
||||
if (err) {
|
||||
err.responses = responses;
|
||||
return reject(err);
|
||||
} else {
|
||||
return resolve(responses);
|
||||
}
|
||||
});
|
||||
this.emit(ev, ...(args as any[] as EventParams<EmitEvents, Ev>));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of clients.
|
||||
*
|
||||
@@ -444,10 +485,44 @@ export class RemoteSocket<EmitEvents extends EventsMap, SocketData>
|
||||
this.data = details.data;
|
||||
this.operator = new BroadcastOperator<EmitEvents, SocketData>(
|
||||
adapter,
|
||||
new Set([this.id])
|
||||
new Set([this.id]),
|
||||
new Set(),
|
||||
{
|
||||
expectSingleResponse: true, // so that remoteSocket.emit() with acknowledgement behaves like socket.emit()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a timeout in milliseconds for the next operation.
|
||||
*
|
||||
* @example
|
||||
* const sockets = await io.fetchSockets();
|
||||
*
|
||||
* for (const socket of sockets) {
|
||||
* if (someCondition) {
|
||||
* socket.timeout(1000).emit("some-event", (err) => {
|
||||
* if (err) {
|
||||
* // the client did not acknowledge the event in the given delay
|
||||
* }
|
||||
* });
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // note: if possible, using a room instead of looping over all sockets is preferable
|
||||
* io.timeout(1000).to(someConditionRoom).emit("some-event", (err, responses) => {
|
||||
* // ...
|
||||
* });
|
||||
*
|
||||
* @param timeout
|
||||
*/
|
||||
public timeout(timeout: number) {
|
||||
return this.operator.timeout(timeout) as BroadcastOperator<
|
||||
DecorateAcknowledgements<EmitEvents>,
|
||||
SocketData
|
||||
>;
|
||||
}
|
||||
|
||||
public emit<Ev extends EventNames<EmitEvents>>(
|
||||
ev: Ev,
|
||||
...args: EventParams<EmitEvents, Ev>
|
||||
|
||||
@@ -114,7 +114,7 @@ export class Client<
|
||||
* @param {Object} auth - the auth parameters
|
||||
* @private
|
||||
*/
|
||||
private connect(name: string, auth: object = {}): void {
|
||||
private connect(name: string, auth: Record<string, unknown> = {}): void {
|
||||
if (this.server._nsps.has(name)) {
|
||||
debug("connecting to namespace %s", name);
|
||||
return this.doConnect(name, auth);
|
||||
@@ -152,10 +152,10 @@ export class Client<
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private doConnect(name: string, auth: object): void {
|
||||
private doConnect(name: string, auth: Record<string, unknown>): void {
|
||||
const nsp = this.server.of(name);
|
||||
|
||||
const socket = nsp._add(this, auth, () => {
|
||||
nsp._add(this, auth, (socket) => {
|
||||
this.sockets.set(socket.id, socket);
|
||||
this.nsps.set(nsp.name, socket);
|
||||
|
||||
@@ -228,7 +228,7 @@ export class Client<
|
||||
}
|
||||
|
||||
private writeToEngine(
|
||||
encodedPackets: Array<String | Buffer>,
|
||||
encodedPackets: Array<string | Buffer>,
|
||||
opts: WriteOptions
|
||||
): void {
|
||||
if (opts.volatile && !this.conn.transport.writable) {
|
||||
@@ -267,7 +267,7 @@ export class Client<
|
||||
*/
|
||||
private ondecoded(packet: Packet): void {
|
||||
let namespace: string;
|
||||
let authPayload;
|
||||
let authPayload: Record<string, unknown>;
|
||||
if (this.conn.protocol === 3) {
|
||||
const parsed = url.parse(packet.nsp, true);
|
||||
namespace = parsed.pathname!;
|
||||
@@ -311,9 +311,13 @@ export class Client<
|
||||
* Called upon transport close.
|
||||
*
|
||||
* @param reason
|
||||
* @param description
|
||||
* @private
|
||||
*/
|
||||
private onclose(reason: CloseReason | "forced server close"): void {
|
||||
private onclose(
|
||||
reason: CloseReason | "forced server close",
|
||||
description?: any
|
||||
): void {
|
||||
debug("client close with reason %s", reason);
|
||||
|
||||
// ignore a potential subsequent `close` event
|
||||
@@ -321,7 +325,7 @@ export class Client<
|
||||
|
||||
// `nsps` and `sockets` are cleaned up seamlessly
|
||||
for (const socket of this.sockets.values()) {
|
||||
socket._onclose(reason);
|
||||
socket._onclose(reason, description);
|
||||
}
|
||||
this.sockets.clear();
|
||||
|
||||
|
||||
132
lib/index.ts
132
lib/index.ts
@@ -17,11 +17,16 @@ import { Client } from "./client";
|
||||
import { EventEmitter } from "events";
|
||||
import { ExtendedError, Namespace, ServerReservedEventsMap } from "./namespace";
|
||||
import { ParentNamespace } from "./parent-namespace";
|
||||
import { Adapter, Room, SocketId } from "socket.io-adapter";
|
||||
import {
|
||||
Adapter,
|
||||
SessionAwareAdapter,
|
||||
Room,
|
||||
SocketId,
|
||||
} from "socket.io-adapter";
|
||||
import * as parser from "socket.io-parser";
|
||||
import type { Encoder } from "socket.io-parser";
|
||||
import debugModule from "debug";
|
||||
import { Socket } from "./socket";
|
||||
import { Socket, DisconnectReason } from "./socket";
|
||||
import type { BroadcastOperator, RemoteSocket } from "./broadcast-operator";
|
||||
import {
|
||||
EventsMap,
|
||||
@@ -29,8 +34,14 @@ import {
|
||||
EventParams,
|
||||
StrictEventEmitter,
|
||||
EventNames,
|
||||
DecorateAcknowledgementsWithTimeoutAndMultipleResponses,
|
||||
AllButLast,
|
||||
Last,
|
||||
FirstArg,
|
||||
SecondArg,
|
||||
} from "./typed-events";
|
||||
import { patchAdapter, restoreAdapter, serveFile } from "./uws";
|
||||
import type { BaseServer } from "engine.io/build/server";
|
||||
|
||||
const debug = debugModule("socket.io:server");
|
||||
|
||||
@@ -71,6 +82,30 @@ interface ServerOptions extends EngineOptions, AttachOptions {
|
||||
* @default 45000
|
||||
*/
|
||||
connectTimeout: number;
|
||||
/**
|
||||
* Whether to enable the recovery of connection state when a client temporarily disconnects.
|
||||
*
|
||||
* The connection state includes the missed packets, the rooms the socket was in and the `data` attribute.
|
||||
*/
|
||||
connectionStateRecovery: {
|
||||
/**
|
||||
* The backup duration of the sessions and the packets.
|
||||
*
|
||||
* @default 120000 (2 minutes)
|
||||
*/
|
||||
maxDisconnectionDuration?: number;
|
||||
/**
|
||||
* Whether to skip middlewares upon successful connection state recovery.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
skipMiddlewares?: boolean;
|
||||
};
|
||||
/**
|
||||
* Whether to remove child namespaces that have no sockets connected to them
|
||||
* @default false
|
||||
*/
|
||||
cleanupEmptyChildNamespaces: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,7 +162,7 @@ export class Server<
|
||||
* const clientsCount = io.engine.clientsCount;
|
||||
*
|
||||
*/
|
||||
public engine: any;
|
||||
public engine: BaseServer;
|
||||
|
||||
/** @private */
|
||||
readonly _parser: typeof parser;
|
||||
@@ -147,7 +182,7 @@ export class Server<
|
||||
> = new Map();
|
||||
private _adapter?: AdapterConstructor;
|
||||
private _serveClient: boolean;
|
||||
private opts: Partial<EngineOptions>;
|
||||
private readonly opts: Partial<ServerOptions>;
|
||||
private eio: Engine;
|
||||
private _path: string;
|
||||
private clientPathRegex: RegExp;
|
||||
@@ -203,15 +238,31 @@ export class Server<
|
||||
this.serveClient(false !== opts.serveClient);
|
||||
this._parser = opts.parser || parser;
|
||||
this.encoder = new this._parser.Encoder();
|
||||
this.adapter(opts.adapter || Adapter);
|
||||
this.sockets = this.of("/");
|
||||
this.opts = opts;
|
||||
if (opts.connectionStateRecovery) {
|
||||
opts.connectionStateRecovery = Object.assign(
|
||||
{
|
||||
maxDisconnectionDuration: 2 * 60 * 1000,
|
||||
skipMiddlewares: true,
|
||||
},
|
||||
opts.connectionStateRecovery
|
||||
);
|
||||
this.adapter(opts.adapter || SessionAwareAdapter);
|
||||
} else {
|
||||
this.adapter(opts.adapter || Adapter);
|
||||
}
|
||||
opts.cleanupEmptyChildNamespaces = !!opts.cleanupEmptyChildNamespaces;
|
||||
this.sockets = this.of("/");
|
||||
if (srv || typeof srv == "number")
|
||||
this.attach(
|
||||
srv as http.Server | HTTPSServer | Http2SecureServer | number
|
||||
);
|
||||
}
|
||||
|
||||
get _opts() {
|
||||
return this.opts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets/gets whether client code is being served.
|
||||
*
|
||||
@@ -437,7 +488,7 @@ export class Server<
|
||||
res.writeHeader("cache-control", "public, max-age=0");
|
||||
res.writeHeader(
|
||||
"content-type",
|
||||
"application/" + (isMap ? "json" : "javascript")
|
||||
"application/" + (isMap ? "json" : "javascript") + "; charset=utf-8"
|
||||
);
|
||||
res.writeHeader("etag", expectedEtag);
|
||||
|
||||
@@ -530,7 +581,7 @@ export class Server<
|
||||
res.setHeader("Cache-Control", "public, max-age=0");
|
||||
res.setHeader(
|
||||
"Content-Type",
|
||||
"application/" + (isMap ? "json" : "javascript")
|
||||
"application/" + (isMap ? "json" : "javascript") + "; charset=utf-8"
|
||||
);
|
||||
res.setHeader("ETag", expectedEtag);
|
||||
|
||||
@@ -582,10 +633,10 @@ export class Server<
|
||||
/**
|
||||
* Binds socket.io to an engine.io instance.
|
||||
*
|
||||
* @param {engine.Server} engine engine.io (or compatible) server
|
||||
* @param engine engine.io (or compatible) server
|
||||
* @return self
|
||||
*/
|
||||
public bind(engine): this {
|
||||
public bind(engine: BaseServer): this {
|
||||
this.engine = engine;
|
||||
this.engine.on("connection", this.onconnection.bind(this));
|
||||
return this;
|
||||
@@ -763,6 +814,26 @@ export class Server<
|
||||
return this.sockets.except(room);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event and waits for an acknowledgement from all clients.
|
||||
*
|
||||
* @example
|
||||
* try {
|
||||
* const responses = await io.timeout(1000).emitWithAck("some-event");
|
||||
* console.log(responses); // one response per client
|
||||
* } catch (e) {
|
||||
* // some clients did not acknowledge the event in the given delay
|
||||
* }
|
||||
*
|
||||
* @return a Promise that will be fulfilled when all clients have acknowledged the event
|
||||
*/
|
||||
public emitWithAck<Ev extends EventNames<EmitEvents>>(
|
||||
ev: Ev,
|
||||
...args: AllButLast<EventParams<EmitEvents, Ev>>
|
||||
): Promise<SecondArg<Last<EventParams<EmitEvents, Ev>>>> {
|
||||
return this.sockets.emitWithAck(ev, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a `message` event to all clients.
|
||||
*
|
||||
@@ -806,9 +877,9 @@ export class Server<
|
||||
* // acknowledgements (without binary content) are supported too:
|
||||
* io.serverSideEmit("ping", (err, responses) => {
|
||||
* if (err) {
|
||||
* // some clients did not acknowledge the event in the given delay
|
||||
* // some servers did not acknowledge the event in the given delay
|
||||
* } else {
|
||||
* console.log(responses); // one response per client
|
||||
* console.log(responses); // one response per server (except the current one)
|
||||
* }
|
||||
* });
|
||||
*
|
||||
@@ -821,11 +892,37 @@ export class Server<
|
||||
*/
|
||||
public serverSideEmit<Ev extends EventNames<ServerSideEvents>>(
|
||||
ev: Ev,
|
||||
...args: EventParams<ServerSideEvents, Ev>
|
||||
...args: EventParams<
|
||||
DecorateAcknowledgementsWithTimeoutAndMultipleResponses<ServerSideEvents>,
|
||||
Ev
|
||||
>
|
||||
): boolean {
|
||||
return this.sockets.serverSideEmit(ev, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message and expect an acknowledgement from the other Socket.IO servers of the cluster.
|
||||
*
|
||||
* @example
|
||||
* try {
|
||||
* const responses = await io.serverSideEmitWithAck("ping");
|
||||
* console.log(responses); // one response per server (except the current one)
|
||||
* } catch (e) {
|
||||
* // some servers did not acknowledge the event in the given delay
|
||||
* }
|
||||
*
|
||||
* @param ev - the event name
|
||||
* @param args - an array of arguments
|
||||
*
|
||||
* @return a Promise that will be fulfilled when all servers have acknowledged the event
|
||||
*/
|
||||
public serverSideEmitWithAck<Ev extends EventNames<ServerSideEvents>>(
|
||||
ev: Ev,
|
||||
...args: AllButLast<EventParams<ServerSideEvents, Ev>>
|
||||
): Promise<FirstArg<Last<EventParams<ServerSideEvents, Ev>>>[]> {
|
||||
return this.sockets.serverSideEmitWithAck(ev, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of socket ids.
|
||||
*
|
||||
@@ -999,5 +1096,12 @@ module.exports.Server = Server;
|
||||
module.exports.Namespace = Namespace;
|
||||
module.exports.Socket = Socket;
|
||||
|
||||
export { Socket, ServerOptions, Namespace, BroadcastOperator, RemoteSocket };
|
||||
export {
|
||||
Socket,
|
||||
DisconnectReason,
|
||||
ServerOptions,
|
||||
Namespace,
|
||||
BroadcastOperator,
|
||||
RemoteSocket,
|
||||
};
|
||||
export { Event } from "./socket";
|
||||
|
||||
159
lib/namespace.ts
159
lib/namespace.ts
@@ -6,11 +6,16 @@ import {
|
||||
EventsMap,
|
||||
StrictEventEmitter,
|
||||
DefaultEventsMap,
|
||||
DecorateAcknowledgementsWithTimeoutAndMultipleResponses,
|
||||
AllButLast,
|
||||
Last,
|
||||
FirstArg,
|
||||
SecondArg,
|
||||
} from "./typed-events";
|
||||
import type { Client } from "./client";
|
||||
import debugModule from "debug";
|
||||
import type { Adapter, Room, SocketId } from "socket.io-adapter";
|
||||
import { BroadcastOperator, RemoteSocket } from "./broadcast-operator";
|
||||
import { BroadcastOperator } from "./broadcast-operator";
|
||||
|
||||
const debug = debugModule("socket.io:namespace");
|
||||
|
||||
@@ -296,13 +301,25 @@ export class Namespace<
|
||||
* @return {Socket}
|
||||
* @private
|
||||
*/
|
||||
_add(
|
||||
async _add(
|
||||
client: Client<ListenEvents, EmitEvents, ServerSideEvents>,
|
||||
query,
|
||||
fn?: () => void
|
||||
): Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData> {
|
||||
auth: Record<string, unknown>,
|
||||
fn: (
|
||||
socket: Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
) => void
|
||||
) {
|
||||
debug("adding socket to nsp %s", this.name);
|
||||
const socket = new Socket(this, client, query);
|
||||
const socket = await this._createSocket(client, auth);
|
||||
|
||||
if (
|
||||
// @ts-ignore
|
||||
this.server.opts.connectionStateRecovery?.skipMiddlewares &&
|
||||
socket.recovered &&
|
||||
client.conn.readyState === "open"
|
||||
) {
|
||||
return this._doConnect(socket, fn);
|
||||
}
|
||||
|
||||
this.run(socket, (err) => {
|
||||
process.nextTick(() => {
|
||||
if ("open" !== client.conn.readyState) {
|
||||
@@ -324,22 +341,53 @@ export class Namespace<
|
||||
}
|
||||
}
|
||||
|
||||
// track socket
|
||||
this.sockets.set(socket.id, socket);
|
||||
|
||||
// it's paramount that the internal `onconnect` logic
|
||||
// fires before user-set events to prevent state order
|
||||
// violations (such as a disconnection before the connection
|
||||
// logic is complete)
|
||||
socket._onconnect();
|
||||
if (fn) fn();
|
||||
|
||||
// fire user-set events
|
||||
this.emitReserved("connect", socket);
|
||||
this.emitReserved("connection", socket);
|
||||
this._doConnect(socket, fn);
|
||||
});
|
||||
});
|
||||
return socket;
|
||||
}
|
||||
|
||||
private async _createSocket(
|
||||
client: Client<ListenEvents, EmitEvents, ServerSideEvents>,
|
||||
auth: Record<string, unknown>
|
||||
) {
|
||||
const sessionId = auth.pid;
|
||||
const offset = auth.offset;
|
||||
if (
|
||||
// @ts-ignore
|
||||
this.server.opts.connectionStateRecovery &&
|
||||
typeof sessionId === "string" &&
|
||||
typeof offset === "string"
|
||||
) {
|
||||
const session = await this.adapter.restoreSession(sessionId, offset);
|
||||
if (session) {
|
||||
debug("connection state recovered for sid %s", session.sid);
|
||||
return new Socket(this, client, auth, session);
|
||||
} else {
|
||||
debug("unable to restore session state");
|
||||
}
|
||||
}
|
||||
return new Socket(this, client, auth);
|
||||
}
|
||||
|
||||
private _doConnect(
|
||||
socket: Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData>,
|
||||
fn: (
|
||||
socket: Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
) => void
|
||||
) {
|
||||
// track socket
|
||||
this.sockets.set(socket.id, socket);
|
||||
|
||||
// it's paramount that the internal `onconnect` logic
|
||||
// fires before user-set events to prevent state order
|
||||
// violations (such as a disconnection before the connection
|
||||
// logic is complete)
|
||||
socket._onconnect();
|
||||
if (fn) fn(socket);
|
||||
|
||||
// fire user-set events
|
||||
this.emitReserved("connect", socket);
|
||||
this.emitReserved("connection", socket);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -389,6 +437,30 @@ export class Namespace<
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event and waits for an acknowledgement from all clients.
|
||||
*
|
||||
* @example
|
||||
* const myNamespace = io.of("/my-namespace");
|
||||
*
|
||||
* try {
|
||||
* const responses = await myNamespace.timeout(1000).emitWithAck("some-event");
|
||||
* console.log(responses); // one response per client
|
||||
* } catch (e) {
|
||||
* // some clients did not acknowledge the event in the given delay
|
||||
* }
|
||||
*
|
||||
* @return a Promise that will be fulfilled when all clients have acknowledged the event
|
||||
*/
|
||||
public emitWithAck<Ev extends EventNames<EmitEvents>>(
|
||||
ev: Ev,
|
||||
...args: AllButLast<EventParams<EmitEvents, Ev>>
|
||||
): Promise<SecondArg<Last<EventParams<EmitEvents, Ev>>>> {
|
||||
return new BroadcastOperator<EmitEvents, SocketData>(
|
||||
this.adapter
|
||||
).emitWithAck(ev, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a `message` event to all clients.
|
||||
*
|
||||
@@ -436,9 +508,9 @@ export class Namespace<
|
||||
* // acknowledgements (without binary content) are supported too:
|
||||
* myNamespace.serverSideEmit("ping", (err, responses) => {
|
||||
* if (err) {
|
||||
* // some clients did not acknowledge the event in the given delay
|
||||
* // some servers did not acknowledge the event in the given delay
|
||||
* } else {
|
||||
* console.log(responses); // one response per client
|
||||
* console.log(responses); // one response per server (except the current one)
|
||||
* }
|
||||
* });
|
||||
*
|
||||
@@ -451,7 +523,10 @@ export class Namespace<
|
||||
*/
|
||||
public serverSideEmit<Ev extends EventNames<ServerSideEvents>>(
|
||||
ev: Ev,
|
||||
...args: EventParams<ServerSideEvents, Ev>
|
||||
...args: EventParams<
|
||||
DecorateAcknowledgementsWithTimeoutAndMultipleResponses<ServerSideEvents>,
|
||||
Ev
|
||||
>
|
||||
): boolean {
|
||||
if (RESERVED_EVENTS.has(ev)) {
|
||||
throw new Error(`"${String(ev)}" is a reserved event name`);
|
||||
@@ -461,6 +536,44 @@ export class Namespace<
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message and expect an acknowledgement from the other Socket.IO servers of the cluster.
|
||||
*
|
||||
* @example
|
||||
* const myNamespace = io.of("/my-namespace");
|
||||
*
|
||||
* try {
|
||||
* const responses = await myNamespace.serverSideEmitWithAck("ping");
|
||||
* console.log(responses); // one response per server (except the current one)
|
||||
* } catch (e) {
|
||||
* // some servers did not acknowledge the event in the given delay
|
||||
* }
|
||||
*
|
||||
* @param ev - the event name
|
||||
* @param args - an array of arguments
|
||||
*
|
||||
* @return a Promise that will be fulfilled when all servers have acknowledged the event
|
||||
*/
|
||||
public serverSideEmitWithAck<Ev extends EventNames<ServerSideEvents>>(
|
||||
ev: Ev,
|
||||
...args: AllButLast<EventParams<ServerSideEvents, Ev>>
|
||||
): Promise<FirstArg<Last<EventParams<ServerSideEvents, Ev>>>[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
args.push((err, responses) => {
|
||||
if (err) {
|
||||
err.responses = responses;
|
||||
return reject(err);
|
||||
} else {
|
||||
return resolve(responses);
|
||||
}
|
||||
});
|
||||
this.serverSideEmit(
|
||||
ev,
|
||||
...(args as any[] as EventParams<ServerSideEvents, Ev>)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a packet is received from another Socket.IO server
|
||||
*
|
||||
|
||||
@@ -7,6 +7,9 @@ import type {
|
||||
DefaultEventsMap,
|
||||
} from "./typed-events";
|
||||
import type { BroadcastOptions } from "socket.io-adapter";
|
||||
import debugModule from "debug";
|
||||
|
||||
const debug = debugModule("socket.io:parent-namespace");
|
||||
|
||||
export class ParentNamespace<
|
||||
ListenEvents extends EventsMap = DefaultEventsMap,
|
||||
@@ -52,6 +55,7 @@ export class ParentNamespace<
|
||||
createChild(
|
||||
name: string
|
||||
): Namespace<ListenEvents, EmitEvents, ServerSideEvents, SocketData> {
|
||||
debug("creating child namespace %s", name);
|
||||
const namespace = new Namespace(this.server, name);
|
||||
namespace._fns = this._fns.slice(0);
|
||||
this.listeners("connect").forEach((listener) =>
|
||||
@@ -61,6 +65,21 @@ export class ParentNamespace<
|
||||
namespace.on("connection", listener)
|
||||
);
|
||||
this.children.add(namespace);
|
||||
|
||||
if (this.server._opts.cleanupEmptyChildNamespaces) {
|
||||
const remove = namespace._remove;
|
||||
|
||||
namespace._remove = (socket) => {
|
||||
remove.call(namespace, socket);
|
||||
if (namespace.sockets.size === 0) {
|
||||
debug("closing child namespace %s", name);
|
||||
namespace.adapter.close();
|
||||
this.server._nsps.delete(namespace.name);
|
||||
this.children.delete(namespace);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.server._nsps.set(name, namespace);
|
||||
return namespace;
|
||||
}
|
||||
|
||||
166
lib/socket.ts
166
lib/socket.ts
@@ -2,19 +2,26 @@ import { Packet, PacketType } from "socket.io-parser";
|
||||
import debugModule from "debug";
|
||||
import type { Server } from "./index";
|
||||
import {
|
||||
EventParams,
|
||||
EventNames,
|
||||
EventsMap,
|
||||
StrictEventEmitter,
|
||||
AllButLast,
|
||||
DecorateAcknowledgements,
|
||||
DecorateAcknowledgementsWithMultipleResponses,
|
||||
DefaultEventsMap,
|
||||
EventNames,
|
||||
EventParams,
|
||||
EventsMap,
|
||||
FirstArg,
|
||||
Last,
|
||||
StrictEventEmitter,
|
||||
} from "./typed-events";
|
||||
import type { Client } from "./client";
|
||||
import type { Namespace, NamespaceReservedEventsMap } from "./namespace";
|
||||
import type { IncomingMessage, IncomingHttpHeaders } from "http";
|
||||
import type { IncomingHttpHeaders, IncomingMessage } from "http";
|
||||
import type {
|
||||
Adapter,
|
||||
BroadcastFlags,
|
||||
PrivateSessionId,
|
||||
Room,
|
||||
Session,
|
||||
SocketId,
|
||||
} from "socket.io-adapter";
|
||||
import base64id from "base64id";
|
||||
@@ -39,9 +46,18 @@ export type DisconnectReason =
|
||||
| "client namespace disconnect"
|
||||
| "server namespace disconnect";
|
||||
|
||||
const RECOVERABLE_DISCONNECT_REASONS: ReadonlySet<DisconnectReason> = new Set([
|
||||
"transport error",
|
||||
"transport close",
|
||||
"forced close",
|
||||
"ping timeout",
|
||||
"server shutting down",
|
||||
"forced server close",
|
||||
]);
|
||||
|
||||
export interface SocketReservedEventsMap {
|
||||
disconnect: (reason: DisconnectReason) => void;
|
||||
disconnecting: (reason: DisconnectReason) => void;
|
||||
disconnect: (reason: DisconnectReason, description?: any) => void;
|
||||
disconnecting: (reason: DisconnectReason, description?: any) => void;
|
||||
error: (err: Error) => void;
|
||||
}
|
||||
|
||||
@@ -173,6 +189,11 @@ export class Socket<
|
||||
* An unique identifier for the session.
|
||||
*/
|
||||
public readonly id: SocketId;
|
||||
/**
|
||||
* Whether the connection state was recovered after a temporary disconnection. In that case, any missed packets will
|
||||
* be transmitted to the client, the data attribute and the rooms will be restored.
|
||||
*/
|
||||
public readonly recovered: boolean = false;
|
||||
/**
|
||||
* The handshake details.
|
||||
*/
|
||||
@@ -197,6 +218,14 @@ export class Socket<
|
||||
*/
|
||||
public connected: boolean = false;
|
||||
|
||||
/**
|
||||
* The session ID, which must not be shared (unlike {@link id}).
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private readonly pid: PrivateSessionId;
|
||||
|
||||
// TODO: remove this unused reference
|
||||
private readonly server: Server<
|
||||
ListenEvents,
|
||||
EmitEvents,
|
||||
@@ -221,16 +250,34 @@ export class Socket<
|
||||
constructor(
|
||||
readonly nsp: Namespace<ListenEvents, EmitEvents, ServerSideEvents>,
|
||||
readonly client: Client<ListenEvents, EmitEvents, ServerSideEvents>,
|
||||
auth: object
|
||||
auth: Record<string, unknown>,
|
||||
previousSession?: Session
|
||||
) {
|
||||
super();
|
||||
this.server = nsp.server;
|
||||
this.adapter = this.nsp.adapter;
|
||||
if (client.conn.protocol === 3) {
|
||||
// @ts-ignore
|
||||
this.id = nsp.name !== "/" ? nsp.name + "#" + client.id : client.id;
|
||||
if (previousSession) {
|
||||
this.id = previousSession.sid;
|
||||
this.pid = previousSession.pid;
|
||||
previousSession.rooms.forEach((room) => this.join(room));
|
||||
this.data = previousSession.data as Partial<SocketData>;
|
||||
previousSession.missedPackets.forEach((packet) => {
|
||||
this.packet({
|
||||
type: PacketType.EVENT,
|
||||
data: packet,
|
||||
});
|
||||
});
|
||||
this.recovered = true;
|
||||
} else {
|
||||
this.id = base64id.generateId(); // don't reuse the Engine.IO id because it's sensitive information
|
||||
if (client.conn.protocol === 3) {
|
||||
// @ts-ignore
|
||||
this.id = nsp.name !== "/" ? nsp.name + "#" + client.id : client.id;
|
||||
} else {
|
||||
this.id = base64id.generateId(); // don't reuse the Engine.IO id because it's sensitive information
|
||||
}
|
||||
if (this.server._opts.connectionStateRecovery) {
|
||||
this.pid = base64id.generateId();
|
||||
}
|
||||
}
|
||||
this.handshake = this.buildHandshake(auth);
|
||||
}
|
||||
@@ -299,12 +346,58 @@ export class Socket<
|
||||
const flags = Object.assign({}, this.flags);
|
||||
this.flags = {};
|
||||
|
||||
this.notifyOutgoingListeners(packet);
|
||||
this.packet(packet, flags);
|
||||
// @ts-ignore
|
||||
if (this.nsp.server.opts.connectionStateRecovery) {
|
||||
// this ensures the packet is stored and can be transmitted upon reconnection
|
||||
this.adapter.broadcast(packet, {
|
||||
rooms: new Set([this.id]),
|
||||
except: new Set(),
|
||||
flags,
|
||||
});
|
||||
} else {
|
||||
this.notifyOutgoingListeners(packet);
|
||||
this.packet(packet, flags);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event and waits for an acknowledgement
|
||||
*
|
||||
* @example
|
||||
* io.on("connection", async (socket) => {
|
||||
* // without timeout
|
||||
* const response = await socket.emitWithAck("hello", "world");
|
||||
*
|
||||
* // with a specific timeout
|
||||
* try {
|
||||
* const response = await socket.timeout(1000).emitWithAck("hello", "world");
|
||||
* } catch (err) {
|
||||
* // the client did not acknowledge the event in the given delay
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* @return a Promise that will be fulfilled when the client acknowledges the event
|
||||
*/
|
||||
public emitWithAck<Ev extends EventNames<EmitEvents>>(
|
||||
ev: Ev,
|
||||
...args: AllButLast<EventParams<EmitEvents, Ev>>
|
||||
): Promise<FirstArg<Last<EventParams<EmitEvents, Ev>>>> {
|
||||
// the timeout flag is optional
|
||||
const withErr = this.flags.timeout !== undefined;
|
||||
return new Promise((resolve, reject) => {
|
||||
args.push((arg1, arg2) => {
|
||||
if (withErr) {
|
||||
return arg1 ? reject(arg1) : resolve(arg2);
|
||||
} else {
|
||||
return resolve(arg1);
|
||||
}
|
||||
});
|
||||
this.emit(ev, ...(args as any[] as EventParams<EmitEvents, Ev>));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@@ -508,7 +601,10 @@ export class Socket<
|
||||
if (this.conn.protocol === 3) {
|
||||
this.packet({ type: PacketType.CONNECT });
|
||||
} else {
|
||||
this.packet({ type: PacketType.CONNECT, data: { sid: this.id } });
|
||||
this.packet({
|
||||
type: PacketType.CONNECT,
|
||||
data: { sid: this.id, pid: this.pid },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -636,19 +732,34 @@ export class Socket<
|
||||
* Called upon closing. Called by `Client`.
|
||||
*
|
||||
* @param {String} reason
|
||||
* @param description
|
||||
* @throw {Error} optional error object
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onclose(reason: DisconnectReason): this | undefined {
|
||||
_onclose(reason: DisconnectReason, description?: any): this | undefined {
|
||||
if (!this.connected) return this;
|
||||
debug("closing socket - reason %s", reason);
|
||||
this.emitReserved("disconnecting", reason);
|
||||
this.emitReserved("disconnecting", reason, description);
|
||||
|
||||
if (
|
||||
this.server._opts.connectionStateRecovery &&
|
||||
RECOVERABLE_DISCONNECT_REASONS.has(reason)
|
||||
) {
|
||||
debug("connection state recovery is enabled for sid %s", this.id);
|
||||
this.adapter.persistSession({
|
||||
sid: this.id,
|
||||
pid: this.pid,
|
||||
rooms: [...this.rooms],
|
||||
data: this.data,
|
||||
});
|
||||
}
|
||||
|
||||
this._cleanup();
|
||||
this.nsp._remove(this);
|
||||
this.client._remove(this);
|
||||
this.connected = false;
|
||||
this.emitReserved("disconnect", reason);
|
||||
this.emitReserved("disconnect", reason, description);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -778,7 +889,14 @@ export class Socket<
|
||||
*
|
||||
* @returns self
|
||||
*/
|
||||
public timeout(timeout: number): this {
|
||||
public timeout(
|
||||
timeout: number
|
||||
): Socket<
|
||||
ListenEvents,
|
||||
DecorateAcknowledgements<EmitEvents>,
|
||||
ServerSideEvents,
|
||||
SocketData
|
||||
> {
|
||||
this.flags.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
@@ -1088,11 +1206,9 @@ export class Socket<
|
||||
private newBroadcastOperator() {
|
||||
const flags = Object.assign({}, this.flags);
|
||||
this.flags = {};
|
||||
return new BroadcastOperator<EmitEvents, SocketData>(
|
||||
this.adapter,
|
||||
new Set<Room>(),
|
||||
new Set<Room>([this.id]),
|
||||
flags
|
||||
);
|
||||
return new BroadcastOperator<
|
||||
DecorateAcknowledgementsWithMultipleResponses<EmitEvents>,
|
||||
SocketData
|
||||
>(this.adapter, new Set<Room>(), new Set<Room>([this.id]), flags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,3 +178,66 @@ export abstract class StrictEventEmitter<
|
||||
>[];
|
||||
}
|
||||
}
|
||||
|
||||
export type Last<T extends any[]> = T extends [...infer H, infer L] ? L : any;
|
||||
export type AllButLast<T extends any[]> = T extends [...infer H, infer L]
|
||||
? H
|
||||
: any[];
|
||||
export type FirstArg<T> = T extends (arg: infer Param) => infer Result
|
||||
? Param
|
||||
: any;
|
||||
export type SecondArg<T> = T extends (
|
||||
err: Error,
|
||||
arg: infer Param
|
||||
) => infer Result
|
||||
? Param
|
||||
: any;
|
||||
|
||||
type PrependTimeoutError<T extends any[]> = {
|
||||
[K in keyof T]: T[K] extends (...args: infer Params) => infer Result
|
||||
? (err: Error, ...args: Params) => Result
|
||||
: T[K];
|
||||
};
|
||||
|
||||
type ExpectMultipleResponses<T extends any[]> = {
|
||||
[K in keyof T]: T[K] extends (err: Error, arg: infer Param) => infer Result
|
||||
? (err: Error, arg: Param[]) => Result
|
||||
: T[K];
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility type to decorate the acknowledgement callbacks with a timeout error.
|
||||
*
|
||||
* This is needed because the timeout() flag breaks the symmetry between the sender and the receiver:
|
||||
*
|
||||
* @example
|
||||
* interface Events {
|
||||
* "my-event": (val: string) => void;
|
||||
* }
|
||||
*
|
||||
* socket.on("my-event", (cb) => {
|
||||
* cb("123"); // one single argument here
|
||||
* });
|
||||
*
|
||||
* socket.timeout(1000).emit("my-event", (err, val) => {
|
||||
* // two arguments there (the "err" argument is not properly typed)
|
||||
* });
|
||||
*
|
||||
*/
|
||||
export type DecorateAcknowledgements<E> = {
|
||||
[K in keyof E]: E[K] extends (...args: infer Params) => infer Result
|
||||
? (...args: PrependTimeoutError<Params>) => Result
|
||||
: E[K];
|
||||
};
|
||||
|
||||
export type DecorateAcknowledgementsWithTimeoutAndMultipleResponses<E> = {
|
||||
[K in keyof E]: E[K] extends (...args: infer Params) => infer Result
|
||||
? (...args: ExpectMultipleResponses<PrependTimeoutError<Params>>) => Result
|
||||
: E[K];
|
||||
};
|
||||
|
||||
export type DecorateAcknowledgementsWithMultipleResponses<E> = {
|
||||
[K in keyof E]: E[K] extends (...args: infer Params) => infer Result
|
||||
? (...args: ExpectMultipleResponses<Params>) => Result
|
||||
: E[K];
|
||||
};
|
||||
|
||||
172
package-lock.json
generated
172
package-lock.json
generated
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"name": "socket.io",
|
||||
"version": "4.5.3",
|
||||
"version": "4.5.4",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "socket.io",
|
||||
"version": "4.5.3",
|
||||
"version": "4.5.4",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "~2.0.0",
|
||||
"debug": "~4.3.2",
|
||||
"engine.io": "~6.2.1",
|
||||
"socket.io-adapter": "~2.4.0",
|
||||
"engine.io": "~6.4.0",
|
||||
"socket.io-adapter": "~2.5.2",
|
||||
"socket.io-parser": "~4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -23,7 +23,7 @@
|
||||
"nyc": "^15.1.0",
|
||||
"prettier": "^2.3.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"socket.io-client": "4.5.4",
|
||||
"socket.io-client": "4.6.0",
|
||||
"socket.io-client-v2": "npm:socket.io-client@^2.4.0",
|
||||
"superagent": "^8.0.0",
|
||||
"supertest": "^6.1.6",
|
||||
@@ -1334,9 +1334,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dezalgo": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz",
|
||||
"integrity": "sha512-K7i4zNfT2kgQz3GylDw40ot9GAE47sFZ9EXHFSPP6zONLgH6kWXE0KWJchkbQJLBkRazq4APwZ4OwiFFlT95OQ==",
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
|
||||
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asap": "^2.0.0",
|
||||
@@ -1377,9 +1377,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/engine.io": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.1.tgz",
|
||||
"integrity": "sha512-ECceEFcAaNRybd3lsGQKas3ZlMVjN3cyWwMP25D2i0zWfyiytVbTpRPa34qrr+FHddtpBVOmq4H/DCv1O0lZRA==",
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.0.tgz",
|
||||
"integrity": "sha512-OgxY1c/RuCSeO/rTr8DIFXx76IzUUft86R7/P7MMbbkuzeqJoTNw2lmeD91IyGz41QYleIIjWeMJGgug043sfQ==",
|
||||
"dependencies": {
|
||||
"@types/cookie": "^0.4.1",
|
||||
"@types/cors": "^2.8.12",
|
||||
@@ -1390,22 +1390,22 @@
|
||||
"cors": "~2.8.5",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.0.3",
|
||||
"ws": "~8.2.3"
|
||||
"ws": "~8.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-client": {
|
||||
"version": "6.2.3",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.2.3.tgz",
|
||||
"integrity": "sha512-aXPtgF1JS3RuuKcpSrBtimSjYvrbhKW9froICH4s0F3XQWLxsKNxqzG39nnvQZQnva4CMvUK63T7shevxRyYHw==",
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.4.0.tgz",
|
||||
"integrity": "sha512-GyKPDyoEha+XZ7iEqam49vz6auPnNJ9ZBfy89f+rMMas8AuiMWOZ9PVzu8xb9ZC6rafUqiGHSCfu22ih66E+1g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.0.3",
|
||||
"ws": "~8.2.3",
|
||||
"ws": "~8.11.0",
|
||||
"xmlhttprequest-ssl": "~2.0.0"
|
||||
}
|
||||
},
|
||||
@@ -1613,32 +1613,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/formidable": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz",
|
||||
"integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz",
|
||||
"integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"dezalgo": "1.0.3",
|
||||
"hexoid": "1.0.0",
|
||||
"once": "1.4.0",
|
||||
"qs": "6.9.3"
|
||||
"dezalgo": "^1.0.4",
|
||||
"hexoid": "^1.0.0",
|
||||
"once": "^1.4.0",
|
||||
"qs": "^6.11.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://ko-fi.com/tunnckoCore/commissions"
|
||||
}
|
||||
},
|
||||
"node_modules/formidable/node_modules/qs": {
|
||||
"version": "6.9.3",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz",
|
||||
"integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/fromentries": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz",
|
||||
@@ -2254,9 +2242,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"json5": "lib/cli.js"
|
||||
@@ -3467,19 +3455,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-adapter": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz",
|
||||
"integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg=="
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz",
|
||||
"integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==",
|
||||
"dependencies": {
|
||||
"ws": "~8.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-client": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.5.4.tgz",
|
||||
"integrity": "sha512-ZpKteoA06RzkD32IbqILZ+Cnst4xewU7ZYK12aS1mzHftFFjpoMz69IuhP/nL25pJfao/amoPI527KnuhFm01g==",
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.6.0.tgz",
|
||||
"integrity": "sha512-2XOp18xnGghUICSd5ziUIS4rB0dhr6S8OvAps8y+HhOjFQlqGcf+FIh6fCIsKKZyWFxJeFPrZRNPGsHDTsz1Ug==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.2",
|
||||
"engine.io-client": "~6.2.3",
|
||||
"engine.io-client": "~6.4.0",
|
||||
"socket.io-parser": "~4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3595,9 +3586,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-parser": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.1.tgz",
|
||||
"integrity": "sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz",
|
||||
"integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1"
|
||||
@@ -4205,9 +4196,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz",
|
||||
"integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
|
||||
"version": "8.11.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
|
||||
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
@@ -5359,9 +5350,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"dezalgo": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz",
|
||||
"integrity": "sha512-K7i4zNfT2kgQz3GylDw40ot9GAE47sFZ9EXHFSPP6zONLgH6kWXE0KWJchkbQJLBkRazq4APwZ4OwiFFlT95OQ==",
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
|
||||
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"asap": "^2.0.0",
|
||||
@@ -5396,9 +5387,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"engine.io": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.1.tgz",
|
||||
"integrity": "sha512-ECceEFcAaNRybd3lsGQKas3ZlMVjN3cyWwMP25D2i0zWfyiytVbTpRPa34qrr+FHddtpBVOmq4H/DCv1O0lZRA==",
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.0.tgz",
|
||||
"integrity": "sha512-OgxY1c/RuCSeO/rTr8DIFXx76IzUUft86R7/P7MMbbkuzeqJoTNw2lmeD91IyGz41QYleIIjWeMJGgug043sfQ==",
|
||||
"requires": {
|
||||
"@types/cookie": "^0.4.1",
|
||||
"@types/cors": "^2.8.12",
|
||||
@@ -5409,19 +5400,19 @@
|
||||
"cors": "~2.8.5",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.0.3",
|
||||
"ws": "~8.2.3"
|
||||
"ws": "~8.11.0"
|
||||
}
|
||||
},
|
||||
"engine.io-client": {
|
||||
"version": "6.2.3",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.2.3.tgz",
|
||||
"integrity": "sha512-aXPtgF1JS3RuuKcpSrBtimSjYvrbhKW9froICH4s0F3XQWLxsKNxqzG39nnvQZQnva4CMvUK63T7shevxRyYHw==",
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.4.0.tgz",
|
||||
"integrity": "sha512-GyKPDyoEha+XZ7iEqam49vz6auPnNJ9ZBfy89f+rMMas8AuiMWOZ9PVzu8xb9ZC6rafUqiGHSCfu22ih66E+1g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.0.3",
|
||||
"ws": "~8.2.3",
|
||||
"ws": "~8.11.0",
|
||||
"xmlhttprequest-ssl": "~2.0.0"
|
||||
}
|
||||
},
|
||||
@@ -5577,23 +5568,15 @@
|
||||
}
|
||||
},
|
||||
"formidable": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz",
|
||||
"integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz",
|
||||
"integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"dezalgo": "1.0.3",
|
||||
"hexoid": "1.0.0",
|
||||
"once": "1.4.0",
|
||||
"qs": "6.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"qs": {
|
||||
"version": "6.9.3",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz",
|
||||
"integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==",
|
||||
"dev": true
|
||||
}
|
||||
"dezalgo": "^1.0.4",
|
||||
"hexoid": "^1.0.0",
|
||||
"once": "^1.4.0",
|
||||
"qs": "^6.11.0"
|
||||
}
|
||||
},
|
||||
"fromentries": {
|
||||
@@ -6050,9 +6033,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"json5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"dev": true
|
||||
},
|
||||
"kind-of": {
|
||||
@@ -6931,19 +6914,22 @@
|
||||
"dev": true
|
||||
},
|
||||
"socket.io-adapter": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz",
|
||||
"integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg=="
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz",
|
||||
"integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==",
|
||||
"requires": {
|
||||
"ws": "~8.11.0"
|
||||
}
|
||||
},
|
||||
"socket.io-client": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.5.4.tgz",
|
||||
"integrity": "sha512-ZpKteoA06RzkD32IbqILZ+Cnst4xewU7ZYK12aS1mzHftFFjpoMz69IuhP/nL25pJfao/amoPI527KnuhFm01g==",
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.6.0.tgz",
|
||||
"integrity": "sha512-2XOp18xnGghUICSd5ziUIS4rB0dhr6S8OvAps8y+HhOjFQlqGcf+FIh6fCIsKKZyWFxJeFPrZRNPGsHDTsz1Ug==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.2",
|
||||
"engine.io-client": "~6.2.3",
|
||||
"engine.io-client": "~6.4.0",
|
||||
"socket.io-parser": "~4.2.1"
|
||||
}
|
||||
},
|
||||
@@ -7040,9 +7026,9 @@
|
||||
}
|
||||
},
|
||||
"socket.io-parser": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.1.tgz",
|
||||
"integrity": "sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz",
|
||||
"integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==",
|
||||
"requires": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1"
|
||||
@@ -7492,9 +7478,9 @@
|
||||
}
|
||||
},
|
||||
"ws": {
|
||||
"version": "8.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz",
|
||||
"integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
|
||||
"version": "8.11.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
|
||||
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
|
||||
"requires": {}
|
||||
},
|
||||
"xmlhttprequest-ssl": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "socket.io",
|
||||
"version": "4.5.4",
|
||||
"version": "4.6.0",
|
||||
"description": "node.js realtime framework server",
|
||||
"keywords": [
|
||||
"realtime",
|
||||
@@ -49,8 +49,8 @@
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "~2.0.0",
|
||||
"debug": "~4.3.2",
|
||||
"engine.io": "~6.2.1",
|
||||
"socket.io-adapter": "~2.4.0",
|
||||
"engine.io": "~6.4.0",
|
||||
"socket.io-adapter": "~2.5.2",
|
||||
"socket.io-parser": "~4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -60,7 +60,7 @@
|
||||
"nyc": "^15.1.0",
|
||||
"prettier": "^2.3.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"socket.io-client": "4.5.4",
|
||||
"socket.io-client": "4.6.0",
|
||||
"socket.io-client-v2": "npm:socket.io-client@^2.4.0",
|
||||
"superagent": "^8.0.0",
|
||||
"supertest": "^6.1.6",
|
||||
|
||||
@@ -4,46 +4,13 @@ import { join } from "path";
|
||||
import { exec } from "child_process";
|
||||
import { Server } from "..";
|
||||
import expect from "expect.js";
|
||||
import { createClient, getPort } from "./support/util";
|
||||
import request from "supertest";
|
||||
|
||||
// TODO: update superagent as latest release now supports promises
|
||||
const eioHandshake = (httpServer): Promise<string> => {
|
||||
return new Promise((resolve) => {
|
||||
request(httpServer)
|
||||
.get("/socket.io/")
|
||||
.query({ transport: "polling", EIO: 4 })
|
||||
.end((err, res) => {
|
||||
const sid = JSON.parse(res.text.substring(1)).sid;
|
||||
resolve(sid);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const eioPush = (httpServer, sid: string, body: string): Promise<void> => {
|
||||
return new Promise((resolve) => {
|
||||
request(httpServer)
|
||||
.post("/socket.io/")
|
||||
.send(body)
|
||||
.query({ transport: "polling", EIO: 4, sid })
|
||||
.expect(200)
|
||||
.end(() => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const eioPoll = (httpServer, sid): Promise<string> => {
|
||||
return new Promise((resolve) => {
|
||||
request(httpServer)
|
||||
.get("/socket.io/")
|
||||
.query({ transport: "polling", EIO: 4, sid })
|
||||
.expect(200)
|
||||
.end((err, res) => {
|
||||
resolve(res.text);
|
||||
});
|
||||
});
|
||||
};
|
||||
import {
|
||||
createClient,
|
||||
eioHandshake,
|
||||
eioPoll,
|
||||
eioPush,
|
||||
getPort,
|
||||
} from "./support/util";
|
||||
|
||||
describe("close", () => {
|
||||
it("should be able to close sio sending a srv", (done) => {
|
||||
|
||||
247
test/connection-state-recovery.ts
Normal file
247
test/connection-state-recovery.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
import { Server, Socket } from "..";
|
||||
import expect from "expect.js";
|
||||
import { waitFor, eioHandshake, eioPush, eioPoll } from "./support/util";
|
||||
import { createServer, Server as HttpServer } from "http";
|
||||
import { Adapter } from "socket.io-adapter";
|
||||
|
||||
async function init(httpServer: HttpServer, io: Server) {
|
||||
// Engine.IO handshake
|
||||
const sid = await eioHandshake(httpServer);
|
||||
|
||||
// Socket.IO handshake
|
||||
await eioPush(httpServer, sid, "40");
|
||||
const handshakeBody = await eioPoll(httpServer, sid);
|
||||
|
||||
expect(handshakeBody.startsWith("40")).to.be(true);
|
||||
|
||||
const handshake = JSON.parse(handshakeBody.substring(2));
|
||||
|
||||
expect(handshake.sid).to.not.be(undefined);
|
||||
// in that case, the handshake also contains a private session ID
|
||||
expect(handshake.pid).to.not.be(undefined);
|
||||
|
||||
io.emit("hello");
|
||||
|
||||
const message = await eioPoll(httpServer, sid);
|
||||
|
||||
expect(message.startsWith('42["hello"')).to.be(true);
|
||||
|
||||
const offset = JSON.parse(message.substring(2))[1];
|
||||
// in that case, each packet also includes an offset in the data array
|
||||
expect(offset).to.not.be(undefined);
|
||||
|
||||
await eioPush(httpServer, sid, "1");
|
||||
|
||||
return [handshake.sid, handshake.pid, offset];
|
||||
}
|
||||
|
||||
describe("connection state recovery", () => {
|
||||
it("should restore session and missed packets", async () => {
|
||||
const httpServer = createServer().listen(0);
|
||||
const io = new Server(httpServer, {
|
||||
connectionStateRecovery: {},
|
||||
});
|
||||
|
||||
let serverSocket;
|
||||
|
||||
io.once("connection", (socket) => {
|
||||
socket.join("room1");
|
||||
serverSocket = socket;
|
||||
});
|
||||
|
||||
const [sid, pid, offset] = await init(httpServer, io);
|
||||
|
||||
io.emit("hello1"); // broadcast
|
||||
io.to("room1").emit("hello2"); // broadcast to room
|
||||
serverSocket.emit("hello3"); // direct message
|
||||
|
||||
const newSid = await eioHandshake(httpServer);
|
||||
await eioPush(
|
||||
httpServer,
|
||||
newSid,
|
||||
`40{"pid":"${pid}","offset":"${offset}"}`
|
||||
);
|
||||
|
||||
const payload = await eioPoll(httpServer, newSid);
|
||||
const packets = payload.split("\x1e");
|
||||
|
||||
expect(packets.length).to.eql(4);
|
||||
|
||||
// note: EVENT packets are received before the CONNECT packet, which is a bit weird
|
||||
// see also: https://github.com/socketio/socket.io-deno/commit/518f534e1c205b746b1cb21fe76b187dabc96f34
|
||||
expect(packets[0].startsWith('42["hello1"')).to.be(true);
|
||||
expect(packets[1].startsWith('42["hello2"')).to.be(true);
|
||||
expect(packets[2].startsWith('42["hello3"')).to.be(true);
|
||||
expect(packets[3]).to.eql(`40{"sid":"${sid}","pid":"${pid}"}`);
|
||||
|
||||
io.close();
|
||||
});
|
||||
|
||||
it("should restore rooms and data attributes", async () => {
|
||||
const httpServer = createServer().listen(0);
|
||||
const io = new Server(httpServer, {
|
||||
connectionStateRecovery: {},
|
||||
});
|
||||
|
||||
io.once("connection", (socket) => {
|
||||
expect(socket.recovered).to.eql(false);
|
||||
|
||||
socket.join("room1");
|
||||
socket.join("room2");
|
||||
socket.data.foo = "bar";
|
||||
});
|
||||
|
||||
const [sid, pid, offset] = await init(httpServer, io);
|
||||
|
||||
const newSid = await eioHandshake(httpServer);
|
||||
|
||||
const [socket] = await Promise.all([
|
||||
waitFor<Socket>(io, "connection"),
|
||||
eioPush(httpServer, newSid, `40{"pid":"${pid}","offset":"${offset}"}`),
|
||||
]);
|
||||
|
||||
expect(socket.id).to.eql(sid);
|
||||
expect(socket.recovered).to.eql(true);
|
||||
|
||||
expect(socket.rooms.has(socket.id)).to.eql(true);
|
||||
expect(socket.rooms.has("room1")).to.eql(true);
|
||||
expect(socket.rooms.has("room2")).to.eql(true);
|
||||
|
||||
expect(socket.data.foo).to.eql("bar");
|
||||
|
||||
await eioPoll(httpServer, newSid); // drain buffer
|
||||
io.close();
|
||||
});
|
||||
|
||||
it("should not run middlewares upon recovery by default", async () => {
|
||||
const httpServer = createServer().listen(0);
|
||||
const io = new Server(httpServer, {
|
||||
connectionStateRecovery: {},
|
||||
});
|
||||
|
||||
const [_, pid, offset] = await init(httpServer, io);
|
||||
|
||||
io.use((socket, next) => {
|
||||
socket.data.middlewareWasCalled = true;
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
const newSid = await eioHandshake(httpServer);
|
||||
|
||||
const [socket] = await Promise.all([
|
||||
waitFor<Socket>(io, "connection"),
|
||||
eioPush(httpServer, newSid, `40{"pid":"${pid}","offset":"${offset}"}`),
|
||||
]);
|
||||
|
||||
expect(socket.recovered).to.be(true);
|
||||
expect(socket.data.middlewareWasCalled).to.be(undefined);
|
||||
|
||||
await eioPoll(httpServer, newSid); // drain buffer
|
||||
io.close();
|
||||
});
|
||||
|
||||
it("should run middlewares even upon recovery", async () => {
|
||||
const httpServer = createServer().listen(0);
|
||||
const io = new Server(httpServer, {
|
||||
connectionStateRecovery: {
|
||||
skipMiddlewares: false,
|
||||
},
|
||||
});
|
||||
|
||||
const [_, pid, offset] = await init(httpServer, io);
|
||||
|
||||
io.use((socket, next) => {
|
||||
socket.data.middlewareWasCalled = true;
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
const newSid = await eioHandshake(httpServer);
|
||||
|
||||
const [socket] = await Promise.all([
|
||||
waitFor<Socket>(io, "connection"),
|
||||
eioPush(httpServer, newSid, `40{"pid":"${pid}","offset":"${offset}"}`),
|
||||
]);
|
||||
|
||||
expect(socket.recovered).to.be(true);
|
||||
expect(socket.data.middlewareWasCalled).to.be(true);
|
||||
|
||||
await eioPoll(httpServer, newSid); // drain buffer
|
||||
io.close();
|
||||
});
|
||||
|
||||
it("should fail to restore an unknown session", async () => {
|
||||
const httpServer = createServer().listen(0);
|
||||
const io = new Server(httpServer, {
|
||||
connectionStateRecovery: {},
|
||||
});
|
||||
|
||||
// Engine.IO handshake
|
||||
const sid = await eioHandshake(httpServer);
|
||||
|
||||
// Socket.IO handshake
|
||||
await eioPush(httpServer, sid, '40{"pid":"foo","offset":"bar"}');
|
||||
|
||||
const handshakeBody = await eioPoll(httpServer, sid);
|
||||
|
||||
expect(handshakeBody.startsWith("40")).to.be(true);
|
||||
|
||||
const handshake = JSON.parse(handshakeBody.substring(2));
|
||||
|
||||
expect(handshake.sid).to.not.eql("foo");
|
||||
expect(handshake.pid).to.not.eql("bar");
|
||||
|
||||
io.close();
|
||||
});
|
||||
|
||||
it("should be disabled by default", async () => {
|
||||
const httpServer = createServer().listen(0);
|
||||
const io = new Server(httpServer);
|
||||
|
||||
// Engine.IO handshake
|
||||
const sid = await eioHandshake(httpServer);
|
||||
|
||||
// Socket.IO handshake
|
||||
await eioPush(httpServer, sid, "40");
|
||||
|
||||
const handshakeBody = await eioPoll(httpServer, sid);
|
||||
|
||||
expect(handshakeBody.startsWith("40")).to.be(true);
|
||||
|
||||
const handshake = JSON.parse(handshakeBody.substring(2));
|
||||
|
||||
expect(handshake.sid).to.not.be(undefined);
|
||||
expect(handshake.pid).to.be(undefined);
|
||||
|
||||
io.close();
|
||||
});
|
||||
|
||||
it("should not call adapter#persistSession or adapter#restoreSession if disabled", async () => {
|
||||
const httpServer = createServer().listen(0);
|
||||
|
||||
class DummyAdapter extends Adapter {
|
||||
override persistSession(session) {
|
||||
expect.fail();
|
||||
}
|
||||
|
||||
override restoreSession(pid, offset) {
|
||||
expect.fail();
|
||||
return Promise.reject("should not happen");
|
||||
}
|
||||
}
|
||||
|
||||
const io = new Server(httpServer, {
|
||||
adapter: DummyAdapter,
|
||||
});
|
||||
|
||||
// Engine.IO handshake
|
||||
const sid = await eioHandshake(httpServer);
|
||||
|
||||
await eioPush(httpServer, sid, '40{"pid":"foo","offset":"bar"}');
|
||||
await eioPoll(httpServer, sid);
|
||||
await eioPush(httpServer, sid, "1");
|
||||
|
||||
io.close();
|
||||
});
|
||||
});
|
||||
@@ -20,4 +20,5 @@ describe("socket.io", () => {
|
||||
require("./socket-timeout");
|
||||
require("./uws");
|
||||
require("./utility-methods");
|
||||
require("./connection-state-recovery");
|
||||
});
|
||||
|
||||
@@ -471,6 +471,74 @@ describe("messaging many", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should broadcast and expect multiple acknowledgements (promise)", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket1 = createClient(io, "/", { multiplex: false });
|
||||
const socket2 = createClient(io, "/", { multiplex: false });
|
||||
const socket3 = createClient(io, "/", { multiplex: false });
|
||||
|
||||
socket1.on("some event", (cb) => {
|
||||
cb(1);
|
||||
});
|
||||
|
||||
socket2.on("some event", (cb) => {
|
||||
cb(2);
|
||||
});
|
||||
|
||||
socket3.on("some event", (cb) => {
|
||||
cb(3);
|
||||
});
|
||||
|
||||
Promise.all([
|
||||
waitFor(socket1, "connect"),
|
||||
waitFor(socket2, "connect"),
|
||||
waitFor(socket3, "connect"),
|
||||
]).then(async () => {
|
||||
const responses = await io.timeout(2000).emitWithAck("some event");
|
||||
expect(responses).to.contain(1, 2, 3);
|
||||
|
||||
success(done, io, socket1, socket2, socket3);
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail when a client does not acknowledge the event in the given delay (promise)", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket1 = createClient(io, "/", { multiplex: false });
|
||||
const socket2 = createClient(io, "/", { multiplex: false });
|
||||
const socket3 = createClient(io, "/", { multiplex: false });
|
||||
|
||||
socket1.on("some event", (cb) => {
|
||||
cb(1);
|
||||
});
|
||||
|
||||
socket2.on("some event", (cb) => {
|
||||
cb(2);
|
||||
});
|
||||
|
||||
socket3.on("some event", () => {
|
||||
// timeout
|
||||
});
|
||||
|
||||
Promise.all([
|
||||
waitFor(socket1, "connect"),
|
||||
waitFor(socket2, "connect"),
|
||||
waitFor(socket3, "connect"),
|
||||
]).then(async () => {
|
||||
try {
|
||||
await io.timeout(200).emitWithAck("some event");
|
||||
expect.fail();
|
||||
} catch (err) {
|
||||
expect(err).to.be.an(Error);
|
||||
// @ts-ignore
|
||||
expect(err.responses).to.have.length(2);
|
||||
// @ts-ignore
|
||||
expect(err.responses).to.contain(1, 2);
|
||||
|
||||
success(done, io, socket1, socket2, socket3);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should broadcast and return if the packet is sent to 0 client", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket1 = createClient(io, "/", { multiplex: false });
|
||||
@@ -498,4 +566,22 @@ describe("messaging many", () => {
|
||||
success(done, io, socket1, socket2, socket3);
|
||||
});
|
||||
});
|
||||
|
||||
it("should precompute the WebSocket frame when broadcasting", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket = createClient(io, "/chat", {
|
||||
transports: ["websocket"],
|
||||
});
|
||||
const partialDone = createPartialDone(2, successFn(done, io, socket));
|
||||
|
||||
io.of("/chat").on("connection", (s) => {
|
||||
s.conn.once("packetCreate", (packet) => {
|
||||
expect(packet.options.wsPreEncodedFrame).to.be.an(Array);
|
||||
partialDone();
|
||||
});
|
||||
io.of("/chat").compress(false).emit("woot", "hi");
|
||||
});
|
||||
|
||||
socket.on("woot", partialDone);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -473,6 +473,24 @@ describe("namespaces", () => {
|
||||
io.of("/nsp");
|
||||
});
|
||||
|
||||
it("should not clean up a non-dynamic namespace", (done) => {
|
||||
const io = new Server(0, { cleanupEmptyChildNamespaces: true });
|
||||
const c1 = createClient(io, "/chat");
|
||||
|
||||
c1.on("connect", () => {
|
||||
c1.disconnect();
|
||||
|
||||
// Give it some time to disconnect the client
|
||||
setTimeout(() => {
|
||||
expect(io._nsps.has("/chat")).to.be(true);
|
||||
expect(io._nsps.get("/chat")!.sockets.size).to.be(0);
|
||||
success(done, io);
|
||||
}, 100);
|
||||
});
|
||||
|
||||
io.of("/chat");
|
||||
});
|
||||
|
||||
describe("dynamic namespaces", () => {
|
||||
it("should allow connections to dynamic namespaces with a regex", (done) => {
|
||||
const io = new Server(0);
|
||||
@@ -571,5 +589,68 @@ describe("namespaces", () => {
|
||||
one.on("message", handler);
|
||||
two.on("message", handler);
|
||||
});
|
||||
|
||||
it("should clean up namespace when cleanupEmptyChildNamespaces is on and there are no more sockets in a namespace", (done) => {
|
||||
const io = new Server(0, { cleanupEmptyChildNamespaces: true });
|
||||
const c1 = createClient(io, "/dynamic-101");
|
||||
|
||||
c1.on("connect", () => {
|
||||
c1.disconnect();
|
||||
|
||||
// Give it some time to disconnect and clean up the namespace
|
||||
setTimeout(() => {
|
||||
expect(io._nsps.has("/dynamic-101")).to.be(false);
|
||||
success(done, io);
|
||||
}, 100);
|
||||
});
|
||||
|
||||
io.of(/^\/dynamic-\d+$/);
|
||||
});
|
||||
|
||||
it("should allow a client to connect to a cleaned up namespace", (done) => {
|
||||
const io = new Server(0, { cleanupEmptyChildNamespaces: true });
|
||||
const c1 = createClient(io, "/dynamic-101");
|
||||
|
||||
c1.on("connect", () => {
|
||||
c1.disconnect();
|
||||
|
||||
// Give it some time to disconnect and clean up the namespace
|
||||
setTimeout(() => {
|
||||
expect(io._nsps.has("/dynamic-101")).to.be(false);
|
||||
|
||||
const c2 = createClient(io, "/dynamic-101");
|
||||
|
||||
c2.on("connect", () => {
|
||||
success(done, io, c2);
|
||||
});
|
||||
|
||||
c2.on("connect_error", () => {
|
||||
done(
|
||||
new Error("Client got error when connecting to dynamic namespace")
|
||||
);
|
||||
});
|
||||
}, 100);
|
||||
});
|
||||
|
||||
io.of(/^\/dynamic-\d+$/);
|
||||
});
|
||||
|
||||
it("should not clean up namespace when cleanupEmptyChildNamespaces is off and there are no more sockets in a namespace", (done) => {
|
||||
const io = new Server(0);
|
||||
const c1 = createClient(io, "/dynamic-101");
|
||||
|
||||
c1.on("connect", () => {
|
||||
c1.disconnect();
|
||||
|
||||
// Give it some time to disconnect and clean up the namespace
|
||||
setTimeout(() => {
|
||||
expect(io._nsps.has("/dynamic-101")).to.be(true);
|
||||
expect(io._nsps.get("/dynamic-101")!.sockets.size).to.be(0);
|
||||
success(done, io);
|
||||
}, 100);
|
||||
});
|
||||
|
||||
io.of(/^\/dynamic-\d+$/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,7 +16,9 @@ describe("server attachment", () => {
|
||||
.buffer(true)
|
||||
.end((err, res) => {
|
||||
if (err) return done(err);
|
||||
expect(res.headers["content-type"]).to.be("application/javascript");
|
||||
expect(res.headers["content-type"]).to.be(
|
||||
"application/javascript; charset=utf-8"
|
||||
);
|
||||
expect(res.headers.etag).to.be('"' + clientVersion + '"');
|
||||
expect(res.headers["x-sourcemap"]).to.be(undefined);
|
||||
expect(res.text).to.match(/engine\.io/);
|
||||
@@ -33,7 +35,9 @@ describe("server attachment", () => {
|
||||
.buffer(true)
|
||||
.end((err, res) => {
|
||||
if (err) return done(err);
|
||||
expect(res.headers["content-type"]).to.be("application/json");
|
||||
expect(res.headers["content-type"]).to.be(
|
||||
"application/json; charset=utf-8"
|
||||
);
|
||||
expect(res.headers.etag).to.be('"' + clientVersion + '"');
|
||||
expect(res.text).to.match(/engine\.io/);
|
||||
expect(res.status).to.be(200);
|
||||
|
||||
@@ -54,4 +54,34 @@ describe("timeout", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should timeout if the client does not acknowledge the event (promise)", (done) => {
|
||||
const io = new Server(0);
|
||||
const client = createClient(io, "/");
|
||||
|
||||
io.on("connection", async (socket) => {
|
||||
try {
|
||||
await socket.timeout(50).emitWithAck("unknown");
|
||||
expect.fail();
|
||||
} catch (err) {
|
||||
expect(err).to.be.an(Error);
|
||||
success(done, io, client);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should not timeout if the client does acknowledge the event (promise)", (done) => {
|
||||
const io = new Server(0);
|
||||
const client = createClient(io, "/");
|
||||
|
||||
client.on("echo", (arg, cb) => {
|
||||
cb(arg);
|
||||
});
|
||||
|
||||
io.on("connection", async (socket) => {
|
||||
const value = await socket.timeout(50).emitWithAck("echo", 42);
|
||||
expect(value).to.be(42);
|
||||
success(done, io, client);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -92,6 +92,28 @@ describe("server", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("emitWithAck", () => {
|
||||
it("accepts any parameters", () => {
|
||||
const srv = createServer();
|
||||
const sio = new Server(srv);
|
||||
srv.listen(async () => {
|
||||
const value = await sio
|
||||
.timeout(1000)
|
||||
.emitWithAck("ackFromServerSingleArg", true, "123");
|
||||
expectType<any>(value);
|
||||
|
||||
sio.on("connection", async (s) => {
|
||||
const value1 = await s.emitWithAck(
|
||||
"ackFromServerSingleArg",
|
||||
true,
|
||||
"123"
|
||||
);
|
||||
expectType<any>(value1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("single event map", () => {
|
||||
@@ -167,10 +189,32 @@ describe("server", () => {
|
||||
describe("listen and emit event maps", () => {
|
||||
interface ClientToServerEvents {
|
||||
helloFromClient: (message: string) => void;
|
||||
ackFromClient: (
|
||||
a: string,
|
||||
b: number,
|
||||
ack: (c: string, d: number) => void
|
||||
) => void;
|
||||
}
|
||||
|
||||
interface ServerToClientEvents {
|
||||
helloFromServer: (message: string, x: number) => void;
|
||||
ackFromServer: (
|
||||
a: boolean,
|
||||
b: string,
|
||||
ack: (c: boolean, d: string) => void
|
||||
) => void;
|
||||
|
||||
ackFromServerSingleArg: (
|
||||
a: boolean,
|
||||
b: string,
|
||||
ack: (c: string) => void
|
||||
) => void;
|
||||
|
||||
multipleAckFromServer: (
|
||||
a: boolean,
|
||||
b: string,
|
||||
ack: (c: string) => void
|
||||
) => void;
|
||||
}
|
||||
|
||||
describe("on", () => {
|
||||
@@ -185,6 +229,13 @@ describe("server", () => {
|
||||
expectType<string>(message);
|
||||
done();
|
||||
});
|
||||
|
||||
s.on("ackFromClient", (a, b, cb) => {
|
||||
expectType<string>(a);
|
||||
expectType<number>(b);
|
||||
expectType<(c: string, d: number) => void>(cb);
|
||||
cb("123", 456);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -213,8 +264,41 @@ describe("server", () => {
|
||||
sio.to("room").emit("helloFromServer", "hi", 1);
|
||||
sio.timeout(1000).emit("helloFromServer", "hi", 1);
|
||||
|
||||
sio
|
||||
.timeout(1000)
|
||||
.emit("multipleAckFromServer", true, "123", (err, c) => {
|
||||
expectType<Error>(err);
|
||||
expectType<string[]>(c);
|
||||
});
|
||||
|
||||
sio.on("connection", (s) => {
|
||||
s.emit("helloFromServer", "hi", 10);
|
||||
|
||||
s.emit("ackFromServer", true, "123", (c, d) => {
|
||||
expectType<boolean>(c);
|
||||
expectType<string>(d);
|
||||
});
|
||||
|
||||
s.timeout(1000).emit("ackFromServer", true, "123", (err, c, d) => {
|
||||
expectType<Error>(err);
|
||||
expectType<boolean>(c);
|
||||
expectType<string>(d);
|
||||
});
|
||||
|
||||
s.timeout(1000)
|
||||
.to("room")
|
||||
.emit("multipleAckFromServer", true, "123", (err, c) => {
|
||||
expectType<Error>(err);
|
||||
expectType<string[]>(c);
|
||||
});
|
||||
|
||||
s.to("room")
|
||||
.timeout(1000)
|
||||
.emit("multipleAckFromServer", true, "123", (err, c) => {
|
||||
expectType<Error>(err);
|
||||
expectType<string[]>(c);
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -240,6 +324,42 @@ describe("server", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("emitWithAck", () => {
|
||||
it("accepts arguments of the correct types", (done) => {
|
||||
const srv = createServer();
|
||||
const sio = new Server<ClientToServerEvents, ServerToClientEvents>(srv);
|
||||
srv.listen(async () => {
|
||||
const value = await sio
|
||||
.timeout(1000)
|
||||
.emitWithAck("multipleAckFromServer", true, "123");
|
||||
expectType<string[]>(value);
|
||||
|
||||
sio.on("connection", async (s) => {
|
||||
const value1 = await s
|
||||
.timeout(1000)
|
||||
.to("room")
|
||||
.emitWithAck("multipleAckFromServer", true, "123");
|
||||
expectType<string[]>(value1);
|
||||
|
||||
const value2 = await s
|
||||
.to("room")
|
||||
.timeout(1000)
|
||||
.emitWithAck("multipleAckFromServer", true, "123");
|
||||
expectType<string[]>(value2);
|
||||
|
||||
const value3 = await s.emitWithAck(
|
||||
"ackFromServerSingleArg",
|
||||
true,
|
||||
"123"
|
||||
);
|
||||
expectType<string>(value3);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("listen and emit event maps for the serverSideEmit method", () => {
|
||||
@@ -253,6 +373,7 @@ describe("server", () => {
|
||||
|
||||
interface InterServerEvents {
|
||||
helloFromServerToServer: (message: string, x: number) => void;
|
||||
ackFromServerToServer: (foo: string, cb: (bar: number) => void) => void;
|
||||
}
|
||||
|
||||
describe("on", () => {
|
||||
@@ -267,7 +388,7 @@ describe("server", () => {
|
||||
expectType<
|
||||
Server<ClientToServerEvents, ServerToClientEvents, InterServerEvents>
|
||||
>(sio);
|
||||
srv.listen(() => {
|
||||
srv.listen(async () => {
|
||||
sio.serverSideEmit("helloFromServerToServer", "hello", 10);
|
||||
sio
|
||||
.of("/test")
|
||||
@@ -281,6 +402,22 @@ describe("server", () => {
|
||||
expectType<string>(message);
|
||||
expectType<number>(x);
|
||||
});
|
||||
|
||||
sio.serverSideEmit("ackFromServerToServer", "foo", (err, bar) => {
|
||||
expectType<Error>(err);
|
||||
expectType<number[]>(bar);
|
||||
});
|
||||
|
||||
const value = await sio.serverSideEmitWithAck(
|
||||
"ackFromServerToServer",
|
||||
"foo"
|
||||
);
|
||||
expectType<number[]>(value);
|
||||
|
||||
sio.on("ackFromServerToServer", (foo, cb) => {
|
||||
expectType<string>(foo);
|
||||
expectType<(bar: number) => void>(cb);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import fs = require("fs");
|
||||
import { join } from "path";
|
||||
import { createClient, getPort, success, successFn } from "./support/util";
|
||||
import {
|
||||
createClient,
|
||||
createPartialDone,
|
||||
getPort,
|
||||
success,
|
||||
successFn,
|
||||
} from "./support/util";
|
||||
import { Server } from "..";
|
||||
import expect from "expect.js";
|
||||
|
||||
@@ -599,6 +605,24 @@ describe("socket", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should emit an event and wait for the acknowledgement", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket = createClient(io);
|
||||
|
||||
io.on("connection", async (s) => {
|
||||
socket.on("hi", (a, b, fn) => {
|
||||
expect(a).to.be(1);
|
||||
expect(b).to.be(2);
|
||||
fn(3);
|
||||
});
|
||||
|
||||
const val = await s.emitWithAck("hi", 1, 2);
|
||||
expect(val).to.be(3);
|
||||
|
||||
success(done, io, socket);
|
||||
});
|
||||
});
|
||||
|
||||
it("should have access to the client", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket = createClient(io);
|
||||
@@ -731,7 +755,7 @@ describe("socket", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should enable compresion by default", (done) => {
|
||||
it("should enable compression by default", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket = createClient(io, "/chat");
|
||||
|
||||
@@ -740,11 +764,11 @@ describe("socket", () => {
|
||||
expect(packet.options.compress).to.be(true);
|
||||
success(done, io, socket);
|
||||
});
|
||||
io.of("/chat").emit("woot", "hi");
|
||||
s.emit("woot", "hi");
|
||||
});
|
||||
});
|
||||
|
||||
it("should disable compresion", (done) => {
|
||||
it("should disable compression", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket = createClient(io, "/chat");
|
||||
|
||||
@@ -753,7 +777,7 @@ describe("socket", () => {
|
||||
expect(packet.options.compress).to.be(false);
|
||||
success(done, io, socket);
|
||||
});
|
||||
io.of("/chat").compress(false).emit("woot", "hi");
|
||||
s.compress(false).emit("woot", "hi");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -995,6 +1019,22 @@ describe("socket", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should call listener when broadcasting binary data", (done) => {
|
||||
const io = new Server(0);
|
||||
const clientSocket = createClient(io, "/", { multiplex: false });
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
socket.onAnyOutgoing((event, arg1) => {
|
||||
expect(event).to.be("my-event");
|
||||
expect(arg1).to.be.an(Uint8Array);
|
||||
|
||||
success(done, io, clientSocket);
|
||||
});
|
||||
|
||||
io.emit("my-event", Uint8Array.of(1, 2, 3));
|
||||
});
|
||||
});
|
||||
|
||||
it("should prepend listener", (done) => {
|
||||
const io = new Server(0);
|
||||
const clientSocket = createClient(io, "/", { multiplex: false });
|
||||
@@ -1039,5 +1079,30 @@ describe("socket", () => {
|
||||
socket.emit("my-event", "123");
|
||||
});
|
||||
});
|
||||
|
||||
it("should disconnect all namespaces when calling disconnect(true)", (done) => {
|
||||
const io = new Server(0);
|
||||
io.of("/foo");
|
||||
io.of("/bar");
|
||||
|
||||
const socket1 = createClient(io, "/", {
|
||||
transports: ["websocket"],
|
||||
});
|
||||
const socket2 = createClient(io, "/foo");
|
||||
const socket3 = createClient(io, "/bar");
|
||||
|
||||
io.of("/bar").on("connection", (socket) => {
|
||||
socket.disconnect(true);
|
||||
});
|
||||
|
||||
const partialDone = createPartialDone(
|
||||
3,
|
||||
successFn(done, io, socket1, socket2, socket3)
|
||||
);
|
||||
|
||||
socket1.on("disconnect", partialDone);
|
||||
socket2.on("disconnect", partialDone);
|
||||
socket3.on("disconnect", partialDone);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
Socket as ClientSocket,
|
||||
SocketOptions,
|
||||
} from "socket.io-client";
|
||||
import request from "supertest";
|
||||
|
||||
const expect = require("expect.js");
|
||||
const i = expect.stringify;
|
||||
@@ -73,8 +74,46 @@ export function createPartialDone(count: number, done: (err?: Error) => void) {
|
||||
};
|
||||
}
|
||||
|
||||
export function waitFor(emitter, event) {
|
||||
return new Promise((resolve) => {
|
||||
export function waitFor<T = unknown>(emitter, event) {
|
||||
return new Promise<T>((resolve) => {
|
||||
emitter.once(event, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: update superagent as latest release now supports promises
|
||||
export function eioHandshake(httpServer): Promise<string> {
|
||||
return new Promise((resolve) => {
|
||||
request(httpServer)
|
||||
.get("/socket.io/")
|
||||
.query({ transport: "polling", EIO: 4 })
|
||||
.end((err, res) => {
|
||||
const sid = JSON.parse(res.text.substring(1)).sid;
|
||||
resolve(sid);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function eioPush(httpServer, sid: string, body: string): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
request(httpServer)
|
||||
.post("/socket.io/")
|
||||
.send(body)
|
||||
.query({ transport: "polling", EIO: 4, sid })
|
||||
.expect(200)
|
||||
.end(() => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function eioPoll(httpServer, sid): Promise<string> {
|
||||
return new Promise((resolve) => {
|
||||
request(httpServer)
|
||||
.get("/socket.io/")
|
||||
.query({ transport: "polling", EIO: 4, sid })
|
||||
.expect(200)
|
||||
.end((err, res) => {
|
||||
resolve(res.text);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ describe("socket.io with uWebSocket.js-based engine", () => {
|
||||
|
||||
const partialDone = createPartialDone(done, 4);
|
||||
client.on("connect", partialDone);
|
||||
clientWSOnly.on("connect", partialDone);
|
||||
clientWSOnly.once("connect", partialDone);
|
||||
clientPollingOnly.on("connect", partialDone);
|
||||
clientCustomNamespace.on("connect", partialDone);
|
||||
});
|
||||
@@ -200,7 +200,9 @@ describe("socket.io with uWebSocket.js-based engine", () => {
|
||||
.buffer(true)
|
||||
.end((err, res) => {
|
||||
if (err) return done(err);
|
||||
expect(res.headers["content-type"]).to.be("application/javascript");
|
||||
expect(res.headers["content-type"]).to.be(
|
||||
"application/javascript; charset=utf-8"
|
||||
);
|
||||
expect(res.headers.etag).to.be('"' + clientVersion + '"');
|
||||
expect(res.headers["x-sourcemap"]).to.be(undefined);
|
||||
expect(res.text).to.match(/engine\.io/);
|
||||
|
||||
Reference in New Issue
Block a user