mirror of
https://github.com/socketio/socket.io.git
synced 2026-01-08 22:48:20 -05:00
Merge remote-tracking branch 'engine.io-client/main' into monorepo
Source: https://github.com/socketio/engine.io-client
This commit is contained in:
2
packages/engine.io-client/.prettierignore
Normal file
2
packages/engine.io-client/.prettierignore
Normal file
@@ -0,0 +1,2 @@
|
||||
test/support/public/engine.io.min.js
|
||||
lib/contrib/*
|
||||
686
packages/engine.io-client/CHANGELOG.md
Normal file
686
packages/engine.io-client/CHANGELOG.md
Normal file
@@ -0,0 +1,686 @@
|
||||
# History
|
||||
|
||||
| Version | Release date | Bundle size (UMD min+gzip) |
|
||||
|-------------------------------------------------------------------------------------------------------------|----------------|----------------------------|
|
||||
| [6.6.0](#660-2024-06-21) | June 2024 | `8.6 KB` |
|
||||
| [6.5.4](#654-2024-06-18) (from the [6.5.x](https://github.com/socketio/engine.io-client/tree/6.5.x) branch) | June 2024 | `8.8 KB` |
|
||||
| [3.5.4](#354-2024-06-18) (from the [3.5.x](https://github.com/socketio/engine.io-client/tree/3.5.x) branch) | June 2024 | `-` |
|
||||
| [6.5.3](#653-2023-11-09) | November 2023 | `8.8 KB` |
|
||||
| [6.5.2](#652-2023-08-01) | August 2023 | `8.8 KB` |
|
||||
| [6.5.1](#651-2023-06-28) | June 2023 | `8.4 KB` |
|
||||
| [6.5.0](#650-2023-06-16) | June 2023 | `7.8 KB` |
|
||||
| [6.4.0](#640-2023-02-06) | February 2023 | `7.8 KB` |
|
||||
| [6.3.1](#631-2023-02-04) | February 2023 | `7.8 KB` |
|
||||
| [6.3.0](#630-2023-01-10) | January 2023 | `8.0 KB` |
|
||||
| [6.2.3](#623-2022-10-13) | October 2022 | `7.8 KB` |
|
||||
| [3.5.3](#353-2022-09-07) | September 2022 | `-` |
|
||||
| [6.2.2](#622-2022-05-02) | May 2022 | `7.8 KB` |
|
||||
| [6.2.1](#621-2022-04-17) | April 2022 | `7.8 KB` |
|
||||
| [6.2.0](#620-2022-04-17) | April 2022 | `7.8 KB` |
|
||||
| [6.0.3](#603-2021-11-14) (from the [6.0.x](https://github.com/socketio/engine.io-client/tree/6.0.x) branch) | November 2021 | `7.4 KB` |
|
||||
| [6.1.1](#611-2021-11-14) | November 2021 | `7.4 KB` |
|
||||
| [6.1.0](#610-2021-11-08) | November 2021 | `7.4 KB` |
|
||||
| [6.0.2](#602-2021-10-15) | October 2021 | `7.4 KB` |
|
||||
| [6.0.1](#601-2021-10-14) | October 2021 | `7.4 KB` |
|
||||
| [**6.0.0**](#600-2021-10-08) | October 2021 | `7.5 KB` |
|
||||
| [5.2.0](#520-2021-08-29) | August 2021 | `9.4 KB` |
|
||||
| [5.1.2](#512-2021-06-24) | June 2021 | `9.3 KB` |
|
||||
| [5.1.1](#511-2021-05-11) | May 2021 | `9.2 KB` |
|
||||
| [4.1.4](#414-2021-05-05) (from the [4.1.x](https://github.com/socketio/engine.io-client/tree/4.1.x) branch) | May 2021 | `9.1 KB` |
|
||||
| [3.5.2](#352-2021-05-05) (from the [3.5.x](https://github.com/socketio/engine.io-client/tree/3.5.x) branch) | May 2021 | `-` |
|
||||
| [5.1.0](#510-2021-05-04) | May 2021 | `9.2 KB` |
|
||||
| [5.0.1](#501-2021-03-31) | March 2021 | `9.2 KB` |
|
||||
| [**5.0.0**](#500-2021-03-10) | March 2021 | `9.3 KB` |
|
||||
| [3.5.1](#351-2021-03-02) (from the [3.5.x](https://github.com/socketio/engine.io-client/tree/3.5.x) branch) | March 2021 | `-` |
|
||||
| [4.1.2](#412-2021-02-25) | February 2021 | `9.2 KB` |
|
||||
| [4.1.1](#411-2021-02-02) | February 2021 | `9.1 KB` |
|
||||
| [4.1.0](#410-2021-01-14) | January 2021 | `9.1 KB` |
|
||||
|
||||
# Release notes
|
||||
|
||||
## [6.6.0](https://github.com/socketio/engine.io-client/compare/6.5.3...6.6.0) (2024-06-21)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
#### Custom transport implementations
|
||||
|
||||
The `transports` option now accepts an array of transport implementations:
|
||||
|
||||
```js
|
||||
import { Socket, XHR, WebSocket } from "engine.io-client";
|
||||
|
||||
const socket = new Socket({
|
||||
transports: [XHR, WebSocket]
|
||||
});
|
||||
```
|
||||
|
||||
Here is the list of provided implementations:
|
||||
|
||||
| Transport | Description |
|
||||
|-----------------|------------------------------------------------------------------------------------------------------|
|
||||
| `Fetch` | HTTP long-polling based on the built-in `fetch()` method. |
|
||||
| `NodeXHR` | HTTP long-polling based on the `XMLHttpRequest` object provided by the `xmlhttprequest-ssl` package. |
|
||||
| `XHR` | HTTP long-polling based on the built-in `XMLHttpRequest` object. |
|
||||
| `NodeWebSocket` | WebSocket transport based on the `WebSocket` object provided by the `ws` package. |
|
||||
| `WebSocket` | WebSocket transport based on the built-in `WebSocket` object. |
|
||||
| `WebTransport` | WebTransport transport based on the built-in `WebTransport` object. |
|
||||
|
||||
Usage:
|
||||
|
||||
| Transport | browser | Node.js | Deno | Bun |
|
||||
|-----------------|--------------------|------------------------|--------------------|--------------------|
|
||||
| `Fetch` | :white_check_mark: | :white_check_mark: (1) | :white_check_mark: | :white_check_mark: |
|
||||
| `NodeXHR` | | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| `XHR` | :white_check_mark: | | | |
|
||||
| `NodeWebSocket` | | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| `WebSocket` | :white_check_mark: | :white_check_mark: (2) | :white_check_mark: | :white_check_mark: |
|
||||
| `WebTransport` | :white_check_mark: | :white_check_mark: | | |
|
||||
|
||||
(1) since [v18.0.0](https://nodejs.org/api/globals.html#fetch)
|
||||
(2) since [v21.0.0](https://nodejs.org/api/globals.html#websocket)
|
||||
|
||||
Added in [f4d898e](https://github.com/socketio/engine.io-client/commit/f4d898ee9652939a4550a41ac0e8143056154c0a) and [b11763b](https://github.com/socketio/engine.io-client/commit/b11763beecfe4622867b4dec9d1db77460733ffb).
|
||||
|
||||
|
||||
#### Transport tree-shaking
|
||||
|
||||
The feature above also comes with the ability to exclude the code related to unused transports (a.k.a. "tree-shaking"):
|
||||
|
||||
```js
|
||||
import { SocketWithoutUpgrade, WebSocket } from "engine.io-client";
|
||||
|
||||
const socket = new SocketWithoutUpgrade({
|
||||
transports: [WebSocket]
|
||||
});
|
||||
```
|
||||
|
||||
In that case, the code related to HTTP long-polling and WebTransport will be excluded from the final bundle.
|
||||
|
||||
Added in [f4d898e](https://github.com/socketio/engine.io-client/commit/f4d898ee9652939a4550a41ac0e8143056154c0a)
|
||||
|
||||
|
||||
#### Test each low-level transports
|
||||
|
||||
When setting the `tryAllTransports` option to `true`, if the first transport (usually, HTTP long-polling) fails, then the other transports will be tested too:
|
||||
|
||||
```js
|
||||
import { Socket } from "engine.io-client";
|
||||
|
||||
const socket = new Socket({
|
||||
tryAllTransports: true
|
||||
});
|
||||
```
|
||||
|
||||
This feature is useful in two cases:
|
||||
|
||||
- when HTTP long-polling is disabled on the server, or if CORS fails
|
||||
- when WebSocket is tested first (with `transports: ["websocket", "polling"]`)
|
||||
|
||||
The only potential downside is that the connection attempt could take more time in case of failure, as there have been reports of WebSocket connection errors taking several seconds before being detected (that's one reason for using HTTP long-polling first). That's why the option defaults to `false` for now.
|
||||
|
||||
Added in [579b243](https://github.com/socketio/engine.io-client/commit/579b243e89ac7dc58233f9844ef70817364ecf52).
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add some randomness to the cache busting string generator ([b624c50](https://github.com/socketio/engine.io-client/commit/b624c508325615fe5f0ba82293d14831d8861324))
|
||||
* fix cookie management with WebSocket (Node.js only) ([e105551](https://github.com/socketio/engine.io-client/commit/e105551ef17ff8a23aa3ebdea9119619ae4208ad))
|
||||
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`ws@~8.17.1`](https://github.com/websockets/ws/releases/tag/8.17.1) (no change)
|
||||
|
||||
|
||||
|
||||
## [6.5.4](https://github.com/socketio/engine.io-client/compare/6.5.3...6.5.4) (2024-06-18)
|
||||
|
||||
This release contains a bump of the `ws` dependency, which includes an important [security fix](https://github.com/websockets/ws/commit/e55e5106f10fcbaac37cfa89759e4cc0d073a52c).
|
||||
|
||||
Advisory: https://github.com/advisories/GHSA-3h5v-q93c-6h6q
|
||||
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`ws@~8.17.1`](https://github.com/websockets/ws/releases/tag/8.17.1) ([diff](https://github.com/websockets/ws/compare/8.11.0...8.17.1))
|
||||
|
||||
|
||||
|
||||
## [3.5.4](https://github.com/socketio/engine.io-client/compare/3.5.3...3.5.4) (2024-06-18)
|
||||
|
||||
This release contains a bump of the `ws` dependency, which includes an important [security fix](https://github.com/websockets/ws/commit/e55e5106f10fcbaac37cfa89759e4cc0d073a52c).
|
||||
|
||||
Advisory: https://github.com/advisories/GHSA-3h5v-q93c-6h6q
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`ws@~7.5.10`](https://github.com/websockets/ws/releases/tag/7.5.10) ([diff](https://github.com/websockets/ws/compare/7.4.2...7.5.10))
|
||||
|
||||
|
||||
|
||||
## [6.5.3](https://github.com/socketio/engine.io-client/compare/6.5.2...6.5.3) (2023-11-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add a maximum length for the URL ([707597d](https://github.com/socketio/engine.io-client/commit/707597df26abfa1e6b569b2a62918dfcc8b80b5d))
|
||||
* improve compatibility with node16 module resolution ([#711](https://github.com/socketio/engine.io-client/issues/711)) ([46ef851](https://github.com/socketio/engine.io-client/commit/46ef8512edac758069ed4d519f7517bafbace4a9))
|
||||
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
|
||||
|
||||
|
||||
|
||||
## [6.5.2](https://github.com/socketio/engine.io-client/compare/6.5.1...6.5.2) (2023-08-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **webtransport:** add proper framing ([d55c39e](https://github.com/socketio/engine.io-client/commit/d55c39e0ed5cb7b3a34875a398efc111c91184f6))
|
||||
* **webtransport:** honor the binaryType attribute ([8270e00](https://github.com/socketio/engine.io-client/commit/8270e00d5b865278d136a4d349b344cbc2b38dc5))
|
||||
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
|
||||
|
||||
|
||||
|
||||
## [6.5.1](https://github.com/socketio/engine.io-client/compare/6.5.0...6.5.1) (2023-06-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* make closeOnBeforeunload default to false ([a63066b](https://github.com/socketio/engine.io-client/commit/a63066bdc8ae9e6746c3113d06c2ead78f4a4851))
|
||||
* **webtransport:** properly handle abruptly closed connections ([cf6aa1f](https://github.com/socketio/engine.io-client/commit/cf6aa1f43c27a56c076bf26fddfce74bfeb65040))
|
||||
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
|
||||
|
||||
|
||||
|
||||
## [6.5.0](https://github.com/socketio/engine.io-client/compare/6.4.0...6.5.0) (2023-06-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
#### Support for WebTransport
|
||||
|
||||
The Engine.IO client 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/
|
||||
|
||||
**For Node.js clients**: until WebTransport support lands [in Node.js](https://github.com/nodejs/node/issues/38478), you can use the `@fails-components/webtransport` package:
|
||||
|
||||
```js
|
||||
import { WebTransport } from "@fails-components/webtransport";
|
||||
|
||||
global.WebTransport = WebTransport;
|
||||
```
|
||||
|
||||
Added in [7195c0f](https://github.com/socketio/engine.io-client/commit/7195c0f305b482f7b1ca2ed812030caaf72c0906).
|
||||
|
||||
#### Cookie management for the Node.js client
|
||||
|
||||
When setting the `withCredentials` option to `true`, the Node.js client will now include the cookies in the HTTP requests, making it easier to use it with cookie-based sticky sessions.
|
||||
|
||||
```js
|
||||
import { Socket } from "engine.io-client";
|
||||
|
||||
const socket = new Socket("https://example.com", {
|
||||
withCredentials: true
|
||||
});
|
||||
```
|
||||
|
||||
Added in [5fc88a6](https://github.com/socketio/engine.io-client/commit/5fc88a62d4017cdc144fa39b9755deadfff2db34).
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
|
||||
|
||||
|
||||
|
||||
## [6.4.0](https://github.com/socketio/engine.io-client/compare/6.3.1...6.4.0) (2023-02-06)
|
||||
|
||||
The minor bump is due to changes on the server side.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
|
||||
|
||||
|
||||
|
||||
## [6.3.1](https://github.com/socketio/engine.io-client/compare/6.3.0...6.3.1) (2023-02-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **typings:** do not expose browser-specific types ([37d7a0a](https://github.com/socketio/engine.io-client/commit/37d7a0aa791a4666ca405b11d0d8bdb199222e50))
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
|
||||
|
||||
|
||||
## [6.3.0](https://github.com/socketio/engine.io-client/compare/6.2.3...6.3.0) (2023-01-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* properly parse relative URL with a "@" character ([12b7d78](https://github.com/socketio/engine.io-client/commit/12b7d7817e9c0016c970f903de15ed8b4255ea90))
|
||||
* use explicit context for setTimeout function ([#699](https://github.com/socketio/engine.io-client/issues/699)) ([047f420](https://github.com/socketio/engine.io-client/commit/047f420b86a669752536ff425261e7be60a80692))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add the "addTrailingSlash" option ([#694](https://github.com/socketio/engine.io-client/issues/694)) ([21a6e12](https://github.com/socketio/engine.io-client/commit/21a6e1219add92157c5442537d24fbe1129a50f5))
|
||||
|
||||
The trailing slash which was added by default can now be disabled:
|
||||
|
||||
```js
|
||||
import { Socket } from "engine.io-client";
|
||||
|
||||
const socket = new Socket("https://example.com", {
|
||||
addTrailingSlash: false
|
||||
});
|
||||
```
|
||||
|
||||
In the example above, the request URL will be `https://example.com/engine.io` instead of `https://example.com/engine.io/`.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`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))
|
||||
|
||||
|
||||
|
||||
## [6.2.3](https://github.com/socketio/engine.io-client/compare/6.2.2...6.2.3) (2022-10-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* properly clear "beforeunload" event listener ([99925a4](https://github.com/socketio/engine.io-client/commit/99925a47750f66d2ad36313243545181512579ee))
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`ws@~8.2.3`](https://github.com/websockets/ws/releases/tag/8.2.3) (no change)
|
||||
|
||||
|
||||
|
||||
## [3.5.3](https://github.com/socketio/engine.io-client/compare/3.5.2...3.5.3) (2022-09-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix usage with vite ([280de36](https://github.com/socketio/engine.io-client/commit/280de368092b17648b59b7467fa49f2425edcd45))
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`ws@~7.4.2`](https://github.com/websockets/ws/releases/tag/7.4.2) (no change)
|
||||
|
||||
|
||||
|
||||
## [6.2.2](https://github.com/socketio/engine.io-client/compare/6.2.1...6.2.2) (2022-05-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* simplify the check for WebSocket availability ([f158c8e](https://github.com/socketio/engine.io-client/commit/f158c8e255be9e849313e53201adf1642c60345a))
|
||||
|
||||
This check was added for the flashsocket transport, which has been deprecated for a while now ([1]). But it fails with latest webpack versions, as the expression `"__initialize" in WebSocket` gets evaluated to `true`.
|
||||
|
||||
* use named export for globalThis shim ([#688](https://github.com/socketio/engine.io-client/issues/688)) ([32878ea](https://github.com/socketio/engine.io-client/commit/32878ea047c38e2b2f0444e828ac71f4d833971f))
|
||||
|
||||
Default export of globalThis seems to have a problem in the "browser" field when the library is loaded asynchronously with webpack.
|
||||
|
||||
|
||||
|
||||
## [6.2.1](https://github.com/socketio/engine.io-client/compare/6.2.0...6.2.1) (2022-04-17)
|
||||
|
||||
|
||||
|
||||
# [6.2.0](https://github.com/socketio/engine.io-client/compare/6.1.1...6.2.0) (2022-04-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add details to the "close" event ([b9252e2](https://github.com/socketio/engine.io-client/commit/b9252e207413a850db7e4f0f0ef7dd2ef0ed26da))
|
||||
|
||||
The close event will now include additional details to help debugging if anything has gone wrong.
|
||||
|
||||
Example when a payload is over the maxHttpBufferSize value in HTTP long-polling mode:
|
||||
|
||||
```js
|
||||
socket.on("close", (reason, details) => {
|
||||
console.log(reason); // "transport error"
|
||||
|
||||
// in that case, details is an error object
|
||||
console.log(details.message); "xhr post error"
|
||||
console.log(details.description); // 413 (the HTTP status of the response)
|
||||
|
||||
// details.context refers to the XMLHttpRequest object
|
||||
console.log(details.context.status); // 413
|
||||
console.log(details.context.responseText); // ""
|
||||
});
|
||||
```
|
||||
|
||||
Note: the error object was already included before this commit and is kept for backward compatibility.
|
||||
|
||||
* slice write buffer according to the maxPayload value ([46fdc2f](https://github.com/socketio/engine.io-client/commit/46fdc2f0ed352b454614247406689edc9d908927))
|
||||
|
||||
The server will now include a "maxPayload" field in the handshake details, allowing the clients to decide how many
|
||||
packets they have to send to stay under the maxHttpBufferSize value.
|
||||
|
||||
|
||||
|
||||
## [6.0.3](https://github.com/socketio/engine.io-client/compare/6.0.2...6.0.3) (2021-11-14)
|
||||
|
||||
Some bug fixes were backported from master, to be included by the latest `socket.io-client` version.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add package name in nested package.json ([32511ee](https://github.com/socketio/engine.io-client/commit/32511ee32a0a6122e99db35833ed948aa4e427ac))
|
||||
* fix vite build for CommonJS users ([9fcaf58](https://github.com/socketio/engine.io-client/commit/9fcaf58d18c013c0b92fdaf27481f0383efb3658))
|
||||
|
||||
|
||||
|
||||
## [6.1.1](https://github.com/socketio/engine.io-client/compare/6.1.0...6.1.1) (2021-11-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add package name in nested package.json ([6e798fb](https://github.com/socketio/engine.io-client/commit/6e798fbb5b11a1cfec03ece3dfce03213b5f9a12))
|
||||
* fix vite build for CommonJS users ([c557707](https://github.com/socketio/engine.io-client/commit/c557707fb694bd10397b4cd8b4ec2fbe59128faa))
|
||||
|
||||
|
||||
|
||||
# [6.1.0](https://github.com/socketio/engine.io-client/compare/6.0.2...6.1.0) (2021-11-08)
|
||||
|
||||
The minor bump is due to changes on the server side.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **typings:** allow any value in the query option ([018e1af](https://github.com/socketio/engine.io-client/commit/018e1afcc5ef5eac81e9e1629db053bda44120ee))
|
||||
* **typings:** allow port to be a number ([#680](https://github.com/socketio/engine.io-client/issues/680)) ([8f68f77](https://github.com/socketio/engine.io-client/commit/8f68f77825af069fe2c612a3200a025d4130ac0a))
|
||||
|
||||
|
||||
|
||||
## [6.0.2](https://github.com/socketio/engine.io-client/compare/6.0.1...6.0.2) (2021-10-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bundle:** fix vite build ([faa9f31](https://github.com/socketio/engine.io-client/commit/faa9f318e70cd037af79bfa20e9d21b284ddf257))
|
||||
|
||||
|
||||
|
||||
## [6.0.1](https://github.com/socketio/engine.io-client/compare/6.0.0...6.0.1) (2021-10-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix usage with vite ([4971914](https://github.com/socketio/engine.io-client/commit/49719142f65e23efa65fca4f66765ded5d955972))
|
||||
|
||||
|
||||
# [6.0.0](https://github.com/socketio/engine.io-client/compare/5.2.0...6.0.0) (2021-10-08)
|
||||
|
||||
This major release contains three important changes:
|
||||
|
||||
- the codebase was migrated to TypeScript ([7245b80](https://github.com/socketio/engine.io-client/commit/7245b803e0c8d57cfc1f1cd8b8c8d598e8397967))
|
||||
- rollup is now used instead of webpack to create the bundles ([27de300](https://github.com/socketio/engine.io-client/commit/27de300de42420ab59a02ec7a3445e636cbcc78e))
|
||||
- code that provided support for ancient browsers (think IE8) was removed ([c656192](https://github.com/socketio/engine.io-client/commit/c6561928be628084fd2f5e7a70943c8e5c582873) and [b2c7381](https://github.com/socketio/engine.io-client/commit/b2c73812e978489b5dfbe516a26b6b8fd628856d))
|
||||
|
||||
There is now three distinct builds (in the build/ directory):
|
||||
|
||||
- CommonJS
|
||||
- ESM with debug
|
||||
- ESM without debug (rationale here: [00d7e7d](https://github.com/socketio/engine.io-client/commit/00d7e7d7ee85b4cfa6f9f547203cc692083ac61c))
|
||||
|
||||
And three bundles (in the dist/ directory) :
|
||||
|
||||
- `engine.io.js`: unminified UMD bundle
|
||||
- `engine.io.min.js`: minified UMD bundle
|
||||
- `engine.io.esm.min.js`: ESM bundle
|
||||
|
||||
Please note that the communication protocol was not updated, so a v5 client will be able to reach a v6 server (and vice-versa).
|
||||
|
||||
Reference: https://github.com/socketio/engine.io-protocol
|
||||
|
||||
### Features
|
||||
|
||||
* provide an ESM build without debug ([00d7e7d](https://github.com/socketio/engine.io-client/commit/00d7e7d7ee85b4cfa6f9f547203cc692083ac61c))
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* the enableXDR option is removed ([c656192](https://github.com/socketio/engine.io-client/commit/c6561928be628084fd2f5e7a70943c8e5c582873))
|
||||
* the jsonp and forceJSONP options are removed ([b2c7381](https://github.com/socketio/engine.io-client/commit/b2c73812e978489b5dfbe516a26b6b8fd628856d))
|
||||
|
||||
`ws` version: `~8.2.3`
|
||||
|
||||
# [5.2.0](https://github.com/socketio/engine.io-client/compare/5.1.2...5.2.0) (2021-08-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add an option to use native timer functions ([#672](https://github.com/socketio/engine.io-client/issues/672)) ([5d1d5be](https://github.com/socketio/engine.io-client/commit/5d1d5bea11ab6854473ddc02a3391929ea4fc8f4))
|
||||
|
||||
|
||||
## [5.1.2](https://github.com/socketio/engine.io-client/compare/5.1.1...5.1.2) (2021-06-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* emit ping when receiving a ping from the server ([589d3ad](https://github.com/socketio/engine.io-client/commit/589d3ad63840329b5a61186603a415c534f8d4fc))
|
||||
* **websocket:** fix timer blocking writes ([#670](https://github.com/socketio/engine.io-client/issues/670)) ([f30a10b](https://github.com/socketio/engine.io-client/commit/f30a10b7f45517fcb3abd02511c58a89e0ef498f))
|
||||
|
||||
|
||||
## [5.1.1](https://github.com/socketio/engine.io-client/compare/5.1.0...5.1.1) (2021-05-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix JSONP transport on IE9 ([bddd992](https://github.com/socketio/engine.io-client/commit/bddd9928fcdb33c79e0289bcafef337359dee12b))
|
||||
|
||||
|
||||
## [4.1.4](https://github.com/socketio/engine.io-client/compare/4.1.3...4.1.4) (2021-05-05)
|
||||
|
||||
This release only contains a bump of `xmlhttprequest-ssl`, in order to fix the following vulnerability: https://www.npmjs.com/advisories/1665.
|
||||
|
||||
Please note that `engine.io-client` was not directly impacted by this vulnerability, since we are always using `async: true`.
|
||||
|
||||
|
||||
## [3.5.2](https://github.com/socketio/engine.io-client/compare/3.5.1...3.5.2) (2021-05-05)
|
||||
|
||||
This release only contains a bump of `xmlhttprequest-ssl`, in order to fix the following vulnerability: https://www.npmjs.com/advisories/1665.
|
||||
|
||||
Please note that `engine.io-client` was not directly impacted by this vulnerability, since we are always using `async: true`.
|
||||
|
||||
|
||||
# [5.1.0](https://github.com/socketio/engine.io-client/compare/5.0.1...5.1.0) (2021-05-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add the "closeOnBeforeunload" option ([dcb85e9](https://github.com/socketio/engine.io-client/commit/dcb85e902d129b2d1a94943b4f6d471532f70dc9))
|
||||
|
||||
|
||||
## [5.0.1](https://github.com/socketio/engine.io-client/compare/5.0.0...5.0.1) (2021-03-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* ignore packets when the transport is silently closed ([d291a4c](https://github.com/socketio/engine.io-client/commit/d291a4c9f6accfc86fcd96683a5d493a87e3644c))
|
||||
|
||||
|
||||
# [5.0.0](https://github.com/socketio/engine.io-client/compare/4.1.2...5.0.0) (2021-03-10)
|
||||
|
||||
The major bump is due to a breaking change on the server side.
|
||||
|
||||
### Features
|
||||
|
||||
* add autoUnref option ([6551683](https://github.com/socketio/engine.io-client/commit/65516836b2b6fe28d80e9a5918f9e10baa7451d8))
|
||||
* listen to the "offline" event ([c361bc6](https://github.com/socketio/engine.io-client/commit/c361bc691f510b96f8909c5e6c62a4635d50275c))
|
||||
|
||||
|
||||
## [3.5.1](https://github.com/socketio/engine.io-client/compare/3.5.0...3.5.1) (2021-03-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* replace default nulls in SSL options with undefineds ([d0c551c](https://github.com/socketio/engine.io-client/commit/d0c551cca1e37301e8b28843c8f6e7ad5cf561d3))
|
||||
|
||||
|
||||
## [4.1.2](https://github.com/socketio/engine.io-client/compare/4.1.1...4.1.2) (2021-02-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* silently close the transport in the beforeunload hook ([ed48b5d](https://github.com/socketio/engine.io-client/commit/ed48b5dc3407e5ded45072606b3bb0eafa49c01f))
|
||||
|
||||
|
||||
## [4.1.1](https://github.com/socketio/engine.io-client/compare/4.1.0...4.1.1) (2021-02-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove polyfill for process in the bundle ([c95fdea](https://github.com/socketio/engine.io-client/commit/c95fdea83329b264964641bb48e3be2a8772f7a1))
|
||||
|
||||
|
||||
# [4.1.0](https://github.com/socketio/engine.io-client/compare/4.0.6...4.1.0) (2021-01-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add missing ws options ([d134fee](https://github.com/socketio/engine.io-client/commit/d134feeaa615afc4cbe0aa45aa4344c899b65df0))
|
||||
|
||||
|
||||
## [4.0.6](https://github.com/socketio/engine.io-client/compare/4.0.5...4.0.6) (2021-01-04)
|
||||
|
||||
|
||||
# [3.5.0](https://github.com/socketio/engine.io-client/compare/3.4.4...3.5.0) (2020-12-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* check the type of the initial packet ([8750356](https://github.com/socketio/engine.io-client/commit/8750356dba5409ba0e1d3a27da6d214118702b3e))
|
||||
|
||||
|
||||
|
||||
## [4.0.5](https://github.com/socketio/engine.io-client/compare/4.0.4...4.0.5) (2020-12-07)
|
||||
|
||||
|
||||
## [4.0.4](https://github.com/socketio/engine.io-client/compare/4.0.3...4.0.4) (2020-11-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* check the type of the initial packet ([1c8cba8](https://github.com/socketio/engine.io-client/commit/1c8cba8818e930205918a70f05c1164865842a48))
|
||||
* restore the cherry-picking of the WebSocket options ([4873a23](https://github.com/socketio/engine.io-client/commit/4873a237f1ce5fcb18e255dd604d50dcfc624ea8))
|
||||
|
||||
|
||||
## [4.0.3](https://github.com/socketio/engine.io-client/compare/4.0.2...4.0.3) (2020-11-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **react-native:** add a default value for the withCredentials option ([ccb99e3](https://github.com/socketio/engine.io-client/commit/ccb99e3718e8ee2c50960430d2bd6c12a3dcb0dc))
|
||||
* **react-native:** exclude the localAddress option ([177b95f](https://github.com/socketio/engine.io-client/commit/177b95fe463ad049b35170f042a771380fdaedee))
|
||||
|
||||
|
||||
## [4.0.2](https://github.com/socketio/engine.io-client/compare/4.0.1...4.0.2) (2020-11-09)
|
||||
|
||||
|
||||
## [4.0.1](https://github.com/socketio/engine.io-client/compare/4.0.0...4.0.1) (2020-10-21)
|
||||
|
||||
|
||||
|
||||
## [3.4.4](https://github.com/socketio/engine.io-client/compare/3.4.3...3.4.4) (2020-09-30)
|
||||
|
||||
|
||||
|
||||
# [4.0.0](https://github.com/socketio/engine.io-client/compare/v4.0.0-alpha.1...4.0.0) (2020-09-10)
|
||||
|
||||
More details about this release in the blog post: https://socket.io/blog/engine-io-4-release/
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **react-native:** restrict the list of options for the WebSocket object ([2f5c948](https://github.com/socketio/engine.io-client/commit/2f5c948abe8fd1c0fdb010e88f96bd933a3792ea))
|
||||
* use globalThis polyfill instead of self/global ([#634](https://github.com/socketio/engine.io-client/issues/634)) ([3f3a6f9](https://github.com/socketio/engine.io-client/commit/3f3a6f991404ef601252193382d2d2029cff6c45))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* strip debug from the browser bundle ([f7ba966](https://github.com/socketio/engine.io-client/commit/f7ba966e53f4609f755880be8fa504f7252b0817))
|
||||
|
||||
#### Links
|
||||
|
||||
- Diff: [v4.0.0-alpha.1...4.0.0](https://github.com/socketio/engine.io-client/compare/v4.0.0-alpha.1...4.0.0)
|
||||
- Full diff: [3.4.0...4.0.0](https://github.com/socketio/engine.io-client/compare/3.4.0...4.0.0)
|
||||
- Server release: [4.0.0](https://github.com/socketio/engine.io/releases/tag/4.0.0)
|
||||
- ws version: [~7.2.1](https://github.com/websockets/ws/releases/tag/7.2.1)
|
||||
|
||||
|
||||
## [3.4.1](https://github.com/socketio/engine.io-client/compare/3.4.0...3.4.1) (2020-04-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use globalThis polyfill instead of self/global ([357f01d](https://github.com/socketio/engine.io-client/commit/357f01d90448d8565b650377bc7cabb351d991bd))
|
||||
|
||||
#### Links
|
||||
|
||||
- Diff: [3.4.0...3.4.1](https://github.com/socketio/engine.io-client/compare/3.4.0...3.4.1)
|
||||
- Server release: [3.4.1](https://github.com/socketio/engine.io/releases/tag/3.4.1)
|
||||
- ws version: [~6.1.0](https://github.com/websockets/ws/releases/tag/6.1.0)
|
||||
|
||||
|
||||
# [4.0.0-alpha.1](https://github.com/socketio/engine.io-client/compare/v4.0.0-alpha.0...v4.0.0-alpha.1) (2020-02-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* properly assign options when creating the transport ([7c7f1a9](https://github.com/socketio/engine.io-client/commit/7c7f1a9fe24856e3a155db1dc67d12d1586ffa37))
|
||||
|
||||
#### Links
|
||||
|
||||
- Diff: [v4.0.0-alpha.0...v4.0.0-alpha.1](https://github.com/socketio/engine.io-client/compare/v4.0.0-alpha.0...v4.0.0-alpha.1)
|
||||
- Server release: [v4.0.0-alpha.1](https://github.com/socketio/engine.io/releases/tag/v4.0.0-alpha.1)
|
||||
- ws version: [~7.2.1](https://github.com/websockets/ws/releases/tag/7.2.1)
|
||||
|
||||
|
||||
# [4.0.0-alpha.0](https://github.com/socketio/engine.io-client/compare/3.4.0...v4.0.0-alpha.0) (2020-02-12)
|
||||
|
||||
|
||||
### chore
|
||||
|
||||
* migrate to webpack 4 ([11dc4f3](https://github.com/socketio/engine.io-client/commit/11dc4f3a56d440f24b8a091485fef038d592bd6e))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* reverse the ping-pong mechanism ([81d7171](https://github.com/socketio/engine.io-client/commit/81d7171c6bb4053c802e3cc4b29a0e42dcf9c065))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* v3.x clients will not be able to connect anymore (they
|
||||
will send a ping packet and timeout while waiting for a pong packet).
|
||||
|
||||
* the output bundle will now be found in the dist/ folder.
|
||||
|
||||
|
||||
#### Links
|
||||
|
||||
- Diff: [3.4.0...v4.0.0-alpha.0](https://github.com/socketio/engine.io-client/compare/3.4.0...v4.0.0-alpha.0)
|
||||
- Server release: [v4.0.0-alpha.0](https://github.com/socketio/engine.io/releases/tag/v4.0.0-alpha.0)
|
||||
- ws version: [~7.2.1](https://github.com/websockets/ws/releases/tag/7.2.1)
|
||||
22
packages/engine.io-client/LICENSE
Normal file
22
packages/engine.io-client/LICENSE
Normal file
@@ -0,0 +1,22 @@
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2014-2015 Automattic <dev@cloudup.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
331
packages/engine.io-client/README.md
Normal file
331
packages/engine.io-client/README.md
Normal file
@@ -0,0 +1,331 @@
|
||||
|
||||
# Engine.IO client
|
||||
|
||||
[](https://github.com/socketio/engine.io-client/actions)
|
||||
[](http://badge.fury.io/js/engine.io-client)
|
||||
|
||||
This is the client for [Engine.IO](http://github.com/socketio/engine.io),
|
||||
the implementation of transport-based cross-browser/cross-device
|
||||
bi-directional communication layer for [Socket.IO](http://github.com/socketio/socket.io).
|
||||
|
||||
## How to use
|
||||
|
||||
### Standalone
|
||||
|
||||
You can find an `engine.io.js` file in this repository, which is a
|
||||
standalone build you can use as follows:
|
||||
|
||||
```html
|
||||
<script src="/path/to/engine.io.js"></script>
|
||||
<script>
|
||||
// eio = Socket
|
||||
const socket = eio('ws://localhost');
|
||||
socket.on('open', () => {
|
||||
socket.on('message', (data) => {});
|
||||
socket.on('close', () => {});
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
### With browserify
|
||||
|
||||
Engine.IO is a commonjs module, which means you can include it by using
|
||||
`require` on the browser and package using [browserify](http://browserify.org/):
|
||||
|
||||
1. install the client package
|
||||
|
||||
```bash
|
||||
$ npm install engine.io-client
|
||||
```
|
||||
|
||||
1. write your app code
|
||||
|
||||
```js
|
||||
const { Socket } = require('engine.io-client');
|
||||
const socket = new Socket('ws://localhost');
|
||||
socket.on('open', () => {
|
||||
socket.on('message', (data) => {});
|
||||
socket.on('close', () => {});
|
||||
});
|
||||
```
|
||||
|
||||
1. build your app bundle
|
||||
|
||||
```bash
|
||||
$ browserify app.js > bundle.js
|
||||
```
|
||||
|
||||
1. include on your page
|
||||
|
||||
```html
|
||||
<script src="/path/to/bundle.js"></script>
|
||||
```
|
||||
|
||||
### Sending and receiving binary
|
||||
|
||||
```html
|
||||
<script src="/path/to/engine.io.js"></script>
|
||||
<script>
|
||||
const socket = eio('ws://localhost/');
|
||||
socket.binaryType = 'blob';
|
||||
socket.on('open', () => {
|
||||
socket.send(new Int8Array(5));
|
||||
socket.on('message', (blob) => {});
|
||||
socket.on('close', () => {});
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
### Node.JS
|
||||
|
||||
Add `engine.io-client` to your `package.json` and then:
|
||||
|
||||
```js
|
||||
const { Socket } = require('engine.io-client');
|
||||
const socket = new Socket('ws://localhost');
|
||||
socket.on('open', () => {
|
||||
socket.on('message', (data) => {});
|
||||
socket.on('close', () => {});
|
||||
});
|
||||
```
|
||||
|
||||
### Node.js with certificates
|
||||
```js
|
||||
const opts = {
|
||||
key: fs.readFileSync('test/fixtures/client.key'),
|
||||
cert: fs.readFileSync('test/fixtures/client.crt'),
|
||||
ca: fs.readFileSync('test/fixtures/ca.crt')
|
||||
};
|
||||
|
||||
const { Socket } = require('engine.io-client');
|
||||
const socket = new Socket('ws://localhost', opts);
|
||||
socket.on('open', () => {
|
||||
socket.on('message', (data) => {});
|
||||
socket.on('close', () => {});
|
||||
});
|
||||
```
|
||||
|
||||
### Node.js with extraHeaders
|
||||
```js
|
||||
const opts = {
|
||||
extraHeaders: {
|
||||
'X-Custom-Header-For-My-Project': 'my-secret-access-token',
|
||||
'Cookie': 'user_session=NI2JlCKF90aE0sJZD9ZzujtdsUqNYSBYxzlTsvdSUe35ZzdtVRGqYFr0kdGxbfc5gUOkR9RGp20GVKza; path=/; expires=Tue, 07-Apr-2015 18:18:08 GMT; secure; HttpOnly'
|
||||
}
|
||||
};
|
||||
|
||||
const { Socket } = require('engine.io-client');
|
||||
const socket = new Socket('ws://localhost', opts);
|
||||
socket.on('open', () => {
|
||||
socket.on('message', (data) => {});
|
||||
socket.on('close', () => {});
|
||||
});
|
||||
```
|
||||
|
||||
In the browser, the [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) object does not support additional headers.
|
||||
In case you want to add some headers as part of some authentication mechanism, you can use the `transportOptions` attribute.
|
||||
Please note that in this case the headers won't be sent in the WebSocket upgrade request.
|
||||
|
||||
```js
|
||||
// WILL NOT WORK in the browser
|
||||
const socket = new Socket('http://localhost', {
|
||||
extraHeaders: {
|
||||
'X-Custom-Header-For-My-Project': 'will not be sent'
|
||||
}
|
||||
});
|
||||
// WILL NOT WORK
|
||||
const socket = new Socket('http://localhost', {
|
||||
transports: ['websocket'], // polling is disabled
|
||||
transportOptions: {
|
||||
polling: {
|
||||
extraHeaders: {
|
||||
'X-Custom-Header-For-My-Project': 'will not be sent'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// WILL WORK
|
||||
const socket = new Socket('http://localhost', {
|
||||
transports: ['polling', 'websocket'],
|
||||
transportOptions: {
|
||||
polling: {
|
||||
extraHeaders: {
|
||||
'X-Custom-Header-For-My-Project': 'will be used'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Lightweight
|
||||
- Runs on browser and node.js seamlessly
|
||||
- Transports are independent of `Engine`
|
||||
- Easy to debug
|
||||
- Easy to unit test
|
||||
- Runs inside HTML5 WebWorker
|
||||
- Can send and receive binary data
|
||||
- Receives as ArrayBuffer or Blob when in browser, and Buffer or ArrayBuffer
|
||||
in Node
|
||||
- When XHR2 or WebSockets are used, binary is emitted directly. Otherwise
|
||||
binary is encoded into base64 strings, and decoded when binary types are
|
||||
supported.
|
||||
- With browsers that don't support ArrayBuffer, an object { base64: true,
|
||||
data: dataAsBase64String } is emitted on the `message` event.
|
||||
|
||||
## API
|
||||
|
||||
### Socket
|
||||
|
||||
The client class. Mixes in [Emitter](http://github.com/component/emitter).
|
||||
Exposed as `eio` in the browser standalone build.
|
||||
|
||||
#### Properties
|
||||
|
||||
- `protocol` _(Number)_: protocol revision number
|
||||
- `binaryType` _(String)_ : can be set to 'arraybuffer' or 'blob' in browsers,
|
||||
and `buffer` or `arraybuffer` in Node. Blob is only used in browser if it's
|
||||
supported.
|
||||
|
||||
#### Events
|
||||
|
||||
- `open`
|
||||
- Fired upon successful connection.
|
||||
- `message`
|
||||
- Fired when data is received from the server.
|
||||
- **Arguments**
|
||||
- `String` | `ArrayBuffer`: utf-8 encoded data or ArrayBuffer containing
|
||||
binary data
|
||||
- `close`
|
||||
- Fired upon disconnection. In compliance with the WebSocket API spec, this event may be
|
||||
fired even if the `open` event does not occur (i.e. due to connection error or `close()`).
|
||||
- `error`
|
||||
- Fired when an error occurs.
|
||||
- `flush`
|
||||
- Fired upon completing a buffer flush
|
||||
- `drain`
|
||||
- Fired after `drain` event of transport if writeBuffer is empty
|
||||
- `upgradeError`
|
||||
- Fired if an error occurs with a transport we're trying to upgrade to.
|
||||
- `upgrade`
|
||||
- Fired upon upgrade success, after the new transport is set
|
||||
- `ping`
|
||||
- Fired upon receiving a ping packet.
|
||||
- `pong`
|
||||
- Fired upon _flushing_ a pong packet (ie: actual packet write out)
|
||||
|
||||
#### Methods
|
||||
|
||||
- **constructor**
|
||||
- Initializes the client
|
||||
- **Parameters**
|
||||
- `String` uri
|
||||
- `Object`: optional, options object
|
||||
- **Options**
|
||||
- `agent` (`http.Agent`): `http.Agent` to use, defaults to `false` (NodeJS only)
|
||||
- `upgrade` (`Boolean`): defaults to true, whether the client should try
|
||||
to upgrade the transport from long-polling to something better.
|
||||
- `forceBase64` (`Boolean`): forces base 64 encoding for polling transport even when XHR2 responseType is available and WebSocket even if the used standard supports binary.
|
||||
- `withCredentials` (`Boolean`): defaults to `false`, whether to include credentials (cookies, authorization headers, TLS client certificates, etc.) with cross-origin XHR polling requests.
|
||||
- `timestampRequests` (`Boolean`): whether to add the timestamp with each
|
||||
transport request. Note: polling requests are always stamped unless this
|
||||
option is explicitly set to `false` (`false`)
|
||||
- `timestampParam` (`String`): timestamp parameter (`t`)
|
||||
- `path` (`String`): path to connect to, default is `/engine.io`
|
||||
- `transports` (`Array`): a list of transports to try (in order).
|
||||
Defaults to `['polling', 'websocket', 'webtransport']`. `Engine`
|
||||
always attempts to connect directly with the first one, provided the
|
||||
feature detection test for it passes.
|
||||
- `transportOptions` (`Object`): hash of options, indexed by transport name, overriding the common options for the given transport
|
||||
- `rememberUpgrade` (`Boolean`): defaults to false.
|
||||
If true and if the previous websocket connection to the server succeeded,
|
||||
the connection attempt will bypass the normal upgrade process and will initially
|
||||
try websocket. A connection attempt following a transport error will use the
|
||||
normal upgrade process. It is recommended you turn this on only when using
|
||||
SSL/TLS connections, or if you know that your network does not block websockets.
|
||||
- `pfx` (`String`|`Buffer`): Certificate, Private key and CA certificates to use for SSL. Can be used in Node.js client environment to manually specify certificate information.
|
||||
- `key` (`String`): Private key to use for SSL. Can be used in Node.js client environment to manually specify certificate information.
|
||||
- `passphrase` (`String`): A string of passphrase for the private key or pfx. Can be used in Node.js client environment to manually specify certificate information.
|
||||
- `cert` (`String`): Public x509 certificate to use. Can be used in Node.js client environment to manually specify certificate information.
|
||||
- `ca` (`String`|`Array`): An authority certificate or array of authority certificates to check the remote host against.. Can be used in Node.js client environment to manually specify certificate information.
|
||||
- `ciphers` (`String`): A string describing the ciphers to use or exclude. Consult the [cipher format list](http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT) for details on the format. Can be used in Node.js client environment to manually specify certificate information.
|
||||
- `rejectUnauthorized` (`Boolean`): If true, the server certificate is verified against the list of supplied CAs. An 'error' event is emitted if verification fails. Verification happens at the connection level, before the HTTP request is sent. Can be used in Node.js client environment to manually specify certificate information.
|
||||
- `perMessageDeflate` (`Object|Boolean`): parameters of the WebSocket permessage-deflate extension
|
||||
(see [ws module](https://github.com/einaros/ws) api docs). Set to `false` to disable. (`true`)
|
||||
- `threshold` (`Number`): data is compressed only if the byte size is above this value. This option is ignored on the browser. (`1024`)
|
||||
- `extraHeaders` (`Object`): Headers that will be passed for each request to the server (via xhr-polling and via websockets). These values then can be used during handshake or for special proxies. Can only be used in Node.js client environment.
|
||||
- `localAddress` (`String`): the local IP address to connect to
|
||||
- `autoUnref` (`Boolean`): whether the transport should be `unref`'d upon creation. This calls `unref` on the underlying timers and sockets so that the program is allowed to exit if they are the only timers/sockets in the event system (Node.js only)
|
||||
- `useNativeTimers` (`Boolean`): Whether to always use the native timeouts. This allows the client to reconnect when the native timeout functions are overridden, such as when mock clocks are installed with [`@sinonjs/fake-timers`](https://github.com/sinonjs/fake-timers).
|
||||
- **Polling-only options**
|
||||
- `requestTimeout` (`Number`): Timeout for xhr-polling requests in milliseconds (`0`)
|
||||
- **Websocket-only options**
|
||||
- `protocols` (`Array`): a list of subprotocols (see [MDN reference](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#Subprotocols))
|
||||
- `closeOnBeforeunload` (`Boolean`): whether to silently close the connection when the [`beforeunload`](https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event) event is emitted in the browser (defaults to `false`)
|
||||
- `send`
|
||||
- Sends a message to the server
|
||||
- **Parameters**
|
||||
- `String` | `ArrayBuffer` | `ArrayBufferView` | `Blob`: data to send
|
||||
- `Object`: optional, options object
|
||||
- `Function`: optional, callback upon `drain`
|
||||
- **Options**
|
||||
- `compress` (`Boolean`): whether to compress sending data. This option is ignored and forced to be `true` on the browser. (`true`)
|
||||
- `close`
|
||||
- Disconnects the client.
|
||||
|
||||
### Transport
|
||||
|
||||
The transport class. Private. _Inherits from EventEmitter_.
|
||||
|
||||
#### Events
|
||||
|
||||
- `poll`: emitted by polling transports upon starting a new request
|
||||
- `pollComplete`: emitted by polling transports upon completing a request
|
||||
- `drain`: emitted by polling transports upon a buffer drain
|
||||
|
||||
## Tests
|
||||
|
||||
`engine.io-client` is used to test
|
||||
[engine](http://github.com/socketio/engine.io). Running the `engine.io`
|
||||
test suite ensures the client works and vice-versa.
|
||||
|
||||
Browser tests are run using [zuul](https://github.com/defunctzombie/zuul). You can
|
||||
run the tests locally using the following command.
|
||||
|
||||
```
|
||||
./node_modules/.bin/zuul --local 8080 -- test/index.js
|
||||
```
|
||||
|
||||
Additionally, `engine.io-client` has a standalone test suite you can run
|
||||
with `make test` which will run node.js and browser tests. You must have zuul setup with
|
||||
a saucelabs account.
|
||||
|
||||
## Support
|
||||
|
||||
The support channels for `engine.io-client` are the same as `socket.io`:
|
||||
- irc.freenode.net **#socket.io**
|
||||
- [Google Groups](http://groups.google.com/group/socket_io)
|
||||
- [Website](http://socket.io)
|
||||
|
||||
## Development
|
||||
|
||||
To contribute patches, run tests or benchmarks, make sure to clone the
|
||||
repository:
|
||||
|
||||
```bash
|
||||
git clone git://github.com/socketio/engine.io-client.git
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```bash
|
||||
cd engine.io-client
|
||||
npm install
|
||||
```
|
||||
|
||||
See the `Tests` section above for how to run tests before submitting any patches.
|
||||
|
||||
## License
|
||||
|
||||
MIT - Copyright (c) 2014 Automattic, Inc.
|
||||
22
packages/engine.io-client/SECURITY.md
Normal file
22
packages/engine.io-client/SECURITY.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | `socket.io-client` version | Supported |
|
||||
|---------|----------------------------|--------------------|
|
||||
| 6.x | 4.x | :white_check_mark: |
|
||||
| 4.x | 3.x | :white_check_mark: |
|
||||
| 3.5.x | 2.4.x | :white_check_mark: |
|
||||
| < 3.5.0 | < 2.4.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
To report a security vulnerability in this package, please send an email to [@darrachequesne](https://github.com/darrachequesne) (see address in profile) describing the vulnerability and how to reproduce it.
|
||||
|
||||
We will get back to you as soon as possible and publish a fix if necessary.
|
||||
|
||||
:warning: IMPORTANT :warning: please do not create an issue in this repository, as attackers might take advantage of it. Thank you in advance for your responsible disclosure.
|
||||
|
||||
## History
|
||||
|
||||
- Mar 2016: [Insecure Defaults Allow MITM Over TLS in engine.io-client](https://github.com/advisories/GHSA-4r4m-hjwj-43p8) (CVE-2016-10536)
|
||||
7
packages/engine.io-client/dist/engine.io.esm.min.js
vendored
Normal file
7
packages/engine.io-client/dist/engine.io.esm.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
packages/engine.io-client/dist/engine.io.esm.min.js.map
vendored
Normal file
1
packages/engine.io-client/dist/engine.io.esm.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2929
packages/engine.io-client/dist/engine.io.js
vendored
Normal file
2929
packages/engine.io-client/dist/engine.io.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
packages/engine.io-client/dist/engine.io.js.map
vendored
Normal file
1
packages/engine.io-client/dist/engine.io.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
7
packages/engine.io-client/dist/engine.io.min.js
vendored
Normal file
7
packages/engine.io-client/dist/engine.io.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
packages/engine.io-client/dist/engine.io.min.js.map
vendored
Normal file
1
packages/engine.io-client/dist/engine.io.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
3
packages/engine.io-client/lib/browser-entrypoint.ts
Normal file
3
packages/engine.io-client/lib/browser-entrypoint.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { Socket } from "./socket.js";
|
||||
|
||||
export default (uri, opts) => new Socket(uri, opts);
|
||||
12
packages/engine.io-client/lib/contrib/has-cors.ts
Normal file
12
packages/engine.io-client/lib/contrib/has-cors.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
// imported from https://github.com/component/has-cors
|
||||
let value = false;
|
||||
|
||||
try {
|
||||
value = typeof XMLHttpRequest !== 'undefined' &&
|
||||
'withCredentials' in new XMLHttpRequest();
|
||||
} catch (err) {
|
||||
// if XMLHttp support is disabled in IE then it will throw
|
||||
// when trying to create
|
||||
}
|
||||
|
||||
export const hasCORS = value;
|
||||
38
packages/engine.io-client/lib/contrib/parseqs.ts
Normal file
38
packages/engine.io-client/lib/contrib/parseqs.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
// imported from https://github.com/galkn/querystring
|
||||
/**
|
||||
* Compiles a querystring
|
||||
* Returns string representation of the object
|
||||
*
|
||||
* @param {Object}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
export function encode (obj) {
|
||||
let str = '';
|
||||
|
||||
for (let i in obj) {
|
||||
if (obj.hasOwnProperty(i)) {
|
||||
if (str.length) str += '&';
|
||||
str += encodeURIComponent(i) + '=' + encodeURIComponent(obj[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a simple querystring into an object
|
||||
*
|
||||
* @param {String} qs
|
||||
* @api private
|
||||
*/
|
||||
|
||||
export function decode (qs) {
|
||||
let qry = {};
|
||||
let pairs = qs.split('&');
|
||||
for (let i = 0, l = pairs.length; i < l; i++) {
|
||||
let pair = pairs[i].split('=');
|
||||
qry[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
|
||||
}
|
||||
return qry;
|
||||
}
|
||||
84
packages/engine.io-client/lib/contrib/parseuri.ts
Normal file
84
packages/engine.io-client/lib/contrib/parseuri.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
// imported from https://github.com/galkn/parseuri
|
||||
/**
|
||||
* Parses a URI
|
||||
*
|
||||
* Note: we could also have used the built-in URL object, but it isn't supported on all platforms.
|
||||
*
|
||||
* See:
|
||||
* - https://developer.mozilla.org/en-US/docs/Web/API/URL
|
||||
* - https://caniuse.com/url
|
||||
* - https://www.rfc-editor.org/rfc/rfc3986#appendix-B
|
||||
*
|
||||
* History of the parse() method:
|
||||
* - first commit: https://github.com/socketio/socket.io-client/commit/4ee1d5d94b3906a9c052b459f1a818b15f38f91c
|
||||
* - export into its own module: https://github.com/socketio/engine.io-client/commit/de2c561e4564efeb78f1bdb1ba39ef81b2822cb3
|
||||
* - reimport: https://github.com/socketio/engine.io-client/commit/df32277c3f6d622eec5ed09f493cae3f3391d242
|
||||
*
|
||||
* @author Steven Levithan <stevenlevithan.com> (MIT license)
|
||||
* @api private
|
||||
*/
|
||||
const re = /^(?:(?![^:@\/?#]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@\/?#]*)(?::([^:@\/?#]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
|
||||
|
||||
const parts = [
|
||||
'source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor'
|
||||
];
|
||||
|
||||
export function parse(str: string) {
|
||||
if (str.length > 8000) {
|
||||
throw "URI too long";
|
||||
}
|
||||
|
||||
const src = str,
|
||||
b = str.indexOf('['),
|
||||
e = str.indexOf(']');
|
||||
|
||||
if (b != -1 && e != -1) {
|
||||
str = str.substring(0, b) + str.substring(b, e).replace(/:/g, ';') + str.substring(e, str.length);
|
||||
}
|
||||
|
||||
let m = re.exec(str || ''),
|
||||
uri = {} as any,
|
||||
i = 14;
|
||||
|
||||
while (i--) {
|
||||
uri[parts[i]] = m[i] || '';
|
||||
}
|
||||
|
||||
if (b != -1 && e != -1) {
|
||||
uri.source = src;
|
||||
uri.host = uri.host.substring(1, uri.host.length - 1).replace(/;/g, ':');
|
||||
uri.authority = uri.authority.replace('[', '').replace(']', '').replace(/;/g, ':');
|
||||
uri.ipv6uri = true;
|
||||
}
|
||||
|
||||
uri.pathNames = pathNames(uri, uri['path']);
|
||||
uri.queryKey = queryKey(uri, uri['query']);
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
function pathNames(obj, path) {
|
||||
const regx = /\/{2,9}/g,
|
||||
names = path.replace(regx, "/").split("/");
|
||||
|
||||
if (path.slice(0, 1) == '/' || path.length === 0) {
|
||||
names.splice(0, 1);
|
||||
}
|
||||
if (path.slice(-1) == '/') {
|
||||
names.splice(names.length - 1, 1);
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
function queryKey(uri, query) {
|
||||
const data = {};
|
||||
|
||||
query.replace(/(?:^|&)([^&=]*)=?([^&]*)/g, function ($0, $1, $2) {
|
||||
if ($1) {
|
||||
data[$1] = $2;
|
||||
}
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
113
packages/engine.io-client/lib/globals.node.ts
Normal file
113
packages/engine.io-client/lib/globals.node.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
export const nextTick = process.nextTick;
|
||||
export const globalThisShim = global;
|
||||
export const defaultBinaryType = "nodebuffer";
|
||||
|
||||
export function createCookieJar() {
|
||||
return new CookieJar();
|
||||
}
|
||||
|
||||
interface Cookie {
|
||||
name: string;
|
||||
value: string;
|
||||
expires?: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
|
||||
*/
|
||||
export function parse(setCookieString: string): Cookie {
|
||||
const parts = setCookieString.split("; ");
|
||||
const i = parts[0].indexOf("=");
|
||||
|
||||
if (i === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const name = parts[0].substring(0, i).trim();
|
||||
|
||||
if (!name.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let value = parts[0].substring(i + 1).trim();
|
||||
|
||||
if (value.charCodeAt(0) === 0x22) {
|
||||
// remove double quotes
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
|
||||
const cookie: Cookie = {
|
||||
name,
|
||||
value,
|
||||
};
|
||||
|
||||
for (let j = 1; j < parts.length; j++) {
|
||||
const subParts = parts[j].split("=");
|
||||
if (subParts.length !== 2) {
|
||||
continue;
|
||||
}
|
||||
const key = subParts[0].trim();
|
||||
const value = subParts[1].trim();
|
||||
switch (key) {
|
||||
case "Expires":
|
||||
cookie.expires = new Date(value);
|
||||
break;
|
||||
case "Max-Age":
|
||||
const expiration = new Date();
|
||||
expiration.setUTCSeconds(
|
||||
expiration.getUTCSeconds() + parseInt(value, 10)
|
||||
);
|
||||
cookie.expires = expiration;
|
||||
break;
|
||||
default:
|
||||
// ignore other keys
|
||||
}
|
||||
}
|
||||
|
||||
return cookie;
|
||||
}
|
||||
|
||||
export class CookieJar {
|
||||
private _cookies = new Map<string, Cookie>();
|
||||
|
||||
public parseCookies(values: string[]) {
|
||||
if (!values) {
|
||||
return;
|
||||
}
|
||||
values.forEach((value) => {
|
||||
const parsed = parse(value);
|
||||
if (parsed) {
|
||||
this._cookies.set(parsed.name, parsed);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get cookies() {
|
||||
const now = Date.now();
|
||||
this._cookies.forEach((cookie, name) => {
|
||||
if (cookie.expires?.getTime() < now) {
|
||||
this._cookies.delete(name);
|
||||
}
|
||||
});
|
||||
return this._cookies.entries();
|
||||
}
|
||||
|
||||
public addCookies(xhr: any) {
|
||||
const cookies = [];
|
||||
|
||||
for (const [name, cookie] of this.cookies) {
|
||||
cookies.push(`${name}=${cookie.value}`);
|
||||
}
|
||||
|
||||
if (cookies.length) {
|
||||
xhr.setDisableHeaderCheck(true);
|
||||
xhr.setRequestHeader("cookie", cookies.join("; "));
|
||||
}
|
||||
}
|
||||
|
||||
public appendCookies(headers: Headers) {
|
||||
for (const [name, cookie] of this.cookies) {
|
||||
headers.append("cookie", `${name}=${cookie.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
packages/engine.io-client/lib/globals.ts
Normal file
23
packages/engine.io-client/lib/globals.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export const nextTick = (() => {
|
||||
const isPromiseAvailable =
|
||||
typeof Promise === "function" && typeof Promise.resolve === "function";
|
||||
if (isPromiseAvailable) {
|
||||
return (cb) => Promise.resolve().then(cb);
|
||||
} else {
|
||||
return (cb, setTimeoutFn) => setTimeoutFn(cb, 0);
|
||||
}
|
||||
})();
|
||||
|
||||
export const globalThisShim = (() => {
|
||||
if (typeof self !== "undefined") {
|
||||
return self;
|
||||
} else if (typeof window !== "undefined") {
|
||||
return window;
|
||||
} else {
|
||||
return Function("return this")();
|
||||
}
|
||||
})();
|
||||
|
||||
export const defaultBinaryType = "arraybuffer";
|
||||
|
||||
export function createCookieJar() {}
|
||||
21
packages/engine.io-client/lib/index.ts
Normal file
21
packages/engine.io-client/lib/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Socket } from "./socket.js";
|
||||
|
||||
export { Socket };
|
||||
export {
|
||||
SocketOptions,
|
||||
SocketWithoutUpgrade,
|
||||
SocketWithUpgrade,
|
||||
} from "./socket.js";
|
||||
export const protocol = Socket.protocol;
|
||||
export { Transport, TransportError } from "./transport.js";
|
||||
export { transports } from "./transports/index.js";
|
||||
export { installTimerFunctions } from "./util.js";
|
||||
export { parse } from "./contrib/parseuri.js";
|
||||
export { nextTick } from "./globals.node.js";
|
||||
|
||||
export { Fetch } from "./transports/polling-fetch.js";
|
||||
export { XHR as NodeXHR } from "./transports/polling-xhr.node.js";
|
||||
export { XHR } from "./transports/polling-xhr.js";
|
||||
export { WS as NodeWebSocket } from "./transports/websocket.node.js";
|
||||
export { WS as WebSocket } from "./transports/websocket.js";
|
||||
export { WT as WebTransport } from "./transports/webtransport.js";
|
||||
1117
packages/engine.io-client/lib/socket.ts
Normal file
1117
packages/engine.io-client/lib/socket.ts
Normal file
File diff suppressed because it is too large
Load Diff
212
packages/engine.io-client/lib/transport.ts
Normal file
212
packages/engine.io-client/lib/transport.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
import { decodePacket } from "engine.io-parser";
|
||||
import type { Packet, RawData } from "engine.io-parser";
|
||||
import { Emitter } from "@socket.io/component-emitter";
|
||||
import { installTimerFunctions } from "./util.js";
|
||||
import type { Socket, SocketOptions } from "./socket.js";
|
||||
import { encode } from "./contrib/parseqs.js";
|
||||
import debugModule from "debug"; // debug()
|
||||
|
||||
const debug = debugModule("engine.io-client:transport"); // debug()
|
||||
|
||||
export class TransportError extends Error {
|
||||
public readonly type = "TransportError";
|
||||
|
||||
constructor(
|
||||
reason: string,
|
||||
readonly description: any,
|
||||
readonly context: any
|
||||
) {
|
||||
super(reason);
|
||||
}
|
||||
}
|
||||
|
||||
export interface CloseDetails {
|
||||
description: string;
|
||||
context?: unknown; // context should be typed as CloseEvent | XMLHttpRequest, but these types are not available on non-browser platforms
|
||||
}
|
||||
|
||||
interface TransportReservedEvents {
|
||||
open: () => void;
|
||||
error: (err: TransportError) => void;
|
||||
packet: (packet: Packet) => void;
|
||||
close: (details?: CloseDetails) => void;
|
||||
poll: () => void;
|
||||
pollComplete: () => void;
|
||||
drain: () => void;
|
||||
}
|
||||
|
||||
type TransportState = "opening" | "open" | "closed" | "pausing" | "paused";
|
||||
|
||||
export abstract class Transport extends Emitter<
|
||||
Record<never, never>,
|
||||
Record<never, never>,
|
||||
TransportReservedEvents
|
||||
> {
|
||||
public query: Record<string, string>;
|
||||
public writable: boolean = false;
|
||||
|
||||
protected opts: SocketOptions;
|
||||
protected supportsBinary: boolean;
|
||||
protected readyState: TransportState;
|
||||
protected socket: Socket;
|
||||
protected setTimeoutFn: typeof setTimeout;
|
||||
|
||||
/**
|
||||
* Transport abstract constructor.
|
||||
*
|
||||
* @param {Object} opts - options
|
||||
* @protected
|
||||
*/
|
||||
constructor(opts) {
|
||||
super();
|
||||
installTimerFunctions(this, opts);
|
||||
|
||||
this.opts = opts;
|
||||
this.query = opts.query;
|
||||
this.socket = opts.socket;
|
||||
this.supportsBinary = !opts.forceBase64;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an error.
|
||||
*
|
||||
* @param {String} reason
|
||||
* @param description
|
||||
* @param context - the error context
|
||||
* @return {Transport} for chaining
|
||||
* @protected
|
||||
*/
|
||||
protected onError(reason: string, description: any, context?: any) {
|
||||
super.emitReserved(
|
||||
"error",
|
||||
new TransportError(reason, description, context)
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the transport.
|
||||
*/
|
||||
public open() {
|
||||
this.readyState = "opening";
|
||||
this.doOpen();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the transport.
|
||||
*/
|
||||
public close() {
|
||||
if (this.readyState === "opening" || this.readyState === "open") {
|
||||
this.doClose();
|
||||
this.onClose();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends multiple packets.
|
||||
*
|
||||
* @param {Array} packets
|
||||
*/
|
||||
public send(packets) {
|
||||
if (this.readyState === "open") {
|
||||
this.write(packets);
|
||||
} else {
|
||||
// this might happen if the transport was silently closed in the beforeunload event handler
|
||||
debug("transport is not open, discarding packets");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called upon open
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
protected onOpen() {
|
||||
this.readyState = "open";
|
||||
this.writable = true;
|
||||
super.emitReserved("open");
|
||||
}
|
||||
|
||||
/**
|
||||
* Called with data.
|
||||
*
|
||||
* @param {String} data
|
||||
* @protected
|
||||
*/
|
||||
protected onData(data: RawData) {
|
||||
const packet = decodePacket(data, this.socket.binaryType);
|
||||
this.onPacket(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called with a decoded packet.
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
protected onPacket(packet: Packet) {
|
||||
super.emitReserved("packet", packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called upon close.
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
protected onClose(details?: CloseDetails) {
|
||||
this.readyState = "closed";
|
||||
super.emitReserved("close", details);
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the transport
|
||||
*/
|
||||
public abstract get name(): string;
|
||||
|
||||
/**
|
||||
* Pauses the transport, in order not to lose packets during an upgrade.
|
||||
*
|
||||
* @param onPause
|
||||
*/
|
||||
public pause(onPause: () => void) {}
|
||||
|
||||
protected createUri(schema: string, query: Record<string, unknown> = {}) {
|
||||
return (
|
||||
schema +
|
||||
"://" +
|
||||
this._hostname() +
|
||||
this._port() +
|
||||
this.opts.path +
|
||||
this._query(query)
|
||||
);
|
||||
}
|
||||
|
||||
private _hostname() {
|
||||
const hostname = this.opts.hostname;
|
||||
return hostname.indexOf(":") === -1 ? hostname : "[" + hostname + "]";
|
||||
}
|
||||
|
||||
private _port() {
|
||||
if (
|
||||
this.opts.port &&
|
||||
((this.opts.secure && Number(this.opts.port !== 443)) ||
|
||||
(!this.opts.secure && Number(this.opts.port) !== 80))
|
||||
) {
|
||||
return ":" + this.opts.port;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private _query(query: Record<string, unknown>) {
|
||||
const encodedQuery = encode(query);
|
||||
return encodedQuery.length ? "?" + encodedQuery : "";
|
||||
}
|
||||
|
||||
protected abstract doOpen();
|
||||
protected abstract doClose();
|
||||
protected abstract write(packets: Packet[]);
|
||||
}
|
||||
9
packages/engine.io-client/lib/transports/index.ts
Executable file
9
packages/engine.io-client/lib/transports/index.ts
Executable file
@@ -0,0 +1,9 @@
|
||||
import { XHR } from "./polling-xhr.node.js";
|
||||
import { WS } from "./websocket.node.js";
|
||||
import { WT } from "./webtransport.js";
|
||||
|
||||
export const transports = {
|
||||
websocket: WS,
|
||||
webtransport: WT,
|
||||
polling: XHR,
|
||||
};
|
||||
64
packages/engine.io-client/lib/transports/polling-fetch.ts
Normal file
64
packages/engine.io-client/lib/transports/polling-fetch.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { Polling } from "./polling.js";
|
||||
import { CookieJar, createCookieJar } from "../globals.node.js";
|
||||
|
||||
/**
|
||||
* HTTP long-polling based on the built-in `fetch()` method.
|
||||
*
|
||||
* Usage: browser, Node.js (since v18), Deno, Bun
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/fetch
|
||||
* @see https://caniuse.com/fetch
|
||||
* @see https://nodejs.org/api/globals.html#fetch
|
||||
*/
|
||||
export class Fetch extends Polling {
|
||||
override doPoll() {
|
||||
this._fetch()
|
||||
.then((res) => {
|
||||
if (!res.ok) {
|
||||
return this.onError("fetch read error", res.status, res);
|
||||
}
|
||||
|
||||
res.text().then((data) => this.onData(data));
|
||||
})
|
||||
.catch((err) => {
|
||||
this.onError("fetch read error", err);
|
||||
});
|
||||
}
|
||||
|
||||
override doWrite(data: string, callback: () => void) {
|
||||
this._fetch(data)
|
||||
.then((res) => {
|
||||
if (!res.ok) {
|
||||
return this.onError("fetch write error", res.status, res);
|
||||
}
|
||||
|
||||
callback();
|
||||
})
|
||||
.catch((err) => {
|
||||
this.onError("fetch write error", err);
|
||||
});
|
||||
}
|
||||
|
||||
private _fetch(data?: string) {
|
||||
const isPost = data !== undefined;
|
||||
const headers = new Headers(this.opts.extraHeaders);
|
||||
|
||||
if (isPost) {
|
||||
headers.set("content-type", "text/plain;charset=UTF-8");
|
||||
}
|
||||
|
||||
this.socket._cookieJar?.appendCookies(headers);
|
||||
|
||||
return fetch(this.uri(), {
|
||||
method: isPost ? "POST" : "GET",
|
||||
body: isPost ? data : null,
|
||||
headers,
|
||||
credentials: this.opts.withCredentials ? "include" : "omit",
|
||||
}).then((res) => {
|
||||
// @ts-ignore getSetCookie() was added in Node.js v19.7.0
|
||||
this.socket._cookieJar?.parseCookies(res.headers.getSetCookie());
|
||||
|
||||
return res;
|
||||
});
|
||||
}
|
||||
}
|
||||
26
packages/engine.io-client/lib/transports/polling-xhr.node.ts
Normal file
26
packages/engine.io-client/lib/transports/polling-xhr.node.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import * as XMLHttpRequestModule from "xmlhttprequest-ssl";
|
||||
import { BaseXHR, Request, RequestOptions } from "./polling-xhr.js";
|
||||
|
||||
const XMLHttpRequest = XMLHttpRequestModule.default || XMLHttpRequestModule;
|
||||
|
||||
/**
|
||||
* HTTP long-polling based on the `XMLHttpRequest` object provided by the `xmlhttprequest-ssl` package.
|
||||
*
|
||||
* Usage: Node.js, Deno (compat), Bun (compat)
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest
|
||||
*/
|
||||
export class XHR extends BaseXHR {
|
||||
request(opts: Record<string, any> = {}) {
|
||||
Object.assign(
|
||||
opts,
|
||||
{ xd: this.xd, cookieJar: this.socket?._cookieJar },
|
||||
this.opts
|
||||
);
|
||||
return new Request(
|
||||
(opts) => new XMLHttpRequest(opts),
|
||||
this.uri(),
|
||||
opts as RequestOptions
|
||||
);
|
||||
}
|
||||
}
|
||||
361
packages/engine.io-client/lib/transports/polling-xhr.ts
Normal file
361
packages/engine.io-client/lib/transports/polling-xhr.ts
Normal file
@@ -0,0 +1,361 @@
|
||||
import { Polling } from "./polling.js";
|
||||
import { Emitter } from "@socket.io/component-emitter";
|
||||
import type { SocketOptions } from "../socket.js";
|
||||
import { installTimerFunctions, pick } from "../util.js";
|
||||
import { globalThisShim as globalThis } from "../globals.node.js";
|
||||
import type { CookieJar } from "../globals.node.js";
|
||||
import type { RawData } from "engine.io-parser";
|
||||
import { hasCORS } from "../contrib/has-cors.js";
|
||||
import debugModule from "debug"; // debug()
|
||||
|
||||
const debug = debugModule("engine.io-client:polling"); // debug()
|
||||
|
||||
function empty() {}
|
||||
|
||||
export abstract class BaseXHR extends Polling {
|
||||
protected readonly xd: boolean;
|
||||
|
||||
private pollXhr: any;
|
||||
|
||||
/**
|
||||
* XHR Polling constructor.
|
||||
*
|
||||
* @param {Object} opts
|
||||
* @package
|
||||
*/
|
||||
constructor(opts) {
|
||||
super(opts);
|
||||
|
||||
if (typeof location !== "undefined") {
|
||||
const isSSL = "https:" === location.protocol;
|
||||
let port = location.port;
|
||||
|
||||
// some user agents have empty `location.port`
|
||||
if (!port) {
|
||||
port = isSSL ? "443" : "80";
|
||||
}
|
||||
|
||||
this.xd =
|
||||
(typeof location !== "undefined" &&
|
||||
opts.hostname !== location.hostname) ||
|
||||
port !== opts.port;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a request.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
abstract request(opts?: Record<string, any>);
|
||||
|
||||
/**
|
||||
* Sends data.
|
||||
*
|
||||
* @param {String} data to send.
|
||||
* @param {Function} called upon flush.
|
||||
* @private
|
||||
*/
|
||||
override doWrite(data, fn) {
|
||||
const req = this.request({
|
||||
method: "POST",
|
||||
data: data,
|
||||
});
|
||||
req.on("success", fn);
|
||||
req.on("error", (xhrStatus, context) => {
|
||||
this.onError("xhr post error", xhrStatus, context);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a poll cycle.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
override doPoll() {
|
||||
debug("xhr poll");
|
||||
const req = this.request();
|
||||
req.on("data", this.onData.bind(this));
|
||||
req.on("error", (xhrStatus, context) => {
|
||||
this.onError("xhr poll error", xhrStatus, context);
|
||||
});
|
||||
this.pollXhr = req;
|
||||
}
|
||||
}
|
||||
|
||||
interface RequestReservedEvents {
|
||||
success: () => void;
|
||||
data: (data: RawData) => void;
|
||||
error: (err: number | Error, context: unknown) => void; // context should be typed as XMLHttpRequest, but this type is not available on non-browser platforms
|
||||
}
|
||||
|
||||
export type RequestOptions = SocketOptions & {
|
||||
method?: string;
|
||||
data?: RawData;
|
||||
xd: boolean;
|
||||
cookieJar: CookieJar;
|
||||
};
|
||||
|
||||
export class Request extends Emitter<
|
||||
Record<never, never>,
|
||||
Record<never, never>,
|
||||
RequestReservedEvents
|
||||
> {
|
||||
private readonly _opts: RequestOptions;
|
||||
private readonly _method: string;
|
||||
private readonly _uri: string;
|
||||
private readonly _data: string | ArrayBuffer;
|
||||
|
||||
private _xhr: any;
|
||||
private setTimeoutFn: typeof setTimeout;
|
||||
private _index: number;
|
||||
|
||||
static requestsCount = 0;
|
||||
static requests = {};
|
||||
|
||||
/**
|
||||
* Request constructor
|
||||
*
|
||||
* @param {Object} options
|
||||
* @package
|
||||
*/
|
||||
constructor(
|
||||
private readonly createRequest: (opts: RequestOptions) => XMLHttpRequest,
|
||||
uri: string,
|
||||
opts: RequestOptions
|
||||
) {
|
||||
super();
|
||||
installTimerFunctions(this, opts);
|
||||
this._opts = opts;
|
||||
|
||||
this._method = opts.method || "GET";
|
||||
this._uri = uri;
|
||||
this._data = undefined !== opts.data ? opts.data : null;
|
||||
|
||||
this._create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the XHR object and sends the request.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _create() {
|
||||
const opts = pick(
|
||||
this._opts,
|
||||
"agent",
|
||||
"pfx",
|
||||
"key",
|
||||
"passphrase",
|
||||
"cert",
|
||||
"ca",
|
||||
"ciphers",
|
||||
"rejectUnauthorized",
|
||||
"autoUnref"
|
||||
);
|
||||
opts.xdomain = !!this._opts.xd;
|
||||
|
||||
const xhr = (this._xhr = this.createRequest(opts));
|
||||
|
||||
try {
|
||||
debug("xhr open %s: %s", this._method, this._uri);
|
||||
xhr.open(this._method, this._uri, true);
|
||||
try {
|
||||
if (this._opts.extraHeaders) {
|
||||
// @ts-ignore
|
||||
xhr.setDisableHeaderCheck && xhr.setDisableHeaderCheck(true);
|
||||
for (let i in this._opts.extraHeaders) {
|
||||
if (this._opts.extraHeaders.hasOwnProperty(i)) {
|
||||
xhr.setRequestHeader(i, this._opts.extraHeaders[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
if ("POST" === this._method) {
|
||||
try {
|
||||
xhr.setRequestHeader("Content-type", "text/plain;charset=UTF-8");
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
try {
|
||||
xhr.setRequestHeader("Accept", "*/*");
|
||||
} catch (e) {}
|
||||
|
||||
this._opts.cookieJar?.addCookies(xhr);
|
||||
|
||||
// ie6 check
|
||||
if ("withCredentials" in xhr) {
|
||||
xhr.withCredentials = this._opts.withCredentials;
|
||||
}
|
||||
|
||||
if (this._opts.requestTimeout) {
|
||||
xhr.timeout = this._opts.requestTimeout;
|
||||
}
|
||||
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === 3) {
|
||||
this._opts.cookieJar?.parseCookies(
|
||||
// @ts-ignore
|
||||
xhr.getResponseHeader("set-cookie")
|
||||
);
|
||||
}
|
||||
|
||||
if (4 !== xhr.readyState) return;
|
||||
if (200 === xhr.status || 1223 === xhr.status) {
|
||||
this._onLoad();
|
||||
} else {
|
||||
// make sure the `error` event handler that's user-set
|
||||
// does not throw in the same tick and gets caught here
|
||||
this.setTimeoutFn(() => {
|
||||
this._onError(typeof xhr.status === "number" ? xhr.status : 0);
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
debug("xhr data %s", this._data);
|
||||
xhr.send(this._data);
|
||||
} catch (e) {
|
||||
// Need to defer since .create() is called directly from the constructor
|
||||
// and thus the 'error' event can only be only bound *after* this exception
|
||||
// occurs. Therefore, also, we cannot throw here at all.
|
||||
this.setTimeoutFn(() => {
|
||||
this._onError(e);
|
||||
}, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof document !== "undefined") {
|
||||
this._index = Request.requestsCount++;
|
||||
Request.requests[this._index] = this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called upon error.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _onError(err: number | Error) {
|
||||
this.emitReserved("error", err, this._xhr);
|
||||
this._cleanup(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up house.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _cleanup(fromError?) {
|
||||
if ("undefined" === typeof this._xhr || null === this._xhr) {
|
||||
return;
|
||||
}
|
||||
this._xhr.onreadystatechange = empty;
|
||||
|
||||
if (fromError) {
|
||||
try {
|
||||
this._xhr.abort();
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (typeof document !== "undefined") {
|
||||
delete Request.requests[this._index];
|
||||
}
|
||||
|
||||
this._xhr = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called upon load.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _onLoad() {
|
||||
const data = this._xhr.responseText;
|
||||
if (data !== null) {
|
||||
this.emitReserved("data", data);
|
||||
this.emitReserved("success");
|
||||
this._cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts the request.
|
||||
*
|
||||
* @package
|
||||
*/
|
||||
public abort() {
|
||||
this._cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts pending requests when unloading the window. This is needed to prevent
|
||||
* memory leaks (e.g. when using IE) and to ensure that no spurious error is
|
||||
* emitted.
|
||||
*/
|
||||
|
||||
if (typeof document !== "undefined") {
|
||||
// @ts-ignore
|
||||
if (typeof attachEvent === "function") {
|
||||
// @ts-ignore
|
||||
attachEvent("onunload", unloadHandler);
|
||||
} else if (typeof addEventListener === "function") {
|
||||
const terminationEvent = "onpagehide" in globalThis ? "pagehide" : "unload";
|
||||
addEventListener(terminationEvent, unloadHandler, false);
|
||||
}
|
||||
}
|
||||
|
||||
function unloadHandler() {
|
||||
for (let i in Request.requests) {
|
||||
if (Request.requests.hasOwnProperty(i)) {
|
||||
Request.requests[i].abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hasXHR2 = (function () {
|
||||
const xhr = newRequest({
|
||||
xdomain: false,
|
||||
});
|
||||
return xhr && xhr.responseType !== null;
|
||||
})();
|
||||
|
||||
/**
|
||||
* HTTP long-polling based on the built-in `XMLHttpRequest` object.
|
||||
*
|
||||
* Usage: browser
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest
|
||||
*/
|
||||
export class XHR extends BaseXHR {
|
||||
constructor(opts) {
|
||||
super(opts);
|
||||
const forceBase64 = opts && opts.forceBase64;
|
||||
this.supportsBinary = hasXHR2 && !forceBase64;
|
||||
}
|
||||
|
||||
request(opts: Record<string, any> = {}) {
|
||||
Object.assign(opts, { xd: this.xd }, this.opts);
|
||||
return new Request(newRequest, this.uri(), opts as RequestOptions);
|
||||
}
|
||||
}
|
||||
|
||||
function newRequest(opts) {
|
||||
const xdomain = opts.xdomain;
|
||||
|
||||
// XMLHttpRequest can be disabled on IE
|
||||
try {
|
||||
if ("undefined" !== typeof XMLHttpRequest && (!xdomain || hasCORS)) {
|
||||
return new XMLHttpRequest();
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
if (!xdomain) {
|
||||
try {
|
||||
return new globalThis[["Active"].concat("Object").join("X")](
|
||||
"Microsoft.XMLHTTP"
|
||||
);
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
179
packages/engine.io-client/lib/transports/polling.ts
Normal file
179
packages/engine.io-client/lib/transports/polling.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
import { Transport } from "../transport.js";
|
||||
import { randomString } from "../util.js";
|
||||
import { encodePayload, decodePayload } from "engine.io-parser";
|
||||
import debugModule from "debug"; // debug()
|
||||
|
||||
const debug = debugModule("engine.io-client:polling"); // debug()
|
||||
|
||||
export abstract class Polling extends Transport {
|
||||
private _polling: boolean = false;
|
||||
|
||||
override get name() {
|
||||
return "polling";
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the socket (triggers polling). We write a PING message to determine
|
||||
* when the transport is open.
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
override doOpen() {
|
||||
this._poll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses polling.
|
||||
*
|
||||
* @param {Function} onPause - callback upon buffers are flushed and transport is paused
|
||||
* @package
|
||||
*/
|
||||
override pause(onPause) {
|
||||
this.readyState = "pausing";
|
||||
|
||||
const pause = () => {
|
||||
debug("paused");
|
||||
this.readyState = "paused";
|
||||
onPause();
|
||||
};
|
||||
|
||||
if (this._polling || !this.writable) {
|
||||
let total = 0;
|
||||
|
||||
if (this._polling) {
|
||||
debug("we are currently polling - waiting to pause");
|
||||
total++;
|
||||
this.once("pollComplete", function () {
|
||||
debug("pre-pause polling complete");
|
||||
--total || pause();
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.writable) {
|
||||
debug("we are currently writing - waiting to pause");
|
||||
total++;
|
||||
this.once("drain", function () {
|
||||
debug("pre-pause writing complete");
|
||||
--total || pause();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
pause();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts polling cycle.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _poll() {
|
||||
debug("polling");
|
||||
this._polling = true;
|
||||
this.doPoll();
|
||||
this.emitReserved("poll");
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloads onData to detect payloads.
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
override onData(data) {
|
||||
debug("polling got data %s", data);
|
||||
const callback = (packet) => {
|
||||
// if its the first message we consider the transport open
|
||||
if ("opening" === this.readyState && packet.type === "open") {
|
||||
this.onOpen();
|
||||
}
|
||||
|
||||
// if its a close packet, we close the ongoing requests
|
||||
if ("close" === packet.type) {
|
||||
this.onClose({ description: "transport closed by the server" });
|
||||
return false;
|
||||
}
|
||||
|
||||
// otherwise bypass onData and handle the message
|
||||
this.onPacket(packet);
|
||||
};
|
||||
|
||||
// decode payload
|
||||
decodePayload(data, this.socket.binaryType).forEach(callback);
|
||||
|
||||
// if an event did not trigger closing
|
||||
if ("closed" !== this.readyState) {
|
||||
// if we got data we're not polling
|
||||
this._polling = false;
|
||||
this.emitReserved("pollComplete");
|
||||
|
||||
if ("open" === this.readyState) {
|
||||
this._poll();
|
||||
} else {
|
||||
debug('ignoring poll - transport state "%s"', this.readyState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For polling, send a close packet.
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
override doClose() {
|
||||
const close = () => {
|
||||
debug("writing close packet");
|
||||
this.write([{ type: "close" }]);
|
||||
};
|
||||
|
||||
if ("open" === this.readyState) {
|
||||
debug("transport open - closing");
|
||||
close();
|
||||
} else {
|
||||
// in case we're trying to close while
|
||||
// handshaking is in progress (GH-164)
|
||||
debug("transport not open - deferring close");
|
||||
this.once("open", close);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a packets payload.
|
||||
*
|
||||
* @param {Array} packets - data packets
|
||||
* @protected
|
||||
*/
|
||||
override write(packets) {
|
||||
this.writable = false;
|
||||
|
||||
encodePayload(packets, (data) => {
|
||||
this.doWrite(data, () => {
|
||||
this.writable = true;
|
||||
this.emitReserved("drain");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates uri for connection.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
protected uri() {
|
||||
const schema = this.opts.secure ? "https" : "http";
|
||||
const query: { b64?: number; sid?: string } = this.query || {};
|
||||
|
||||
// cache busting is forced
|
||||
if (false !== this.opts.timestampRequests) {
|
||||
query[this.opts.timestampParam] = randomString();
|
||||
}
|
||||
|
||||
if (!this.supportsBinary && !query.sid) {
|
||||
query.b64 = 1;
|
||||
}
|
||||
|
||||
return this.createUri(schema, query);
|
||||
}
|
||||
|
||||
abstract doPoll();
|
||||
abstract doWrite(data: string, callback: () => void);
|
||||
}
|
||||
50
packages/engine.io-client/lib/transports/websocket.node.ts
Normal file
50
packages/engine.io-client/lib/transports/websocket.node.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { WebSocket } from "ws";
|
||||
import type { Packet, RawData } from "engine.io-parser";
|
||||
import { BaseWS } from "./websocket.js";
|
||||
|
||||
/**
|
||||
* WebSocket transport based on the `WebSocket` object provided by the `ws` package.
|
||||
*
|
||||
* Usage: Node.js, Deno (compat), Bun (compat)
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
|
||||
* @see https://caniuse.com/mdn-api_websocket
|
||||
*/
|
||||
export class WS extends BaseWS {
|
||||
createSocket(
|
||||
uri: string,
|
||||
protocols: string | string[] | undefined,
|
||||
opts: Record<string, any>
|
||||
) {
|
||||
if (this.socket?._cookieJar) {
|
||||
opts.headers = opts.headers || {};
|
||||
|
||||
opts.headers.cookie =
|
||||
typeof opts.headers.cookie === "string"
|
||||
? [opts.headers.cookie]
|
||||
: opts.headers.cookie || [];
|
||||
for (const [name, cookie] of this.socket._cookieJar.cookies) {
|
||||
opts.headers.cookie.push(`${name}=${cookie.value}`);
|
||||
}
|
||||
}
|
||||
return new WebSocket(uri, protocols, opts);
|
||||
}
|
||||
|
||||
doWrite(packet: Packet, data: RawData) {
|
||||
const opts: { compress?: boolean } = {};
|
||||
if (packet.options) {
|
||||
opts.compress = packet.options.compress;
|
||||
}
|
||||
|
||||
if (this.opts.perMessageDeflate) {
|
||||
const len =
|
||||
// @ts-ignore
|
||||
"string" === typeof data ? Buffer.byteLength(data) : data.length;
|
||||
if (len < this.opts.perMessageDeflate.threshold) {
|
||||
opts.compress = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.ws.send(data, opts);
|
||||
}
|
||||
}
|
||||
181
packages/engine.io-client/lib/transports/websocket.ts
Normal file
181
packages/engine.io-client/lib/transports/websocket.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import { Transport } from "../transport.js";
|
||||
import { pick, randomString } from "../util.js";
|
||||
import { encodePacket } from "engine.io-parser";
|
||||
import type { Packet, RawData } from "engine.io-parser";
|
||||
import { globalThisShim as globalThis, nextTick } from "../globals.node.js";
|
||||
import debugModule from "debug"; // debug()
|
||||
|
||||
const debug = debugModule("engine.io-client:websocket"); // debug()
|
||||
|
||||
// detect ReactNative environment
|
||||
const isReactNative =
|
||||
typeof navigator !== "undefined" &&
|
||||
typeof navigator.product === "string" &&
|
||||
navigator.product.toLowerCase() === "reactnative";
|
||||
|
||||
export abstract class BaseWS extends Transport {
|
||||
protected ws: any;
|
||||
|
||||
override get name() {
|
||||
return "websocket";
|
||||
}
|
||||
|
||||
override doOpen() {
|
||||
const uri = this.uri();
|
||||
const protocols = this.opts.protocols;
|
||||
|
||||
// React Native only supports the 'headers' option, and will print a warning if anything else is passed
|
||||
const opts = isReactNative
|
||||
? {}
|
||||
: pick(
|
||||
this.opts,
|
||||
"agent",
|
||||
"perMessageDeflate",
|
||||
"pfx",
|
||||
"key",
|
||||
"passphrase",
|
||||
"cert",
|
||||
"ca",
|
||||
"ciphers",
|
||||
"rejectUnauthorized",
|
||||
"localAddress",
|
||||
"protocolVersion",
|
||||
"origin",
|
||||
"maxPayload",
|
||||
"family",
|
||||
"checkServerIdentity"
|
||||
);
|
||||
|
||||
if (this.opts.extraHeaders) {
|
||||
opts.headers = this.opts.extraHeaders;
|
||||
}
|
||||
|
||||
try {
|
||||
this.ws = this.createSocket(uri, protocols, opts);
|
||||
} catch (err) {
|
||||
return this.emitReserved("error", err);
|
||||
}
|
||||
|
||||
this.ws.binaryType = this.socket.binaryType;
|
||||
|
||||
this.addEventListeners();
|
||||
}
|
||||
|
||||
abstract createSocket(
|
||||
uri: string,
|
||||
protocols: string | string[] | undefined,
|
||||
opts: Record<string, any>
|
||||
);
|
||||
|
||||
/**
|
||||
* Adds event listeners to the socket
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private addEventListeners() {
|
||||
this.ws.onopen = () => {
|
||||
if (this.opts.autoUnref) {
|
||||
this.ws._socket.unref();
|
||||
}
|
||||
this.onOpen();
|
||||
};
|
||||
this.ws.onclose = (closeEvent) =>
|
||||
this.onClose({
|
||||
description: "websocket connection closed",
|
||||
context: closeEvent,
|
||||
});
|
||||
this.ws.onmessage = (ev) => this.onData(ev.data);
|
||||
this.ws.onerror = (e) => this.onError("websocket error", e);
|
||||
}
|
||||
|
||||
override write(packets) {
|
||||
this.writable = false;
|
||||
|
||||
// encodePacket efficient as it uses WS framing
|
||||
// no need for encodePayload
|
||||
for (let i = 0; i < packets.length; i++) {
|
||||
const packet = packets[i];
|
||||
const lastPacket = i === packets.length - 1;
|
||||
|
||||
encodePacket(packet, this.supportsBinary, (data) => {
|
||||
// Sometimes the websocket has already been closed but the browser didn't
|
||||
// have a chance of informing us about it yet, in that case send will
|
||||
// throw an error
|
||||
try {
|
||||
this.doWrite(packet, data);
|
||||
} catch (e) {
|
||||
debug("websocket closed before onclose event");
|
||||
}
|
||||
|
||||
if (lastPacket) {
|
||||
// fake drain
|
||||
// defer to next tick to allow Socket to clear writeBuffer
|
||||
nextTick(() => {
|
||||
this.writable = true;
|
||||
this.emitReserved("drain");
|
||||
}, this.setTimeoutFn);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
abstract doWrite(packet: Packet, data: RawData);
|
||||
|
||||
override doClose() {
|
||||
if (typeof this.ws !== "undefined") {
|
||||
this.ws.close();
|
||||
this.ws = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates uri for connection.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private uri() {
|
||||
const schema = this.opts.secure ? "wss" : "ws";
|
||||
const query: { b64?: number } = this.query || {};
|
||||
|
||||
// append timestamp to URI
|
||||
if (this.opts.timestampRequests) {
|
||||
query[this.opts.timestampParam] = randomString();
|
||||
}
|
||||
|
||||
// communicate binary support capabilities
|
||||
if (!this.supportsBinary) {
|
||||
query.b64 = 1;
|
||||
}
|
||||
|
||||
return this.createUri(schema, query);
|
||||
}
|
||||
}
|
||||
|
||||
const WebSocketCtor = globalThis.WebSocket || globalThis.MozWebSocket;
|
||||
|
||||
/**
|
||||
* WebSocket transport based on the built-in `WebSocket` object.
|
||||
*
|
||||
* Usage: browser, Node.js (since v21), Deno, Bun
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
|
||||
* @see https://caniuse.com/mdn-api_websocket
|
||||
* @see https://nodejs.org/api/globals.html#websocket
|
||||
*/
|
||||
export class WS extends BaseWS {
|
||||
createSocket(
|
||||
uri: string,
|
||||
protocols: string | string[] | undefined,
|
||||
opts: Record<string, any>
|
||||
) {
|
||||
return !isReactNative
|
||||
? protocols
|
||||
? new WebSocketCtor(uri, protocols)
|
||||
: new WebSocketCtor(uri)
|
||||
: new WebSocketCtor(uri, protocols, opts);
|
||||
}
|
||||
|
||||
doWrite(_packet: Packet, data: RawData) {
|
||||
this.ws.send(data);
|
||||
}
|
||||
}
|
||||
111
packages/engine.io-client/lib/transports/webtransport.ts
Normal file
111
packages/engine.io-client/lib/transports/webtransport.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { Transport } from "../transport.js";
|
||||
import { nextTick } from "../globals.node.js";
|
||||
import {
|
||||
Packet,
|
||||
createPacketDecoderStream,
|
||||
createPacketEncoderStream,
|
||||
} from "engine.io-parser";
|
||||
import debugModule from "debug"; // debug()
|
||||
|
||||
const debug = debugModule("engine.io-client:webtransport"); // debug()
|
||||
|
||||
/**
|
||||
* WebTransport transport based on the built-in `WebTransport` object.
|
||||
*
|
||||
* Usage: browser, Node.js (with the `@fails-components/webtransport` package)
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/WebTransport
|
||||
* @see https://caniuse.com/webtransport
|
||||
*/
|
||||
export class WT extends Transport {
|
||||
private _transport: any;
|
||||
private _writer: any;
|
||||
|
||||
get name() {
|
||||
return "webtransport";
|
||||
}
|
||||
|
||||
protected doOpen() {
|
||||
try {
|
||||
// @ts-ignore
|
||||
this._transport = new WebTransport(
|
||||
this.createUri("https"),
|
||||
this.opts.transportOptions[this.name]
|
||||
);
|
||||
} catch (err) {
|
||||
return this.emitReserved("error", err);
|
||||
}
|
||||
|
||||
this._transport.closed
|
||||
.then(() => {
|
||||
debug("transport closed gracefully");
|
||||
this.onClose();
|
||||
})
|
||||
.catch((err) => {
|
||||
debug("transport closed due to %s", err);
|
||||
this.onError("webtransport error", err);
|
||||
});
|
||||
|
||||
// note: we could have used async/await, but that would require some additional polyfills
|
||||
this._transport.ready.then(() => {
|
||||
this._transport.createBidirectionalStream().then((stream) => {
|
||||
const decoderStream = createPacketDecoderStream(
|
||||
Number.MAX_SAFE_INTEGER,
|
||||
this.socket.binaryType
|
||||
);
|
||||
const reader = stream.readable.pipeThrough(decoderStream).getReader();
|
||||
|
||||
const encoderStream = createPacketEncoderStream();
|
||||
encoderStream.readable.pipeTo(stream.writable);
|
||||
this._writer = encoderStream.writable.getWriter();
|
||||
|
||||
const read = () => {
|
||||
reader
|
||||
.read()
|
||||
.then(({ done, value }) => {
|
||||
if (done) {
|
||||
debug("session is closed");
|
||||
return;
|
||||
}
|
||||
debug("received chunk: %o", value);
|
||||
this.onPacket(value);
|
||||
read();
|
||||
})
|
||||
.catch((err) => {
|
||||
debug("an error occurred while reading: %s", err);
|
||||
});
|
||||
};
|
||||
|
||||
read();
|
||||
|
||||
const packet: Packet = { type: "open" };
|
||||
if (this.query.sid) {
|
||||
packet.data = `{"sid":"${this.query.sid}"}`;
|
||||
}
|
||||
this._writer.write(packet).then(() => this.onOpen());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected write(packets: Packet[]) {
|
||||
this.writable = false;
|
||||
|
||||
for (let i = 0; i < packets.length; i++) {
|
||||
const packet = packets[i];
|
||||
const lastPacket = i === packets.length - 1;
|
||||
|
||||
this._writer.write(packet).then(() => {
|
||||
if (lastPacket) {
|
||||
nextTick(() => {
|
||||
this.writable = true;
|
||||
this.emitReserved("drain");
|
||||
}, this.setTimeoutFn);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected doClose() {
|
||||
this._transport?.close();
|
||||
}
|
||||
}
|
||||
65
packages/engine.io-client/lib/util.ts
Normal file
65
packages/engine.io-client/lib/util.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { globalThisShim as globalThis } from "./globals.node.js";
|
||||
|
||||
export function pick(obj, ...attr) {
|
||||
return attr.reduce((acc, k) => {
|
||||
if (obj.hasOwnProperty(k)) {
|
||||
acc[k] = obj[k];
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
// Keep a reference to the real timeout functions so they can be used when overridden
|
||||
const NATIVE_SET_TIMEOUT = globalThis.setTimeout;
|
||||
const NATIVE_CLEAR_TIMEOUT = globalThis.clearTimeout;
|
||||
|
||||
export function installTimerFunctions(obj, opts) {
|
||||
if (opts.useNativeTimers) {
|
||||
obj.setTimeoutFn = NATIVE_SET_TIMEOUT.bind(globalThis);
|
||||
obj.clearTimeoutFn = NATIVE_CLEAR_TIMEOUT.bind(globalThis);
|
||||
} else {
|
||||
obj.setTimeoutFn = globalThis.setTimeout.bind(globalThis);
|
||||
obj.clearTimeoutFn = globalThis.clearTimeout.bind(globalThis);
|
||||
}
|
||||
}
|
||||
|
||||
// base64 encoded buffers are about 33% bigger (https://en.wikipedia.org/wiki/Base64)
|
||||
const BASE64_OVERHEAD = 1.33;
|
||||
|
||||
// we could also have used `new Blob([obj]).size`, but it isn't supported in IE9
|
||||
export function byteLength(obj) {
|
||||
if (typeof obj === "string") {
|
||||
return utf8Length(obj);
|
||||
}
|
||||
// arraybuffer or blob
|
||||
return Math.ceil((obj.byteLength || obj.size) * BASE64_OVERHEAD);
|
||||
}
|
||||
|
||||
function utf8Length(str) {
|
||||
let c = 0,
|
||||
length = 0;
|
||||
for (let i = 0, l = str.length; i < l; i++) {
|
||||
c = str.charCodeAt(i);
|
||||
if (c < 0x80) {
|
||||
length += 1;
|
||||
} else if (c < 0x800) {
|
||||
length += 2;
|
||||
} else if (c < 0xd800 || c >= 0xe000) {
|
||||
length += 3;
|
||||
} else {
|
||||
i++;
|
||||
length += 4;
|
||||
}
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random 8-characters string.
|
||||
*/
|
||||
export function randomString() {
|
||||
return (
|
||||
Date.now().toString(36).substring(3) +
|
||||
Math.random().toString(36).substring(2, 5)
|
||||
);
|
||||
}
|
||||
26016
packages/engine.io-client/package-lock.json
generated
Normal file
26016
packages/engine.io-client/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
123
packages/engine.io-client/package.json
Normal file
123
packages/engine.io-client/package.json
Normal file
@@ -0,0 +1,123 @@
|
||||
{
|
||||
"name": "engine.io-client",
|
||||
"description": "Client for the realtime Engine",
|
||||
"license": "MIT",
|
||||
"version": "6.6.0",
|
||||
"main": "./build/cjs/index.js",
|
||||
"module": "./build/esm/index.js",
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
"./dist/engine.io.esm.min.js": "./dist/engine.io.esm.min.js",
|
||||
"./dist/engine.io.js": "./dist/engine.io.js",
|
||||
"./dist/engine.io.min.js": "./dist/engine.io.min.js",
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./build/esm/index.d.ts",
|
||||
"node": "./build/esm-debug/index.js",
|
||||
"default": "./build/esm/index.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./build/cjs/index.d.ts",
|
||||
"default": "./build/cjs/index.js"
|
||||
}
|
||||
},
|
||||
"./debug": {
|
||||
"import": {
|
||||
"types": "./build/esm/index.d.ts",
|
||||
"default": "./build/esm-debug/index.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./build/cjs/index.d.ts",
|
||||
"default": "./build/cjs/index.js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"types": "build/esm/index.d.ts",
|
||||
"homepage": "https://github.com/socketio/engine.io-client",
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Guillermo Rauch",
|
||||
"email": "rauchg@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Vladimir Dronnikov",
|
||||
"email": "dronnikov@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Christoph Dorn",
|
||||
"web": "https://github.com/cadorn"
|
||||
},
|
||||
{
|
||||
"name": "Mark Mokryn",
|
||||
"email": "mokesmokes@gmail.com"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.2.1",
|
||||
"ws": "~8.17.1",
|
||||
"xmlhttprequest-ssl": "~2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.9",
|
||||
"@babel/plugin-transform-object-assign": "^7.12.1",
|
||||
"@babel/preset-env": "^7.12.7",
|
||||
"@fails-components/webtransport": "^0.1.7",
|
||||
"@rollup/plugin-babel": "^5.3.0",
|
||||
"@rollup/plugin-commonjs": "^21.0.0",
|
||||
"@rollup/plugin-node-resolve": "^13.0.5",
|
||||
"@sinonjs/fake-timers": "^7.1.2",
|
||||
"@types/debug": "^4.1.12",
|
||||
"@types/mocha": "^9.0.0",
|
||||
"@types/node": "^16.10.1",
|
||||
"@types/sinonjs__fake-timers": "^6.0.3",
|
||||
"babel-loader": "^8.2.2",
|
||||
"blob": "0.0.5",
|
||||
"engine.io": "^6.5.2-alpha.1",
|
||||
"expect.js": "^0.3.1",
|
||||
"express": "^4.17.1",
|
||||
"mocha": "^10.2.0",
|
||||
"node-forge": "^1.3.1",
|
||||
"prettier": "^2.8.1",
|
||||
"rollup": "^2.58.0",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"socket.io-browsers": "~1.0.4",
|
||||
"typescript": "^4.9.5",
|
||||
"webpack": "^4.44.2",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-remove-debug": "^0.1.0",
|
||||
"zuul": "~3.11.1",
|
||||
"zuul-builder-webpack": "^1.2.0",
|
||||
"zuul-ngrok": "4.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"compile": "rimraf ./build && tsc && tsc -p tsconfig.esm.json && ./postcompile.sh",
|
||||
"test": "npm run format:check && npm run compile && if test \"$BROWSERS\" = \"1\" ; then npm run test:browser; else npm run test:node; fi",
|
||||
"test:node": "mocha --bail --require test/support/hooks.js test/index.js test/webtransport.mjs",
|
||||
"test:node-fetch": "USE_FETCH=1 npm run test:node",
|
||||
"test:browser": "zuul test/index.js",
|
||||
"build": "rimraf ./dist && rollup -c support/rollup.config.umd.js && rollup -c support/rollup.config.esm.js",
|
||||
"bundle-size": "node support/bundle-size.js",
|
||||
"format:check": "prettier --check 'lib/**/*.ts' 'test/**/*.js' 'test/webtransport.mjs' 'support/**/*.js'",
|
||||
"format:fix": "prettier --write 'lib/**/*.ts' 'test/**/*.js' 'test/webtransport.mjs' 'support/**/*.js'",
|
||||
"prepack": "npm run compile"
|
||||
},
|
||||
"browser": {
|
||||
"./test/node.js": false,
|
||||
"./build/esm/transports/polling-xhr.node.js": "./build/esm/transports/polling-xhr.js",
|
||||
"./build/esm/transports/websocket.node.js": "./build/esm/transports/websocket.js",
|
||||
"./build/esm/globals.node.js": "./build/esm/globals.js",
|
||||
"./build/cjs/transports/polling-xhr.node.js": "./build/cjs/transports/polling-xhr.js",
|
||||
"./build/cjs/transports/websocket.node.js": "./build/cjs/transports/websocket.js",
|
||||
"./build/cjs/globals.node.js": "./build/cjs/globals.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/socketio/engine.io-client.git"
|
||||
},
|
||||
"files": [
|
||||
"build/",
|
||||
"dist/"
|
||||
]
|
||||
}
|
||||
12
packages/engine.io-client/postcompile.sh
Executable file
12
packages/engine.io-client/postcompile.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cp ./support/package.cjs.json ./build/cjs/package.json
|
||||
cp ./support/package.esm.json ./build/esm/package.json
|
||||
|
||||
cp -r ./build/esm/ ./build/esm-debug/
|
||||
|
||||
if [ "${OSTYPE:0:6}" = darwin ]; then
|
||||
sed -i '' -e '/debug(/d' ./build/esm/*.js ./build/esm/**/*.js
|
||||
else
|
||||
sed -i -e '/debug(/d' ./build/esm/*.js ./build/esm/**/*.js
|
||||
fi
|
||||
35
packages/engine.io-client/support/bundle-size.js
Normal file
35
packages/engine.io-client/support/bundle-size.js
Normal file
@@ -0,0 +1,35 @@
|
||||
const { resolve } = require("node:path");
|
||||
const { readFile } = require("node:fs/promises");
|
||||
const { gzipSync, brotliCompressSync } = require("node:zlib");
|
||||
|
||||
const bundles = [
|
||||
{
|
||||
name: "UMD bundle",
|
||||
path: "dist/engine.io.min.js",
|
||||
},
|
||||
{
|
||||
name: "ESM bundle",
|
||||
path: "dist/engine.io.esm.min.js",
|
||||
},
|
||||
];
|
||||
|
||||
function format(size) {
|
||||
return (size / 1024).toFixed(1);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
for (const bundle of bundles) {
|
||||
const path = resolve(bundle.path);
|
||||
const content = await readFile(path);
|
||||
const gzip = gzipSync(content);
|
||||
const brotli = brotliCompressSync(content);
|
||||
|
||||
console.log(`${bundle.name}`);
|
||||
console.log(`min: ${format(content.length)} KB`);
|
||||
console.log(`min+gzip: ${format(gzip.length)} KB`);
|
||||
console.log(`min+br: ${format(brotli.length)} KB`);
|
||||
console.log();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
10
packages/engine.io-client/support/package.cjs.json
Normal file
10
packages/engine.io-client/support/package.cjs.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "engine.io-client",
|
||||
"type": "commonjs",
|
||||
"browser": {
|
||||
"ws": false,
|
||||
"./transports/polling-xhr.node.js": "./transports/polling-xhr.js",
|
||||
"./transports/websocket.node.js": "./transports/websocket.js",
|
||||
"./globals.node.js": "./globals.js"
|
||||
}
|
||||
}
|
||||
10
packages/engine.io-client/support/package.esm.json
Normal file
10
packages/engine.io-client/support/package.esm.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "engine.io-client",
|
||||
"type": "module",
|
||||
"browser": {
|
||||
"ws": false,
|
||||
"./transports/polling-xhr.node.js": "./transports/polling-xhr.js",
|
||||
"./transports/websocket.node.js": "./transports/websocket.js",
|
||||
"./globals.node.js": "./globals.js"
|
||||
}
|
||||
}
|
||||
19
packages/engine.io-client/support/prod.config.js
Normal file
19
packages/engine.io-client/support/prod.config.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const config = require("./webpack.config");
|
||||
|
||||
module.exports = {
|
||||
...config,
|
||||
output: {
|
||||
...config.output,
|
||||
filename: "engine.io.min.js",
|
||||
},
|
||||
mode: "production",
|
||||
module: {
|
||||
rules: [
|
||||
...config.module.rules,
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: "webpack-remove-debug",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
33
packages/engine.io-client/support/rollup.config.esm.js
Normal file
33
packages/engine.io-client/support/rollup.config.esm.js
Normal file
@@ -0,0 +1,33 @@
|
||||
const { nodeResolve } = require("@rollup/plugin-node-resolve");
|
||||
const { terser } = require("rollup-plugin-terser");
|
||||
|
||||
const version = require("../package.json").version;
|
||||
const banner = `/*!
|
||||
* Engine.IO v${version}
|
||||
* (c) 2014-${new Date().getFullYear()} Guillermo Rauch
|
||||
* Released under the MIT License.
|
||||
*/`;
|
||||
|
||||
module.exports = {
|
||||
input: "./build/esm/index.js",
|
||||
output: {
|
||||
file: "./dist/engine.io.esm.min.js",
|
||||
format: "esm",
|
||||
sourcemap: true,
|
||||
plugins: [
|
||||
terser({
|
||||
mangle: {
|
||||
properties: {
|
||||
regex: /^_/,
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
banner,
|
||||
},
|
||||
plugins: [
|
||||
nodeResolve({
|
||||
browser: true,
|
||||
}),
|
||||
],
|
||||
};
|
||||
80
packages/engine.io-client/support/rollup.config.umd.js
Normal file
80
packages/engine.io-client/support/rollup.config.umd.js
Normal file
@@ -0,0 +1,80 @@
|
||||
const { nodeResolve } = require("@rollup/plugin-node-resolve");
|
||||
const { babel } = require("@rollup/plugin-babel");
|
||||
const { terser } = require("rollup-plugin-terser");
|
||||
const commonjs = require("@rollup/plugin-commonjs");
|
||||
|
||||
const version = require("../package.json").version;
|
||||
const banner = `/*!
|
||||
* Engine.IO v${version}
|
||||
* (c) 2014-${new Date().getFullYear()} Guillermo Rauch
|
||||
* Released under the MIT License.
|
||||
*/`;
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
input: "./build/esm-debug/browser-entrypoint.js",
|
||||
output: {
|
||||
file: "./dist/engine.io.js",
|
||||
format: "umd",
|
||||
name: "eio",
|
||||
sourcemap: true,
|
||||
banner,
|
||||
},
|
||||
plugins: [
|
||||
nodeResolve({
|
||||
browser: true,
|
||||
}),
|
||||
commonjs(),
|
||||
babel({
|
||||
babelHelpers: "bundled",
|
||||
presets: [["@babel/env"]],
|
||||
plugins: [
|
||||
"@babel/plugin-transform-object-assign",
|
||||
[
|
||||
"@babel/plugin-transform-classes",
|
||||
{
|
||||
loose: true,
|
||||
},
|
||||
],
|
||||
],
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
input: "./build/esm/browser-entrypoint.js",
|
||||
output: {
|
||||
file: "./dist/engine.io.min.js",
|
||||
format: "umd",
|
||||
name: "eio",
|
||||
sourcemap: true,
|
||||
plugins: [
|
||||
terser({
|
||||
mangle: {
|
||||
properties: {
|
||||
regex: /^_/,
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
banner,
|
||||
},
|
||||
plugins: [
|
||||
nodeResolve({
|
||||
browser: true,
|
||||
}),
|
||||
babel({
|
||||
babelHelpers: "bundled",
|
||||
presets: [["@babel/env"]],
|
||||
plugins: [
|
||||
"@babel/plugin-transform-object-assign",
|
||||
[
|
||||
"@babel/plugin-transform-classes",
|
||||
{
|
||||
loose: true,
|
||||
},
|
||||
],
|
||||
],
|
||||
}),
|
||||
],
|
||||
},
|
||||
];
|
||||
33
packages/engine.io-client/support/webpack.config.js
Normal file
33
packages/engine.io-client/support/webpack.config.js
Normal file
@@ -0,0 +1,33 @@
|
||||
const { BannerPlugin } = require("webpack");
|
||||
const version = require("../package.json").version;
|
||||
|
||||
const banner = `Engine.IO v${version}
|
||||
(c) 2014-${new Date().getFullYear()} Guillermo Rauch
|
||||
Released under the MIT License.`;
|
||||
|
||||
module.exports = {
|
||||
entry: "./build/esm/index.js",
|
||||
output: {
|
||||
filename: "engine.io.js",
|
||||
library: "eio",
|
||||
libraryTarget: "umd",
|
||||
globalObject: "self",
|
||||
},
|
||||
mode: "development",
|
||||
node: false,
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.m?js$/,
|
||||
use: {
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
presets: ["@babel/preset-env"],
|
||||
plugins: ["@babel/plugin-transform-object-assign"],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [new BannerPlugin(banner)],
|
||||
};
|
||||
6
packages/engine.io-client/test/arraybuffer/index.js
Normal file
6
packages/engine.io-client/test/arraybuffer/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const env = require("../support/env");
|
||||
|
||||
require("./polling.js");
|
||||
if (env.wsSupport && !env.isOldSimulator && !env.isAndroid && !env.isIE11) {
|
||||
require("./ws.js");
|
||||
}
|
||||
96
packages/engine.io-client/test/arraybuffer/polling.js
Normal file
96
packages/engine.io-client/test/arraybuffer/polling.js
Normal file
@@ -0,0 +1,96 @@
|
||||
const expect = require("expect.js");
|
||||
const { Socket } = require("../../");
|
||||
const { repeat } = require("../util");
|
||||
|
||||
describe("arraybuffer", function () {
|
||||
this.timeout(30000);
|
||||
|
||||
it("should be able to receive binary data when bouncing it back (polling)", (done) => {
|
||||
const binaryData = new Int8Array(5);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
binaryData[i] = i;
|
||||
}
|
||||
const socket = new Socket({ transports: ["polling"] });
|
||||
socket.binaryType = "arraybuffer";
|
||||
socket.on("open", () => {
|
||||
socket.send(binaryData);
|
||||
socket.on("message", (data) => {
|
||||
if (data === "hi") return;
|
||||
|
||||
expect(data).to.be.an(ArrayBuffer);
|
||||
expect(new Int8Array(data)).to.eql(binaryData);
|
||||
socket.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should be able to receive binary data and a multibyte utf-8 string (polling)", (done) => {
|
||||
const binaryData = new Int8Array(5);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
binaryData[i] = i;
|
||||
}
|
||||
|
||||
let msg = 0;
|
||||
const socket = new Socket({ transports: ["polling"] });
|
||||
socket.binaryType = "arraybuffer";
|
||||
socket.on("open", () => {
|
||||
socket.send(binaryData);
|
||||
socket.send("cash money €€€");
|
||||
socket.on("message", (data) => {
|
||||
if (data === "hi") return;
|
||||
|
||||
if (msg === 0) {
|
||||
expect(data).to.be.an(ArrayBuffer);
|
||||
expect(new Int8Array(data)).to.eql(binaryData);
|
||||
msg++;
|
||||
} else {
|
||||
expect(data).to.be("cash money €€€");
|
||||
socket.close();
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should be able to receive binary data when forcing base64 (polling)", (done) => {
|
||||
const binaryData = new Int8Array(5);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
binaryData[i] = i;
|
||||
}
|
||||
const socket = new Socket({ forceBase64: true });
|
||||
socket.binaryType = "arraybuffer";
|
||||
socket.on("open", () => {
|
||||
socket.send(binaryData);
|
||||
socket.on("message", (data) => {
|
||||
if (typeof data === "string") return;
|
||||
|
||||
expect(data).to.be.an(ArrayBuffer);
|
||||
const ia = new Int8Array(data);
|
||||
expect(ia).to.eql(binaryData);
|
||||
socket.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should merge binary packets according to maxPayload value", (done) => {
|
||||
const socket = new Socket({ transports: ["polling"] });
|
||||
socket.on("open", () => {
|
||||
socket.send(new Uint8Array(72));
|
||||
socket.send(new Uint8Array(20));
|
||||
socket.send(repeat("a", 20));
|
||||
socket.send(new Uint8Array(20).buffer);
|
||||
socket.send(new Uint8Array(72));
|
||||
|
||||
let count = 0;
|
||||
socket.on("message", () => {
|
||||
count++;
|
||||
if (count === 5) {
|
||||
socket.close();
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
82
packages/engine.io-client/test/arraybuffer/ws.js
Normal file
82
packages/engine.io-client/test/arraybuffer/ws.js
Normal file
@@ -0,0 +1,82 @@
|
||||
const expect = require("expect.js");
|
||||
const eio = require("../../");
|
||||
|
||||
describe("arraybuffer", function () {
|
||||
this.timeout(30000);
|
||||
|
||||
it("should be able to receive binary data when bouncing it back (ws)", (done) => {
|
||||
const binaryData = new Int8Array(5);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
binaryData[i] = i;
|
||||
}
|
||||
const socket = new eio.Socket();
|
||||
socket.binaryType = "arraybuffer";
|
||||
socket.on("open", () => {
|
||||
socket.on("upgrade", () => {
|
||||
socket.send(binaryData);
|
||||
socket.on("message", (data) => {
|
||||
if (typeof data === "string") return;
|
||||
|
||||
expect(data).to.be.an(ArrayBuffer);
|
||||
expect(new Int8Array(data)).to.eql(binaryData);
|
||||
|
||||
socket.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should be able to receive binary data and a multibyte utf-8 string (ws)", (done) => {
|
||||
const binaryData = new Int8Array(5);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
binaryData[i] = i;
|
||||
}
|
||||
|
||||
let msg = 0;
|
||||
const socket = new eio.Socket();
|
||||
socket.binaryType = "arraybuffer";
|
||||
socket.on("open", () => {
|
||||
socket.on("upgrade", () => {
|
||||
socket.send(binaryData);
|
||||
socket.send("cash money €€€");
|
||||
socket.on("message", (data) => {
|
||||
if (data === "hi") return;
|
||||
|
||||
if (msg === 0) {
|
||||
expect(data).to.be.an(ArrayBuffer);
|
||||
expect(new Int8Array(data)).to.eql(binaryData);
|
||||
msg++;
|
||||
} else {
|
||||
expect(data).to.be("cash money €€€");
|
||||
socket.close();
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should be able to receive binary data when bouncing it back and forcing base64 (ws)", (done) => {
|
||||
const binaryData = new Int8Array(5);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
binaryData[i] = i;
|
||||
}
|
||||
const socket = new eio.Socket({ forceBase64: true });
|
||||
socket.binaryType = "arraybuffer";
|
||||
socket.on("open", () => {
|
||||
socket.on("upgrade", () => {
|
||||
socket.send(binaryData);
|
||||
socket.on("message", (data) => {
|
||||
if (typeof data === "string") return;
|
||||
|
||||
expect(data).to.be.an(ArrayBuffer);
|
||||
expect(new Int8Array(data)).to.eql(binaryData);
|
||||
|
||||
socket.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
26
packages/engine.io-client/test/binary-fallback.js
Normal file
26
packages/engine.io-client/test/binary-fallback.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const expect = require("expect.js");
|
||||
const eio = require("../");
|
||||
|
||||
describe("binary fallback", function () {
|
||||
this.timeout(10000);
|
||||
|
||||
it("should be able to receive binary data when ArrayBuffer not available (polling)", (done) => {
|
||||
const socket = new eio.Socket({ forceBase64: true });
|
||||
socket.on("open", () => {
|
||||
socket.send("give binary");
|
||||
let firstPacket = true;
|
||||
socket.on("message", (data) => {
|
||||
if (firstPacket) {
|
||||
firstPacket = false;
|
||||
return;
|
||||
}
|
||||
|
||||
expect(data.base64).to.be(true);
|
||||
expect(data.data).to.equal("AAECAwQ=");
|
||||
|
||||
socket.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
6
packages/engine.io-client/test/blob/index.js
Normal file
6
packages/engine.io-client/test/blob/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const env = require("../support/env");
|
||||
|
||||
require("./polling.js");
|
||||
if (env.wsSupport && !env.isOldSimulator && !env.isAndroid && !env.isIE11) {
|
||||
require("./ws.js");
|
||||
}
|
||||
74
packages/engine.io-client/test/blob/polling.js
Normal file
74
packages/engine.io-client/test/blob/polling.js
Normal file
@@ -0,0 +1,74 @@
|
||||
const expect = require("expect.js");
|
||||
const { Socket } = require("../../");
|
||||
|
||||
const Blob = require("blob");
|
||||
const { repeat } = require("../util");
|
||||
|
||||
describe("blob", function () {
|
||||
this.timeout(30000);
|
||||
|
||||
it("should be able to receive binary data as blob when bouncing it back (polling)", (done) => {
|
||||
const binaryData = new Int8Array(5);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
binaryData[i] = i;
|
||||
}
|
||||
const socket = new Socket();
|
||||
socket.binaryType = "blob";
|
||||
socket.on("open", () => {
|
||||
socket.send(binaryData);
|
||||
socket.on("message", (data) => {
|
||||
if (typeof data === "string") return;
|
||||
|
||||
expect(data).to.be.a(Blob);
|
||||
const fr = new FileReader();
|
||||
fr.onload = function () {
|
||||
const ab = this.result;
|
||||
const ia = new Int8Array(ab);
|
||||
expect(ia).to.eql(binaryData);
|
||||
socket.close();
|
||||
done();
|
||||
};
|
||||
fr.readAsArrayBuffer(data);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should be able to send data as a blob when bouncing it back (polling)", (done) => {
|
||||
const binaryData = new Int8Array(5);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
binaryData[i] = i;
|
||||
}
|
||||
const socket = new Socket();
|
||||
socket.on("open", () => {
|
||||
socket.send(new Blob([binaryData.buffer]));
|
||||
socket.on("message", (data) => {
|
||||
if (typeof data === "string") return;
|
||||
|
||||
expect(data).to.be.an(ArrayBuffer);
|
||||
expect(new Int8Array(data)).to.eql(binaryData);
|
||||
socket.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should merge binary packets according to maxPayload value", (done) => {
|
||||
const socket = new Socket({ transports: ["polling"] });
|
||||
socket.on("open", () => {
|
||||
socket.send(new Blob([new Uint8Array(72)]));
|
||||
socket.send(new Blob([new Uint8Array(20)]));
|
||||
socket.send(repeat("a", 20));
|
||||
socket.send(new Blob([new Uint8Array(20).buffer]));
|
||||
socket.send(new Blob([new Uint8Array(72)]));
|
||||
|
||||
let count = 0;
|
||||
socket.on("message", () => {
|
||||
count++;
|
||||
if (count === 5) {
|
||||
socket.close();
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
72
packages/engine.io-client/test/blob/ws.js
Normal file
72
packages/engine.io-client/test/blob/ws.js
Normal file
@@ -0,0 +1,72 @@
|
||||
const expect = require("expect.js");
|
||||
const eio = require("../../");
|
||||
|
||||
const Blob = require("blob");
|
||||
|
||||
describe("blob", function () {
|
||||
this.timeout(30000);
|
||||
|
||||
it("should be able to receive binary data as blob when bouncing it back (ws)", (done) => {
|
||||
const binaryData = new Int8Array(5);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
binaryData[i] = i;
|
||||
}
|
||||
const socket = new eio.Socket();
|
||||
socket.binaryType = "blob";
|
||||
socket.on("open", () => {
|
||||
socket.on("upgrade", () => {
|
||||
socket.send(binaryData);
|
||||
socket.on("message", (data) => {
|
||||
expect(data).to.be.a(Blob);
|
||||
const fr = new FileReader();
|
||||
fr.onload = function () {
|
||||
const ab = this.result;
|
||||
const ia = new Int8Array(ab);
|
||||
expect(ia).to.eql(binaryData);
|
||||
socket.close();
|
||||
done();
|
||||
};
|
||||
fr.readAsArrayBuffer(data);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should be able to send data as a blob when bouncing it back (ws)", (done) => {
|
||||
const binaryData = new Int8Array(5);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
binaryData[i] = i;
|
||||
}
|
||||
const socket = new eio.Socket();
|
||||
socket.on("open", () => {
|
||||
socket.on("upgrade", () => {
|
||||
socket.send(new Blob([binaryData.buffer]));
|
||||
socket.on("message", (data) => {
|
||||
expect(data).to.be.an(ArrayBuffer);
|
||||
expect(new Int8Array(data)).to.eql(binaryData);
|
||||
socket.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should be able to send data as a blob encoded into base64 when bouncing it back (ws)", (done) => {
|
||||
const binaryData = new Int8Array(5);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
binaryData[i] = i;
|
||||
}
|
||||
const socket = new eio.Socket({ forceBase64: true });
|
||||
socket.on("open", () => {
|
||||
socket.on("upgrade", () => {
|
||||
socket.send(new Blob([binaryData.buffer]));
|
||||
socket.on("message", (data) => {
|
||||
expect(data).to.be.an(ArrayBuffer);
|
||||
expect(new Int8Array(data)).to.eql(binaryData);
|
||||
socket.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
231
packages/engine.io-client/test/connection.js
Normal file
231
packages/engine.io-client/test/connection.js
Normal file
@@ -0,0 +1,231 @@
|
||||
const expect = require("expect.js");
|
||||
const Socket = require("../").Socket;
|
||||
const env = require("./support/env");
|
||||
const { repeat } = require("./util");
|
||||
|
||||
describe("connection", function () {
|
||||
this.timeout(20000);
|
||||
|
||||
it("should connect to localhost", (done) => {
|
||||
const socket = new Socket();
|
||||
socket.on("open", () => {
|
||||
socket.on("message", (data) => {
|
||||
expect(data).to.equal("hi");
|
||||
socket.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should receive multibyte utf-8 strings with polling", (done) => {
|
||||
const socket = new Socket();
|
||||
socket.on("open", () => {
|
||||
socket.send("cash money €€€");
|
||||
socket.on("message", (data) => {
|
||||
if ("hi" === data) return;
|
||||
expect(data).to.be("cash money €€€");
|
||||
socket.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should receive emoji", (done) => {
|
||||
const socket = new Socket();
|
||||
socket.on("open", () => {
|
||||
socket.send(
|
||||
"\uD800\uDC00-\uDB7F\uDFFF\uDB80\uDC00-\uDBFF\uDFFF\uE000-\uF8FF"
|
||||
);
|
||||
socket.on("message", (data) => {
|
||||
if ("hi" === data) return;
|
||||
expect(data).to.be(
|
||||
"\uD800\uDC00-\uDB7F\uDFFF\uDB80\uDC00-\uDBFF\uDFFF\uE000-\uF8FF"
|
||||
);
|
||||
socket.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should not send packets if socket closes", (done) => {
|
||||
const socket = new Socket();
|
||||
socket.on("open", () => {
|
||||
let noPacket = true;
|
||||
socket.on("packetCreate", () => {
|
||||
noPacket = false;
|
||||
});
|
||||
socket.close();
|
||||
socket.send("hi");
|
||||
setTimeout(() => {
|
||||
expect(noPacket).to.be(true);
|
||||
done();
|
||||
}, 1200);
|
||||
});
|
||||
});
|
||||
|
||||
it("should merge packets according to maxPayload value", (done) => {
|
||||
const socket = new Socket({ transports: ["polling"] });
|
||||
socket.on("open", () => {
|
||||
socket.send(repeat("a", 99));
|
||||
socket.send(repeat("b", 30));
|
||||
socket.send(repeat("c", 30));
|
||||
socket.send(repeat("d", 35)); // 3 * 1 (packet type) + 2 * 1 (separator) + 30 + 30 + 35 = 100
|
||||
socket.send(repeat("€", 33));
|
||||
socket.send(repeat("f", 99));
|
||||
|
||||
let count = 0;
|
||||
socket.on("message", () => {
|
||||
count++;
|
||||
if (count === 6) {
|
||||
socket.close();
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should send a packet whose length is above the maxPayload value anyway", (done) => {
|
||||
const socket = new Socket({ transports: ["polling"] });
|
||||
socket.on("open", () => {
|
||||
socket.send(repeat("a", 101));
|
||||
socket.send("b");
|
||||
|
||||
socket.on("close", () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// no `Worker` on old IE
|
||||
if (typeof Worker !== "undefined") {
|
||||
it("should work in a worker", (done) => {
|
||||
const worker = new Worker("/test/support/worker.js");
|
||||
let msg = 0;
|
||||
const utf8yay = "пойду спать всем спокойной ночи";
|
||||
worker.onmessage = (e) => {
|
||||
msg++;
|
||||
if (msg === 1) {
|
||||
expect(e.data).to.be("hi");
|
||||
} else if (msg < 11) {
|
||||
expect(e.data).to.be(utf8yay);
|
||||
} else if (msg < 20) {
|
||||
testBinary(e.data);
|
||||
} else {
|
||||
testBinary(e.data);
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
function testBinary(data) {
|
||||
const byteArray = new Uint8Array(data);
|
||||
for (let i = 0; i < byteArray.byteLength; i++) {
|
||||
expect(byteArray[i]).to.be(i);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (env.wsSupport && !env.isOldSimulator && !env.isAndroid && !env.isIE11) {
|
||||
it("should defer close when upgrading", (done) => {
|
||||
const socket = new Socket();
|
||||
socket.on("open", () => {
|
||||
let upgraded = false;
|
||||
socket
|
||||
.on("upgrade", () => {
|
||||
upgraded = true;
|
||||
})
|
||||
.on("upgrading", () => {
|
||||
socket.on("close", () => {
|
||||
expect(upgraded).to.be(true);
|
||||
done();
|
||||
});
|
||||
socket.close();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should close on upgradeError if closing is deferred", (done) => {
|
||||
const socket = new Socket();
|
||||
socket.on("open", () => {
|
||||
let upgradeError = false;
|
||||
socket
|
||||
.on("upgradeError", () => {
|
||||
upgradeError = true;
|
||||
})
|
||||
.on("upgrading", () => {
|
||||
socket.on("close", () => {
|
||||
expect(upgradeError).to.be(true);
|
||||
done();
|
||||
});
|
||||
socket.close();
|
||||
socket.transport.onError("upgrade error");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should not send packets if closing is deferred", (done) => {
|
||||
const socket = new Socket();
|
||||
socket.on("open", () => {
|
||||
let noPacket = true;
|
||||
socket.on("upgrading", () => {
|
||||
socket.on("packetCreate", () => {
|
||||
noPacket = false;
|
||||
});
|
||||
socket.close();
|
||||
socket.send("hi");
|
||||
});
|
||||
setTimeout(() => {
|
||||
expect(noPacket).to.be(true);
|
||||
done();
|
||||
}, 1200);
|
||||
});
|
||||
});
|
||||
|
||||
it("should send all buffered packets if closing is deferred", (done) => {
|
||||
const socket = new Socket();
|
||||
socket.on("open", () => {
|
||||
socket
|
||||
.on("upgrading", () => {
|
||||
socket.send("hi");
|
||||
socket.close();
|
||||
})
|
||||
.on("close", () => {
|
||||
expect(socket.writeBuffer).to.have.length(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (env.browser && typeof addEventListener === "function") {
|
||||
it("should close the socket when receiving a beforeunload event", (done) => {
|
||||
const socket = new Socket({
|
||||
closeOnBeforeunload: true,
|
||||
});
|
||||
|
||||
const createEvent = (name) => {
|
||||
if (typeof Event === "function") {
|
||||
return new Event(name);
|
||||
} else {
|
||||
// polyfill for IE
|
||||
const event = document.createEvent("Event");
|
||||
event.initEvent(name, true, true);
|
||||
return event;
|
||||
}
|
||||
};
|
||||
|
||||
socket.on("open", () => {
|
||||
const handler = () => {
|
||||
expect(socket.transport.readyState).to.eql("closed");
|
||||
expect(() => socket.write("ignored")).to.not.throwException();
|
||||
|
||||
removeEventListener("beforeunload", handler, false);
|
||||
done();
|
||||
};
|
||||
|
||||
addEventListener("beforeunload", handler, false);
|
||||
dispatchEvent(createEvent("beforeunload"));
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
113
packages/engine.io-client/test/engine.io-client.js
Normal file
113
packages/engine.io-client/test/engine.io-client.js
Normal file
@@ -0,0 +1,113 @@
|
||||
const expect = require("expect.js");
|
||||
const { Socket, protocol } = require("..");
|
||||
const { randomString } = require("../build/cjs/util.js");
|
||||
|
||||
const expectedPort =
|
||||
typeof location !== "undefined" && "https:" === location.protocol
|
||||
? "443"
|
||||
: "80";
|
||||
|
||||
describe("engine.io-client", () => {
|
||||
let open;
|
||||
|
||||
before(() => {
|
||||
open = Socket.prototype.open;
|
||||
// override Socket#open to not connect
|
||||
Socket.prototype.open = () => {};
|
||||
});
|
||||
|
||||
after(() => {
|
||||
Socket.prototype.open = open;
|
||||
});
|
||||
|
||||
it("should expose protocol number", () => {
|
||||
expect(protocol).to.be.a("number");
|
||||
});
|
||||
|
||||
it("should properly parse http uri without port", () => {
|
||||
const client = new Socket("http://localhost");
|
||||
expect(client.port).to.be("80");
|
||||
});
|
||||
|
||||
it("should properly parse https uri without port", () => {
|
||||
const client = new Socket("https://localhost");
|
||||
expect(client.hostname).to.be("localhost");
|
||||
expect(client.port).to.be("443");
|
||||
});
|
||||
|
||||
it("should properly parse wss uri without port", () => {
|
||||
const client = new Socket("wss://localhost");
|
||||
expect(client.hostname).to.be("localhost");
|
||||
expect(client.port).to.be("443");
|
||||
});
|
||||
|
||||
it("should properly parse wss uri with port", () => {
|
||||
const client = new Socket("wss://localhost:2020");
|
||||
expect(client.hostname).to.be("localhost");
|
||||
expect(client.port).to.be("2020");
|
||||
});
|
||||
|
||||
it("should properly parse a host without port", () => {
|
||||
const client = new Socket({ host: "localhost" });
|
||||
expect(client.hostname).to.be("localhost");
|
||||
expect(client.port).to.be(expectedPort);
|
||||
});
|
||||
|
||||
it("should properly parse a host with port", () => {
|
||||
const client = new Socket({ host: "localhost", port: "8080" });
|
||||
expect(client.hostname).to.be("localhost");
|
||||
expect(client.port).to.be("8080");
|
||||
});
|
||||
|
||||
it("should properly handle the addTrailingSlash option", () => {
|
||||
const client = new Socket({ host: "localhost", addTrailingSlash: false });
|
||||
expect(client.hostname).to.be("localhost");
|
||||
expect(client.opts.path).to.be("/engine.io");
|
||||
});
|
||||
|
||||
it("should properly parse an IPv6 uri without port", () => {
|
||||
const client = new Socket("http://[::1]");
|
||||
expect(client.hostname).to.be("::1");
|
||||
expect(client.port).to.be("80");
|
||||
});
|
||||
|
||||
it("should properly parse an IPv6 uri with port", () => {
|
||||
const client = new Socket("http://[::1]:8080");
|
||||
expect(client.hostname).to.be("::1");
|
||||
expect(client.port).to.be("8080");
|
||||
});
|
||||
|
||||
it("should properly parse an IPv6 host without port (1/2)", () => {
|
||||
const client = new Socket({ host: "[::1]" });
|
||||
expect(client.hostname).to.be("::1");
|
||||
expect(client.port).to.be(expectedPort);
|
||||
});
|
||||
|
||||
it("should properly parse an IPv6 host without port (2/2)", () => {
|
||||
const client = new Socket({ secure: true, host: "[::1]" });
|
||||
expect(client.hostname).to.be("::1");
|
||||
expect(client.port).to.be("443");
|
||||
});
|
||||
|
||||
it("should properly parse an IPv6 host with port", () => {
|
||||
const client = new Socket({ host: "[::1]", port: "8080" });
|
||||
expect(client.hostname).to.be("::1");
|
||||
expect(client.port).to.be("8080");
|
||||
});
|
||||
|
||||
it("should properly parse an IPv6 host without brace", () => {
|
||||
const client = new Socket({ host: "::1" });
|
||||
expect(client.hostname).to.be("::1");
|
||||
expect(client.port).to.be(expectedPort);
|
||||
});
|
||||
|
||||
it("should generate a random string", () => {
|
||||
const a = randomString();
|
||||
const b = randomString();
|
||||
const c = randomString();
|
||||
|
||||
expect(a.length).to.eql(8);
|
||||
expect(a).to.not.equal(b);
|
||||
expect(b).to.not.equal(c);
|
||||
});
|
||||
});
|
||||
8
packages/engine.io-client/test/fixtures/no-unref.js
vendored
Normal file
8
packages/engine.io-client/test/fixtures/no-unref.js
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
const { Socket } = require("../..");
|
||||
const socket = new Socket("http://localhost:3000", {
|
||||
autoUnref: false,
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
console.log("process should not exit");
|
||||
}, 500);
|
||||
13
packages/engine.io-client/test/fixtures/unref-polling-only.js
vendored
Normal file
13
packages/engine.io-client/test/fixtures/unref-polling-only.js
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
const { Socket } = require("../..");
|
||||
const socket = new Socket("http://localhost:3000", {
|
||||
autoUnref: true,
|
||||
transports: ["polling"],
|
||||
});
|
||||
|
||||
socket.on("open", () => {
|
||||
console.log("open");
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
console.log("process should exit now");
|
||||
}, 500);
|
||||
13
packages/engine.io-client/test/fixtures/unref-websocket-only.js
vendored
Normal file
13
packages/engine.io-client/test/fixtures/unref-websocket-only.js
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
const { Socket } = require("../..");
|
||||
const socket = new Socket("http://localhost:3000", {
|
||||
autoUnref: true,
|
||||
transports: ["websocket"],
|
||||
});
|
||||
|
||||
socket.on("open", () => {
|
||||
console.log("open");
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
console.log("process should exit now");
|
||||
}, 500);
|
||||
12
packages/engine.io-client/test/fixtures/unref.js
vendored
Normal file
12
packages/engine.io-client/test/fixtures/unref.js
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
const { Socket } = require("../..");
|
||||
const socket = new Socket("http://localhost:3000", {
|
||||
autoUnref: true,
|
||||
});
|
||||
|
||||
socket.on("open", () => {
|
||||
console.log("open");
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
console.log("process should exit now");
|
||||
}, 500);
|
||||
28
packages/engine.io-client/test/index.js
Normal file
28
packages/engine.io-client/test/index.js
Normal file
@@ -0,0 +1,28 @@
|
||||
const env = require("./support/env");
|
||||
|
||||
// whitelist some globals to avoid warnings
|
||||
if (env.browser) {
|
||||
window.___eio = null;
|
||||
} else {
|
||||
require("./node");
|
||||
}
|
||||
|
||||
const Blob = require("blob");
|
||||
|
||||
require("./engine.io-client");
|
||||
require("./socket");
|
||||
require("./transport");
|
||||
require("./connection");
|
||||
require("./xmlhttprequest");
|
||||
require("./parseuri");
|
||||
|
||||
if (typeof ArrayBuffer !== "undefined") {
|
||||
require("./arraybuffer");
|
||||
} else {
|
||||
require("./binary-fallback");
|
||||
}
|
||||
|
||||
// Blob is available in Node.js since v18, but not yet supported by the `engine.io-parser` package
|
||||
if (Blob && env.browser) {
|
||||
require("./blob");
|
||||
}
|
||||
138
packages/engine.io-client/test/node.js
Normal file
138
packages/engine.io-client/test/node.js
Normal file
@@ -0,0 +1,138 @@
|
||||
const path = require("path");
|
||||
const { exec } = require("child_process");
|
||||
const { Socket } = require("../");
|
||||
const { repeat } = require("./util");
|
||||
const expect = require("expect.js");
|
||||
const { parse } = require("../build/cjs/globals.node.js");
|
||||
|
||||
describe("node.js", () => {
|
||||
describe("autoRef option", () => {
|
||||
const fixture = (filename) =>
|
||||
process.execPath + " " + path.join(__dirname, "fixtures", filename);
|
||||
|
||||
it("should stop once the timer is triggered", (done) => {
|
||||
exec(fixture("unref.js"), done);
|
||||
});
|
||||
|
||||
it("should stop once the timer is triggered (polling)", (done) => {
|
||||
exec(fixture("unref-polling-only.js"), done);
|
||||
});
|
||||
|
||||
it("should stop once the timer is triggered (websocket)", (done) => {
|
||||
exec(fixture("unref-websocket-only.js"), done);
|
||||
});
|
||||
|
||||
it("should not stop with autoUnref set to false", (done) => {
|
||||
let isComplete = false;
|
||||
|
||||
const process = exec(fixture("no-unref.js"), () => {
|
||||
if (!isComplete) {
|
||||
done(new Error("should not happen"));
|
||||
}
|
||||
});
|
||||
setTimeout(() => {
|
||||
isComplete = true;
|
||||
process.kill();
|
||||
done();
|
||||
}, 1000);
|
||||
});
|
||||
});
|
||||
|
||||
it("should merge binary packets according to maxPayload value", (done) => {
|
||||
const socket = new Socket({ transports: ["polling"] });
|
||||
socket.on("open", () => {
|
||||
socket.send(Buffer.allocUnsafe(72));
|
||||
socket.send(Buffer.allocUnsafe(20));
|
||||
socket.send(repeat("a", 20));
|
||||
socket.send(Buffer.allocUnsafe(20));
|
||||
socket.send(Buffer.allocUnsafe(72));
|
||||
|
||||
let count = 0;
|
||||
socket.on("message", () => {
|
||||
count++;
|
||||
if (count === 5) {
|
||||
socket.close();
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should send cookies with withCredentials: true", (done) => {
|
||||
const socket = new Socket("http://localhost:3000", {
|
||||
transports: ["polling"],
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
socket.on("open", () => {
|
||||
setTimeout(() => {
|
||||
socket.send("sendHeaders");
|
||||
}, 10);
|
||||
});
|
||||
|
||||
socket.on("message", (data) => {
|
||||
if (data === "hi") {
|
||||
return;
|
||||
}
|
||||
const headers = JSON.parse(data);
|
||||
expect(headers.cookie).to.eql("1=1; 2=2");
|
||||
|
||||
socket.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not send cookies with withCredentials: false", (done) => {
|
||||
const socket = new Socket("http://localhost:3000", {
|
||||
transports: ["polling"],
|
||||
withCredentials: false,
|
||||
});
|
||||
|
||||
socket.on("open", () => {
|
||||
socket.send("sendHeaders");
|
||||
});
|
||||
|
||||
socket.on("message", (data) => {
|
||||
if (data === "hi") {
|
||||
return;
|
||||
}
|
||||
const headers = JSON.parse(data);
|
||||
expect(headers.cookie).to.eql(undefined);
|
||||
|
||||
socket.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("cookie parsing", () => {
|
||||
it("should parse a simple set-cookie header", () => {
|
||||
const cookieStr = "foo=bar";
|
||||
|
||||
expect(parse(cookieStr)).to.eql({
|
||||
name: "foo",
|
||||
value: "bar",
|
||||
});
|
||||
});
|
||||
|
||||
it("should parse a complex set-cookie header", () => {
|
||||
const cookieStr =
|
||||
"foo=bar; Max-Age=1000; Domain=.example.com; Path=/; Expires=Tue, 01 Jul 2025 10:01:11 GMT; HttpOnly; Secure; SameSite=strict";
|
||||
|
||||
expect(parse(cookieStr)).to.eql({
|
||||
name: "foo",
|
||||
value: "bar",
|
||||
expires: new Date("Tue Jul 01 2025 06:01:11 GMT-0400 (EDT)"),
|
||||
});
|
||||
});
|
||||
|
||||
it("should parse a weird but valid cookie", () => {
|
||||
const cookieStr =
|
||||
"foo=bar=bar&foo=foo&John=Doe&Doe=John; Domain=.example.com; Path=/; HttpOnly; Secure";
|
||||
|
||||
expect(parse(cookieStr)).to.eql({
|
||||
name: "foo",
|
||||
value: "bar=bar&foo=foo&John=Doe&Doe=John",
|
||||
});
|
||||
});
|
||||
});
|
||||
71
packages/engine.io-client/test/parseuri.js
Normal file
71
packages/engine.io-client/test/parseuri.js
Normal file
@@ -0,0 +1,71 @@
|
||||
// imported from https://github.com/galkn/parseuri
|
||||
const expect = require("expect.js");
|
||||
const parseuri = require("..").parse;
|
||||
const { repeat } = require("./util");
|
||||
|
||||
describe("parseuri", function () {
|
||||
it("should parse an uri", function () {
|
||||
const http = parseuri("http://google.com"),
|
||||
https = parseuri("https://www.google.com:80"),
|
||||
query = parseuri("google.com:8080/foo/bar?foo=bar"),
|
||||
localhost = parseuri("localhost:8080"),
|
||||
ipv6 = parseuri("2001:0db8:85a3:0042:1000:8a2e:0370:7334"),
|
||||
ipv6short = parseuri("2001:db8:85a3:42:1000:8a2e:370:7334"),
|
||||
ipv6port = parseuri("2001:db8:85a3:42:1000:8a2e:370:7334:80"),
|
||||
ipv6abbrev = parseuri("2001::7334:a:80"),
|
||||
ipv6http = parseuri("http://[2001::7334:a]:80"),
|
||||
ipv6query = parseuri("http://[2001::7334:a]:80/foo/bar?foo=bar");
|
||||
|
||||
expect(http.protocol).to.be("http");
|
||||
expect(http.port).to.be("");
|
||||
expect(http.host).to.be("google.com");
|
||||
expect(https.protocol).to.be("https");
|
||||
expect(https.port).to.be("80");
|
||||
expect(https.host).to.be("www.google.com");
|
||||
expect(query.port).to.be("8080");
|
||||
expect(query.query).to.be("foo=bar");
|
||||
expect(query.path).to.be("/foo/bar");
|
||||
expect(query.relative).to.be("/foo/bar?foo=bar");
|
||||
expect(query.queryKey.foo).to.be("bar");
|
||||
expect(query.pathNames[0]).to.be("foo");
|
||||
expect(query.pathNames[1]).to.be("bar");
|
||||
expect(localhost.protocol).to.be("");
|
||||
expect(localhost.host).to.be("localhost");
|
||||
expect(localhost.port).to.be("8080");
|
||||
expect(ipv6.protocol).to.be("");
|
||||
expect(ipv6.host).to.be("2001:0db8:85a3:0042:1000:8a2e:0370:7334");
|
||||
expect(ipv6.port).to.be("");
|
||||
expect(ipv6short.protocol).to.be("");
|
||||
expect(ipv6short.host).to.be("2001:db8:85a3:42:1000:8a2e:370:7334");
|
||||
expect(ipv6short.port).to.be("");
|
||||
expect(ipv6port.protocol).to.be("");
|
||||
expect(ipv6port.host).to.be("2001:db8:85a3:42:1000:8a2e:370:7334");
|
||||
expect(ipv6port.port).to.be("80");
|
||||
expect(ipv6abbrev.protocol).to.be("");
|
||||
expect(ipv6abbrev.host).to.be("2001::7334:a:80");
|
||||
expect(ipv6abbrev.port).to.be("");
|
||||
expect(ipv6http.protocol).to.be("http");
|
||||
expect(ipv6http.port).to.be("80");
|
||||
expect(ipv6http.host).to.be("2001::7334:a");
|
||||
expect(ipv6query.protocol).to.be("http");
|
||||
expect(ipv6query.port).to.be("80");
|
||||
expect(ipv6query.host).to.be("2001::7334:a");
|
||||
expect(ipv6query.relative).to.be("/foo/bar?foo=bar");
|
||||
|
||||
const withUserInfo = parseuri("ws://foo:bar@google.com");
|
||||
|
||||
expect(withUserInfo.protocol).to.eql("ws");
|
||||
expect(withUserInfo.userInfo).to.eql("foo:bar");
|
||||
expect(withUserInfo.user).to.eql("foo");
|
||||
expect(withUserInfo.password).to.eql("bar");
|
||||
expect(withUserInfo.host).to.eql("google.com");
|
||||
|
||||
const relativeWithQuery = parseuri("/foo?bar=@example.com");
|
||||
|
||||
expect(relativeWithQuery.host).to.be("");
|
||||
expect(relativeWithQuery.path).to.be("/foo");
|
||||
expect(relativeWithQuery.query).to.be("bar=@example.com");
|
||||
|
||||
expect(() => parseuri(repeat("a", 8001))).to.throwError("URI too long");
|
||||
});
|
||||
});
|
||||
273
packages/engine.io-client/test/socket.js
Normal file
273
packages/engine.io-client/test/socket.js
Normal file
@@ -0,0 +1,273 @@
|
||||
const expect = require("expect.js");
|
||||
const { Socket, NodeXHR, NodeWebSocket } = require("../");
|
||||
const {
|
||||
isIE11,
|
||||
isAndroid,
|
||||
isEdge,
|
||||
isIPad,
|
||||
useFetch,
|
||||
} = require("./support/env");
|
||||
const FakeTimers = require("@sinonjs/fake-timers");
|
||||
const { repeat } = require("./util");
|
||||
|
||||
describe("Socket", function () {
|
||||
this.timeout(10000);
|
||||
|
||||
describe("filterUpgrades", () => {
|
||||
it("should return only available transports", () => {
|
||||
const socket = new Socket({ transports: ["polling"] });
|
||||
expect(socket._filterUpgrades(["polling", "websocket"])).to.eql([
|
||||
"polling",
|
||||
]);
|
||||
socket.close();
|
||||
});
|
||||
});
|
||||
|
||||
it("throws an error when no transports are available", (done) => {
|
||||
const socket = new Socket({ transports: [] });
|
||||
let errorMessage = "";
|
||||
socket.on("error", (error) => {
|
||||
errorMessage = error;
|
||||
});
|
||||
setTimeout(() => {
|
||||
expect(errorMessage).to.be("No transports available");
|
||||
socket.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should connect with the 2nd transport if tryAllTransports is `true` (polling)", (done) => {
|
||||
const socket = new Socket({
|
||||
transports: ["websocket", "polling"],
|
||||
transportOptions: {
|
||||
websocket: {
|
||||
query: {
|
||||
deny: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
tryAllTransports: true,
|
||||
});
|
||||
|
||||
socket.on("open", () => {
|
||||
expect(socket.transport.name).to.eql("polling");
|
||||
socket.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should connect with the 2nd transport if tryAllTransports is `true` (websocket)", (done) => {
|
||||
const socket = new Socket({
|
||||
transports: ["polling", "websocket"],
|
||||
transportOptions: {
|
||||
polling: {
|
||||
query: {
|
||||
deny: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
tryAllTransports: true,
|
||||
});
|
||||
|
||||
socket.on("open", () => {
|
||||
expect(socket.transport.name).to.eql("websocket");
|
||||
socket.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not connect with the 2nd transport if tryAllTransports is `false`", (done) => {
|
||||
const socket = new Socket({
|
||||
transports: ["polling", "websocket"],
|
||||
transportOptions: {
|
||||
polling: {
|
||||
query: {
|
||||
deny: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
socket.on("error", (err) => {
|
||||
expect(err.message).to.eql(
|
||||
useFetch ? "fetch read error" : "xhr poll error"
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should connect with a custom transport implementation (polling)", (done) => {
|
||||
const socket = new Socket({
|
||||
transports: [NodeXHR],
|
||||
});
|
||||
|
||||
socket.on("open", () => {
|
||||
expect(socket.transport.name).to.eql("polling");
|
||||
socket.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should connect with a custom transport implementation (websocket)", (done) => {
|
||||
const socket = new Socket({
|
||||
transports: [NodeWebSocket],
|
||||
});
|
||||
|
||||
socket.on("open", () => {
|
||||
expect(socket.transport.name).to.eql("websocket");
|
||||
socket.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe("fake timers", function () {
|
||||
before(function () {
|
||||
if (isIE11 || isAndroid || isEdge || isIPad) {
|
||||
this.skip();
|
||||
}
|
||||
});
|
||||
|
||||
it("uses window timeout by default", (done) => {
|
||||
const clock = FakeTimers.install();
|
||||
const socket = new Socket({ transports: [] });
|
||||
let errorMessage = "";
|
||||
socket.on("error", (error) => {
|
||||
errorMessage = error;
|
||||
});
|
||||
clock.tick(1); // Should trigger error emit.
|
||||
expect(errorMessage).to.be("No transports available");
|
||||
clock.uninstall();
|
||||
socket.close();
|
||||
done();
|
||||
});
|
||||
|
||||
it.skip("uses custom timeout when provided", (done) => {
|
||||
const clock = FakeTimers.install();
|
||||
const socket = new Socket({
|
||||
transports: [],
|
||||
useNativeTimers: true,
|
||||
});
|
||||
|
||||
let errorMessage = "";
|
||||
socket.on("error", (error) => {
|
||||
errorMessage = error;
|
||||
});
|
||||
socket.open();
|
||||
// Socket should not use the mocked clock, so this should have no side
|
||||
// effects.
|
||||
clock.tick(1);
|
||||
expect(errorMessage).to.be("");
|
||||
clock.uninstall();
|
||||
|
||||
setTimeout(() => {
|
||||
try {
|
||||
expect(errorMessage).to.be("No transports available");
|
||||
socket.close();
|
||||
done();
|
||||
} finally {
|
||||
}
|
||||
}, 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("close", () => {
|
||||
it("provides details when maxHttpBufferSize is reached (polling)", (done) => {
|
||||
const socket = new Socket({ transports: ["polling"] });
|
||||
socket.on("open", () => {
|
||||
socket.send(repeat("a", 101)); // over the maxHttpBufferSize value of the server
|
||||
});
|
||||
|
||||
socket.on("error", (err) => {
|
||||
expect(err).to.be.an(Error);
|
||||
expect(err.type).to.eql("TransportError");
|
||||
expect(err.description).to.eql(413);
|
||||
if (useFetch) {
|
||||
expect(err.message).to.eql("fetch write error");
|
||||
} else {
|
||||
expect(err.message).to.eql("xhr post error");
|
||||
// err.context is a XMLHttpRequest object
|
||||
expect(err.context.readyState).to.eql(4);
|
||||
expect(err.context.responseText).to.eql("");
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("close", (reason, details) => {
|
||||
expect(reason).to.eql("transport error");
|
||||
expect(details).to.be.an(Error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("provides details when maxHttpBufferSize is reached (websocket)", (done) => {
|
||||
const socket = new Socket({ transports: ["websocket"] });
|
||||
socket.on("open", () => {
|
||||
socket.send(repeat("a", 101)); // over the maxHttpBufferSize value of the server
|
||||
});
|
||||
|
||||
socket.on("close", (reason, details) => {
|
||||
if (isIE11) {
|
||||
expect(reason).to.eql("transport error");
|
||||
expect(details).to.be.an(Error);
|
||||
} else {
|
||||
expect(reason).to.eql("transport close");
|
||||
expect(details.description).to.eql("websocket connection closed");
|
||||
// details.context is a CloseEvent object
|
||||
expect(details.context.code).to.eql(1009); // "Message Too Big" (see https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code)
|
||||
expect(details.context.reason).to.eql("");
|
||||
// note: details.context.wasClean is false in the browser, but true in Node.js
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("provides details when the session ID is unknown (polling)", (done) => {
|
||||
const socket = new Socket({
|
||||
transports: ["polling"],
|
||||
query: { sid: "abc" },
|
||||
});
|
||||
|
||||
socket.on("error", (err) => {
|
||||
expect(err).to.be.an(Error);
|
||||
expect(err.type).to.eql("TransportError");
|
||||
expect(err.description).to.eql(400);
|
||||
if (useFetch) {
|
||||
expect(err.message).to.eql("fetch read error");
|
||||
} else {
|
||||
expect(err.message).to.eql("xhr poll error");
|
||||
// err.context is a XMLHttpRequest object
|
||||
expect(err.context.readyState).to.eql(4);
|
||||
expect(err.context.responseText).to.eql(
|
||||
'{"code":1,"message":"Session ID unknown"}'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("close", (reason, details) => {
|
||||
expect(reason).to.eql("transport error");
|
||||
expect(details).to.be.an(Error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("provides details when the session ID is unknown (websocket)", (done) => {
|
||||
const socket = new Socket({
|
||||
transports: ["websocket"],
|
||||
query: { sid: "abc" },
|
||||
});
|
||||
|
||||
socket.on("error", (err) => {
|
||||
expect(err).to.be.an(Error);
|
||||
expect(err.type).to.eql("TransportError");
|
||||
expect(err.message).to.eql("websocket error");
|
||||
// err.description is a generic Event
|
||||
expect(err.description.type).to.be("error");
|
||||
});
|
||||
|
||||
socket.on("close", (reason, details) => {
|
||||
expect(reason).to.eql("transport error");
|
||||
expect(details).to.be.an(Error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
37
packages/engine.io-client/test/support/env.js
Normal file
37
packages/engine.io-client/test/support/env.js
Normal file
@@ -0,0 +1,37 @@
|
||||
/* global location:true */
|
||||
|
||||
// WARNING this is bad practice
|
||||
// we only do this in our tests because we need to test engine.io-client
|
||||
// support in browsers and in node.js
|
||||
// some tests do not yet work in both
|
||||
exports.browser = typeof window !== "undefined";
|
||||
exports.wsSupport = !!(
|
||||
typeof window === "undefined" ||
|
||||
window.WebSocket ||
|
||||
window.MozWebSocket
|
||||
);
|
||||
|
||||
const userAgent = typeof navigator !== "undefined" ? navigator.userAgent : "";
|
||||
exports.isOldSimulator =
|
||||
~userAgent.indexOf("iPhone OS 4") || ~userAgent.indexOf("iPhone OS 5");
|
||||
exports.isIE9 = /MSIE 9/.test(userAgent);
|
||||
exports.isIE10 = /MSIE 10/.test(userAgent);
|
||||
exports.isIE11 = !!userAgent.match(/Trident.*rv[ :]*11\./); // ws doesn't work at all in sauce labs
|
||||
exports.isAndroid = userAgent.match(/Android/i);
|
||||
exports.isEdge = /Edg/.test(userAgent);
|
||||
exports.isIPad = /iPad/.test(userAgent);
|
||||
|
||||
if (typeof location === "undefined") {
|
||||
location = {
|
||||
hostname: "localhost",
|
||||
port: 3000,
|
||||
};
|
||||
}
|
||||
|
||||
exports.useFetch = !exports.browser && process.env.USE_FETCH !== undefined;
|
||||
|
||||
if (exports.useFetch) {
|
||||
console.warn("testing with fetch() instead of XMLHttpRequest");
|
||||
const { transports, Fetch } = require("../..");
|
||||
transports.polling = Fetch;
|
||||
}
|
||||
88
packages/engine.io-client/test/support/hooks.js
Normal file
88
packages/engine.io-client/test/support/hooks.js
Normal file
@@ -0,0 +1,88 @@
|
||||
// this is a test server to support tests which make requests
|
||||
|
||||
const express = require("express");
|
||||
const { join } = require("path");
|
||||
const { createServer } = require("http");
|
||||
const { attach } = require("engine.io");
|
||||
const { rollup } = require("rollup");
|
||||
|
||||
const rollupConfig = require("../../support/rollup.config.umd.js")[1];
|
||||
const { serialize } = require("cookie");
|
||||
|
||||
let httpServer, engine;
|
||||
|
||||
exports.mochaHooks = {
|
||||
beforeAll() {
|
||||
const app = express();
|
||||
httpServer = createServer(app);
|
||||
|
||||
engine = attach(httpServer, {
|
||||
pingInterval: 500,
|
||||
maxHttpBufferSize: 100,
|
||||
allowRequest: (req, fn) => {
|
||||
const denyRequest = new URL(`http://${req.url}`).searchParams.has(
|
||||
"deny"
|
||||
);
|
||||
fn(null, !denyRequest);
|
||||
},
|
||||
});
|
||||
|
||||
rollup(rollupConfig).then(async (bundle) => {
|
||||
await bundle.write({
|
||||
...rollupConfig.output,
|
||||
file: "./test/support/public/engine.io.min.js",
|
||||
sourcemap: false,
|
||||
});
|
||||
|
||||
await bundle.close();
|
||||
});
|
||||
|
||||
httpServer.listen(process.env.ZUUL_PORT || 3000);
|
||||
|
||||
// serve worker.js and engine.io.js as raw file
|
||||
app.use("/test/support", express.static(join(__dirname, "public")));
|
||||
|
||||
engine.on("connection", (socket) => {
|
||||
socket.send("hi");
|
||||
|
||||
// Bounce any received messages back
|
||||
socket.on("message", (data) => {
|
||||
if (data === "give binary") {
|
||||
const abv = new Int8Array(5);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
abv[i] = i;
|
||||
}
|
||||
socket.send(abv);
|
||||
return;
|
||||
} else if (data === "give utf8") {
|
||||
socket.send("пойду спать всем спокойной ночи");
|
||||
return;
|
||||
} else if (data === "sendHeaders") {
|
||||
const headers = socket.transport?.dataReq?.headers;
|
||||
return socket.send(JSON.stringify(headers));
|
||||
}
|
||||
|
||||
socket.send(data);
|
||||
});
|
||||
});
|
||||
|
||||
engine.on("initial_headers", (headers) => {
|
||||
headers["set-cookie"] = [
|
||||
serialize("1", "1", { maxAge: 86400 }),
|
||||
serialize("2", "2", {
|
||||
sameSite: true,
|
||||
path: "/",
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
}),
|
||||
serialize("3", "3", { maxAge: 0 }),
|
||||
serialize("4", "4", { expires: new Date() }),
|
||||
];
|
||||
});
|
||||
},
|
||||
|
||||
afterAll() {
|
||||
httpServer.close();
|
||||
engine.close();
|
||||
},
|
||||
};
|
||||
16
packages/engine.io-client/test/support/public/worker.js
Normal file
16
packages/engine.io-client/test/support/public/worker.js
Normal file
@@ -0,0 +1,16 @@
|
||||
/* global importScripts,eio,postMessage */
|
||||
|
||||
importScripts("/test/support/engine.io.min.js");
|
||||
|
||||
var socket = eio();
|
||||
|
||||
var count = 0;
|
||||
socket.on("message", function (msg) {
|
||||
count++;
|
||||
if (count < 10) {
|
||||
socket.send("give utf8");
|
||||
} else if (count < 20) {
|
||||
socket.send("give binary");
|
||||
}
|
||||
postMessage(msg);
|
||||
});
|
||||
49
packages/engine.io-client/test/support/server.js
Normal file
49
packages/engine.io-client/test/support/server.js
Normal file
@@ -0,0 +1,49 @@
|
||||
// this is a test server to support tests which make requests
|
||||
|
||||
const express = require("express");
|
||||
const app = express();
|
||||
const join = require("path").join;
|
||||
const http = require("http").Server(app);
|
||||
const server = require("engine.io").attach(http, {
|
||||
pingInterval: 500,
|
||||
maxHttpBufferSize: 100,
|
||||
});
|
||||
const { rollup } = require("rollup");
|
||||
|
||||
const rollupConfig = require("../../support/rollup.config.umd.js")[1];
|
||||
|
||||
rollup(rollupConfig).then(async (bundle) => {
|
||||
await bundle.write({
|
||||
...rollupConfig.output,
|
||||
file: "./test/support/public/engine.io.min.js",
|
||||
sourcemap: false,
|
||||
});
|
||||
|
||||
await bundle.close();
|
||||
});
|
||||
|
||||
http.listen(process.env.ZUUL_PORT || 3000);
|
||||
|
||||
// serve worker.js and engine.io.js as raw file
|
||||
app.use("/test/support", express.static(join(__dirname, "public")));
|
||||
|
||||
server.on("connection", (socket) => {
|
||||
socket.send("hi");
|
||||
|
||||
// Bounce any received messages back
|
||||
socket.on("message", (data) => {
|
||||
if (data === "give binary") {
|
||||
const abv = new Int8Array(5);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
abv[i] = i;
|
||||
}
|
||||
socket.send(abv);
|
||||
return;
|
||||
} else if (data === "give utf8") {
|
||||
socket.send("пойду спать всем спокойной ночи");
|
||||
return;
|
||||
}
|
||||
|
||||
socket.send(data);
|
||||
});
|
||||
});
|
||||
330
packages/engine.io-client/test/transport.js
Normal file
330
packages/engine.io-client/test/transport.js
Normal file
@@ -0,0 +1,330 @@
|
||||
const expect = require("expect.js");
|
||||
const eio = require("../");
|
||||
const env = require("./support/env");
|
||||
|
||||
// Disables eslint to capitalise constructor names
|
||||
/* eslint-disable new-cap */
|
||||
|
||||
describe("Transport", () => {
|
||||
describe("rememberUpgrade", () => {
|
||||
it("should remember websocket connection", (done) => {
|
||||
const socket = new eio.Socket();
|
||||
expect(socket.transport.name).to.be("polling");
|
||||
|
||||
let timedout = false;
|
||||
const timeout = setTimeout(() => {
|
||||
timedout = true;
|
||||
socket.close();
|
||||
done();
|
||||
}, 300);
|
||||
|
||||
socket.on("upgrade", (transport) => {
|
||||
if (timedout) return;
|
||||
clearTimeout(timeout);
|
||||
socket.close();
|
||||
if (transport.name === "websocket") {
|
||||
const socket2 = new eio.Socket({ rememberUpgrade: true });
|
||||
expect(socket2.transport.name).to.be("websocket");
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not remember websocket connection", (done) => {
|
||||
const socket = new eio.Socket();
|
||||
expect(socket.transport.name).to.be("polling");
|
||||
|
||||
let timedout = false;
|
||||
const timeout = setTimeout(() => {
|
||||
timedout = true;
|
||||
socket.close();
|
||||
done();
|
||||
}, 300);
|
||||
|
||||
socket.on("upgrade", (transport) => {
|
||||
if (timedout) return;
|
||||
clearTimeout(timeout);
|
||||
socket.close();
|
||||
if (transport.name === "websocket") {
|
||||
const socket2 = new eio.Socket({ rememberUpgrade: false });
|
||||
expect(socket2.transport.name).to.not.be("websocket");
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("public constructors", () => {
|
||||
it("should include Transport", () => {
|
||||
expect(eio.Transport).to.be.a("function");
|
||||
});
|
||||
|
||||
it("should include Polling and WebSocket", () => {
|
||||
expect(eio.transports).to.be.an("object");
|
||||
expect(eio.transports.polling).to.be.a("function");
|
||||
expect(eio.transports.websocket).to.be.a("function");
|
||||
});
|
||||
});
|
||||
|
||||
describe("transport uris", () => {
|
||||
it("should generate an http uri", () => {
|
||||
const polling = new eio.transports.polling({
|
||||
path: "/engine.io",
|
||||
hostname: "localhost",
|
||||
secure: false,
|
||||
query: { sid: "test" },
|
||||
timestampRequests: false,
|
||||
});
|
||||
expect(polling.uri()).to.contain("http://localhost/engine.io?sid=test");
|
||||
});
|
||||
|
||||
it("should generate an http uri w/o a port", () => {
|
||||
const polling = new eio.transports.polling({
|
||||
path: "/engine.io",
|
||||
hostname: "localhost",
|
||||
secure: false,
|
||||
query: { sid: "test" },
|
||||
port: 80,
|
||||
timestampRequests: false,
|
||||
});
|
||||
expect(polling.uri()).to.contain("http://localhost/engine.io?sid=test");
|
||||
});
|
||||
|
||||
it("should generate an http uri with a port", () => {
|
||||
const polling = new eio.transports.polling({
|
||||
path: "/engine.io",
|
||||
hostname: "localhost",
|
||||
secure: false,
|
||||
query: { sid: "test" },
|
||||
port: 3000,
|
||||
timestampRequests: false,
|
||||
});
|
||||
expect(polling.uri()).to.contain(
|
||||
"http://localhost:3000/engine.io?sid=test"
|
||||
);
|
||||
});
|
||||
|
||||
it("should generate an https uri w/o a port", () => {
|
||||
const polling = new eio.transports.polling({
|
||||
path: "/engine.io",
|
||||
hostname: "localhost",
|
||||
secure: true,
|
||||
query: { sid: "test" },
|
||||
port: 443,
|
||||
timestampRequests: false,
|
||||
});
|
||||
expect(polling.uri()).to.contain("https://localhost/engine.io?sid=test");
|
||||
});
|
||||
|
||||
it("should generate a timestamped uri", () => {
|
||||
const polling = new eio.transports.polling({
|
||||
path: "/engine.io",
|
||||
hostname: "localhost",
|
||||
timestampParam: "t",
|
||||
timestampRequests: true,
|
||||
});
|
||||
expect(polling.uri()).to.match(
|
||||
/http:\/\/localhost\/engine\.io\?(j=[0-9]+&)?(t=[0-9A-Za-z-_]+)/
|
||||
);
|
||||
});
|
||||
|
||||
it("should generate an ipv6 uri", () => {
|
||||
const polling = new eio.transports.polling({
|
||||
path: "/engine.io",
|
||||
hostname: "::1",
|
||||
secure: false,
|
||||
port: 80,
|
||||
timestampRequests: false,
|
||||
});
|
||||
expect(polling.uri()).to.contain("http://[::1]/engine.io");
|
||||
});
|
||||
|
||||
it("should generate an ipv6 uri with port", () => {
|
||||
const polling = new eio.transports.polling({
|
||||
path: "/engine.io",
|
||||
hostname: "::1",
|
||||
secure: false,
|
||||
port: 8080,
|
||||
timestampRequests: false,
|
||||
});
|
||||
expect(polling.uri()).to.contain("http://[::1]:8080/engine.io");
|
||||
});
|
||||
|
||||
it("should generate a ws uri", () => {
|
||||
const ws = new eio.transports.websocket({
|
||||
path: "/engine.io",
|
||||
hostname: "test",
|
||||
secure: false,
|
||||
query: { transport: "websocket" },
|
||||
timestampRequests: false,
|
||||
});
|
||||
expect(ws.uri()).to.be("ws://test/engine.io?transport=websocket");
|
||||
});
|
||||
|
||||
it("should generate a wss uri", () => {
|
||||
const ws = new eio.transports.websocket({
|
||||
path: "/engine.io",
|
||||
hostname: "test",
|
||||
secure: true,
|
||||
query: {},
|
||||
timestampRequests: false,
|
||||
});
|
||||
expect(ws.uri()).to.be("wss://test/engine.io");
|
||||
});
|
||||
|
||||
it("should timestamp ws uris", () => {
|
||||
const ws = new eio.transports.websocket({
|
||||
path: "/engine.io",
|
||||
hostname: "localhost",
|
||||
timestampParam: "woot",
|
||||
timestampRequests: true,
|
||||
});
|
||||
expect(ws.uri()).to.match(
|
||||
/ws:\/\/localhost\/engine\.io\?woot=[0-9A-Za-z-_]+/
|
||||
);
|
||||
});
|
||||
|
||||
it("should generate a ws ipv6 uri", () => {
|
||||
const ws = new eio.transports.websocket({
|
||||
path: "/engine.io",
|
||||
hostname: "::1",
|
||||
secure: false,
|
||||
port: 80,
|
||||
timestampRequests: false,
|
||||
});
|
||||
expect(ws.uri()).to.be("ws://[::1]/engine.io");
|
||||
});
|
||||
|
||||
it("should generate a ws ipv6 uri with port", () => {
|
||||
const ws = new eio.transports.websocket({
|
||||
path: "/engine.io",
|
||||
hostname: "::1",
|
||||
secure: false,
|
||||
port: 8080,
|
||||
timestampRequests: false,
|
||||
});
|
||||
expect(ws.uri()).to.be("ws://[::1]:8080/engine.io");
|
||||
});
|
||||
});
|
||||
|
||||
// these are server only
|
||||
if (!env.browser) {
|
||||
describe("options", () => {
|
||||
it("should accept an `agent` option for WebSockets", (done) => {
|
||||
const polling = new eio.transports.websocket({
|
||||
path: "/engine.io",
|
||||
hostname: "localhost",
|
||||
agent: {
|
||||
addRequest: () => {
|
||||
done();
|
||||
},
|
||||
},
|
||||
});
|
||||
polling.doOpen();
|
||||
});
|
||||
it("should accept an `agent` option for XMLHttpRequest", function (done) {
|
||||
if (env.useFetch) {
|
||||
return this.skip();
|
||||
}
|
||||
|
||||
const polling = new eio.transports.polling({
|
||||
path: "/engine.io",
|
||||
hostname: "localhost",
|
||||
agent: {
|
||||
addRequest: () => {
|
||||
done();
|
||||
},
|
||||
},
|
||||
});
|
||||
polling.doOpen();
|
||||
});
|
||||
|
||||
describe("for extraHeaders", () => {
|
||||
it("should correctly set them for WebSockets", () => {
|
||||
const headers = {
|
||||
"X-Custom-Header-For-My-Project": "my-secret-access-token",
|
||||
Cookie:
|
||||
"user_session=NI2JlCKF90aE0sJZD9ZzujtdsUqNYSBYxzlTsvdSUe35ZzdtVRGqYFr0kdGxbfc5gUOkR9RGp20GVKza; path=/; expires=Tue, 07-Apr-2015 18:18:08 GMT; secure; HttpOnly",
|
||||
};
|
||||
const polling = new eio.transports.websocket({
|
||||
path: "/engine.io",
|
||||
hostname: "localhost",
|
||||
extraHeaders: headers,
|
||||
});
|
||||
expect(polling.opts.extraHeaders).to.equal(headers);
|
||||
});
|
||||
it("should correctly set them for XMLHttpRequest", () => {
|
||||
const headers = {
|
||||
"X-Custom-Header-For-My-Project": "my-secret-access-token",
|
||||
Cookie:
|
||||
"user_session=NI2JlCKF90aE0sJZD9ZzujtdsUqNYSBYxzlTsvdSUe35ZzdtVRGqYFr0kdGxbfc5gUOkR9RGp20GVKza; path=/; expires=Tue, 07-Apr-2015 18:18:08 GMT; secure; HttpOnly",
|
||||
};
|
||||
const polling = new eio.transports.polling({
|
||||
path: "/engine.io",
|
||||
hostname: "localhost",
|
||||
extraHeaders: headers,
|
||||
});
|
||||
expect(polling.opts.extraHeaders).to.equal(headers);
|
||||
});
|
||||
});
|
||||
|
||||
describe("perMessageDeflate", () => {
|
||||
it("should set threshold", (done) => {
|
||||
const socket = new eio.Socket({
|
||||
transports: ["websocket"],
|
||||
perMessageDeflate: { threshold: 0 },
|
||||
});
|
||||
socket.on("open", () => {
|
||||
const ws = socket.transport.ws;
|
||||
const send = ws.send;
|
||||
ws.send = (data, opts, callback) => {
|
||||
ws.send = send;
|
||||
ws.send(data, opts, callback);
|
||||
|
||||
expect(opts.compress).to.be(true);
|
||||
socket.close();
|
||||
done();
|
||||
};
|
||||
socket.send("hi", { compress: true });
|
||||
});
|
||||
});
|
||||
|
||||
it("should not compress when the byte size is below threshold", (done) => {
|
||||
const socket = new eio.Socket({ transports: ["websocket"] });
|
||||
socket.on("open", () => {
|
||||
const ws = socket.transport.ws;
|
||||
const send = ws.send;
|
||||
ws.send = (data, opts, callback) => {
|
||||
ws.send = send;
|
||||
ws.send(data, opts, callback);
|
||||
|
||||
expect(opts.compress).to.be(false);
|
||||
socket.close();
|
||||
done();
|
||||
};
|
||||
socket.send("hi", { compress: true });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe("options", () => {
|
||||
it("should accept an `extraHeaders` option for XMLHttpRequest in browser", () => {
|
||||
const headers = {
|
||||
"X-Custom-Header-For-My-Project": "my-secret-access-token",
|
||||
Cookie:
|
||||
"user_session=NI2JlCKF90aE0sJZD9ZzujtdsUqNYSBYxzlTsvdSUe35ZzdtVRGqYFr0kdGxbfc5gUOkR9RGp20GVKza; path=/; expires=Tue, 07-Apr-2015 18:18:08 GMT; secure; HttpOnly",
|
||||
};
|
||||
const socket = new eio.Socket({
|
||||
transportOptions: {
|
||||
polling: {
|
||||
extraHeaders: headers,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(socket.transport.name).to.be("polling");
|
||||
expect(socket.transport.opts.extraHeaders).to.equal(headers);
|
||||
});
|
||||
});
|
||||
});
|
||||
404
packages/engine.io-client/test/util-wt.mjs
Normal file
404
packages/engine.io-client/test/util-wt.mjs
Normal file
@@ -0,0 +1,404 @@
|
||||
// imported from https://github.com/fails-components/webtransport/blob/master/test/fixtures/certificate.js
|
||||
|
||||
// @ts-expect-error node-forge has no types and @types/node-forge do not include oids
|
||||
import forge from 'node-forge'
|
||||
import { webcrypto as crypto, X509Certificate } from 'crypto'
|
||||
|
||||
const { pki, asn1, oids } = forge
|
||||
// taken from node-forge
|
||||
/**
|
||||
* Converts an X.509 subject or issuer to an ASN.1 RDNSequence.
|
||||
*
|
||||
* @param {any} obj the subject or issuer (distinguished name).
|
||||
*
|
||||
* @return the ASN.1 RDNSequence.
|
||||
*/
|
||||
function _dnToAsn1(obj) {
|
||||
// create an empty RDNSequence
|
||||
const rval = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [])
|
||||
|
||||
// iterate over attributes
|
||||
let attr, set
|
||||
const attrs = obj.attributes
|
||||
for (let i = 0; i < attrs.length; ++i) {
|
||||
attr = attrs[i]
|
||||
let value = attr.value
|
||||
|
||||
// reuse tag class for attribute value if available
|
||||
let valueTagClass = asn1.Type.PRINTABLESTRING
|
||||
if ('valueTagClass' in attr) {
|
||||
valueTagClass = attr.valueTagClass
|
||||
|
||||
if (valueTagClass === asn1.Type.UTF8) {
|
||||
value = forge.util.encodeUtf8(value)
|
||||
}
|
||||
// FIXME: handle more encodings
|
||||
}
|
||||
|
||||
// create a RelativeDistinguishedName set
|
||||
// each value in the set is an AttributeTypeAndValue first
|
||||
// containing the type (an OID) and second the value
|
||||
set = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
|
||||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||||
// AttributeType
|
||||
asn1.create(
|
||||
asn1.Class.UNIVERSAL,
|
||||
asn1.Type.OID,
|
||||
false,
|
||||
asn1.oidToDer(attr.type).getBytes()
|
||||
),
|
||||
// AttributeValue
|
||||
asn1.create(asn1.Class.UNIVERSAL, valueTagClass, false, value)
|
||||
])
|
||||
])
|
||||
rval.value.push(set)
|
||||
}
|
||||
|
||||
return rval
|
||||
}
|
||||
|
||||
const jan_1_1950 = new Date('1950-01-01T00:00:00Z') // eslint-disable-line camelcase
|
||||
const jan_1_2050 = new Date('2050-01-01T00:00:00Z') // eslint-disable-line camelcase
|
||||
// taken from node-forge almost not modified
|
||||
/**
|
||||
* Converts a Date object to ASN.1
|
||||
* Handles the different format before and after 1st January 2050
|
||||
*
|
||||
* @param {Date} date date object.
|
||||
*
|
||||
* @return the ASN.1 object representing the date.
|
||||
*/
|
||||
function _dateToAsn1(date) {
|
||||
// eslint-disable-next-line camelcase
|
||||
if (date >= jan_1_1950 && date < jan_1_2050) {
|
||||
return asn1.create(
|
||||
asn1.Class.UNIVERSAL,
|
||||
asn1.Type.UTCTIME,
|
||||
false,
|
||||
asn1.dateToUtcTime(date)
|
||||
)
|
||||
} else {
|
||||
return asn1.create(
|
||||
asn1.Class.UNIVERSAL,
|
||||
asn1.Type.GENERALIZEDTIME,
|
||||
false,
|
||||
asn1.dateToGeneralizedTime(date)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// taken from node-forge almost not modified
|
||||
/**
|
||||
* Convert signature parameters object to ASN.1
|
||||
*
|
||||
* @param {string} oid Signature algorithm OID
|
||||
* @param {any} params The signature parameters object
|
||||
* @return ASN.1 object representing signature parameters
|
||||
*/
|
||||
function _signatureParametersToAsn1(oid, params) {
|
||||
const parts = []
|
||||
|
||||
switch (oid) {
|
||||
case oids['RSASSA-PSS']:
|
||||
if (params.hash.algorithmOid !== undefined) {
|
||||
parts.push(
|
||||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
|
||||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||||
asn1.create(
|
||||
asn1.Class.UNIVERSAL,
|
||||
asn1.Type.OID,
|
||||
false,
|
||||
asn1.oidToDer(params.hash.algorithmOid).getBytes()
|
||||
),
|
||||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
|
||||
])
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
if (params.mgf.algorithmOid !== undefined) {
|
||||
parts.push(
|
||||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [
|
||||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||||
asn1.create(
|
||||
asn1.Class.UNIVERSAL,
|
||||
asn1.Type.OID,
|
||||
false,
|
||||
asn1.oidToDer(params.mgf.algorithmOid).getBytes()
|
||||
),
|
||||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||||
asn1.create(
|
||||
asn1.Class.UNIVERSAL,
|
||||
asn1.Type.OID,
|
||||
false,
|
||||
asn1.oidToDer(params.mgf.hash.algorithmOid).getBytes()
|
||||
),
|
||||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
|
||||
])
|
||||
])
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
if (params.saltLength !== undefined) {
|
||||
parts.push(
|
||||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 2, true, [
|
||||
asn1.create(
|
||||
asn1.Class.UNIVERSAL,
|
||||
asn1.Type.INTEGER,
|
||||
false,
|
||||
asn1.integerToDer(params.saltLength).getBytes()
|
||||
)
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, parts)
|
||||
|
||||
default:
|
||||
return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
|
||||
}
|
||||
}
|
||||
|
||||
// taken from node-forge and modified to work with ECDSA
|
||||
/**
|
||||
* Gets the ASN.1 TBSCertificate part of an X.509v3 certificate.
|
||||
*
|
||||
* @param {any} cert the certificate.
|
||||
*
|
||||
* @return the asn1 TBSCertificate.
|
||||
*/
|
||||
function getTBSCertificate(cert) {
|
||||
// TBSCertificate
|
||||
const notBefore = _dateToAsn1(cert.validity.notBefore)
|
||||
const notAfter = _dateToAsn1(cert.validity.notAfter)
|
||||
|
||||
const tbs = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||||
// version
|
||||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
|
||||
// integer
|
||||
asn1.create(
|
||||
asn1.Class.UNIVERSAL,
|
||||
asn1.Type.INTEGER,
|
||||
false,
|
||||
asn1.integerToDer(cert.version).getBytes()
|
||||
)
|
||||
]),
|
||||
// serialNumber
|
||||
asn1.create(
|
||||
asn1.Class.UNIVERSAL,
|
||||
asn1.Type.INTEGER,
|
||||
false,
|
||||
forge.util.hexToBytes(cert.serialNumber)
|
||||
),
|
||||
// signature
|
||||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||||
// algorithm
|
||||
asn1.create(
|
||||
asn1.Class.UNIVERSAL,
|
||||
asn1.Type.OID,
|
||||
false,
|
||||
asn1.oidToDer(cert.siginfo.algorithmOid).getBytes()
|
||||
),
|
||||
// parameters
|
||||
_signatureParametersToAsn1(
|
||||
cert.siginfo.algorithmOid,
|
||||
cert.siginfo.parameters
|
||||
)
|
||||
]),
|
||||
// issuer
|
||||
_dnToAsn1(cert.issuer),
|
||||
// validity
|
||||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||||
notBefore,
|
||||
notAfter
|
||||
]),
|
||||
// subject
|
||||
_dnToAsn1(cert.subject),
|
||||
// SubjectPublicKeyInfo
|
||||
// here comes our modification, we are other objects here
|
||||
asn1.fromDer(
|
||||
new forge.util.ByteBuffer(
|
||||
cert.publicKey
|
||||
) /* is in already SPKI format but in DER encoding */
|
||||
)
|
||||
])
|
||||
|
||||
if (cert.issuer.uniqueId) {
|
||||
// issuerUniqueID (optional)
|
||||
tbs.value.push(
|
||||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [
|
||||
asn1.create(
|
||||
asn1.Class.UNIVERSAL,
|
||||
asn1.Type.BITSTRING,
|
||||
false,
|
||||
// TODO: support arbitrary bit length ids
|
||||
String.fromCharCode(0x00) + cert.issuer.uniqueId
|
||||
)
|
||||
])
|
||||
)
|
||||
}
|
||||
if (cert.subject.uniqueId) {
|
||||
// subjectUniqueID (optional)
|
||||
tbs.value.push(
|
||||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 2, true, [
|
||||
asn1.create(
|
||||
asn1.Class.UNIVERSAL,
|
||||
asn1.Type.BITSTRING,
|
||||
false,
|
||||
// TODO: support arbitrary bit length ids
|
||||
String.fromCharCode(0x00) + cert.subject.uniqueId
|
||||
)
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
if (cert.extensions.length > 0) {
|
||||
// extensions (optional)
|
||||
tbs.value.push(pki.certificateExtensionsToAsn1(cert.extensions))
|
||||
}
|
||||
|
||||
return tbs
|
||||
}
|
||||
|
||||
// function taken form selfsigned
|
||||
// a hexString is considered negative if it's most significant bit is 1
|
||||
// because serial numbers use ones' complement notation
|
||||
// this RFC in section 4.1.2.2 requires serial numbers to be positive
|
||||
// http://www.ietf.org/rfc/rfc5280.txt
|
||||
/**
|
||||
* @param {string} hexString
|
||||
* @returns
|
||||
*/
|
||||
function toPositiveHex(hexString) {
|
||||
let mostSiginficativeHexAsInt = parseInt(hexString[0], 16)
|
||||
if (mostSiginficativeHexAsInt < 8) {
|
||||
return hexString
|
||||
}
|
||||
|
||||
mostSiginficativeHexAsInt -= 8
|
||||
return mostSiginficativeHexAsInt.toString() + hexString.substring(1)
|
||||
}
|
||||
|
||||
// the next is an edit of the selfsigned function reduced to the function necessary for webtransport
|
||||
/**
|
||||
* @typedef {object} Certificate
|
||||
* @property {string} public
|
||||
* @property {string} private
|
||||
* @property {string} cert
|
||||
* @property {Uint8Array} hash
|
||||
* @property {string} fingerprint
|
||||
*
|
||||
* @param {*} attrs
|
||||
* @param {*} options
|
||||
* @returns {Promise<Certificate | null>}
|
||||
*/
|
||||
export async function generateWebTransportCertificate(attrs, options) {
|
||||
try {
|
||||
const keyPair = await crypto.subtle.generateKey(
|
||||
{
|
||||
name: 'ECDSA',
|
||||
namedCurve: 'P-256'
|
||||
},
|
||||
true,
|
||||
['sign', 'verify']
|
||||
)
|
||||
|
||||
const cert = pki.createCertificate()
|
||||
|
||||
cert.serialNumber = toPositiveHex(
|
||||
forge.util.bytesToHex(forge.random.getBytesSync(9))
|
||||
) // the serial number can be decimal or hex (if preceded by 0x)
|
||||
cert.validity.notBefore = new Date()
|
||||
cert.validity.notAfter = new Date()
|
||||
cert.validity.notAfter.setDate(
|
||||
cert.validity.notBefore.getDate() + (options.days || 14)
|
||||
) // per spec only 14 days allowed
|
||||
|
||||
cert.setSubject(attrs)
|
||||
cert.setIssuer(attrs)
|
||||
|
||||
const privateKey = crypto.subtle.exportKey('pkcs8', keyPair.privateKey)
|
||||
const publicKey = (cert.publicKey = await crypto.subtle.exportKey(
|
||||
'spki',
|
||||
keyPair.publicKey
|
||||
))
|
||||
|
||||
cert.setExtensions(
|
||||
options.extensions || [
|
||||
{
|
||||
name: 'basicConstraints',
|
||||
cA: true
|
||||
},
|
||||
{
|
||||
name: 'keyUsage',
|
||||
keyCertSign: true,
|
||||
digitalSignature: true,
|
||||
nonRepudiation: true,
|
||||
keyEncipherment: true,
|
||||
dataEncipherment: true
|
||||
},
|
||||
{
|
||||
name: 'subjectAltName',
|
||||
altNames: [
|
||||
{
|
||||
type: 6, // URI
|
||||
value: 'http://example.org/webid#me'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
// to signing
|
||||
// patch oids object
|
||||
oids['1.2.840.10045.4.3.2'] = 'ecdsa-with-sha256'
|
||||
oids['ecdsa-with-sha256'] = '1.2.840.10045.4.3.2'
|
||||
|
||||
cert.siginfo.algorithmOid = cert.signatureOid = '1.2.840.10045.4.3.2' // 'ecdsa-with-sha256'
|
||||
|
||||
cert.tbsCertificate = getTBSCertificate(cert)
|
||||
const encoded = Buffer.from(
|
||||
asn1.toDer(cert.tbsCertificate).getBytes(),
|
||||
'binary'
|
||||
)
|
||||
cert.md = crypto.subtle.digest('SHA-256', encoded)
|
||||
cert.signature = crypto.subtle.sign(
|
||||
{
|
||||
name: 'ECDSA',
|
||||
hash: { name: 'SHA-256' }
|
||||
},
|
||||
keyPair.privateKey,
|
||||
encoded
|
||||
)
|
||||
cert.md = await cert.md
|
||||
cert.signature = await cert.signature
|
||||
|
||||
const pemcert = pki.certificateToPem(cert)
|
||||
|
||||
const x509cert = new X509Certificate(pemcert)
|
||||
|
||||
const certhash = Buffer.from(
|
||||
x509cert.fingerprint256.split(':').map((el) => parseInt(el, 16))
|
||||
)
|
||||
|
||||
const pem = {
|
||||
private: forge.pem.encode({
|
||||
type: 'PRIVATE KEY',
|
||||
body: new forge.util.ByteBuffer(await privateKey).getBytes()
|
||||
}),
|
||||
public: forge.pem.encode({
|
||||
type: 'PUBLIC KEY',
|
||||
body: new forge.util.ByteBuffer(publicKey).getBytes()
|
||||
}),
|
||||
cert: pemcert,
|
||||
hash: certhash,
|
||||
fingerprint: x509cert.fingerprint256
|
||||
}
|
||||
|
||||
return pem
|
||||
} catch (error) {
|
||||
console.log('error in generate certificate', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
14
packages/engine.io-client/test/util.js
Normal file
14
packages/engine.io-client/test/util.js
Normal file
@@ -0,0 +1,14 @@
|
||||
// polyfill from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
|
||||
exports.repeat = function (str, count) {
|
||||
if (String.prototype.repeat) {
|
||||
return str.repeat(count);
|
||||
}
|
||||
const maxCount = str.length * count;
|
||||
count = Math.floor(Math.log(count) / Math.log(2));
|
||||
while (count) {
|
||||
str += str;
|
||||
count--;
|
||||
}
|
||||
str += str.substring(0, maxCount - str.length);
|
||||
return str;
|
||||
};
|
||||
302
packages/engine.io-client/test/webtransport.mjs
Normal file
302
packages/engine.io-client/test/webtransport.mjs
Normal file
@@ -0,0 +1,302 @@
|
||||
import { Http3Server, WebTransport } from "@fails-components/webtransport";
|
||||
import { Http3EventLoop } from "@fails-components/webtransport/lib/event-loop.js";
|
||||
import expect from "expect.js";
|
||||
import { Server } from "engine.io";
|
||||
import { Socket } from "../build/esm-debug/index.js";
|
||||
import { generateWebTransportCertificate } from "./util-wt.mjs";
|
||||
import { createServer } from "http";
|
||||
import { TransformStream } from "stream/web";
|
||||
|
||||
if (typeof window === "undefined") {
|
||||
global.WebTransport = WebTransport;
|
||||
global.TransformStream = TransformStream;
|
||||
}
|
||||
|
||||
async function setup(opts, cb) {
|
||||
const certificate = await generateWebTransportCertificate(
|
||||
[{ shortName: "CN", value: "localhost" }],
|
||||
{
|
||||
days: 14, // the total length of the validity period MUST NOT exceed two weeks (https://w3c.github.io/webtransport/#custom-certificate-requirements)
|
||||
}
|
||||
);
|
||||
|
||||
const engine = new Server(opts);
|
||||
|
||||
const h3Server = new Http3Server({
|
||||
port: 0,
|
||||
host: "0.0.0.0",
|
||||
secret: "changeit",
|
||||
cert: certificate.cert,
|
||||
privKey: certificate.private,
|
||||
});
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const stream = await h3Server.sessionStream("/engine.io/");
|
||||
const sessionReader = stream.getReader();
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await sessionReader.read();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
engine.onWebTransportSession(value);
|
||||
}
|
||||
} catch (ex) {
|
||||
console.error("Server error", ex);
|
||||
}
|
||||
})();
|
||||
|
||||
h3Server.startServer();
|
||||
h3Server.onServerListening = () => cb({ engine, h3Server, certificate });
|
||||
}
|
||||
|
||||
function success(engine, h3server, done) {
|
||||
engine.close();
|
||||
h3server.stopServer();
|
||||
done();
|
||||
}
|
||||
|
||||
function createSocket(port, certificate, opts) {
|
||||
return new Socket(
|
||||
`http://127.0.0.1:${port}`,
|
||||
Object.assign(
|
||||
{
|
||||
transportOptions: {
|
||||
webtransport: {
|
||||
serverCertificateHashes: [
|
||||
{
|
||||
algorithm: "sha-256",
|
||||
value: certificate.hash,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
opts
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
describe("WebTransport", () => {
|
||||
after(() => {
|
||||
Http3EventLoop.globalLoop.shutdownEventLoop(); // manually shutdown the event loop, instead of waiting 20s
|
||||
});
|
||||
|
||||
it("should allow to connect with WebTransport directly", (done) => {
|
||||
setup({}, ({ engine, h3Server, certificate }) => {
|
||||
const socket = createSocket(h3Server.port, certificate, {
|
||||
transports: ["webtransport"],
|
||||
});
|
||||
|
||||
socket.on("open", () => {
|
||||
success(engine, h3Server, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should allow to upgrade to WebTransport", (done) => {
|
||||
setup(
|
||||
{
|
||||
transports: ["polling", "webtransport"],
|
||||
},
|
||||
({ engine, h3Server, certificate }) => {
|
||||
const httpServer = createServer();
|
||||
engine.attach(httpServer);
|
||||
httpServer.listen(h3Server.port);
|
||||
|
||||
const socket = createSocket(h3Server.port, certificate, {
|
||||
transports: ["polling", "webtransport"],
|
||||
});
|
||||
|
||||
socket.on("upgrade", () => {
|
||||
httpServer.close();
|
||||
success(engine, h3Server, done);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("should favor WebTransport over WebSocket", (done) => {
|
||||
setup(
|
||||
{
|
||||
transports: ["polling", "websocket", "webtransport"],
|
||||
},
|
||||
({ engine, h3Server, certificate }) => {
|
||||
const httpServer = createServer();
|
||||
engine.attach(httpServer);
|
||||
httpServer.listen(h3Server.port);
|
||||
|
||||
const socket = createSocket(h3Server.port, certificate, {
|
||||
transports: ["polling", "websocket", "webtransport"],
|
||||
});
|
||||
|
||||
socket.on("upgrade", (transport) => {
|
||||
expect(transport.name).to.eql("webtransport");
|
||||
|
||||
httpServer.close();
|
||||
success(engine, h3Server, done);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("should send ping/pong packets", (done) => {
|
||||
setup(
|
||||
{
|
||||
pingInterval: 20,
|
||||
},
|
||||
({ engine, h3Server, certificate }) => {
|
||||
const socket = createSocket(h3Server.port, certificate, {
|
||||
transports: ["webtransport"],
|
||||
});
|
||||
|
||||
let i = 0;
|
||||
|
||||
socket.on("heartbeat", () => {
|
||||
i++;
|
||||
|
||||
if (i === 10) {
|
||||
success(engine, h3Server, done);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("should handle connections closed by the server", (done) => {
|
||||
setup({}, ({ engine, h3Server, certificate }) => {
|
||||
const socket = createSocket(h3Server.port, certificate, {
|
||||
transports: ["webtransport"],
|
||||
});
|
||||
|
||||
engine.on("connection", (serverSocket) => {
|
||||
serverSocket.close();
|
||||
});
|
||||
|
||||
socket.on("close", (reason) => {
|
||||
expect(reason).to.eql("transport close");
|
||||
|
||||
success(engine, h3Server, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle connections closed by the client", (done) => {
|
||||
setup({}, ({ engine, h3Server, certificate }) => {
|
||||
const socket = createSocket(h3Server.port, certificate, {
|
||||
transports: ["webtransport"],
|
||||
});
|
||||
|
||||
engine.on("connection", (serverSocket) => {
|
||||
serverSocket.on("close", (reason) => {
|
||||
expect(reason).to.eql("transport close");
|
||||
|
||||
success(engine, h3Server, done);
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("open", () => {
|
||||
socket.close();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should send some plaintext data (client to server)", (done) => {
|
||||
setup({}, ({ engine, h3Server, certificate }) => {
|
||||
const socket = createSocket(h3Server.port, certificate, {
|
||||
transports: ["webtransport"],
|
||||
});
|
||||
|
||||
engine.on("connection", (serverSocket) => {
|
||||
serverSocket.on("message", (data) => {
|
||||
expect(data).to.eql("hello");
|
||||
|
||||
success(engine, h3Server, done);
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("open", () => {
|
||||
socket.send("hello");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should send some plaintext data (server to client)", (done) => {
|
||||
setup({}, ({ engine, h3Server, certificate }) => {
|
||||
const socket = createSocket(h3Server.port, certificate, {
|
||||
transports: ["webtransport"],
|
||||
});
|
||||
|
||||
engine.on("connection", (serverSocket) => {
|
||||
serverSocket.send("hello");
|
||||
});
|
||||
|
||||
socket.on("message", (data) => {
|
||||
expect(data).to.eql("hello");
|
||||
|
||||
success(engine, h3Server, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should send some binary data (client to server)", (done) => {
|
||||
setup({}, ({ engine, h3Server, certificate }) => {
|
||||
const socket = createSocket(h3Server.port, certificate, {
|
||||
transports: ["webtransport"],
|
||||
});
|
||||
|
||||
engine.on("connection", (serverSocket) => {
|
||||
serverSocket.on("message", (data) => {
|
||||
expect(data).to.eql(Uint8Array.from([1, 2, 3]));
|
||||
|
||||
success(engine, h3Server, done);
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("open", () => {
|
||||
socket.send(Uint8Array.from([1, 2, 3]));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should send some binary data (server to client) (as ArrayBuffer)", (done) => {
|
||||
setup({}, ({ engine, h3Server, certificate }) => {
|
||||
const socket = createSocket(h3Server.port, certificate, {
|
||||
transports: ["webtransport"],
|
||||
});
|
||||
|
||||
socket.binaryType = "arraybuffer";
|
||||
|
||||
engine.on("connection", (serverSocket) => {
|
||||
serverSocket.send(Uint8Array.from([1, 2, 3]));
|
||||
});
|
||||
|
||||
socket.on("message", (data) => {
|
||||
expect(data).to.be.an(ArrayBuffer);
|
||||
expect(new Uint8Array(data)).to.eql(Uint8Array.of(1, 2, 3));
|
||||
|
||||
success(engine, h3Server, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should send some binary data (server to client) (as Buffer)", (done) => {
|
||||
setup({}, ({ engine, h3Server, certificate }) => {
|
||||
const socket = createSocket(h3Server.port, certificate, {
|
||||
transports: ["webtransport"],
|
||||
});
|
||||
|
||||
engine.on("connection", (serverSocket) => {
|
||||
serverSocket.send(Uint8Array.from([1, 2, 3]));
|
||||
});
|
||||
|
||||
socket.on("message", (data) => {
|
||||
expect(Buffer.isBuffer(data)).to.be(true);
|
||||
expect(data).to.eql(Uint8Array.of(1, 2, 3));
|
||||
|
||||
success(engine, h3Server, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
125
packages/engine.io-client/test/xmlhttprequest.js
Normal file
125
packages/engine.io-client/test/xmlhttprequest.js
Normal file
@@ -0,0 +1,125 @@
|
||||
const expect = require("expect.js");
|
||||
const { newRequest } = require("../build/cjs/transports/polling-xhr.node.js");
|
||||
const env = require("./support/env");
|
||||
|
||||
describe("XMLHttpRequest", () => {
|
||||
if (env.isIE9) {
|
||||
describe("IE8_9", () => {
|
||||
context("when xdomain is false", () => {
|
||||
it("should have same properties as XMLHttpRequest does", () => {
|
||||
const xhra = newRequest({
|
||||
xdomain: false,
|
||||
xscheme: false,
|
||||
enablesXDR: false,
|
||||
});
|
||||
expect(xhra).to.be.an("object");
|
||||
expect(xhra).to.have.property("open");
|
||||
expect(xhra).to.have.property("onreadystatechange");
|
||||
const xhrb = newRequest({
|
||||
xdomain: false,
|
||||
xscheme: false,
|
||||
enablesXDR: true,
|
||||
});
|
||||
expect(xhrb).to.be.an("object");
|
||||
expect(xhrb).to.have.property("open");
|
||||
expect(xhrb).to.have.property("onreadystatechange");
|
||||
const xhrc = newRequest({
|
||||
xdomain: false,
|
||||
xscheme: true,
|
||||
enablesXDR: false,
|
||||
});
|
||||
expect(xhrc).to.be.an("object");
|
||||
expect(xhrc).to.have.property("open");
|
||||
expect(xhrc).to.have.property("onreadystatechange");
|
||||
const xhrd = newRequest({
|
||||
xdomain: false,
|
||||
xscheme: true,
|
||||
enablesXDR: true,
|
||||
});
|
||||
expect(xhrd).to.be.an("object");
|
||||
expect(xhrd).to.have.property("open");
|
||||
expect(xhrd).to.have.property("onreadystatechange");
|
||||
});
|
||||
});
|
||||
|
||||
context("when xdomain is true", () => {
|
||||
context("when xscheme is false and enablesXDR is true", () => {
|
||||
it("should have same properties as XDomainRequest does", () => {
|
||||
const xhr = newRequest({
|
||||
xdomain: true,
|
||||
xscheme: false,
|
||||
enablesXDR: true,
|
||||
});
|
||||
expect(xhr).to.be.an("object");
|
||||
expect(xhr).to.have.property("open");
|
||||
expect(xhr).to.have.property("onload");
|
||||
expect(xhr).to.have.property("onerror");
|
||||
});
|
||||
});
|
||||
|
||||
context("when xscheme is true", () => {
|
||||
it("should not have open in properties", () => {
|
||||
const xhra = newRequest({
|
||||
xdomain: true,
|
||||
xscheme: true,
|
||||
enablesXDR: false,
|
||||
});
|
||||
expect(xhra).to.be.an("object");
|
||||
expect(xhra).not.to.have.property("open");
|
||||
const xhrb = newRequest({
|
||||
xdomain: true,
|
||||
xscheme: true,
|
||||
enablesXDR: true,
|
||||
});
|
||||
expect(xhrb).to.be.an("object");
|
||||
expect(xhrb).not.to.have.property("open");
|
||||
});
|
||||
});
|
||||
|
||||
context("when enablesXDR is false", () => {
|
||||
it("should not have open in properties", () => {
|
||||
const xhra = newRequest({
|
||||
xdomain: true,
|
||||
xscheme: false,
|
||||
enablesXDR: false,
|
||||
});
|
||||
expect(xhra).to.be.an("object");
|
||||
expect(xhra).not.to.have.property("open");
|
||||
const xhrb = newRequest({
|
||||
xdomain: true,
|
||||
xscheme: true,
|
||||
enablesXDR: false,
|
||||
});
|
||||
expect(xhrb).to.be.an("object");
|
||||
expect(xhrb).not.to.have.property("open");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (env.isIE10 || env.isIE11) {
|
||||
describe("IE10_11", () => {
|
||||
context("when enablesXDR is true and xscheme is false", () => {
|
||||
it("should have same properties as XMLHttpRequest does", () => {
|
||||
const xhra = newRequest({
|
||||
xdomain: false,
|
||||
xscheme: false,
|
||||
enablesXDR: true,
|
||||
});
|
||||
expect(xhra).to.be.an("object");
|
||||
expect(xhra).to.have.property("open");
|
||||
expect(xhra).to.have.property("onreadystatechange");
|
||||
const xhrb = newRequest({
|
||||
xdomain: true,
|
||||
xscheme: false,
|
||||
enablesXDR: true,
|
||||
});
|
||||
expect(xhrb).to.be.an("object");
|
||||
expect(xhrb).to.have.property("open");
|
||||
expect(xhrb).to.have.property("onreadystatechange");
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
12
packages/engine.io-client/tsconfig.esm.json
Normal file
12
packages/engine.io-client/tsconfig.esm.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "build/esm/",
|
||||
"target": "es2018",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"declaration": true
|
||||
},
|
||||
"include": [
|
||||
"./lib/**/*"
|
||||
]
|
||||
}
|
||||
12
packages/engine.io-client/tsconfig.json
Normal file
12
packages/engine.io-client/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "build/cjs/",
|
||||
"target": "es2018", // Node.js 10 (https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping)
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": [
|
||||
"./lib/**/*"
|
||||
]
|
||||
}
|
||||
56
packages/engine.io-client/zuul.config.js
Normal file
56
packages/engine.io-client/zuul.config.js
Normal file
@@ -0,0 +1,56 @@
|
||||
'use strict';
|
||||
|
||||
const browsers = require('socket.io-browsers');
|
||||
|
||||
const zuulConfig = module.exports = {
|
||||
ui: 'mocha-bdd',
|
||||
|
||||
// test on localhost by default
|
||||
local: true,
|
||||
open: true,
|
||||
|
||||
concurrency: 2, // ngrok only accepts two tunnels by default
|
||||
// if browser does not sends output in 120s since last output:
|
||||
// stop testing, something is wrong
|
||||
browser_output_timeout: 120 * 1000,
|
||||
browser_open_timeout: 60 * 4 * 1000,
|
||||
// we want to be notified something is wrong asap, so no retry
|
||||
browser_retries: 1,
|
||||
|
||||
server: './test/support/server.js',
|
||||
builder: 'zuul-builder-webpack',
|
||||
webpack: require('./support/webpack.config.js')
|
||||
};
|
||||
|
||||
if (process.env.CI === 'true') {
|
||||
zuulConfig.local = false;
|
||||
zuulConfig.tunnel = {
|
||||
type: 'ngrok',
|
||||
bind_tls: true
|
||||
};
|
||||
}
|
||||
|
||||
zuulConfig.browsers = [
|
||||
{
|
||||
name: 'firefox',
|
||||
version: 'latest'
|
||||
}, {
|
||||
name: 'internet explorer',
|
||||
version: '9..11'
|
||||
}, {
|
||||
name: 'safari',
|
||||
version: '14'
|
||||
}, {
|
||||
name: 'iphone',
|
||||
version: '14'
|
||||
}, {
|
||||
name: 'android',
|
||||
version: '5.1..6.0'
|
||||
}, {
|
||||
name: 'ipad',
|
||||
version: '14'
|
||||
}, {
|
||||
name: 'MicrosoftEdge',
|
||||
version: 'latest'
|
||||
}
|
||||
];
|
||||
Reference in New Issue
Block a user