mirror of
https://github.com/socketio/socket.io.git
synced 2026-01-11 16:08:24 -05:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
faf914c9ab | ||
|
|
15af22fc22 | ||
|
|
d3658944e5 | ||
|
|
12b0de4f52 | ||
|
|
3d44aae381 | ||
|
|
cbf0362476 | ||
|
|
59280da20b | ||
|
|
50a4d37cb8 | ||
|
|
6458b2bef1 | ||
|
|
b56da8a99f | ||
|
|
7952312911 | ||
|
|
0d0a7a22b5 | ||
|
|
2a8565fd1e | ||
|
|
d0b22c6302 | ||
|
|
e71f3d7dbe |
84
CHANGELOG.md
84
CHANGELOG.md
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
4
client-dist/socket.io.esm.min.js
vendored
4
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,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
4
client-dist/socket.io.min.js
vendored
4
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
4
client-dist/socket.io.msgpack.min.js
vendored
4
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
18
examples/basic-websocket-client/README.md
Normal file
18
examples/basic-websocket-client/README.md
Normal 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
|
||||
```
|
||||
1
examples/basic-websocket-client/bundle/socket.io.min.js
vendored
Normal file
1
examples/basic-websocket-client/bundle/socket.io.min.js
vendored
Normal 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};
|
||||
17
examples/basic-websocket-client/check-bundle-size.js
Normal file
17
examples/basic-websocket-client/check-bundle-size.js
Normal 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`);
|
||||
18
examples/basic-websocket-client/package.json
Normal file
18
examples/basic-websocket-client/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
10
examples/basic-websocket-client/rollup.config.js
Normal file
10
examples/basic-websocket-client/rollup.config.js
Normal 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()],
|
||||
}
|
||||
};
|
||||
273
examples/basic-websocket-client/src/index.js
Normal file
273
examples/basic-websocket-client/src/index.js
Normal 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);
|
||||
}
|
||||
162
examples/basic-websocket-client/test/index.js
Normal file
162
examples/basic-websocket-client/test/index.js
Normal 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");
|
||||
});
|
||||
});
|
||||
@@ -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"
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
FROM haproxy:1.7-alpine
|
||||
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg
|
||||
@@ -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"
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
FROM httpd:2.4-alpine
|
||||
COPY ./httpd.conf /usr/local/apache2/conf/httpd.conf
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM mhart/alpine-node:6
|
||||
FROM node:14-alpine
|
||||
|
||||
# Create app directory
|
||||
RUN mkdir -p /usr/src/app
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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%",
|
||||
|
||||
@@ -68,7 +68,7 @@ body {
|
||||
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url("/fonts/Lato-Regular.ttf");
|
||||
src: "~/public/fonts/Lato-Regular.ttf";
|
||||
}
|
||||
|
||||
#app {
|
||||
|
||||
30
lib/index.ts
30
lib/index.ts
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
62
package-lock.json
generated
@@ -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"
|
||||
|
||||
12
package.json
12
package.json
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user