mirror of
https://github.com/socketio/socket.io.git
synced 2026-01-12 00:17:56 -05:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c332643ad8 | ||
|
|
3468a197af | ||
|
|
09d45491c4 | ||
|
|
0731c0d2f4 | ||
|
|
03046a64ad | ||
|
|
443e447087 | ||
|
|
2f6cc2fa42 | ||
|
|
00d8ee5b05 | ||
|
|
2dd5fa9dd4 | ||
|
|
a5dff0ac83 | ||
|
|
3035c25982 | ||
|
|
63f181cc12 | ||
|
|
a250e283da | ||
|
|
e5c62cad60 | ||
|
|
01d37624a8 | ||
|
|
faf914c9ab | ||
|
|
15af22fc22 | ||
|
|
d3658944e5 | ||
|
|
12b0de4f52 | ||
|
|
3d44aae381 | ||
|
|
cbf0362476 | ||
|
|
59280da20b | ||
|
|
50a4d37cb8 | ||
|
|
6458b2bef1 | ||
|
|
b56da8a99f | ||
|
|
7952312911 | ||
|
|
0d0a7a22b5 | ||
|
|
2a8565fd1e | ||
|
|
d0b22c6302 | ||
|
|
e71f3d7dbe |
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -16,7 +16,9 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12, 14, 16]
|
||||
node-version:
|
||||
- 16
|
||||
- 20
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
|
||||
201
CHANGELOG.md
201
CHANGELOG.md
@@ -2,6 +2,11 @@
|
||||
|
||||
## 2023
|
||||
|
||||
- [4.7.2](#472-2023-08-02) (Aug 2023)
|
||||
- [4.7.1](#471-2023-06-28) (Jun 2023)
|
||||
- [4.7.0](#470-2023-06-22) (Jun 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 +61,152 @@
|
||||
|
||||
# Release notes
|
||||
|
||||
# [4.6.0](https://github.com/socketio/socket.io/compare/4.5.4...4.6.0) (2023-02-07)
|
||||
## [4.7.2](https://github.com/socketio/socket.io/compare/4.7.1...4.7.2) (2023-08-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* clean up child namespace when client is rejected in middleware ([#4773](https://github.com/socketio/socket.io/issues/4773)) ([0731c0d](https://github.com/socketio/socket.io/commit/0731c0d2f497d5cce596ea1ec32a67c08bcccbcd))
|
||||
* **webtransport:** properly handle WebTransport-only connections ([3468a19](https://github.com/socketio/socket.io/commit/3468a197afe87e65eb0d779fabd347fe683013ab))
|
||||
* **webtransport:** add proper framing ([a306db0](https://github.com/socketio/engine.io/commit/a306db09e8ddb367c7d62f45fec920f979580b7c))
|
||||
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`engine.io@~6.5.2`](https://github.com/socketio/engine.io/releases/tag/6.5.2) ([diff](https://github.com/socketio/engine.io/compare/6.5.0...6.5.2))
|
||||
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
|
||||
|
||||
|
||||
|
||||
## [4.7.1](https://github.com/socketio/socket.io/compare/4.7.0...4.7.1) (2023-06-28)
|
||||
|
||||
The client bundle contains a few fixes regarding the WebTransport support.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`engine.io@~6.5.0`](https://github.com/socketio/engine.io/releases/tag/6.5.0) (no change)
|
||||
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
|
||||
|
||||
|
||||
|
||||
## [4.7.0](https://github.com/socketio/socket.io/compare/4.6.2...4.7.0) (2023-06-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove the Partial modifier from the socket.data type ([#4740](https://github.com/socketio/socket.io/issues/4740)) ([e5c62ca](https://github.com/socketio/socket.io/commit/e5c62cad60fc7d16fbb024fd9be1d1880f4e6f5f))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
#### Support for WebTransport
|
||||
|
||||
The Socket.IO server can now use WebTransport as the underlying transport.
|
||||
|
||||
WebTransport is a web API that uses the HTTP/3 protocol as a bidirectional transport. It's intended for two-way communications between a web client and an HTTP/3 server.
|
||||
|
||||
References:
|
||||
|
||||
- https://w3c.github.io/webtransport/
|
||||
- https://developer.mozilla.org/en-US/docs/Web/API/WebTransport
|
||||
- https://developer.chrome.com/articles/webtransport/
|
||||
|
||||
Until WebTransport support lands [in Node.js](https://github.com/nodejs/node/issues/38478), you can use the `@fails-components/webtransport` package:
|
||||
|
||||
```js
|
||||
import { readFileSync } from "fs";
|
||||
import { createServer } from "https";
|
||||
import { Server } from "socket.io";
|
||||
import { Http3Server } from "@fails-components/webtransport";
|
||||
|
||||
// WARNING: the total length of the validity period MUST NOT exceed two weeks (https://w3c.github.io/webtransport/#custom-certificate-requirements)
|
||||
const cert = readFileSync("/path/to/my/cert.pem");
|
||||
const key = readFileSync("/path/to/my/key.pem");
|
||||
|
||||
const httpsServer = createServer({
|
||||
key,
|
||||
cert
|
||||
});
|
||||
|
||||
httpsServer.listen(3000);
|
||||
|
||||
const io = new Server(httpsServer, {
|
||||
transports: ["polling", "websocket", "webtransport"] // WebTransport is not enabled by default
|
||||
});
|
||||
|
||||
const h3Server = new Http3Server({
|
||||
port: 3000,
|
||||
host: "0.0.0.0",
|
||||
secret: "changeit",
|
||||
cert,
|
||||
privKey: key,
|
||||
});
|
||||
|
||||
(async () => {
|
||||
const stream = await h3Server.sessionStream("/socket.io/");
|
||||
const sessionReader = stream.getReader();
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await sessionReader.read();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
io.engine.onWebTransportSession(value);
|
||||
}
|
||||
})();
|
||||
|
||||
h3Server.startServer();
|
||||
```
|
||||
|
||||
Added in [123b68c](https://github.com/socketio/engine.io/commit/123b68c04f9e971f59b526e0f967a488ee6b0116).
|
||||
|
||||
|
||||
#### Client bundles with CORS headers
|
||||
|
||||
The bundles will now have the right `Access-Control-Allow-xxx` headers.
|
||||
|
||||
Added in [63f181c](https://github.com/socketio/socket.io/commit/63f181cc12cbbbf94ed40eef52d60f36a1214fbe).
|
||||
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`engine.io@~6.5.0`](https://github.com/socketio/engine.io/releases/tag/6.5.0) ([diff](https://github.com/socketio/engine.io/compare/6.4.2...6.5.0))
|
||||
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
|
||||
|
||||
|
||||
|
||||
## [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.2`](https://github.com/socketio/engine.io/releases/tag/6.4.0) ([diff](https://github.com/socketio/engine.io/compare/6.4.1...6.4.2))
|
||||
- [`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.1`](https://github.com/socketio/engine.io/releases/tag/6.4.1) ([diff](https://github.com/socketio/engine.io/compare/6.4.0...6.4.1))
|
||||
- [`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 +383,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 +409,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 +424,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 +447,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 +462,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 +479,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 +491,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 +503,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
File diff suppressed because it is too large
Load Diff
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"
|
||||
|
||||
@@ -51,4 +51,5 @@ RewriteRule /(.*) balancer://nodes_ws/$1 [P,L]
|
||||
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
|
||||
RewriteRule /(.*) balancer://nodes_polling/$1 [P,L]
|
||||
|
||||
ProxyTimeout 3
|
||||
# must be bigger than pingInterval (25s by default) + pingTimeout (20s by default)
|
||||
ProxyTimeout 60
|
||||
@@ -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 {
|
||||
|
||||
1
examples/webtransport/.gitignore
vendored
Normal file
1
examples/webtransport/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.pem
|
||||
18
examples/webtransport/README.md
Normal file
18
examples/webtransport/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
# Socket.IO WebTransport example
|
||||
|
||||
## How to use
|
||||
|
||||
```shell
|
||||
# generate a self-signed certificate
|
||||
$ ./generate_cert.sh
|
||||
|
||||
# install dependencies
|
||||
$ npm i
|
||||
|
||||
# start the server
|
||||
$ node index.js
|
||||
|
||||
# open a Chrome browser
|
||||
$ ./open_chrome.sh
|
||||
```
|
||||
8
examples/webtransport/generate_cert.sh
Executable file
8
examples/webtransport/generate_cert.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
openssl req -new -x509 -nodes \
|
||||
-out cert.pem \
|
||||
-keyout key.pem \
|
||||
-newkey ec \
|
||||
-pkeyopt ec_paramgen_curve:prime256v1 \
|
||||
-subj '/CN=127.0.0.1' \
|
||||
-days 14
|
||||
35
examples/webtransport/index.html
Normal file
35
examples/webtransport/index.html
Normal file
@@ -0,0 +1,35 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Socket.IO WebTransport exampleqg</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script>
|
||||
const socket = io({
|
||||
transportOptions: {
|
||||
webtransport: {
|
||||
hostname: "127.0.0.1"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("connect", () => {
|
||||
console.log(`connect ${socket.id}`);
|
||||
|
||||
socket.io.engine.on("upgrade", (transport) => {
|
||||
console.log(`transport upgraded to ${transport.name}`);
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("connect_error", (err) => {
|
||||
console.log(`connect_error due to ${err.message}`);
|
||||
});
|
||||
|
||||
socket.on("disconnect", (reason) => {
|
||||
console.log(`disconnect due to ${reason}`);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
68
examples/webtransport/index.js
Normal file
68
examples/webtransport/index.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { createServer } from "node:https";
|
||||
import { Server } from "socket.io";
|
||||
import { Http3Server } from "@fails-components/webtransport";
|
||||
|
||||
const key = readFileSync("./key.pem");
|
||||
const cert = readFileSync("./cert.pem");
|
||||
|
||||
const httpsServer = createServer({
|
||||
key,
|
||||
cert
|
||||
}, (req, res) => {
|
||||
if (req.method === "GET" && req.url === "/") {
|
||||
const content = readFileSync("./index.html");
|
||||
res.writeHead(200, {
|
||||
"content-type": "text/html"
|
||||
});
|
||||
res.write(content);
|
||||
res.end();
|
||||
} else {
|
||||
res.writeHead(404).end();
|
||||
}
|
||||
});
|
||||
|
||||
const io = new Server(httpsServer, {
|
||||
transports: ["polling", "websocket", "webtransport"]
|
||||
});
|
||||
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
console.log(`connect ${socket.id}`);
|
||||
|
||||
socket.conn.on("upgrade", (transport) => {
|
||||
console.log(`transport upgraded to ${transport.name}`);
|
||||
});
|
||||
|
||||
socket.on("disconnect", (reason) => {
|
||||
console.log(`disconnect ${socket.id} due to ${reason}`);
|
||||
});
|
||||
});
|
||||
|
||||
httpsServer.listen(port, () => {
|
||||
console.log(`server listening at https://localhost:${port}`);
|
||||
});
|
||||
|
||||
const h3Server = new Http3Server({
|
||||
port,
|
||||
host: "0.0.0.0",
|
||||
secret: "changeit",
|
||||
cert,
|
||||
privKey: key,
|
||||
});
|
||||
|
||||
(async () => {
|
||||
const stream = await h3Server.sessionStream("/socket.io/");
|
||||
const sessionReader = stream.getReader();
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await sessionReader.read();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
io.engine.onWebTransportSession(value);
|
||||
}
|
||||
})();
|
||||
|
||||
h3Server.startServer();
|
||||
10
examples/webtransport/open_chrome.sh
Executable file
10
examples/webtransport/open_chrome.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
HASH=`openssl x509 -pubkey -noout -in cert.pem |
|
||||
openssl pkey -pubin -outform der |
|
||||
openssl dgst -sha256 -binary |
|
||||
base64`
|
||||
|
||||
/opt/google/chrome/chrome \
|
||||
--origin-to-force-quic-on=127.0.0.1:3000 \
|
||||
--ignore-certificate-errors-spki-list=$HASH \
|
||||
https://localhost:3000
|
||||
11
examples/webtransport/package.json
Normal file
11
examples/webtransport/package.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "webtransport",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@fails-components/webtransport": "^0.1.7",
|
||||
"socket.io": "^4.7.1"
|
||||
}
|
||||
}
|
||||
48
lib/index.ts
48
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,7 @@ import {
|
||||
SecondArg,
|
||||
} from "./typed-events";
|
||||
import { patchAdapter, restoreAdapter, serveFile } from "./uws";
|
||||
import type { BaseServer } from "engine.io/build/server";
|
||||
import corsMiddleware from "cors";
|
||||
|
||||
const debug = debugModule("socket.io:server");
|
||||
|
||||
@@ -180,6 +179,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>;
|
||||
@@ -192,6 +203,11 @@ export class Server<
|
||||
*/
|
||||
_connectTimeout: number;
|
||||
private httpServer: http.Server | HTTPSServer | Http2SecureServer;
|
||||
private _corsMiddleware: (
|
||||
req: http.IncomingMessage,
|
||||
res: http.ServerResponse,
|
||||
next: () => void
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* Server constructor.
|
||||
@@ -257,6 +273,10 @@ export class Server<
|
||||
this.attach(
|
||||
srv as http.Server | HTTPSServer | Http2SecureServer | number
|
||||
);
|
||||
|
||||
if (this.opts.cors) {
|
||||
this._corsMiddleware = corsMiddleware(this.opts.cors);
|
||||
}
|
||||
}
|
||||
|
||||
get _opts() {
|
||||
@@ -316,8 +336,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);
|
||||
});
|
||||
};
|
||||
@@ -540,7 +558,13 @@ export class Server<
|
||||
srv.removeAllListeners("request");
|
||||
srv.on("request", (req, res) => {
|
||||
if (this.clientPathRegex.test(req.url!)) {
|
||||
this.serve(req, res);
|
||||
if (this._corsMiddleware) {
|
||||
this._corsMiddleware(req, res, () => {
|
||||
this.serve(req, res);
|
||||
});
|
||||
} else {
|
||||
this.serve(req, res);
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < evs.length; i++) {
|
||||
evs[i].call(srv, req, res);
|
||||
@@ -693,6 +717,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 +730,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -202,7 +202,7 @@ export class Socket<
|
||||
* Additional information that can be attached to the Socket instance and which will be used in the
|
||||
* {@link Server.fetchSockets()} method.
|
||||
*/
|
||||
public data: Partial<SocketData> = {};
|
||||
public data: SocketData = {} as SocketData;
|
||||
/**
|
||||
* Whether the socket is currently connected or not.
|
||||
*
|
||||
@@ -260,7 +260,7 @@ export class Socket<
|
||||
this.id = previousSession.sid;
|
||||
this.pid = previousSession.pid;
|
||||
previousSession.rooms.forEach((room) => this.join(room));
|
||||
this.data = previousSession.data as Partial<SocketData>;
|
||||
this.data = previousSession.data as SocketData;
|
||||
previousSession.missedPackets.forEach((packet) => {
|
||||
this.packet({
|
||||
type: PacketType.EVENT,
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -289,16 +292,16 @@ export class Socket<
|
||||
*/
|
||||
private buildHandshake(auth: object): Handshake {
|
||||
return {
|
||||
headers: this.request.headers,
|
||||
headers: this.request?.headers || {},
|
||||
time: new Date() + "",
|
||||
address: this.conn.remoteAddress,
|
||||
xdomain: !!this.request.headers.origin,
|
||||
xdomain: !!this.request?.headers.origin,
|
||||
// @ts-ignore
|
||||
secure: !!this.request.connection.encrypted,
|
||||
secure: !this.request || !!this.request.connection.encrypted,
|
||||
issued: +new Date(),
|
||||
url: this.request.url!,
|
||||
url: this.request?.url!,
|
||||
// @ts-ignore
|
||||
query: this.request._query,
|
||||
query: this.request?._query || {},
|
||||
auth,
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -756,7 +758,6 @@ export class Socket<
|
||||
}
|
||||
|
||||
this._cleanup();
|
||||
this.nsp._remove(this);
|
||||
this.client._remove(this);
|
||||
this.connected = false;
|
||||
this.emitReserved("disconnect", reason, description);
|
||||
@@ -770,6 +771,7 @@ export class Socket<
|
||||
*/
|
||||
_cleanup() {
|
||||
this.leaveAll();
|
||||
this.nsp._remove(this);
|
||||
this.join = noop;
|
||||
}
|
||||
|
||||
|
||||
113
package-lock.json
generated
113
package-lock.json
generated
@@ -1,20 +1,21 @@
|
||||
{
|
||||
"name": "socket.io",
|
||||
"version": "4.5.4",
|
||||
"version": "4.7.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "socket.io",
|
||||
"version": "4.5.4",
|
||||
"version": "4.7.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "~2.0.0",
|
||||
"cors": "~2.8.5",
|
||||
"debug": "~4.3.2",
|
||||
"engine.io": "~6.4.0",
|
||||
"engine.io": "~6.5.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,17 +24,17 @@
|
||||
"nyc": "^15.1.0",
|
||||
"prettier": "^2.3.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"socket.io-client": "4.6.0",
|
||||
"socket.io-client": "4.7.2",
|
||||
"socket.io-client-v2": "npm:socket.io-client@^2.4.0",
|
||||
"superagent": "^8.0.0",
|
||||
"supertest": "^6.1.6",
|
||||
"ts-node": "^10.2.1",
|
||||
"tsd": "^0.21.0",
|
||||
"typescript": "^4.4.2",
|
||||
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.0.0"
|
||||
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.30.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
"node": ">=10.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
@@ -1228,9 +1229,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 +1378,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.5.2",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.2.tgz",
|
||||
"integrity": "sha512-IXsMcGpw/xRfjra46sVZVHiSWo/nJ/3g1337q9KNXtS6YRzbW5yIzTCb9DjhrBe7r3GZQR0I4+nq+4ODk5g/cA==",
|
||||
"dependencies": {
|
||||
"@types/cookie": "^0.4.1",
|
||||
"@types/cors": "^2.8.12",
|
||||
@@ -1389,30 +1390,30 @@
|
||||
"cookie": "~0.4.1",
|
||||
"cors": "~2.8.5",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.0.3",
|
||||
"engine.io-parser": "~5.2.1",
|
||||
"ws": "~8.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
"node": ">=10.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-client": {
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.4.0.tgz",
|
||||
"integrity": "sha512-GyKPDyoEha+XZ7iEqam49vz6auPnNJ9ZBfy89f+rMMas8AuiMWOZ9PVzu8xb9ZC6rafUqiGHSCfu22ih66E+1g==",
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.2.tgz",
|
||||
"integrity": "sha512-CQZqbrpEYnrpGqC07a9dJDz4gePZUgTPMU3NKJPSeQOyw27Tst4Pl3FemKoFGAlHzgZmKjoRmiJvbWfhCXUlIg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.0.3",
|
||||
"engine.io-parser": "~5.2.1",
|
||||
"ws": "~8.11.0",
|
||||
"xmlhttprequest-ssl": "~2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-parser": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz",
|
||||
"integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==",
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz",
|
||||
"integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
@@ -3463,15 +3464,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.7.2",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.2.tgz",
|
||||
"integrity": "sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w==",
|
||||
"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"
|
||||
"engine.io-client": "~6.5.2",
|
||||
"socket.io-parser": "~4.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
@@ -3586,9 +3587,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"
|
||||
@@ -4105,8 +4106,8 @@
|
||||
}
|
||||
},
|
||||
"node_modules/uWebSockets.js": {
|
||||
"version": "20.0.0",
|
||||
"resolved": "git+https://git@github.com/uNetworking/uWebSockets.js.git#4558ee00f9f1f686fffe1accbfc2e85b1af9c50f",
|
||||
"version": "20.30.0",
|
||||
"resolved": "git+https://git@github.com/uNetworking/uWebSockets.js.git#d39d4181daf5b670d44cbc1b18f8c28c85fd4142",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/v8-compile-cache-lib": {
|
||||
@@ -5271,9 +5272,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 +5388,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.5.2",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.2.tgz",
|
||||
"integrity": "sha512-IXsMcGpw/xRfjra46sVZVHiSWo/nJ/3g1337q9KNXtS6YRzbW5yIzTCb9DjhrBe7r3GZQR0I4+nq+4ODk5g/cA==",
|
||||
"requires": {
|
||||
"@types/cookie": "^0.4.1",
|
||||
"@types/cors": "^2.8.12",
|
||||
@@ -5399,27 +5400,27 @@
|
||||
"cookie": "~0.4.1",
|
||||
"cors": "~2.8.5",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.0.3",
|
||||
"engine.io-parser": "~5.2.1",
|
||||
"ws": "~8.11.0"
|
||||
}
|
||||
},
|
||||
"engine.io-client": {
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.4.0.tgz",
|
||||
"integrity": "sha512-GyKPDyoEha+XZ7iEqam49vz6auPnNJ9ZBfy89f+rMMas8AuiMWOZ9PVzu8xb9ZC6rafUqiGHSCfu22ih66E+1g==",
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.2.tgz",
|
||||
"integrity": "sha512-CQZqbrpEYnrpGqC07a9dJDz4gePZUgTPMU3NKJPSeQOyw27Tst4Pl3FemKoFGAlHzgZmKjoRmiJvbWfhCXUlIg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.0.3",
|
||||
"engine.io-parser": "~5.2.1",
|
||||
"ws": "~8.11.0",
|
||||
"xmlhttprequest-ssl": "~2.0.0"
|
||||
}
|
||||
},
|
||||
"engine.io-parser": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz",
|
||||
"integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg=="
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz",
|
||||
"integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ=="
|
||||
},
|
||||
"error-ex": {
|
||||
"version": "1.3.2",
|
||||
@@ -6922,15 +6923,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.7.2",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.2.tgz",
|
||||
"integrity": "sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w==",
|
||||
"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"
|
||||
"engine.io-client": "~6.5.2",
|
||||
"socket.io-parser": "~4.2.4"
|
||||
}
|
||||
},
|
||||
"socket.io-client-v2": {
|
||||
@@ -7026,9 +7027,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"
|
||||
@@ -7402,9 +7403,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"uWebSockets.js": {
|
||||
"version": "git+https://git@github.com/uNetworking/uWebSockets.js.git#4558ee00f9f1f686fffe1accbfc2e85b1af9c50f",
|
||||
"version": "git+https://git@github.com/uNetworking/uWebSockets.js.git#d39d4181daf5b670d44cbc1b18f8c28c85fd4142",
|
||||
"dev": true,
|
||||
"from": "uWebSockets.js@github:uNetworking/uWebSockets.js#v20.0.0"
|
||||
"from": "uWebSockets.js@github:uNetworking/uWebSockets.js#v20.30.0"
|
||||
},
|
||||
"v8-compile-cache-lib": {
|
||||
"version": "3.0.1",
|
||||
|
||||
17
package.json
17
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "socket.io",
|
||||
"version": "4.6.0",
|
||||
"version": "4.7.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",
|
||||
@@ -48,10 +48,11 @@
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "~2.0.0",
|
||||
"cors": "~2.8.5",
|
||||
"debug": "~4.3.2",
|
||||
"engine.io": "~6.4.0",
|
||||
"engine.io": "~6.5.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,14 +61,14 @@
|
||||
"nyc": "^15.1.0",
|
||||
"prettier": "^2.3.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"socket.io-client": "4.6.0",
|
||||
"socket.io-client": "4.7.2",
|
||||
"socket.io-client-v2": "npm:socket.io-client@^2.4.0",
|
||||
"superagent": "^8.0.0",
|
||||
"supertest": "^6.1.6",
|
||||
"ts-node": "^10.2.1",
|
||||
"tsd": "^0.21.0",
|
||||
"typescript": "^4.4.2",
|
||||
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.0.0"
|
||||
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.30.0"
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
@@ -88,7 +89,7 @@
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
"node": ">=10.2.0"
|
||||
},
|
||||
"tsd": {
|
||||
"directory": "test"
|
||||
|
||||
@@ -652,5 +652,86 @@ describe("namespaces", () => {
|
||||
|
||||
io.of(/^\/dynamic-\d+$/);
|
||||
});
|
||||
|
||||
it("should NOT clean up namespace when cleanupEmptyChildNamespaces is OFF and client is rejected in middleware", (done) => {
|
||||
const io = new Server(0, { cleanupEmptyChildNamespaces: false });
|
||||
io.of(/^\/dynamic-\d+$/).use((socket, next) => {
|
||||
next(new Error("You shall not pass!"));
|
||||
});
|
||||
const c1 = createClient(io, "/dynamic-101");
|
||||
|
||||
c1.on("connect", () => {
|
||||
done(new Error("Should not connect"));
|
||||
});
|
||||
|
||||
c1.on("connect_error", () => {
|
||||
setTimeout(() => {
|
||||
expect(io._nsps.has("/dynamic-101")).to.be(true);
|
||||
expect(io._nsps.get("/dynamic-101")!.sockets.size).to.be(0);
|
||||
success(done, io, c1);
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
|
||||
it("should clean up namespace when cleanupEmptyChildNamespaces is ON and client is rejected in middleware", (done) => {
|
||||
const io = new Server(0, { cleanupEmptyChildNamespaces: true });
|
||||
io.of(/^\/dynamic-\d+$/).use((socket, next) => {
|
||||
next(new Error("You shall not pass!"));
|
||||
});
|
||||
const c1 = createClient(io, "/dynamic-101");
|
||||
|
||||
c1.on("connect", () => {
|
||||
done(new Error("Should not connect"));
|
||||
});
|
||||
|
||||
c1.on("connect_error", () => {
|
||||
setTimeout(() => {
|
||||
expect(io._nsps.has("/dynamic-101")).to.be(false);
|
||||
success(done, io, c1);
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
|
||||
it("should NOT clean up namespace when cleanupEmptyChildNamespaces is ON and client is rejected in middleware but there are other clients connected", (done) => {
|
||||
const io = new Server(0, { cleanupEmptyChildNamespaces: true });
|
||||
let clientIdxToReject = 0;
|
||||
io.of(/^\/dynamic-\d+$/).use((socket, next) => {
|
||||
if (clientIdxToReject) {
|
||||
next(new Error("You shall not pass!"));
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
clientIdxToReject++;
|
||||
});
|
||||
const c1 = createClient(io, "/dynamic-101");
|
||||
|
||||
c1.on("connect", () => {
|
||||
const c2 = createClient(io, "/dynamic-101");
|
||||
c2.on("connect", () => {
|
||||
done(new Error("Client 2 should not connect"));
|
||||
});
|
||||
c2.on("connect_error", () => {
|
||||
setTimeout(() => {
|
||||
expect(io._nsps.has("/dynamic-101")).to.be(true);
|
||||
expect(io._nsps.get("/dynamic-101")!.sockets.size).to.be(1);
|
||||
success(done, io, c1, c2);
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
c1.on("connect_error", () => {
|
||||
done(new Error("Client 1 should not get an error"));
|
||||
});
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -70,6 +70,27 @@ describe("server attachment", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should serve client with necessary CORS headers", (done) => {
|
||||
const srv = createServer();
|
||||
new Server(srv, {
|
||||
cors: {
|
||||
origin: "https://good-origin.com",
|
||||
},
|
||||
});
|
||||
request(srv)
|
||||
.get("/socket.io/socket.io.js")
|
||||
.set("origin", "https://good-origin.com")
|
||||
.buffer(true)
|
||||
.end((err, res) => {
|
||||
if (err) return done(err);
|
||||
expect(res.headers["access-control-allow-origin"]).to.be(
|
||||
"https://good-origin.com"
|
||||
);
|
||||
expect(res.status).to.be(200);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it(
|
||||
"should serve bundle with msgpack parser",
|
||||
testSource("socket.io.msgpack.min.js")
|
||||
|
||||
@@ -684,6 +684,23 @@ describe("socket", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should handshake a client without access to the Engine.IO request (WebTransport-only connection)", (done) => {
|
||||
const io = new Server(0);
|
||||
const clientSocket = createClient(io, "/");
|
||||
|
||||
io.engine.on("connection", (socket) => {
|
||||
delete socket.request;
|
||||
});
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
expect(socket.handshake.secure).to.be(true);
|
||||
expect(socket.handshake.headers).to.eql({});
|
||||
expect(socket.handshake.query).to.eql({});
|
||||
|
||||
success(done, io, clientSocket);
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle very large json", function (done) {
|
||||
this.timeout(30000);
|
||||
const io = new Server(0, { perMessageDeflate: false });
|
||||
@@ -852,10 +869,6 @@ describe("socket", () => {
|
||||
it("should not crash when messing with Object prototype (and other globals)", (done) => {
|
||||
// @ts-ignore
|
||||
Object.prototype.foo = "bar";
|
||||
// @ts-ignore
|
||||
global.File = "";
|
||||
// @ts-ignore
|
||||
global.Blob = [];
|
||||
const io = new Server(0);
|
||||
const socket = createClient(io);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user