Compare commits

..

15 Commits
4.6.0 ... 4.6.2

Author SHA1 Message Date
Damien Arrachequesne
faf914c9ab chore(release): 4.6.2
Diff: https://github.com/socketio/socket.io/compare/4.6.1...4.6.2
2023-05-31 11:15:41 +02:00
Damien Arrachequesne
15af22fc22 refactor: add a noop handler for the error event
We should reduce the scope of the "event" error in the next major
version, as it is overloaded today:

- it can be sent by the client (`socket.emit("error")`, which is a perfectly valid event name)
- it can be emitted when the connection encounters an error (an invalid packet for example)
- it can be emitted when a packet is rejected in a middleware (`socket.use()`)

Related: https://github.com/socketio/socket.io/issues/2047
2023-05-24 10:47:52 +02:00
Damien Arrachequesne
d3658944e5 chore: bump socket.io-parser to version 4.2.3
Reference: https://github.com/advisories/GHSA-cqmj-92xf-r6r9
2023-05-24 07:27:12 +02:00
Damien Arrachequesne
12b0de4f52 chore: bump engine.io to version 6.4.2
Reference: https://github.com/advisories/GHSA-q9mw-68c2-j6m5

Related: https://github.com/socketio/socket.io/issues/4711
2023-05-10 10:20:42 +02:00
Mateusz Burzyński
3d44aae381 fix(exports): move types condition to the top (#4698)
Related: https://github.com/microsoft/TypeScript/issues/50762
2023-05-04 07:27:09 +02:00
Damien Arrachequesne
cbf0362476 docs(examples): bump dependencies for the private messaging example
Related: https://github.com/socketio/socket.io/issues/4681
2023-05-02 18:07:07 +02:00
Damien Arrachequesne
59280da20b docs(examples): update examples to docker compose v2
Reference: https://docs.docker.com/compose/

Related: https://github.com/socketio/socket.io/discussions/4669
2023-04-07 15:57:20 +02:00
Damien Arrachequesne
50a4d37cb8 docs(changelog): add version of transitive dependencies 2023-03-27 17:35:42 +02:00
Damien Arrachequesne
6458b2bef1 docs(example): basic WebSocket-only client 2023-03-24 11:17:29 +01:00
Damien Arrachequesne
b56da8a99f docs(examples): upgrade to React 18
Reference: https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html
2023-03-07 08:31:21 +01:00
Damien Arrachequesne
7952312911 chore(release): 4.6.1
Diff: https://github.com/socketio/socket.io/compare/4.6.0...4.6.1
2023-02-20 17:49:41 +01:00
Damien Arrachequesne
0d0a7a22b5 fix: properly handle manually created dynamic namespaces
Namespaces that match the regex of a parent namespace will now be added
as a child of this namespace:

```js
const parentNamespace = io.of(/^\/dynamic-\d+$/);
const childNamespace = io.of("/dynamic-101");
```

Related:

- https://github.com/socketio/socket.io/issues/4615
- https://github.com/socketio/socket.io/issues/4164
- https://github.com/socketio/socket.io/issues/4015
- https://github.com/socketio/socket.io/issues/3960
2023-02-20 01:19:01 +01:00
Damien Arrachequesne
2a8565fd1e refactor: catch errors when trying to restore the connection state 2023-02-20 01:18:08 +01:00
Igor Lins e Silva
d0b22c6302 fix(types): fix nodenext module resolution compatibility (#4625)
The import added in [1] was invalid, because it used an non-exported
class.

Related: https://github.com/socketio/socket.io/issues/4621

[1]: d4a9b2cdcb
2023-02-20 01:15:35 +01:00
Nabaraj Subedi
e71f3d7dbe docs: minor style fix (#4619) 2023-02-16 09:25:43 +01:00
37 changed files with 7187 additions and 8460 deletions

View File

@@ -2,6 +2,8 @@
## 2023
- [4.6.2](#462-2023-05-31) (May 2023)
- [4.6.1](#461-2023-02-20) (Feb 2023)
- [4.6.0](#460-2023-02-07) (Feb 2023)
## 2022
@@ -56,7 +58,38 @@
# Release notes
# [4.6.0](https://github.com/socketio/socket.io/compare/4.5.4...4.6.0) (2023-02-07)
## [4.6.2](https://github.com/socketio/socket.io/compare/4.6.1...4.6.2) (2023-05-31)
### Bug Fixes
* **exports:** move `types` condition to the top ([#4698](https://github.com/socketio/socket.io/issues/4698)) ([3d44aae](https://github.com/socketio/socket.io/commit/3d44aae381af38349fdb808d510d9f47a0c2507e))
### Dependencies
- [`engine.io@~6.4.0`](https://github.com/socketio/engine.io/releases/tag/6.4.0) (no change)
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
## [4.6.1](https://github.com/socketio/socket.io/compare/4.6.0...4.6.1) (2023-02-20)
### Bug Fixes
* properly handle manually created dynamic namespaces ([0d0a7a2](https://github.com/socketio/socket.io/commit/0d0a7a22b5ff95f864216c529114b7dd41738d1e))
* **types:** fix nodenext module resolution compatibility ([#4625](https://github.com/socketio/socket.io/issues/4625)) ([d0b22c6](https://github.com/socketio/socket.io/commit/d0b22c630208669aceb7ae013180c99ef90279b0))
### Dependencies
- [`engine.io@~6.4.0`](https://github.com/socketio/engine.io/releases/tag/6.4.0) (no change)
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
## [4.6.0](https://github.com/socketio/socket.io/compare/4.5.4...4.6.0) (2023-02-07)
### Bug Fixes
@@ -233,8 +266,8 @@ Added in [d0fd474](https://github.com/socketio/engine.io/commit/d0fd4746afa39629
### 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))
- [`engine.io@~6.4.0`](https://github.com/socketio/engine.io/releases/tag/6.4.0) (https://github.com/socketio/engine.io/compare/6.2.1...6.4.0)
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (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)
@@ -259,6 +292,11 @@ This release contains a bump of:
* **typings:** accept an HTTP2 server in the constructor ([d3d0a2d](https://github.com/socketio/socket.io/commit/d3d0a2d5beaff51fd145f810bcaf6914213f8a06))
* **typings:** apply types to "io.timeout(...).emit()" calls ([e357daf](https://github.com/socketio/socket.io/commit/e357daf5858560bc84e7e50cd36f0278d6721ea1))
### Dependencies
- [`engine.io@~6.2.0`](https://github.com/socketio/engine.io/releases/tag/6.2.1) (no change)
- [`ws@~8.2.3`](https://github.com/websockets/ws/releases/tag/8.2.3) (no change)
## [4.5.2](https://github.com/socketio/socket.io/compare/4.5.1...4.5.2) (2022-09-02)
@@ -269,13 +307,18 @@ This release contains a bump of:
* prevent the socket from joining a room after disconnection ([18f3fda](https://github.com/socketio/socket.io/commit/18f3fdab12947a9fee3e9c37cfc1da97027d1473))
* **uws:** prevent the server from crashing after upgrade ([ba497ee](https://github.com/socketio/socket.io/commit/ba497ee3eb52c4abf1464380d015d8c788714364))
### Dependencies
- [`engine.io@~6.2.0`](https://github.com/socketio/engine.io/releases/tag/6.2.0) (no change)
- [`ws@~8.2.3`](https://github.com/websockets/ws/releases/tag/8.2.3) (no change)
# [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.
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)
@@ -287,6 +330,11 @@ Security advisory: [GHSA-j4f2-536g-r55m](https://github.com/advisories/GHSA-j4f2
* only set 'connected' to true after middleware execution ([226cc16](https://github.com/socketio/socket.io/commit/226cc16165f9fe60f16ff4d295fb91c8971cde35))
* prevent the socket from joining a room after disconnection ([f223178](https://github.com/socketio/socket.io/commit/f223178eb655a7713303b21a78f9ef9e161d6458))
### Dependencies
- [`engine.io@~3.6.0`](https://github.com/socketio/engine.io/releases/tag/3.6.0) (https://github.com/socketio/engine.io/compare/3.5.0...3.6.0)
- [`ws@~7.4.2`](https://github.com/websockets/ws/releases/tag/7.4.2) (no change)
## [4.5.1](https://github.com/socketio/socket.io/compare/4.5.0...4.5.1) (2022-05-17)
@@ -297,6 +345,11 @@ Security advisory: [GHSA-j4f2-536g-r55m](https://github.com/advisories/GHSA-j4f2
* forward the local flag to the adapter when using fetchSockets() ([30430f0](https://github.com/socketio/socket.io/commit/30430f0985f8e7c49394543d4c84913b6a15df60))
* **typings:** add HTTPS server to accepted types ([#4351](https://github.com/socketio/socket.io/issues/4351)) ([9b43c91](https://github.com/socketio/socket.io/commit/9b43c9167cff817c60fa29dbda2ef7cd938aff51))
### Dependencies
- [`engine.io@~6.2.0`](https://github.com/socketio/engine.io/releases/tag/6.2.0) (no change)
- [`ws@~8.2.3`](https://github.com/websockets/ws/releases/tag/8.2.3) (no change)
# [4.5.0](https://github.com/socketio/socket.io/compare/4.4.1...4.5.0) (2022-04-23)
@@ -309,7 +362,7 @@ Security advisory: [GHSA-j4f2-536g-r55m](https://github.com/advisories/GHSA-j4f2
### Features
* add support for catch-all listeners for outgoing packets ([531104d](https://github.com/socketio/socket.io/commit/531104d332690138b7aab84d5583d6204132c8b4))
#### Catch-all listeners for outgoing packets
This is similar to `onAny()`, but for outgoing packets.
@@ -321,7 +374,9 @@ socket.onAnyOutgoing((event, ...args) => {
});
```
* broadcast and expect multiple acks ([8b20457](https://github.com/socketio/socket.io/commit/8b204570a94979bbec307f23ca078f30f5cf07b0))
Added in [531104d](https://github.com/socketio/socket.io/commit/531104d332690138b7aab84d5583d6204132c8b4).
#### Broadcast and expect multiple acknowledgements
Syntax:
@@ -331,18 +386,25 @@ io.timeout(1000).emit("some-event", (err, responses) => {
});
```
* add the "maxPayload" field in the handshake details ([088dcb4](https://github.com/socketio/engine.io/commit/088dcb4dff60df39785df13d0a33d3ceaa1dff38))
Added in [8b20457](https://github.com/socketio/socket.io/commit/8b204570a94979bbec307f23ca078f30f5cf07b0).
So that clients in HTTP long-polling can decide how many packets they have to send to stay under the maxHttpBufferSize
value.
#### `maxHttpBufferSize` value negotiation
This is a backward compatible change which should not mandate a new major revision of the protocol (we stay in v4), as
we only add a field in the JSON-encoded handshake data:
A "maxPayload" field is now included in the Engine.IO handshake, so that clients in HTTP long-polling can decide how many packets they have to send to stay under the `maxHttpBufferSize` value.
This is a backward compatible change which should not mandate a new major revision of the protocol (we stay in v4), as we only add a field in the JSON-encoded handshake data:
```
0{"sid":"lv_VI97HAXpY6yYWAAAC","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":5000,"maxPayload":1000000}
```
Added in [088dcb4](https://github.com/socketio/engine.io/commit/088dcb4dff60df39785df13d0a33d3ceaa1dff38).
### Dependencies
- [`engine.io@~6.2.0`](https://github.com/socketio/engine.io/releases/tag/6.2.0) (https://github.com/socketio/engine.io/compare/6.1.0...6.2.0)
- [`ws@~8.2.3`](https://github.com/websockets/ws/releases/tag/8.2.3) (no change)
## [4.4.1](https://github.com/socketio/socket.io/compare/4.4.0...4.4.1) (2022-01-06)

View File

@@ -126,7 +126,7 @@ io.listen(3000);
Starting with **3.0**, express applications have become request handler
functions that you pass to `http` or `http` `Server` instances. You need
to pass the `Server` to `socket.io`, and not the express application
to pass the `Server` to `socket.io`, not the express application
function. Also make sure to call `.listen` on the `server`, not the `app`.
```js

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
/*!
* Socket.IO v4.6.0
* Socket.IO v4.6.2
* (c) 2014-2023 Guillermo Rauch
* Released under the MIT License.
*/
@@ -2680,6 +2680,12 @@
return data;
}
/**
* These strings must not be used as event names, as they have a special meaning.
*/
var RESERVED_EVENTS$1 = ["connect", "connect_error", "disconnect", "disconnecting", "newListener", "removeListener" // used by the Node.js EventEmitter
];
/**
* Protocol version.
*
@@ -2789,13 +2795,18 @@
}]);
return Encoder;
}();
}(); // see https://stackoverflow.com/questions/8511281/check-if-a-value-is-an-object-in-javascript
function isObject(value) {
return Object.prototype.toString.call(value) === "[object Object]";
}
/**
* A socket.io Decoder instance
*
* @return {Object} decoder
*/
var Decoder = /*#__PURE__*/function (_Emitter) {
_inherits(Decoder, _Emitter);
@@ -2974,17 +2985,17 @@
value: function isPayloadValid(type, payload) {
switch (type) {
case PacketType.CONNECT:
return _typeof(payload) === "object";
return isObject(payload);
case PacketType.DISCONNECT:
return payload === undefined;
case PacketType.CONNECT_ERROR:
return typeof payload === "string" || _typeof(payload) === "object";
return typeof payload === "string" || isObject(payload);
case PacketType.EVENT:
case PacketType.BINARY_EVENT:
return Array.isArray(payload) && payload.length > 0;
return Array.isArray(payload) && (typeof payload[0] === "number" || typeof payload[0] === "string" && RESERVED_EVENTS$1.indexOf(payload[0]) === -1);
case PacketType.ACK:
case PacketType.BINARY_ACK:
@@ -3159,6 +3170,12 @@
*/
_this._queue = [];
/**
* A sequence to generate the ID of the {@link QueuedPacket}.
* @private
*/
_this._queueSeq = 0;
_this.ids = 0;
_this.acks = {};
_this.flags = {};
@@ -3453,7 +3470,7 @@
}
var packet = {
id: this.ids++,
id: this._queueSeq++,
tryCount: 0,
pending: false,
args: args,
@@ -3499,30 +3516,30 @@
}
/**
* Send the first packet of the queue, and wait for an acknowledgement from the server.
* @param force - whether to resend a packet that has not been acknowledged yet
*
* @private
*/
}, {
key: "_drainQueue",
value: function _drainQueue() {
if (this._queue.length === 0) {
var force = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
if (!this.connected || this._queue.length === 0) {
return;
}
var packet = this._queue[0];
if (packet.pending) {
if (packet.pending && !force) {
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.
@@ -3759,6 +3776,8 @@
this.connected = true;
this.emitBuffered();
this.emitReserved("connect");
this._drainQueue(true);
}
/**
* Emit buffered events (received and emitted).
@@ -4504,9 +4523,7 @@
if (!socket) {
socket = new Socket(this, nsp, opts);
this.nsps[nsp] = socket;
}
if (this._autoConnect) {
} else if (this._autoConnect && !socket.active) {
socket.connect();
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,18 @@
# Basic Socket.IO client
Please check the associated guide: https://socket.io/how-to/build-a-basic-client
Content:
```
├── bundle
│ └── socket.io.min.js
├── src
│ └── index.js
├── test
│ └── index.js
├── check-bundle-size.js
├── package.json
├── README.md
└── rollup.config.js
```

View File

@@ -0,0 +1 @@
class e{#e=new Map;on(e,t){let s=this.#e.get(e);s||this.#e.set(e,s=[]),s.push(t)}emit(e,...t){const s=this.#e.get(e);if(s)for(const e of s)e.apply(null,t)}}const t="0",s="1",n="2",i="3",o="4",r={CONNECT:0,DISCONNECT:1,EVENT:2};function c(){}class a extends e{id;connected=!1;#t;#s;#n;#i;#o;#r=[];#c;#a=!0;constructor(e,t){super(),this.#t=e,this.#s=Object.assign({path:"/socket.io/",reconnectionDelay:2e3},t),this.#h()}#h(){this.#n=new WebSocket(this.#u()),this.#n.onmessage=({data:e})=>this.#p(e),this.#n.onerror=c,this.#n.onclose=()=>this.#l("transport close")}#u(){return`${this.#t.replace(/^http/,"ws")}${this.#s.path}?EIO=4&transport=websocket`}#p(e){if("string"==typeof e)switch(e[0]){case t:this.#d(e);break;case s:this.#l("transport close");break;case n:this.#T(),this.#m(i);break;case o:let c;try{c=function(e){let t=1;const s={type:parseInt(e.charAt(t++),10)};e.charAt(t)&&(s.data=JSON.parse(e.substring(t)));if(!function(e){switch(e.type){case r.CONNECT:return"object"==typeof e.data;case r.DISCONNECT:return void 0===e.data;case r.EVENT:{const t=e.data;return Array.isArray(t)&&t.length>0&&"string"==typeof t[0]}default:return!1}}(s))throw new Error("invalid format");return s}(e)}catch(e){return this.#l("parse error")}this.#f(c);break;default:this.#l("parse error")}}#d(e){let t;try{t=JSON.parse(e.substring(1))}catch(e){return this.#l("parse error")}this.#o=t.pingInterval+t.pingTimeout,this.#T(),this.#C()}#f(e){switch(e.type){case r.CONNECT:this.#g(e);break;case r.DISCONNECT:this.#a=!1,this.#l("io server disconnect");break;case r.EVENT:super.emit.apply(this,e.data);break;default:this.#l("parse error")}}#g(e){this.id=e.data.sid,this.connected=!0,this.#r.forEach((e=>this.#y(e))),this.#r.slice(0),super.emit("connect")}#l(e){this.#n&&(this.#n.onclose=c,this.#n.close()),clearTimeout(this.#i),clearTimeout(this.#c),this.connected?(this.connected=!1,this.id=void 0,super.emit("disconnect",e)):super.emit("connect_error",e),this.#a&&(this.#c=setTimeout((()=>this.#h()),this.#s.reconnectionDelay))}#T(){clearTimeout(this.#i),this.#i=setTimeout((()=>{this.#l("ping timeout")}),this.#o)}#m(e){this.#n.readyState===WebSocket.OPEN&&this.#n.send(e)}#y(e){this.#m(o+function(e){let t=""+e.type;e.data&&(t+=JSON.stringify(e.data));return t}(e))}#C(){this.#y({type:r.CONNECT})}emit(...e){const t={type:r.EVENT,data:e};this.connected?this.#y(t):this.#r.push(t)}disconnect(){this.#a=!1,this.#l("io client disconnect")}}function h(e,t){return"string"!=typeof e&&(t=e,e=location.origin),new a(e,t)}export{h as io};

View File

@@ -0,0 +1,17 @@
import { rollup } from "rollup";
import terser from "@rollup/plugin-terser";
import { brotliCompressSync } from "node:zlib";
const rollupBuild = await rollup({
input: "./src/index.js"
});
const rollupOutput = await rollupBuild.generate({
format: "esm",
plugins: [terser()],
});
const bundleAsString = rollupOutput.output[0].code;
const brotliedBundle = brotliCompressSync(Buffer.from(bundleAsString));
console.log(`Bundle size: ${brotliedBundle.length} B`);

View File

@@ -0,0 +1,18 @@
{
"type": "module",
"devDependencies": {
"@rollup/plugin-terser": "^0.4.0",
"chai": "^4.3.7",
"mocha": "^10.2.0",
"prettier": "^2.8.4",
"rollup": "^3.20.2",
"socket.io": "^4.6.1",
"ws": "^8.13.0"
},
"scripts": {
"bundle": "rollup -c",
"check-bundle-size": "node check-bundle-size.js",
"format": "prettier -w src/ test/",
"test": "mocha"
}
}

View File

@@ -0,0 +1,10 @@
import terser from "@rollup/plugin-terser";
export default {
input: "./src/index.js",
output: {
file: "./bundle/socket.io.min.js",
format: "esm",
plugins: [terser()],
}
};

View File

@@ -0,0 +1,273 @@
class EventEmitter {
#listeners = new Map();
on(event, listener) {
let listeners = this.#listeners.get(event);
if (!listeners) {
this.#listeners.set(event, (listeners = []));
}
listeners.push(listener);
}
emit(event, ...args) {
const listeners = this.#listeners.get(event);
if (listeners) {
for (const listener of listeners) {
listener.apply(null, args);
}
}
}
}
const EIOPacketType = {
OPEN: "0",
CLOSE: "1",
PING: "2",
PONG: "3",
MESSAGE: "4",
};
const SIOPacketType = {
CONNECT: 0,
DISCONNECT: 1,
EVENT: 2,
};
function noop() {}
class Socket extends EventEmitter {
id;
connected = false;
#uri;
#opts;
#ws;
#pingTimeoutTimer;
#pingTimeoutDelay;
#sendBuffer = [];
#reconnectTimer;
#shouldReconnect = true;
constructor(uri, opts) {
super();
this.#uri = uri;
this.#opts = Object.assign(
{
path: "/socket.io/",
reconnectionDelay: 2000,
},
opts
);
this.#open();
}
#open() {
this.#ws = new WebSocket(this.#createUrl());
this.#ws.onmessage = ({ data }) => this.#onMessage(data);
// dummy handler for Node.js
this.#ws.onerror = noop;
this.#ws.onclose = () => this.#onClose("transport close");
}
#createUrl() {
const uri = this.#uri.replace(/^http/, "ws");
const queryParams = "?EIO=4&transport=websocket";
return `${uri}${this.#opts.path}${queryParams}`;
}
#onMessage(data) {
if (typeof data !== "string") {
// TODO handle binary payloads
return;
}
switch (data[0]) {
case EIOPacketType.OPEN:
this.#onOpen(data);
break;
case EIOPacketType.CLOSE:
this.#onClose("transport close");
break;
case EIOPacketType.PING:
this.#resetPingTimeout();
this.#send(EIOPacketType.PONG);
break;
case EIOPacketType.MESSAGE:
let packet;
try {
packet = decode(data);
} catch (e) {
return this.#onClose("parse error");
}
this.#onPacket(packet);
break;
default:
this.#onClose("parse error");
break;
}
}
#onOpen(data) {
let handshake;
try {
handshake = JSON.parse(data.substring(1));
} catch (e) {
return this.#onClose("parse error");
}
this.#pingTimeoutDelay = handshake.pingInterval + handshake.pingTimeout;
this.#resetPingTimeout();
this.#doConnect();
}
#onPacket(packet) {
switch (packet.type) {
case SIOPacketType.CONNECT:
this.#onConnect(packet);
break;
case SIOPacketType.DISCONNECT:
this.#shouldReconnect = false;
this.#onClose("io server disconnect");
break;
case SIOPacketType.EVENT:
super.emit.apply(this, packet.data);
break;
default:
this.#onClose("parse error");
break;
}
}
#onConnect(packet) {
this.id = packet.data.sid;
this.connected = true;
this.#sendBuffer.forEach((packet) => this.#sendPacket(packet));
this.#sendBuffer.slice(0);
super.emit("connect");
}
#onClose(reason) {
if (this.#ws) {
this.#ws.onclose = noop;
this.#ws.close();
}
clearTimeout(this.#pingTimeoutTimer);
clearTimeout(this.#reconnectTimer);
if (this.connected) {
this.connected = false;
this.id = undefined;
super.emit("disconnect", reason);
} else {
super.emit("connect_error", reason);
}
if (this.#shouldReconnect) {
this.#reconnectTimer = setTimeout(
() => this.#open(),
this.#opts.reconnectionDelay
);
}
}
#resetPingTimeout() {
clearTimeout(this.#pingTimeoutTimer);
this.#pingTimeoutTimer = setTimeout(() => {
this.#onClose("ping timeout");
}, this.#pingTimeoutDelay);
}
#send(data) {
if (this.#ws.readyState === WebSocket.OPEN) {
this.#ws.send(data);
}
}
#sendPacket(packet) {
this.#send(EIOPacketType.MESSAGE + encode(packet));
}
#doConnect() {
this.#sendPacket({ type: SIOPacketType.CONNECT });
}
emit(...args) {
const packet = {
type: SIOPacketType.EVENT,
data: args,
};
if (this.connected) {
this.#sendPacket(packet);
} else {
this.#sendBuffer.push(packet);
}
}
disconnect() {
this.#shouldReconnect = false;
this.#onClose("io client disconnect");
}
}
function encode(packet) {
let output = "" + packet.type;
if (packet.data) {
output += JSON.stringify(packet.data);
}
return output;
}
function decode(data) {
let i = 1; // skip "4" prefix
const packet = {
type: parseInt(data.charAt(i++), 10),
};
if (data.charAt(i)) {
packet.data = JSON.parse(data.substring(i));
}
if (!isPacketValid(packet)) {
throw new Error("invalid format");
}
return packet;
}
function isPacketValid(packet) {
switch (packet.type) {
case SIOPacketType.CONNECT:
return typeof packet.data === "object";
case SIOPacketType.DISCONNECT:
return packet.data === undefined;
case SIOPacketType.EVENT: {
const args = packet.data;
return (
Array.isArray(args) && args.length > 0 && typeof args[0] === "string"
);
}
default:
return false;
}
}
export function io(uri, opts) {
if (typeof uri !== "string") {
opts = uri;
uri = location.origin;
}
return new Socket(uri, opts);
}

View File

@@ -0,0 +1,162 @@
import { createServer } from "node:http";
import { io as ioc } from "../src/index.js";
import { WebSocket } from "ws";
import { Server } from "socket.io";
import { expect } from "chai";
// @ts-ignore for Node.js
globalThis.WebSocket = WebSocket;
function waitFor(emitter, eventName) {
return new Promise((resolve) => {
emitter.on(eventName, resolve);
});
}
function sleep(delay) {
return new Promise((resolve) => {
setTimeout(resolve, delay);
});
}
describe("basic client", () => {
let io, port, socket;
beforeEach(() => {
const httpServer = createServer();
io = new Server(httpServer);
httpServer.listen(0);
port = httpServer.address().port;
});
afterEach(() => {
io.close();
socket.disconnect();
});
it("should connect", async () => {
socket = ioc(`ws://localhost:${port}`);
await waitFor(socket, "connect");
expect(socket.connected).to.eql(true);
expect(socket.id).to.be.a("string");
});
it("should connect with 'http://' scheme", async () => {
socket = ioc(`http://localhost:${port}`);
await waitFor(socket, "connect");
});
it("should connect with URL inferred from 'window.location'", async () => {
globalThis.location = {
origin: `http://localhost:${port}`,
};
socket = ioc();
await waitFor(socket, "connect");
});
it("should fail to connect to an invalid URL", async () => {
socket = ioc(`http://localhost:4321`);
await waitFor(socket, "connect_error");
});
it("should receive an event", async () => {
io.on("connection", (socket) => {
socket.emit("foo", 123);
});
socket = ioc(`ws://localhost:${port}`);
const value = await waitFor(socket, "foo");
expect(value).to.eql(123);
});
it("should send an event (not buffered)", async () => {
socket = ioc(`ws://localhost:${port}`);
const [serverSocket] = await Promise.all([
waitFor(io, "connection"),
waitFor(socket, "connect"),
]);
socket.emit("foo", 456);
const value = await waitFor(serverSocket, "foo");
expect(value).to.eql(456);
});
it("should send an event (buffered)", async () => {
socket = ioc(`ws://localhost:${port}`);
socket.emit("foo", 789);
const [serverSocket] = await Promise.all([
waitFor(io, "connection"),
waitFor(socket, "connect"),
]);
const value = await waitFor(serverSocket, "foo");
expect(value).to.eql(789);
});
it("should reconnect", async () => {
socket = ioc(`ws://localhost:${port}`, {
reconnectionDelay: 50,
});
await waitFor(socket, "connect");
io.close();
await waitFor(socket, "disconnect");
io.listen(port);
await waitFor(socket, "connect");
});
it("should respond to PING packets", async () => {
io.engine.opts.pingInterval = 50;
io.engine.opts.pingTimeout = 20;
socket = ioc(`ws://localhost:${port}`);
await waitFor(socket, "connect");
await sleep(500);
expect(socket.connected).to.eql(true);
});
it("should disconnect (client side)", async () => {
socket = ioc(`ws://localhost:${port}`);
await waitFor(socket, "connect");
socket.disconnect();
expect(socket.connected).to.eql(false);
expect(socket.id).to.eql(undefined);
});
it("should disconnect (server side)", async () => {
socket = ioc(`ws://localhost:${port}`);
const [serverSocket] = await Promise.all([
waitFor(io, "connection"),
waitFor(socket, "connect"),
]);
serverSocket.disconnect();
await waitFor(socket, "disconnect");
});
});

View File

@@ -1,51 +1,53 @@
services:
haproxy:
image: haproxy:1.7-alpine
volumes:
- ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
links:
- server-john
- server-paul
- server-george
- server-ringo
ports:
- "3000:80"
haproxy:
build: ./haproxy
links:
- server-john
- server-paul
- server-george
- server-ringo
ports:
- "3000:80"
server-john:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=John
server-john:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=John
server-paul:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=Paul
server-paul:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=Paul
server-george:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=George
server-george:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=George
server-ringo:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=Ringo
server-ringo:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=Ringo
redis:
image: redis:alpine
expose:
- "6379"
redis:
image: redis:alpine
expose:
- "6379"

View File

@@ -1,2 +0,0 @@
FROM haproxy:1.7-alpine
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg

View File

@@ -1,51 +1,53 @@
services:
httpd:
image: httpd:2.4-alpine
volumes:
- ./httpd.conf:/usr/local/apache2/conf/httpd.conf:ro
links:
- server-john
- server-paul
- server-george
- server-ringo
ports:
- "3000:80"
httpd:
build: ./httpd
links:
- server-john
- server-paul
- server-george
- server-ringo
ports:
- "3000:80"
server-john:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=John
server-john:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=John
server-paul:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=Paul
server-paul:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=Paul
server-george:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=George
server-george:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=George
server-ringo:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=Ringo
server-ringo:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=Ringo
redis:
image: redis:alpine
expose:
- "6379"
redis:
image: redis:6
expose:
- "6379"

View File

@@ -1,2 +0,0 @@
FROM httpd:2.4-alpine
COPY ./httpd.conf /usr/local/apache2/conf/httpd.conf

View File

@@ -1,4 +1,4 @@
FROM mhart/alpine-node:6
FROM node:14-alpine
# Create app directory
RUN mkdir -p /usr/src/app

View File

@@ -1,58 +1,58 @@
services:
nginx:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
links:
- server-john
- server-paul
- server-george
- server-ringo
ports:
- "3000:80"
nginx:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
links:
- server-john
- server-paul
- server-george
- server-ringo
ports:
- "3000:80"
server-john:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=John
server-john:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=John
server-paul:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=Paul
server-paul:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=Paul
server-george:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=George
server-george:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=George
server-ringo:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=Ringo
server-ringo:
build: ./server
links:
- redis
expose:
- "3000"
environment:
- NAME=Ringo
client:
build: ./client
links:
- nginx
client:
build: ./client
links:
- nginx
redis:
image: redis:alpine
expose:
- "6379"
redis:
image: redis:6
expose:
- "6379"

View File

@@ -3,14 +3,14 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.1",
"socket.io": "4",
"socket.io-client": "4"
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "^5.0.1",
"socket.io": "^4.6.1",
"socket.io-client": "^4.6.1"
},
"scripts": {
"start": "react-scripts start",

View File

@@ -1,14 +1,15 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(
const container = document.getElementById('root');
const root = createRoot(container)
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
</React.StrictMode>
);
// If you want your app to work offline and load faster, you can change

File diff suppressed because it is too large Load Diff

View File

@@ -8,18 +8,19 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.6.5",
"core-js": "^3.8.3",
"socket.io-client": "^4.0.0",
"vue": "^2.6.11"
"vue": "^2.6.14"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"vue-template-compiler": "^2.6.11"
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3",
"vue-template-compiler": "^2.6.14"
},
"eslintConfig": {
"root": true,
@@ -31,9 +32,11 @@
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
"parser": "@babel/eslint-parser"
},
"rules": {}
"rules": {
"vue/multi-word-component-names": "off"
}
},
"browserslist": [
"> 1%",

View File

@@ -68,7 +68,7 @@ body {
@font-face {
font-family: Lato;
src: url("/fonts/Lato-Regular.ttf");
src: "~/public/fonts/Lato-Regular.ttf";
}
#app {

View File

@@ -6,12 +6,11 @@ import { createDeflate, createGzip, createBrotliCompress } from "zlib";
import accepts = require("accepts");
import { pipeline } from "stream";
import path = require("path");
import {
attach,
Server as Engine,
import { attach, Server as Engine, uServer } from "engine.io";
import type {
ServerOptions as EngineOptions,
AttachOptions,
uServer,
BaseServer,
} from "engine.io";
import { Client } from "./client";
import { EventEmitter } from "events";
@@ -41,7 +40,6 @@ import {
SecondArg,
} from "./typed-events";
import { patchAdapter, restoreAdapter, serveFile } from "./uws";
import type { BaseServer } from "engine.io/build/server";
const debug = debugModule("socket.io:server");
@@ -180,6 +178,18 @@ export class Server<
ParentNspNameMatchFn,
ParentNamespace<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
> = new Map();
/**
* A subset of the {@link parentNsps} map, only containing {@link ParentNamespace} which are based on a regular
* expression.
*
* @private
*/
private parentNamespacesFromRegExp: Map<
RegExp,
ParentNamespace<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
> = new Map();
private _adapter?: AdapterConstructor;
private _serveClient: boolean;
private readonly opts: Partial<ServerOptions>;
@@ -316,8 +326,6 @@ export class Server<
}
const namespace = this.parentNsps.get(nextFn.value)!.createChild(name);
debug("dynamic namespace %s was created", name);
// @ts-ignore
this.sockets.emitReserved("new_namespace", namespace);
fn(namespace);
});
};
@@ -693,6 +701,7 @@ export class Server<
(nsp, conn, next) => next(null, (name as RegExp).test(nsp)),
parentNsp
);
this.parentNamespacesFromRegExp.set(name, parentNsp);
}
if (fn) {
// @ts-ignore
@@ -705,6 +714,13 @@ export class Server<
let nsp = this._nsps.get(name);
if (!nsp) {
for (const [regex, parentNamespace] of this.parentNamespacesFromRegExp) {
if (regex.test(name as string)) {
debug("attaching namespace %s to parent namespace %s", name, regex);
return parentNamespace.createChild(name as string);
}
}
debug("initializing namespace %s", name);
nsp = new Namespace(this, name);
this._nsps.set(name, nsp);

View File

@@ -358,12 +358,15 @@ export class Namespace<
typeof sessionId === "string" &&
typeof offset === "string"
) {
const session = await this.adapter.restoreSession(sessionId, offset);
let session;
try {
session = await this.adapter.restoreSession(sessionId, offset);
} catch (e) {
debug("error while restoring session: %s", e);
}
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);

View File

@@ -11,6 +11,21 @@ import debugModule from "debug";
const debug = debugModule("socket.io:parent-namespace");
/**
* A parent namespace is a special {@link Namespace} that holds a list of child namespaces which were created either
* with a regular expression or with a function.
*
* @example
* const parentNamespace = io.of(/\/dynamic-\d+/);
*
* parentNamespace.on("connection", (socket) => {
* const childNamespace = socket.nsp;
* }
*
* // will reach all the clients that are in one of the child namespaces, like "/dynamic-101"
* parentNamespace.emit("hello", "world");
*
*/
export class ParentNamespace<
ListenEvents extends EventsMap = DefaultEventsMap,
EmitEvents extends EventsMap = ListenEvents,
@@ -81,6 +96,10 @@ export class ParentNamespace<
}
this.server._nsps.set(name, namespace);
// @ts-ignore
this.server.sockets.emitReserved("new_namespace", namespace);
return namespace;
}

View File

@@ -280,6 +280,9 @@ export class Socket<
}
}
this.handshake = this.buildHandshake(auth);
// prevents crash when the socket receives an "error" event without listener
this.on("error", noop);
}
/**
@@ -720,12 +723,11 @@ export class Socket<
* @private
*/
_onerror(err: Error): void {
if (this.listeners("error").length) {
this.emitReserved("error", err);
} else {
console.error("Missing error handler on `socket`.");
console.error(err.stack);
}
// FIXME the meaning of the "error" event is overloaded:
// - it can be sent by the client (`socket.emit("error")`)
// - it can be emitted when the connection encounters an error (an invalid packet for example)
// - it can be emitted when a packet is rejected in a middleware (`socket.use()`)
this.emitReserved("error", err);
}
/**

62
package-lock.json generated
View File

@@ -1,20 +1,20 @@
{
"name": "socket.io",
"version": "4.5.4",
"version": "4.6.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "socket.io",
"version": "4.5.4",
"version": "4.6.2",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"debug": "~4.3.2",
"engine.io": "~6.4.0",
"engine.io": "~6.4.2",
"socket.io-adapter": "~2.5.2",
"socket.io-parser": "~4.2.1"
"socket.io-parser": "~4.2.4"
},
"devDependencies": {
"@types/mocha": "^9.0.0",
@@ -23,7 +23,7 @@
"nyc": "^15.1.0",
"prettier": "^2.3.2",
"rimraf": "^3.0.2",
"socket.io-client": "4.6.0",
"socket.io-client": "4.6.2",
"socket.io-client-v2": "npm:socket.io-client@^2.4.0",
"superagent": "^8.0.0",
"supertest": "^6.1.6",
@@ -1228,9 +1228,9 @@
}
},
"node_modules/cookiejar": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz",
"integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==",
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
"integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
"dev": true
},
"node_modules/cors": {
@@ -1377,9 +1377,9 @@
"dev": true
},
"node_modules/engine.io": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.0.tgz",
"integrity": "sha512-OgxY1c/RuCSeO/rTr8DIFXx76IzUUft86R7/P7MMbbkuzeqJoTNw2lmeD91IyGz41QYleIIjWeMJGgug043sfQ==",
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.2.tgz",
"integrity": "sha512-FKn/3oMiJjrOEOeUub2WCox6JhxBXq/Zn3fZOMCBxKnNYtsdKjxhl7yR3fZhM9PV+rdE75SU5SYMc+2PGzo+Tg==",
"dependencies": {
"@types/cookie": "^0.4.1",
"@types/cors": "^2.8.12",
@@ -3463,15 +3463,15 @@
}
},
"node_modules/socket.io-client": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.6.0.tgz",
"integrity": "sha512-2XOp18xnGghUICSd5ziUIS4rB0dhr6S8OvAps8y+HhOjFQlqGcf+FIh6fCIsKKZyWFxJeFPrZRNPGsHDTsz1Ug==",
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.6.2.tgz",
"integrity": "sha512-OwWrMbbA8wSqhBAR0yoPK6EdQLERQAYjXb3A0zLpgxfM1ZGLKoxHx8gVmCHA6pcclRX5oA/zvQf7bghAS11jRA==",
"dev": true,
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.2",
"engine.io-client": "~6.4.0",
"socket.io-parser": "~4.2.1"
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.0.0"
@@ -3586,9 +3586,9 @@
}
},
"node_modules/socket.io-parser": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz",
"integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==",
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
@@ -5271,9 +5271,9 @@
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA=="
},
"cookiejar": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz",
"integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==",
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
"integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
"dev": true
},
"cors": {
@@ -5387,9 +5387,9 @@
"dev": true
},
"engine.io": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.0.tgz",
"integrity": "sha512-OgxY1c/RuCSeO/rTr8DIFXx76IzUUft86R7/P7MMbbkuzeqJoTNw2lmeD91IyGz41QYleIIjWeMJGgug043sfQ==",
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.2.tgz",
"integrity": "sha512-FKn/3oMiJjrOEOeUub2WCox6JhxBXq/Zn3fZOMCBxKnNYtsdKjxhl7yR3fZhM9PV+rdE75SU5SYMc+2PGzo+Tg==",
"requires": {
"@types/cookie": "^0.4.1",
"@types/cors": "^2.8.12",
@@ -6922,15 +6922,15 @@
}
},
"socket.io-client": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.6.0.tgz",
"integrity": "sha512-2XOp18xnGghUICSd5ziUIS4rB0dhr6S8OvAps8y+HhOjFQlqGcf+FIh6fCIsKKZyWFxJeFPrZRNPGsHDTsz1Ug==",
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.6.2.tgz",
"integrity": "sha512-OwWrMbbA8wSqhBAR0yoPK6EdQLERQAYjXb3A0zLpgxfM1ZGLKoxHx8gVmCHA6pcclRX5oA/zvQf7bghAS11jRA==",
"dev": true,
"requires": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.2",
"engine.io-client": "~6.4.0",
"socket.io-parser": "~4.2.1"
"socket.io-parser": "~4.2.4"
}
},
"socket.io-client-v2": {
@@ -7026,9 +7026,9 @@
}
},
"socket.io-parser": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz",
"integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==",
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"requires": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"

View File

@@ -1,6 +1,6 @@
{
"name": "socket.io",
"version": "4.6.0",
"version": "4.6.2",
"description": "node.js realtime framework server",
"keywords": [
"realtime",
@@ -26,9 +26,9 @@
"type": "commonjs",
"main": "./dist/index.js",
"exports": {
"types": "./dist/index.d.ts",
"import": "./wrapper.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
"require": "./dist/index.js"
},
"types": "./dist/index.d.ts",
"license": "MIT",
@@ -49,9 +49,9 @@
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"debug": "~4.3.2",
"engine.io": "~6.4.0",
"engine.io": "~6.4.2",
"socket.io-adapter": "~2.5.2",
"socket.io-parser": "~4.2.1"
"socket.io-parser": "~4.2.4"
},
"devDependencies": {
"@types/mocha": "^9.0.0",
@@ -60,7 +60,7 @@
"nyc": "^15.1.0",
"prettier": "^2.3.2",
"rimraf": "^3.0.2",
"socket.io-client": "4.6.0",
"socket.io-client": "4.6.2",
"socket.io-client-v2": "npm:socket.io-client@^2.4.0",
"superagent": "^8.0.0",
"supertest": "^6.1.6",

View File

@@ -652,5 +652,16 @@ describe("namespaces", () => {
io.of(/^\/dynamic-\d+$/);
});
it("should attach a child namespace to its parent upon manual creation", () => {
const io = new Server(0);
const parentNamespace = io.of(/^\/dynamic-\d+$/);
const childNamespace = io.of("/dynamic-101");
// @ts-ignore
expect(parentNamespace.children.has(childNamespace)).to.be(true);
io.close();
});
});
});