Compare commits

..

65 Commits

Author SHA1 Message Date
Damien Arrachequesne
164ba2a11e chore(release): 4.2.4
Diff: https://github.com/socketio/socket.io-parser/compare/4.2.3...4.2.4
2023-05-31 10:56:08 +02:00
Damien Arrachequesne
b0e6400c93 fix: properly detect plain objects
The typeof check was not sufficient, as it also matches arrays and
nulls.
2023-05-31 10:44:05 +02:00
Damien Arrachequesne
d9db4737a3 fix: ensure reserved events cannot be used as event names 2023-05-31 08:29:52 +02:00
Damien Arrachequesne
6a5a004d1e docs(changelog): include changelog for release 3.4.3 2023-05-22 10:06:05 +02:00
Damien Arrachequesne
b6c824f824 chore(release): 4.2.3
Diff: https://github.com/socketio/socket.io-parser/compare/4.2.2...4.2.3
2023-05-22 08:25:34 +02:00
Damien Arrachequesne
dcc70d9678 refactor: export typescript declarations for the commonjs build
Related: https://github.com/socketio/socket.io/issues/4621#issuecomment-1551853243
2023-05-22 08:25:34 +02:00
Damien Arrachequesne
3b78117bf6 fix: check the format of the event name
A packet like '2[{"toString":"foo"}]' was decoded as:

{
  type: EVENT,
  data: [ { "toString": "foo" } ]
}

Which would then throw an error when passed to the EventEmitter class:

> TypeError: Cannot convert object to primitive value
>    at Socket.emit (node:events:507:25)
>    at .../node_modules/socket.io/lib/socket.js:531:14

History of the isPayloadValid() method:

- added in [78f9fc2](78f9fc2999) (v4.0.1, socket.io@3.0.0)
- updated in [1c220dd](1c220ddbf4) (v4.0.4, socket.io@3.1.0)
2023-05-22 08:25:33 +02:00
dependabot[bot]
0841bd5623 chore: bump ua-parser-js from 1.0.32 to 1.0.33 (#121)
Bumps [ua-parser-js](https://github.com/faisalman/ua-parser-js) from 1.0.32 to 1.0.33.
- [Release notes](https://github.com/faisalman/ua-parser-js/releases)
- [Changelog](https://github.com/faisalman/ua-parser-js/blob/master/changelog.md)
- [Commits](https://github.com/faisalman/ua-parser-js/compare/1.0.32...1.0.33)

---
updated-dependencies:
- dependency-name: ua-parser-js
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-25 07:23:55 +01:00
Damien Arrachequesne
28dd668502 chore(release): 4.2.2
Diff: https://github.com/socketio/socket.io-parser/compare/4.2.1...4.2.2
2023-01-19 10:43:49 +01:00
Damien Arrachequesne
22c42e3545 fix: calling destroy() should clear all internal state
If a client was in the process of receiving some binary attachments
when the connection was abruptly closed, then the manager would call
`decoder.destroy()` ([1]) but was then stuck in a "parse error" loop
upon reconnection (since it expected a binary attachment and not a
CONNECT packet).

[1]: a1c528b089/lib/manager.ts (L520)
2023-01-19 10:16:42 +01:00
Damien Arrachequesne
ae8dd88995 fix: do not modify the input packet upon encoding
Note: this issue has existed since Socket.IO v1.0 (see [1]), because
the `deconstructPacket()` method also mutates its input argument.

This also explains why some adapters (like [2]) need to use
`process.nextTick()` when extending the `broadcast()` method, because
`Adapter.broadcast()` calls `Encoder.encode()` ([3]).

Related:

- https://github.com/socketio/socket.io/issues/4374
- https://github.com/socketio/socket.io-mongo-adapter/issues/10

[1]: 299849b002
[2]: https://github.com/socketio/socket.io-postgres-adapter/blob/0.3.0/lib/index.ts#L587-L590
[3]: https://github.com/socketio/socket.io-adapter/blob/2.4.0/lib/index.ts#L148
2023-01-19 10:06:13 +01:00
Damien Arrachequesne
9143aa4c8e chore: update browserslist 2022-11-15 10:34:46 +01:00
Damien Arrachequesne
194a9b762e ci: migrate from zuul to webdriver.io
zuul is now archived [1] and does not support the new W3C WebDriver
protocol, since it relies on the wd package [2] under the hood, which
uses the (now deprecated) JSON Wire Protocol.

We will now use the webdriver.io test framework, which allows to run
our tests in local and on Sauce Labs (cross-browser and mobile tests).
This allows us to run our tests on latest versions of Android and iOS,
since Sauce Labs only supports the W3C WebDriver protocol for these
platforms ([3]).

[1]: https://github.com/defunctzombie/zuul
[2]: https://github.com/admc/wd
[3]: https://docs.saucelabs.com/dev/w3c-webdriver-capabilities/
2022-11-15 10:13:08 +01:00
Dirk Stolle
a9758da4be ci: update actions in GitHub Actions workflows (#117) 2022-11-15 10:02:20 +01:00
Damien Arrachequesne
f0af8834f8 docs: add missing versions in the changelog
Related: https://github.com/advisories/GHSA-qm95-pgcg-qqfq
2022-11-09 11:42:23 +01:00
Damien Arrachequesne
5a2ccff9d1 chore(release): 4.2.1
Diff: https://github.com/socketio/socket.io-parser/compare/4.2.0...4.2.1
2022-06-27 15:42:25 +02:00
Damien Arrachequesne
b5d0cb7dc5 fix: check the format of the index of each attachment
A specially crafted packet could be incorrectly decoded.

Example:

```js
const decoder = new Decoder();

decoder.on("decoded", (packet) => {
  console.log(packet.data); // prints [ 'hello', [Function: splice] ]
})

decoder.add('51-["hello",{"_placeholder":true,"num":"splice"}]');
decoder.add(Buffer.from("world"));
```

As usual, please remember not to trust user input.
2022-06-27 15:39:24 +02:00
Damien Arrachequesne
c7514b5aa6 chore(release): 4.2.0
Diff: https://github.com/socketio/socket.io-parser/compare/4.1.2...4.2.0
2022-04-18 00:26:27 +02:00
Damien Arrachequesne
931f1526a4 chore: add Node.js 16 in the test matrix
See also: https://github.com/nodejs/Release
2022-04-18 00:21:14 +02:00
Damien Arrachequesne
6c9cb27aeb chore: bump @socket.io/component-emitter to version 3.1.0
Related: https://github.com/socketio/socket.io-client/issues/1536
2022-04-18 00:20:17 +02:00
David Pfeffer
b08bc1a93e feat: allow the usage of custom replacer and reviver (#112)
Co-authored-by: Mocanu Cristian <mocanu.cristian93@gmail.com>
2022-04-18 00:19:02 +02:00
Damien Arrachequesne
aed252c742 chore(release): 4.1.2
Diff: https://github.com/socketio/socket.io-parser/compare/4.1.1...4.1.2
2022-02-17 07:37:18 +01:00
dependabot[bot]
89209fa22a chore: bump cached-path-relative from 1.0.2 to 1.1.0 (#113)
Bumps [cached-path-relative](https://github.com/ashaffer/cached-path-relative) from 1.0.2 to 1.1.0.
- [Release notes](https://github.com/ashaffer/cached-path-relative/releases)
- [Commits](https://github.com/ashaffer/cached-path-relative/commits)

---
updated-dependencies:
- dependency-name: cached-path-relative
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-17 07:19:50 +01:00
dependabot[bot]
0a3b556de3 chore: bump path-parse from 1.0.6 to 1.0.7 (#108)
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-17 07:18:40 +01:00
Gabba90
7f6b262ac8 fix: allow objects with a null prototype in binary packets (#114) 2022-02-17 07:18:11 +01:00
dependabot[bot]
8e8346b706 chore: bump ajv from 6.12.2 to 6.12.6 (#115)
Bumps [ajv](https://github.com/ajv-validator/ajv) from 6.12.2 to 6.12.6.
- [Release notes](https://github.com/ajv-validator/ajv/releases)
- [Commits](https://github.com/ajv-validator/ajv/compare/v6.12.2...v6.12.6)

---
updated-dependencies:
- dependency-name: ajv
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-16 21:57:15 +01:00
Damien Arrachequesne
ea86f413ed chore(release): 4.1.1
DIff: https://github.com/socketio/socket.io-parser/compare/4.1.0...4.1.1
2021-10-14 13:52:49 +02:00
Damien Arrachequesne
eb708d1936 chore: bump @socket.io/component-emitter to version 3.0.0
The typed events have been moved from [1] to [2], in order to remove
the intermediary class and reduce the bundle size.

Diff: https://github.com/socketio/emitter/compare/2.0.0...3.0.0

[1]: https://github.com/socketio/socket.io-client/
[2]: https://github.com/socketio/emitter/
2021-10-14 13:11:55 +02:00
Damien Arrachequesne
5ad3e5cc4b chore(release): 4.1.0
Diff: https://github.com/socketio/socket.io-parser/compare/4.0.4...4.1.0
2021-10-11 22:37:46 +02:00
Damien Arrachequesne
388c616a92 feat: provide an ESM build with and without debug
See also: 00d7e7d7ee
2021-10-11 22:35:12 +02:00
dependabot[bot]
75530b4dcd chore: bump browserslist from 4.12.0 to 4.16.6 (#106)
Bumps [browserslist](https://github.com/browserslist/browserslist) from 4.12.0 to 4.16.6.
- [Release notes](https://github.com/browserslist/browserslist/releases)
- [Changelog](https://github.com/browserslist/browserslist/blob/main/CHANGELOG.md)
- [Commits](https://github.com/browserslist/browserslist/compare/4.12.0...4.16.6)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-13 10:47:42 +02:00
dependabot[bot]
57324f3048 chore: bump elliptic from 6.5.3 to 6.5.4 (#102)
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.3 to 6.5.4.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.3...v6.5.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-13 10:47:33 +02:00
Damien Arrachequesne
af1b23ca85 chore(release): 4.0.4
Diff: https://github.com/socketio/socket.io-parser/compare/4.0.3...4.0.4
2021-01-15 01:45:17 +01:00
Damien Arrachequesne
1c220ddbf4 fix: allow integers as event names
This commit restores the possibility to use integers as event names,
which was possible in Socket.IO v2.
2021-01-15 01:38:03 +01:00
Damien Arrachequesne
444520d6cd chore(release): 4.0.3
Diff: https://github.com/socketio/socket.io-parser/compare/4.0.2...4.0.3
2021-01-05 11:26:13 +01:00
Damien Arrachequesne
b076dbb722 ci: migrate to GitHub Actions
Due to the recent changes to the Travis CI platform (see [1]), we will
now use GitHub Actions to run the tests.

Reference: https://docs.github.com/en/free-pro-team@latest/actions/guides/building-and-testing-nodejs

[1]: https://blog.travis-ci.com/2020-11-02-travis-ci-new-billing
2021-01-05 11:24:49 +01:00
Damien Arrachequesne
7c380d38eb chore: bump debug version 2021-01-05 11:00:39 +01:00
Damien Arrachequesne
f2098b031d chore(release): 4.0.2
Diff: https://github.com/socketio/socket.io-parser/compare/4.0.1...4.0.2
2020-11-25 11:00:16 +01:00
Damien Arrachequesne
66973a340c chore: cleanup dist folder before compilation 2020-11-25 10:59:02 +01:00
Pascal Sthamer
4efa005846 fix: move @types/component-emitter to dependencies (#99)
Otherwise consumers of socket.io-parser (and socket.io) need to have it
listed in their devDependencies.
2020-11-25 10:51:40 +01:00
Damien Arrachequesne
c04443375f docs: add compatibility table 2020-11-05 16:16:36 +01:00
Damien Arrachequesne
e339323654 chore(release): 4.0.1
Diff: https://github.com/socketio/socket.io-parser/compare/3.4.1...4.0.1
2020-11-05 16:07:35 +01:00
Damien Arrachequesne
412769fd18 chore(release): 4.0.1-rc3
Diff: https://github.com/socketio/socket.io-parser/compare/4.0.1-rc2...4.0.1-rc3
2020-10-26 00:16:09 +01:00
Damien Arrachequesne
db1d27432d refactor: rename ERROR to CONNECT_ERROR
The meaning is not modified: this packet type is still used by the
server when the connection to a namespace is refused. But I feel the
name makes more sense:

```js
socket.on("connect", () => {});
socket.on("connect_error", () => {});

// instead of
socket.on("error", () => {});
```
2020-10-25 22:57:26 +01:00
Aleksey Druzhinin
e3d272f542 docs: fix small typo (#98) 2020-10-21 23:28:36 +02:00
Damien Arrachequesne
64b6648236 chore(release): 4.0.1-rc2
Diff: https://github.com/socketio/socket.io-parser/compare/4.0.1-rc1...4.0.1-rc2
2020-10-15 10:24:47 +02:00
Damien Arrachequesne
58b3d09f1c chore: protocol version 5
There are two differences with the 4th version:

- a CONNECT packet can now contain a payload (for authentication purposes)
- the underlying Engine.IO protocol has been updated

Reference: https://github.com/socketio/engine.io-protocol#difference-between-v3-and-v4
2020-10-15 01:51:51 +02:00
Damien Arrachequesne
285e7cd0d8 feat: move binary detection back to the parser
The binary detection was moved from the parser to the client/server in
[1], in order to allow the user to skip the binary detection for huge
JSON payloads.

```js
socket.binary(false).emit(...);
```

The binary detection is needed in the default parser, because the
payload is encoded with JSON.stringify(), which does not support binary
content (ArrayBuffer, Blob, ...).

But other parsers (like [2] or [3]) do not need this check, so we'll
move the binary detection back here and remove the socket.binary()
method, as this use case is now covered by the ability to provide your
own parser.

Note: the hasBinary method was copied from [4].

[1]: f44256c523
[2]: https://github.com/darrachequesne/socket.io-msgpack-parser
[3]: https://github.com/darrachequesne/socket.io-json-parser
[4]: https://github.com/darrachequesne/has-binary
2020-10-15 01:46:47 +02:00
Damien Arrachequesne
7fc3c42234 chore(release): 4.0.1-rc1
Diff: https://github.com/socketio/socket.io-parser/compare/4.0.0...4.0.1-rc1
2020-10-12 15:21:44 +02:00
Damien Arrachequesne
78f9fc2999 feat: add support for a payload in a CONNECT packet 2020-10-08 02:00:09 +02:00
Damien Arrachequesne
9eb8561cbc refactor: use require for debug dependency
So that the lines can be properly excluded by the webpack-remove-debug
loader ([1]).

[1] https://github.com/johngodley/webpack-remove-debug
2020-10-06 01:17:31 +02:00
Damien Arrachequesne
091d25edf1 chore: add dist 2020-10-06 01:15:30 +02:00
Damien Arrachequesne
ccadd5a462 docs(changelog): include changelog for release 3.3.1
Merged from the 3.3.x branch.
2020-09-30 02:45:53 +02:00
Damien Arrachequesne
c04d7f5c47 chore(release): 4.0.0
This release will be included in Socket.IO v3.

Diff: https://github.com/socketio/socket.io-parser/compare/3.4.1...4.0.0
2020-09-28 14:55:19 +02:00
Damien Arrachequesne
9e601c6940 refactor: export Packet interface and refactor imports 2020-09-28 14:37:47 +02:00
Damien Arrachequesne
cfdc4794f6 refactor: use prettier to format test code 2020-09-24 12:02:19 +02:00
Damien Arrachequesne
28d4f0309b refactor: do not convert Blobs
This was needed in a previous version of the parser, which used msgpack
to encode the payload.

Blobs (and Files) will now be included in the array of binary
attachments without any additional transformation.

Breaking change: the encode method is now synchronous

See also 299849b002
2020-09-24 11:48:25 +02:00
Damien Arrachequesne
fe33ff7c87 test: actually test the parser
The assertions were not checked, because the functions are asynchronous.

Besides, the Blob tests were throwing in the browser:

> Uncaught ReferenceError: can't access lexical declaration 'BlobBuilder' before initialization
2020-09-24 11:48:24 +02:00
dependabot[bot]
00e73598a0 chore: bump elliptic from 6.5.2 to 6.5.3 (#96)
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.2 to 6.5.3.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.2...v6.5.3)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-09-23 00:57:41 +02:00
Damien Arrachequesne
dd7cd60ba2 refactor: convert all tests to ES6 syntax 2020-09-23 00:28:54 +02:00
Damien Arrachequesne
aeae87c220 fix: do not catch encoding errors
It does not make sense to catch the errors thrown by JSON.stringify()
and convert them to an ERROR packet (which are meant for namespace
authentication errors), it should be caught higher in the stack.

Related: 92c530da47
2020-09-23 00:28:53 +02:00
Damien Arrachequesne
567c0ca965 refactor: use PacketType enum wherever applicable 2020-09-23 00:24:38 +02:00
Damien Arrachequesne
c327acbc3c fix: throw upon invalid payload format
An invalid packet was previously parsed as an ERROR packet, which was
then ignored because it didn't contain any 'nsp' (namespace) field.

This behavior was wrong because:

- it means the other side is sending invalid payloads, so the
connection must be closed right away

- ERROR packets are meant for namespace authentication failures

Parsing an invalid payload will now throw an error, which must be
caught by the caller.

Closes https://github.com/socketio/socket.io-parser/issues/86
2020-09-22 23:33:03 +02:00
Damien Arrachequesne
b23576a73e refactor: migrate to TypeScript 2020-09-22 22:42:17 +02:00
Damien Arrachequesne
ea41f225ee perf: update benchmarks 2020-09-18 14:09:53 +02:00
32 changed files with 13806 additions and 7339 deletions

View File

@@ -2,8 +2,9 @@ name: CI
on:
push:
branches:
- 'socket.io-parser/3.4.x'
pull_request:
schedule:
- cron: '0 0 * * 0'
permissions:
contents: read
@@ -14,17 +15,15 @@ jobs:
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
node-version:
- 16
node-version: [14, 16]
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
@@ -33,3 +32,25 @@ jobs:
- name: Run tests
run: npm test
test-browser:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Use Node.js 16
uses: actions/setup-node@v3
with:
node-version: 16
- name: Install dependencies
run: npm ci
- run: npm test
env:
BROWSERS: 1
SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}

View File

@@ -1,31 +0,0 @@
# reference: https://docs.npmjs.com/trusted-publishers#for-github-actions
name: Publish
on:
push:
tags:
- 'socket.io-parser@*'
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Use Node.js 24
uses: actions/setup-node@v6
with:
node-version: 24
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: npm ci
- name: Publish package
run: npm publish --tag v2-latest

4
.gitignore vendored
View File

@@ -1,3 +1,3 @@
node_modules
build
components
dist/
build/

View File

@@ -1,9 +1,56 @@
## [3.4.4](https://github.com/socketio/socket.io-parser/compare/3.4.3...3.4.4) (2026-03-17)
# History
## 2023
- [4.2.4](#424-2023-05-31) (May 2023)
- [3.4.3](#343-2023-05-22) (May 2023) (from the [3.4.x](https://github.com/socketio/socket.io-parser/tree/3.4.x) branch)
- [4.2.3](#423-2023-05-22) (May 2023)
- [4.2.2](#422-2023-01-19) (Jan 2023)
## 2022
- [3.3.3](#333-2022-11-09) (Nov 2022) (from the [3.3.x](https://github.com/socketio/socket.io-parser/tree/3.3.x) branch)
- [3.4.2](#342-2022-11-09) (Nov 2022) (from the [3.4.x](https://github.com/socketio/socket.io-parser/tree/3.4.x) branch)
- [4.0.5](#405-2022-06-27) (Jun 2022) (from the [4.0.x](https://github.com/socketio/socket.io-parser/tree/4.0.x) branch)
- [4.2.1](#421-2022-06-27) (Jun 2022)
- [4.2.0](#420-2022-04-17) (Apr 2022)
- [4.1.2](#412-2022-02-17) (Feb 2022)
## 2021
- [4.1.1](#411-2021-10-14) (Oct 2021)
- [4.1.0](#410-2021-10-11) (Oct 2021)
- [4.0.4](#404-2021-01-15) (Jan 2021)
- [3.3.2](#332-2021-01-09) (Jan 2021) (from the [3.3.x](https://github.com/socketio/socket.io-parser/tree/3.3.x) branch)
- [4.0.3](#403-2021-01-05) (Jan 2021)
## 2020
- [4.0.2](#402-2020-11-25) (Nov 2020)
- [4.0.1](#401-2020-11-05) (Nov 2020)
- [3.3.1](#331-2020-09-30) (Sep 2020) (from the [3.3.x](https://github.com/socketio/socket.io-parser/tree/3.3.x) branch)
- [**4.0.0**](#400-2020-09-28) (Sep 2020)
- [3.4.1](#341-2020-05-13) (May 2020)
## 2019
- [3.4.0](#340-2019-09-20) (Sep 2019)
## 2018
- [3.3.0](#330-2018-11-07) (Nov 2018)
# Release notes
## [4.2.4](https://github.com/socketio/socket.io-parser/compare/4.2.3...4.2.4) (2023-05-31)
### Bug Fixes
* add a limit to the number of binary attachments ([719f9eb](https://github.com/socketio/socket.io/commit/719f9ebab0772ffb882bd614b387e585c1aa75d4))
* ensure reserved events cannot be used as event names ([d9db473](https://github.com/socketio/socket.io-parser/commit/d9db4737a3c8ce5f1f49ecc8d928a74f3da591f7))
* properly detect plain objects ([b0e6400](https://github.com/socketio/socket.io-parser/commit/b0e6400c93b5c4aa25e6a629d6448b8627275213))
@@ -16,6 +63,34 @@
## [4.2.3](https://github.com/socketio/socket.io-parser/compare/4.2.2...4.2.3) (2023-05-22)
### Bug Fixes
* check the format of the event name ([3b78117](https://github.com/socketio/socket.io-parser/commit/3b78117bf6ba7e99d7a5cfc1ba54d0477554a7f3))
## [4.2.2](https://github.com/socketio/socket.io-parser/compare/4.2.1...4.2.2) (2023-01-19)
### Bug Fixes
* calling destroy() should clear all internal state ([22c42e3](https://github.com/socketio/socket.io-parser/commit/22c42e3545e4adbc5931276c378f5d62c8b3854a))
* do not modify the input packet upon encoding ([ae8dd88](https://github.com/socketio/socket.io-parser/commit/ae8dd88995dbd7f89c97e5cc15e5b489fa0efece))
## [3.3.3](https://github.com/Automattic/socket.io-parser/compare/3.3.2...3.3.3) (2022-11-09)
### Bug Fixes
* check the format of the index of each attachment ([fb21e42](https://github.com/Automattic/socket.io-parser/commit/fb21e422fc193b34347395a33e0f625bebc09983))
## [3.4.2](https://github.com/socketio/socket.io-parser/compare/3.4.1...3.4.2) (2022-11-09)
@@ -25,9 +100,159 @@
## [4.2.1](https://github.com/socketio/socket.io-parser/compare/4.2.0...4.2.1) (2022-06-27)
### Bug Fixes
* check the format of the index of each attachment ([b5d0cb7](https://github.com/socketio/socket.io-parser/commit/b5d0cb7dc56a0601a09b056beaeeb0e43b160050))
## [4.0.5](https://github.com/socketio/socket.io-parser/compare/4.0.4...4.0.5) (2022-06-27)
### Bug Fixes
* check the format of the index of each attachment ([b559f05](https://github.com/socketio/socket.io-parser/commit/b559f050ee02bd90bd853b9823f8de7fa94a80d4))
## [4.2.0](https://github.com/socketio/socket.io-parser/compare/4.1.2...4.2.0) (2022-04-17)
### Features
* allow the usage of custom replacer and reviver ([#112](https://github.com/socketio/socket.io-parser/issues/112)) ([b08bc1a](https://github.com/socketio/socket.io-parser/commit/b08bc1a93e8e3194b776c8a0bdedee1e29333680))
## [4.1.2](https://github.com/socketio/socket.io-parser/compare/4.1.1...4.1.2) (2022-02-17)
### Bug Fixes
* allow objects with a null prototype in binary packets ([#114](https://github.com/socketio/socket.io-parser/issues/114)) ([7f6b262](https://github.com/socketio/socket.io-parser/commit/7f6b262ac83bdf43c53a7eb02417e56e0cf491c8))
## [4.1.1](https://github.com/socketio/socket.io-parser/compare/4.1.0...4.1.1) (2021-10-14)
## [4.1.0](https://github.com/socketio/socket.io-parser/compare/4.0.4...4.1.0) (2021-10-11)
### Features
* provide an ESM build with and without debug ([388c616](https://github.com/socketio/socket.io-parser/commit/388c616a9221e4341945f8487e729e93a81d2da5))
## [4.0.4](https://github.com/socketio/socket.io-parser/compare/4.0.3...4.0.4) (2021-01-15)
### Bug Fixes
* allow integers as event names ([1c220dd](https://github.com/socketio/socket.io-parser/commit/1c220ddbf45ea4b44bc8dbf6f9ae245f672ba1b9))
## [3.3.2](https://github.com/Automattic/socket.io-parser/compare/3.3.1...3.3.2) (2021-01-09)
### Bug Fixes
* prevent DoS (OOM) via massive packets ([#95](https://github.com/Automattic/socket.io-parser/issues/95)) ([89197a0](https://github.com/Automattic/socket.io-parser/commit/89197a05c43b18cc4569fd178d56e7bb8f403865))
## [4.0.3](https://github.com/socketio/socket.io-parser/compare/4.0.2...4.0.3) (2021-01-05)
## [4.0.2](https://github.com/socketio/socket.io-parser/compare/4.0.1...4.0.2) (2020-11-25)
### Bug Fixes
* move @types/component-emitter to dependencies ([#99](https://github.com/socketio/socket.io-parser/issues/99)) ([4efa005](https://github.com/socketio/socket.io-parser/commit/4efa005846ae15ecc7fb0a7f27141439113b1179))
## [4.0.1](https://github.com/socketio/socket.io-parser/compare/3.4.1...4.0.1) (2020-11-05)
### Features
* move binary detection back to the parser ([285e7cd](https://github.com/socketio/socket.io-parser/commit/285e7cd0d837adfc911c999e7294788681226ae1))
* add support for a payload in a CONNECT packet ([78f9fc2](https://github.com/socketio/socket.io-parser/commit/78f9fc2999b15804b02f2c22a2b4007734a26af9))
### Bug Fixes
* do not catch encoding errors ([aeae87c](https://github.com/socketio/socket.io-parser/commit/aeae87c220287197cb78370dbd86b950a7dd29eb))
* throw upon invalid payload format ([c327acb](https://github.com/socketio/socket.io-parser/commit/c327acbc3c3c2d0b2b439136cbcb56c81db173d6))
### BREAKING CHANGES
* the encode method is now synchronous ([28d4f03](https://github.com/socketio/socket.io-parser/commit/28d4f0309bdd9e306b78d1946d3e1760941d6544))
## [4.0.1-rc3](https://github.com/socketio/socket.io-parser/compare/4.0.1-rc2...4.0.1-rc3) (2020-10-25)
## [4.0.1-rc2](https://github.com/socketio/socket.io-parser/compare/4.0.1-rc1...4.0.1-rc2) (2020-10-15)
### Features
* move binary detection back to the parser ([285e7cd](https://github.com/socketio/socket.io-parser/commit/285e7cd0d837adfc911c999e7294788681226ae1))
## [4.0.1-rc1](https://github.com/socketio/socket.io-parser/compare/4.0.0...4.0.1-rc1) (2020-10-12)
### Features
* add support for a payload in a CONNECT packet ([78f9fc2](https://github.com/socketio/socket.io-parser/commit/78f9fc2999b15804b02f2c22a2b4007734a26af9))
## [3.3.1](https://github.com/socketio/socket.io-parser/compare/3.3.0...3.3.1) (2020-09-30)
## [4.0.0](https://github.com/socketio/socket.io-parser/compare/3.4.1...4.0.0) (2020-09-28)
This release will be included in Socket.IO v3.
There is a breaking API change (see below), but the exchange [protocol](https://github.com/socketio/socket.io-protocol) is left untouched and thus stays in version 4.
### Bug Fixes
* do not catch encoding errors ([aeae87c](https://github.com/socketio/socket.io-parser/commit/aeae87c220287197cb78370dbd86b950a7dd29eb))
* throw upon invalid payload format ([c327acb](https://github.com/socketio/socket.io-parser/commit/c327acbc3c3c2d0b2b439136cbcb56c81db173d6))
### BREAKING CHANGES
* the encode method is now synchronous ([28d4f03](https://github.com/socketio/socket.io-parser/commit/28d4f0309bdd9e306b78d1946d3e1760941d6544))
## [3.4.1](https://github.com/socketio/socket.io-parser/compare/3.4.0...3.4.1) (2020-05-13)
### Bug Fixes
* prevent DoS (OOM) via massive packets ([#95](https://github.com/socketio/socket.io-parser/issues/95)) ([dcb942d](https://github.com/socketio/socket.io-parser/commit/dcb942d24db97162ad16a67c2a0cf30875342d55))
## [3.4.0](https://github.com/socketio/socket.io-parser/compare/3.3.0...3.4.0) (2019-09-20)
## [3.3.0](https://github.com/socketio/socket.io-parser/compare/3.2.0...3.3.0) (2018-11-07)
### Bug Fixes
* remove any reference to the `global` variable ([b47efb2](https://github.com/socketio/socket.io-parser/commit/b47efb2))

View File

@@ -1,14 +0,0 @@
help: ## print this message
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
test: ## run tests either in the browser or in Node.js, based on the `BROWSERS` variable
@if [ "x$(BROWSERS)" = "x" ]; then make test-node; else make test-zuul; fi
test-node: ## run tests in Node.js
@./node_modules/.bin/mocha --reporter dot --bail test/index.js
test-zuul: ## run tests in the browser
@./node_modules/zuul/bin/zuul test/index.js
.PHONY: help test test-node test-zuul

View File

@@ -1,14 +1,22 @@
# socket.io-parser
[![Build Status](https://secure.travis-ci.org/socketio/socket.io-parser.svg?branch=master)](http://travis-ci.org/socketio/socket.io-parser)
[![Build Status](https://github.com/socketio/socket.io-parser/workflows/CI/badge.svg)](https://github.com/socketio/socket.io-parser/actions)
[![NPM version](https://badge.fury.io/js/socket.io-parser.svg)](http://badge.fury.io/js/socket.io-parser)
A socket.io encoder and decoder written in JavaScript complying with version `4`
A socket.io encoder and decoder written in JavaScript complying with version `5`
of [socket.io-protocol](https://github.com/socketio/socket.io-protocol).
Used by [socket.io](https://github.com/automattic/socket.io) and
[socket.io-client](https://github.com/automattic/socket.io-client).
Compatibility table:
| Parser version | Socket.IO server version | Protocol revision |
|----------------| ------------------------ | ----------------- |
| 3.x | 1.x / 2.x | 4 |
| 4.x | 3.x | 5 |
## Parser API
socket.io-parser is the reference implementation of socket.io-protocol. Read
@@ -48,7 +56,7 @@ var parser = require('socket.io-parser');
var encoder = new parser.Encoder();
var packet = {
type: parser.BINARY_EVENT,
data: {i: new Buffer(1234), j: new Blob([new ArrayBuffer(2)])}
data: {i: new Buffer(1234), j: new Blob([new ArrayBuffer(2)])},
id: 15
};
encoder.encode(packet, function(encodedPackets) {

3
babel.config.js Normal file
View File

@@ -0,0 +1,3 @@
module.exports = {
presets: [["@babel/preset-env"]],
};

View File

@@ -1,10 +0,0 @@
var bencher = require('./index');
bencher(function(benchmark) {
function logMean(test) {
console.log(test.name + ' mean run time: ' + test.stats.mean);
}
for (var i = 0; i < benchmark.length; i++) {
logMean(benchmark[i]);
}
});

View File

@@ -1,20 +1,21 @@
var Benchmark = require('benchmark');
var parser = require('../index');
const Benchmark = require('benchmark');
const parser = require('..');
function test(packet, deferred) {
var encoder = new parser.Encoder();
var decoder = new parser.Decoder();
encoder.encode(packet, function(encodedPackets) {
var decoder = new parser.Decoder();
decoder.on('decoded', function(packet) {
const encoder = new parser.Encoder();
encoder.encode(packet, encodedPackets => {
const decoder = new parser.Decoder();
decoder.on('decoded', packet => {
deferred.resolve();
});
decoder.add(encodedPackets[0]);
for (const encodedPacket of encodedPackets) {
decoder.add(encodedPacket);
}
});
}
var dataObject = {
const dataObject = [{
'a': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17],
'b': 'xxxyyyzzzalsdfalskdjfalksdjfalksdjfalksdjfjjfjfjfjjfjfjfj',
'data': {
@@ -25,57 +26,54 @@ var dataObject = {
}
}
}
};
var bigArray = [];
for (var i = 0; i < 250; i++) {
}];
const bigArray = [];
for (let i = 0; i < 250; i++) {
bigArray.push(dataObject);
}
const suite = new Benchmark.Suite();
module.exports = function(callback) {
var suite = new Benchmark.Suite();
suite.add('small json parse', {defer: true, fn: function(deferred) {
var packet = {
suite
.add('small json parse', {defer: true, fn: deferred => {
const packet = {
type: parser.EVENT,
nsp: '/bench',
data: dataObject
};
test(packet, deferred);
}})
.add('big json parse', {defer: true, fn: function(deferred) {
var packet = {
.add('big json parse', {defer: true, fn: deferred => {
const packet = {
type: parser.EVENT,
nsp: '/bench',
data: bigArray
};
test(packet, deferred);
}})
.add('json with small binary parse', {defer: true, fn: function(deferred) {
var packet = {
type: parser.EVENT,
.add('json with small binary parse', {defer: true, fn: deferred => {
const packet = {
type: parser.BINARY_EVENT,
nsp: '/bench',
data: {'a': [1, 2, 3], 'b': 'xxxyyyzzz', 'data': new Buffer(1000)}
data: [{'a': [1, 2, 3], 'b': 'xxxyyyzzz', 'data': Buffer.allocUnsafe(1000)}]
};
test(packet, deferred);
}})
.add('json with big binary parse', {defer: true, fn: function(deferred) {
var bigBinaryData = {
bin1: new Buffer(10000),
.add('json with big binary parse', {defer: true, fn: deferred => {
const bigBinaryData = [{
bin1: Buffer.allocUnsafe(10000),
arr: bigArray,
bin2: new Buffer(10000),
bin3: new Buffer(10000)
};
var packet = {
type: parser.EVENT,
bin2: Buffer.allocUnsafe(10000),
bin3: Buffer.allocUnsafe(10000)
}];
const packet = {
type: parser.BINARY_EVENT,
nsp: '/bench',
data: bigBinaryData
};
test(packet, deferred);
}})
.on('complete', function() {
callback(this);
.on('cycle', function(event) {
console.log(String(event.target));
})
.run({'async': true});
};

4
bench/results.md Normal file
View File

@@ -0,0 +1,4 @@
small json parse x 67,893 ops/sec ±4.30% (76 runs sampled)
big json parse x 1,507 ops/sec ±1.72% (82 runs sampled)
json with small binary parse x 62,367 ops/sec ±6.03% (74 runs sampled)
json with big binary parse x 572 ops/sec ±1.25% (86 runs sampled)

149
binary.js
View File

@@ -1,149 +0,0 @@
/*global Blob,File*/
/**
* Module requirements
*/
var isArray = require('isarray');
var isBuf = require('./is-buffer');
var toString = Object.prototype.toString;
var withNativeBlob = typeof Blob === 'function' || (typeof Blob !== 'undefined' && toString.call(Blob) === '[object BlobConstructor]');
var withNativeFile = typeof File === 'function' || (typeof File !== 'undefined' && toString.call(File) === '[object FileConstructor]');
/**
* Replaces every Buffer | ArrayBuffer in packet with a numbered placeholder.
* Anything with blobs or files should be fed through removeBlobs before coming
* here.
*
* @param {Object} packet - socket.io event packet
* @return {Object} with deconstructed packet and list of buffers
* @api public
*/
exports.deconstructPacket = function(packet) {
var buffers = [];
var packetData = packet.data;
var pack = packet;
pack.data = _deconstructPacket(packetData, buffers);
pack.attachments = buffers.length; // number of binary 'attachments'
return {packet: pack, buffers: buffers};
};
function _deconstructPacket(data, buffers) {
if (!data) return data;
if (isBuf(data)) {
var placeholder = { _placeholder: true, num: buffers.length };
buffers.push(data);
return placeholder;
} else if (isArray(data)) {
var newData = new Array(data.length);
for (var i = 0; i < data.length; i++) {
newData[i] = _deconstructPacket(data[i], buffers);
}
return newData;
} else if (typeof data === 'object' && !(data instanceof Date)) {
var newData = {};
for (var key in data) {
newData[key] = _deconstructPacket(data[key], buffers);
}
return newData;
}
return data;
}
/**
* Reconstructs a binary packet from its placeholder packet and buffers
*
* @param {Object} packet - event packet with placeholders
* @param {Array} buffers - binary buffers to put in placeholder positions
* @return {Object} reconstructed packet
* @api public
*/
exports.reconstructPacket = function(packet, buffers) {
packet.data = _reconstructPacket(packet.data, buffers);
packet.attachments = undefined; // no longer useful
return packet;
};
function _reconstructPacket(data, buffers) {
if (!data) return data;
if (data && data._placeholder === true) {
var isIndexValid =
typeof data.num === "number" &&
data.num >= 0 &&
data.num < buffers.length;
if (isIndexValid) {
return buffers[data.num]; // appropriate buffer (should be natural order anyway)
} else {
throw new Error("illegal attachments");
}
} else if (isArray(data)) {
for (var i = 0; i < data.length; i++) {
data[i] = _reconstructPacket(data[i], buffers);
}
} else if (typeof data === 'object') {
for (var key in data) {
data[key] = _reconstructPacket(data[key], buffers);
}
}
return data;
}
/**
* Asynchronously removes Blobs or Files from data via
* FileReader's readAsArrayBuffer method. Used before encoding
* data as msgpack. Calls callback with the blobless data.
*
* @param {Object} data
* @param {Function} callback
* @api private
*/
exports.removeBlobs = function(data, callback) {
function _removeBlobs(obj, curKey, containingObject) {
if (!obj) return obj;
// convert any blob
if ((withNativeBlob && obj instanceof Blob) ||
(withNativeFile && obj instanceof File)) {
pendingBlobs++;
// async filereader
var fileReader = new FileReader();
fileReader.onload = function() { // this.result == arraybuffer
if (containingObject) {
containingObject[curKey] = this.result;
}
else {
bloblessData = this.result;
}
// if nothing pending its callback time
if(! --pendingBlobs) {
callback(bloblessData);
}
};
fileReader.readAsArrayBuffer(obj); // blob -> arraybuffer
} else if (isArray(obj)) { // handle array
for (var i = 0; i < obj.length; i++) {
_removeBlobs(obj[i], i, obj);
}
} else if (typeof obj === 'object' && !isBuf(obj)) { // and object
for (var key in obj) {
_removeBlobs(obj[key], key, obj);
}
}
}
var pendingBlobs = 0;
var bloblessData = data;
_removeBlobs(bloblessData);
if (!pendingBlobs) {
callback(bloblessData);
}
};

455
index.js
View File

@@ -1,455 +0,0 @@
/**
* Module dependencies.
*/
var debug = require('debug')('socket.io-parser');
var Emitter = require('component-emitter');
var binary = require('./binary');
var isArray = require('isarray');
var isBuf = require('./is-buffer');
/**
* Protocol version.
*
* @api public
*/
exports.protocol = 4;
/**
* Packet types.
*
* @api public
*/
exports.types = [
'CONNECT',
'DISCONNECT',
'EVENT',
'ACK',
'ERROR',
'BINARY_EVENT',
'BINARY_ACK'
];
/**
* Packet type `connect`.
*
* @api public
*/
exports.CONNECT = 0;
/**
* Packet type `disconnect`.
*
* @api public
*/
exports.DISCONNECT = 1;
/**
* Packet type `event`.
*
* @api public
*/
exports.EVENT = 2;
/**
* Packet type `ack`.
*
* @api public
*/
exports.ACK = 3;
/**
* Packet type `error`.
*
* @api public
*/
exports.ERROR = 4;
/**
* Packet type 'binary event'
*
* @api public
*/
exports.BINARY_EVENT = 5;
/**
* Packet type `binary ack`. For acks with binary arguments.
*
* @api public
*/
exports.BINARY_ACK = 6;
/**
* Encoder constructor.
*
* @api public
*/
exports.Encoder = Encoder;
/**
* Decoder constructor.
*
* @api public
*/
exports.Decoder = Decoder;
/**
* A socket.io Encoder instance
*
* @api public
*/
function Encoder() {}
var ERROR_PACKET = exports.ERROR + '"encode error"';
/**
* Encode a packet as a single string if non-binary, or as a
* buffer sequence, depending on packet type.
*
* @param {Object} obj - packet object
* @param {Function} callback - function to handle encodings (likely engine.write)
* @return Calls callback with Array of encodings
* @api public
*/
Encoder.prototype.encode = function(obj, callback){
debug('encoding packet %j', obj);
if (exports.BINARY_EVENT === obj.type || exports.BINARY_ACK === obj.type) {
encodeAsBinary(obj, callback);
} else {
var encoding = encodeAsString(obj);
callback([encoding]);
}
};
/**
* Encode packet as string.
*
* @param {Object} packet
* @return {String} encoded
* @api private
*/
function encodeAsString(obj) {
// first is type
var str = '' + obj.type;
// attachments if we have them
if (exports.BINARY_EVENT === obj.type || exports.BINARY_ACK === obj.type) {
str += obj.attachments + '-';
}
// if we have a namespace other than `/`
// we append it followed by a comma `,`
if (obj.nsp && '/' !== obj.nsp) {
str += obj.nsp + ',';
}
// immediately followed by the id
if (null != obj.id) {
str += obj.id;
}
// json data
if (null != obj.data) {
var payload = tryStringify(obj.data);
if (payload !== false) {
str += payload;
} else {
return ERROR_PACKET;
}
}
debug('encoded %j as %s', obj, str);
return str;
}
function tryStringify(str) {
try {
return JSON.stringify(str);
} catch(e){
return false;
}
}
/**
* Encode packet as 'buffer sequence' by removing blobs, and
* deconstructing packet into object with placeholders and
* a list of buffers.
*
* @param {Object} packet
* @return {Buffer} encoded
* @api private
*/
function encodeAsBinary(obj, callback) {
function writeEncoding(bloblessData) {
var deconstruction = binary.deconstructPacket(bloblessData);
var pack = encodeAsString(deconstruction.packet);
var buffers = deconstruction.buffers;
buffers.unshift(pack); // add packet info to beginning of data list
callback(buffers); // write all the buffers
}
binary.removeBlobs(obj, writeEncoding);
}
/**
* A socket.io Decoder instance
*
* @return {Object} decoder
* @api public
*/
function Decoder(opts) {
this.reconstructor = null;
opts = opts || {};
this.opts = {
maxAttachments: opts.maxAttachments || 10,
};
}
/**
* Mix in `Emitter` with Decoder.
*/
Emitter(Decoder.prototype);
/**
* Decodes an encoded packet string into packet JSON.
*
* @param {String} obj - encoded packet
* @return {Object} packet
* @api public
*/
Decoder.prototype.add = function(obj) {
var packet;
if (typeof obj === 'string') {
if (this.reconstructor) {
throw new Error("got plaintext data when reconstructing a packet");
}
packet = decodeString(obj, this.opts.maxAttachments);
if (exports.BINARY_EVENT === packet.type || exports.BINARY_ACK === packet.type) { // binary packet's json
this.reconstructor = new BinaryReconstructor(packet);
// no attachments, labeled binary but no binary data to follow
if (this.reconstructor.reconPack.attachments === 0) {
this.emit('decoded', packet);
}
} else { // non-binary full packet
this.emit('decoded', packet);
}
} else if (isBuf(obj) || obj.base64) { // raw binary data
if (!this.reconstructor) {
throw new Error('got binary data when not reconstructing a packet');
} else {
packet = this.reconstructor.takeBinaryData(obj);
if (packet) { // received final buffer
this.reconstructor = null;
this.emit('decoded', packet);
}
}
} else {
throw new Error('Unknown type: ' + obj);
}
};
/**
* Decode a packet String (JSON data)
*
* @param {String} str
* @param {Number} maxAttachments - the maximum number of binary attachments
* @return {Object} packet
* @api private
*/
function decodeString(str, maxAttachments) {
var i = 0;
// look up type
var p = {
type: Number(str.charAt(0))
};
if (null == exports.types[p.type]) {
return error('unknown packet type ' + p.type);
}
// look up attachments if type binary
if (exports.BINARY_EVENT === p.type || exports.BINARY_ACK === p.type) {
var start = i + 1;
while (str.charAt(++i) !== '-' && i != str.length) {}
var buf = str.substring(start, i);
if (buf != Number(buf) || str.charAt(i) !== '-') {
throw new Error('Illegal attachments');
}
var n = Number(buf);
if (!isInteger(n) || n < 0) {
throw new Error("Illegal attachments");
} else if (n > maxAttachments) {
throw new Error("too many attachments");
}
p.attachments = n;
}
// look up namespace (if any)
if ('/' === str.charAt(i + 1)) {
var start = i + 1;
while (++i) {
var c = str.charAt(i);
if (',' === c) break;
if (i === str.length) break;
}
p.nsp = str.substring(start, i);
} else {
p.nsp = '/';
}
// look up id
var next = str.charAt(i + 1);
if ('' !== next && Number(next) == next) {
var start = i + 1;
while (++i) {
var c = str.charAt(i);
if (null == c || Number(c) != c) {
--i;
break;
}
if (i === str.length) break;
}
p.id = Number(str.substring(start, i + 1));
}
// look up json data
if (str.charAt(++i)) {
var payload = tryParse(str.substr(i));
if (isPayloadValid(p.type, payload)) {
p.data = payload;
} else {
throw new Error("invalid payload");
}
}
debug('decoded %s as %j', str, p);
return p;
}
function tryParse(str) {
try {
return JSON.parse(str);
} catch(e){
return false;
}
}
function isPayloadValid(type, payload) {
switch (type) {
case 0: // CONNECT
return typeof payload === "object";
case 1: // DISCONNECT
return payload === undefined;
case 4: // ERROR
return typeof payload === "string" || typeof payload === "object";
case 2: // EVENT
case 5: // BINARY_EVENT
return (
isArray(payload) &&
(typeof payload[0] === "string" || typeof payload[0] === "number")
);
case 3: // ACK
case 6: // BINARY_ACK
return isArray(payload);
}
}
/**
* Deallocates a parser's resources
*
* @api public
*/
Decoder.prototype.destroy = function() {
if (this.reconstructor) {
this.reconstructor.finishedReconstruction();
}
};
/**
* A manager of a binary event's 'buffer sequence'. Should
* be constructed whenever a packet of type BINARY_EVENT is
* decoded.
*
* @param {Object} packet
* @return {BinaryReconstructor} initialized reconstructor
* @api private
*/
function BinaryReconstructor(packet) {
this.reconPack = packet;
this.buffers = [];
}
/**
* Method to be called when binary data received from connection
* after a BINARY_EVENT packet.
*
* @param {Buffer | ArrayBuffer} binData - the raw binary data received
* @return {null | Object} returns null if more binary data is expected or
* a reconstructed packet object if all buffers have been received.
* @api private
*/
BinaryReconstructor.prototype.takeBinaryData = function(binData) {
this.buffers.push(binData);
if (this.buffers.length === this.reconPack.attachments) { // done with buffer list
var packet = binary.reconstructPacket(this.reconPack, this.buffers);
this.finishedReconstruction();
return packet;
}
return null;
};
/**
* Cleans up binary packet reconstruction variables.
*
* @api private
*/
BinaryReconstructor.prototype.finishedReconstruction = function() {
this.reconPack = null;
this.buffers = [];
};
function error(msg) {
return {
type: exports.ERROR,
data: 'parser error: ' + msg
};
}
var isInteger =
Number.isInteger ||
function (value) {
return (
typeof value === "number" &&
isFinite(value) &&
Math.floor(value) === value
);
};

View File

@@ -1,20 +0,0 @@
module.exports = isBuf;
var withNativeBuffer = typeof Buffer === 'function' && typeof Buffer.isBuffer === 'function';
var withNativeArrayBuffer = typeof ArrayBuffer === 'function';
var isView = function (obj) {
return typeof ArrayBuffer.isView === 'function' ? ArrayBuffer.isView(obj) : (obj.buffer instanceof ArrayBuffer);
};
/**
* Returns true if obj is a buffer or an arraybuffer.
*
* @api private
*/
function isBuf(obj) {
return (withNativeBuffer && Buffer.isBuffer(obj)) ||
(withNativeArrayBuffer && (obj instanceof ArrayBuffer || isView(obj)));
}

86
lib/binary.ts Normal file
View File

@@ -0,0 +1,86 @@
import { isBinary } from "./is-binary.js";
/**
* Replaces every Buffer | ArrayBuffer | Blob | File in packet with a numbered placeholder.
*
* @param {Object} packet - socket.io event packet
* @return {Object} with deconstructed packet and list of buffers
* @public
*/
export function deconstructPacket(packet) {
const buffers = [];
const packetData = packet.data;
const pack = packet;
pack.data = _deconstructPacket(packetData, buffers);
pack.attachments = buffers.length; // number of binary 'attachments'
return { packet: pack, buffers: buffers };
}
function _deconstructPacket(data, buffers) {
if (!data) return data;
if (isBinary(data)) {
const placeholder = { _placeholder: true, num: buffers.length };
buffers.push(data);
return placeholder;
} else if (Array.isArray(data)) {
const newData = new Array(data.length);
for (let i = 0; i < data.length; i++) {
newData[i] = _deconstructPacket(data[i], buffers);
}
return newData;
} else if (typeof data === "object" && !(data instanceof Date)) {
const newData = {};
for (const key in data) {
if (Object.prototype.hasOwnProperty.call(data, key)) {
newData[key] = _deconstructPacket(data[key], buffers);
}
}
return newData;
}
return data;
}
/**
* Reconstructs a binary packet from its placeholder packet and buffers
*
* @param {Object} packet - event packet with placeholders
* @param {Array} buffers - binary buffers to put in placeholder positions
* @return {Object} reconstructed packet
* @public
*/
export function reconstructPacket(packet, buffers) {
packet.data = _reconstructPacket(packet.data, buffers);
delete packet.attachments; // no longer useful
return packet;
}
function _reconstructPacket(data, buffers) {
if (!data) return data;
if (data && data._placeholder === true) {
const isIndexValid =
typeof data.num === "number" &&
data.num >= 0 &&
data.num < buffers.length;
if (isIndexValid) {
return buffers[data.num]; // appropriate buffer (should be natural order anyway)
} else {
throw new Error("illegal attachments");
}
} else if (Array.isArray(data)) {
for (let i = 0; i < data.length; i++) {
data[i] = _reconstructPacket(data[i], buffers);
}
} else if (typeof data === "object") {
for (const key in data) {
if (Object.prototype.hasOwnProperty.call(data, key)) {
data[key] = _reconstructPacket(data[key], buffers);
}
}
}
return data;
}

361
lib/index.ts Normal file
View File

@@ -0,0 +1,361 @@
import { Emitter } from "@socket.io/component-emitter";
import { deconstructPacket, reconstructPacket } from "./binary.js";
import { isBinary, hasBinary } from "./is-binary.js";
import debugModule from "debug"; // debug()
const debug = debugModule("socket.io-parser"); // debug()
/**
* These strings must not be used as event names, as they have a special meaning.
*/
const RESERVED_EVENTS = [
"connect", // used on the client side
"connect_error", // used on the client side
"disconnect", // used on both sides
"disconnecting", // used on the server side
"newListener", // used by the Node.js EventEmitter
"removeListener", // used by the Node.js EventEmitter
];
/**
* Protocol version.
*
* @public
*/
export const protocol: number = 5;
export enum PacketType {
CONNECT,
DISCONNECT,
EVENT,
ACK,
CONNECT_ERROR,
BINARY_EVENT,
BINARY_ACK,
}
export interface Packet {
type: PacketType;
nsp: string;
data?: any;
id?: number;
attachments?: number;
}
/**
* A socket.io Encoder instance
*/
export class Encoder {
/**
* Encoder constructor
*
* @param {function} replacer - custom replacer to pass down to JSON.parse
*/
constructor(private replacer?: (this: any, key: string, value: any) => any) {}
/**
* Encode a packet as a single string if non-binary, or as a
* buffer sequence, depending on packet type.
*
* @param {Object} obj - packet object
*/
public encode(obj: Packet) {
debug("encoding packet %j", obj);
if (obj.type === PacketType.EVENT || obj.type === PacketType.ACK) {
if (hasBinary(obj)) {
return this.encodeAsBinary({
type:
obj.type === PacketType.EVENT
? PacketType.BINARY_EVENT
: PacketType.BINARY_ACK,
nsp: obj.nsp,
data: obj.data,
id: obj.id,
});
}
}
return [this.encodeAsString(obj)];
}
/**
* Encode packet as string.
*/
private encodeAsString(obj: Packet) {
// first is type
let str = "" + obj.type;
// attachments if we have them
if (
obj.type === PacketType.BINARY_EVENT ||
obj.type === PacketType.BINARY_ACK
) {
str += obj.attachments + "-";
}
// if we have a namespace other than `/`
// we append it followed by a comma `,`
if (obj.nsp && "/" !== obj.nsp) {
str += obj.nsp + ",";
}
// immediately followed by the id
if (null != obj.id) {
str += obj.id;
}
// json data
if (null != obj.data) {
str += JSON.stringify(obj.data, this.replacer);
}
debug("encoded %j as %s", obj, str);
return str;
}
/**
* Encode packet as 'buffer sequence' by removing blobs, and
* deconstructing packet into object with placeholders and
* a list of buffers.
*/
private encodeAsBinary(obj: Packet) {
const deconstruction = deconstructPacket(obj);
const pack = this.encodeAsString(deconstruction.packet);
const buffers = deconstruction.buffers;
buffers.unshift(pack); // add packet info to beginning of data list
return buffers; // write all the buffers
}
}
// see https://stackoverflow.com/questions/8511281/check-if-a-value-is-an-object-in-javascript
function isObject(value: any): boolean {
return Object.prototype.toString.call(value) === "[object Object]";
}
interface DecoderReservedEvents {
decoded: (packet: Packet) => void;
}
/**
* A socket.io Decoder instance
*
* @return {Object} decoder
*/
export class Decoder extends Emitter<{}, {}, DecoderReservedEvents> {
private reconstructor: BinaryReconstructor;
/**
* Decoder constructor
*
* @param {function} reviver - custom reviver to pass down to JSON.stringify
*/
constructor(private reviver?: (this: any, key: string, value: any) => any) {
super();
}
/**
* Decodes an encoded packet string into packet JSON.
*
* @param {String} obj - encoded packet
*/
public add(obj: any) {
let packet;
if (typeof obj === "string") {
if (this.reconstructor) {
throw new Error("got plaintext data when reconstructing a packet");
}
packet = this.decodeString(obj);
const isBinaryEvent = packet.type === PacketType.BINARY_EVENT;
if (isBinaryEvent || packet.type === PacketType.BINARY_ACK) {
packet.type = isBinaryEvent ? PacketType.EVENT : PacketType.ACK;
// binary packet's json
this.reconstructor = new BinaryReconstructor(packet);
// no attachments, labeled binary but no binary data to follow
if (packet.attachments === 0) {
super.emitReserved("decoded", packet);
}
} else {
// non-binary full packet
super.emitReserved("decoded", packet);
}
} else if (isBinary(obj) || obj.base64) {
// raw binary data
if (!this.reconstructor) {
throw new Error("got binary data when not reconstructing a packet");
} else {
packet = this.reconstructor.takeBinaryData(obj);
if (packet) {
// received final buffer
this.reconstructor = null;
super.emitReserved("decoded", packet);
}
}
} else {
throw new Error("Unknown type: " + obj);
}
}
/**
* Decode a packet String (JSON data)
*
* @param {String} str
* @return {Object} packet
*/
private decodeString(str): Packet {
let i = 0;
// look up type
const p: any = {
type: Number(str.charAt(0)),
};
if (PacketType[p.type] === undefined) {
throw new Error("unknown packet type " + p.type);
}
// look up attachments if type binary
if (
p.type === PacketType.BINARY_EVENT ||
p.type === PacketType.BINARY_ACK
) {
const start = i + 1;
while (str.charAt(++i) !== "-" && i != str.length) {}
const buf = str.substring(start, i);
if (buf != Number(buf) || str.charAt(i) !== "-") {
throw new Error("Illegal attachments");
}
p.attachments = Number(buf);
}
// look up namespace (if any)
if ("/" === str.charAt(i + 1)) {
const start = i + 1;
while (++i) {
const c = str.charAt(i);
if ("," === c) break;
if (i === str.length) break;
}
p.nsp = str.substring(start, i);
} else {
p.nsp = "/";
}
// look up id
const next = str.charAt(i + 1);
if ("" !== next && Number(next) == next) {
const start = i + 1;
while (++i) {
const c = str.charAt(i);
if (null == c || Number(c) != c) {
--i;
break;
}
if (i === str.length) break;
}
p.id = Number(str.substring(start, i + 1));
}
// look up json data
if (str.charAt(++i)) {
const payload = this.tryParse(str.substr(i));
if (Decoder.isPayloadValid(p.type, payload)) {
p.data = payload;
} else {
throw new Error("invalid payload");
}
}
debug("decoded %s as %j", str, p);
return p;
}
private tryParse(str) {
try {
return JSON.parse(str, this.reviver);
} catch (e) {
return false;
}
}
private static isPayloadValid(type: PacketType, payload: any): boolean {
switch (type) {
case PacketType.CONNECT:
return isObject(payload);
case PacketType.DISCONNECT:
return payload === undefined;
case PacketType.CONNECT_ERROR:
return typeof payload === "string" || isObject(payload);
case PacketType.EVENT:
case PacketType.BINARY_EVENT:
return (
Array.isArray(payload) &&
(typeof payload[0] === "number" ||
(typeof payload[0] === "string" &&
RESERVED_EVENTS.indexOf(payload[0]) === -1))
);
case PacketType.ACK:
case PacketType.BINARY_ACK:
return Array.isArray(payload);
}
}
/**
* Deallocates a parser's resources
*/
public destroy() {
if (this.reconstructor) {
this.reconstructor.finishedReconstruction();
this.reconstructor = null;
}
}
}
/**
* A manager of a binary event's 'buffer sequence'. Should
* be constructed whenever a packet of type BINARY_EVENT is
* decoded.
*
* @param {Object} packet
* @return {BinaryReconstructor} initialized reconstructor
*/
class BinaryReconstructor {
private reconPack;
private buffers: Array<Buffer | ArrayBuffer> = [];
constructor(readonly packet: Packet) {
this.reconPack = packet;
}
/**
* Method to be called when binary data received from connection
* after a BINARY_EVENT packet.
*
* @param {Buffer | ArrayBuffer} binData - the raw binary data received
* @return {null | Object} returns null if more binary data is expected or
* a reconstructed packet object if all buffers have been received.
*/
public takeBinaryData(binData) {
this.buffers.push(binData);
if (this.buffers.length === this.reconPack.attachments) {
// done with buffer list
const packet = reconstructPacket(this.reconPack, this.buffers);
this.finishedReconstruction();
return packet;
}
return null;
}
/**
* Cleans up binary packet reconstruction variables.
*/
public finishedReconstruction() {
this.reconPack = null;
this.buffers = [];
}
}

66
lib/is-binary.ts Normal file
View File

@@ -0,0 +1,66 @@
const withNativeArrayBuffer: boolean = typeof ArrayBuffer === "function";
const isView = (obj: any) => {
return typeof ArrayBuffer.isView === "function"
? ArrayBuffer.isView(obj)
: obj.buffer instanceof ArrayBuffer;
};
const toString = Object.prototype.toString;
const withNativeBlob =
typeof Blob === "function" ||
(typeof Blob !== "undefined" &&
toString.call(Blob) === "[object BlobConstructor]");
const withNativeFile =
typeof File === "function" ||
(typeof File !== "undefined" &&
toString.call(File) === "[object FileConstructor]");
/**
* Returns true if obj is a Buffer, an ArrayBuffer, a Blob or a File.
*
* @private
*/
export function isBinary(obj: any) {
return (
(withNativeArrayBuffer && (obj instanceof ArrayBuffer || isView(obj))) ||
(withNativeBlob && obj instanceof Blob) ||
(withNativeFile && obj instanceof File)
);
}
export function hasBinary(obj: any, toJSON?: boolean) {
if (!obj || typeof obj !== "object") {
return false;
}
if (Array.isArray(obj)) {
for (let i = 0, l = obj.length; i < l; i++) {
if (hasBinary(obj[i])) {
return true;
}
}
return false;
}
if (isBinary(obj)) {
return true;
}
if (
obj.toJSON &&
typeof obj.toJSON === "function" &&
arguments.length === 1
) {
return hasBinary(obj.toJSON(), true);
}
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key) && hasBinary(obj[key])) {
return true;
}
}
return false;
}

18854
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,34 +1,55 @@
{
"name": "socket.io-parser",
"version": "3.4.4",
"version": "4.2.4",
"description": "socket.io protocol parser",
"repository": {
"type": "git",
"url": "git+https://github.com/socketio/socket.io.git"
"url": "https://github.com/socketio/socket.io-parser.git"
},
"files": [
"binary.js",
"index.js",
"is-buffer.js"
"build/"
],
"main": "./build/cjs/index.js",
"module": "./build/esm/index.js",
"types": "./build/esm/index.d.ts",
"exports": {
"import": {
"node": "./build/esm-debug/index.js",
"default": "./build/esm/index.js"
},
"require": "./build/cjs/index.js"
},
"dependencies": {
"debug": "~4.1.0",
"component-emitter": "1.2.1",
"isarray": "2.0.1"
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
},
"devDependencies": {
"@babel/core": "~7.9.6",
"@babel/preset-env": "~7.9.6",
"babelify": "~10.0.0",
"@babel/register": "^7.18.9",
"@types/debug": "^4.1.5",
"@types/node": "^14.11.1",
"@wdio/cli": "^7.26.0",
"@wdio/local-runner": "^7.26.0",
"@wdio/mocha-framework": "^7.26.0",
"@wdio/sauce-service": "^7.26.0",
"@wdio/spec-reporter": "^7.26.0",
"benchmark": "2.1.2",
"expect.js": "0.3.1",
"mocha": "3.2.0",
"socket.io-browsers": "^1.0.0",
"zuul": "3.11.1",
"zuul-ngrok": "4.0.0"
"mocha": "^10.1.0",
"prettier": "^2.1.2",
"rimraf": "^3.0.2",
"typescript": "^4.0.3",
"wdio-geckodriver-service": "^4.0.0"
},
"scripts": {
"test": "make test"
"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 --reporter dot --bail test/index.js",
"test:browser": "wdio",
"format:fix": "prettier --write --parser typescript '*.js' 'lib/**/*.ts' 'test/**/*.js'",
"format:check": "prettier --check --parser typescript '*.js' 'lib/**/*.ts' 'test/**/*.js'",
"prepack": "npm run compile"
},
"license": "MIT",
"engines": {

8
postcompile.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/bin/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/
sed -i '/debug(/d' ./build/esm/*.js

3
support/package.cjs.json Normal file
View File

@@ -0,0 +1,3 @@
{
"type": "commonjs"
}

3
support/package.esm.json Normal file
View File

@@ -0,0 +1,3 @@
{
"type": "module"
}

View File

@@ -1,70 +1,87 @@
var parser = require('../index.js');
var expect = require('expect.js');
var helpers = require('./helpers.js');
var encoder = new parser.Encoder();
const { PacketType, Decoder, Encoder } = require("..");
const expect = require("expect.js");
const helpers = require("./helpers.js");
const encoder = new Encoder();
describe('parser', function() {
it('encodes an ArrayBuffer', function() {
var packet = {
type: parser.BINARY_EVENT,
data: ['a', new ArrayBuffer(2)],
describe("ArrayBuffer", () => {
it("encodes an ArrayBuffer", () => {
const packet = {
type: PacketType.EVENT,
data: ["a", new ArrayBuffer(2)],
id: 0,
nsp: '/'
nsp: "/",
};
helpers.test_bin(packet);
return helpers.test_bin(packet);
});
it('encodes a TypedArray', function() {
var array = new Uint8Array(5);
for (var i = 0; i < array.length; i++) array[i] = i;
var packet = {
type: parser.BINARY_EVENT,
data: ['a', array],
it("encodes an ArrayBuffer into an object with a null prototype", () => {
const packet = {
type: PacketType.EVENT,
data: [
"a",
Object.create(null, {
array: { value: new ArrayBuffer(2), enumerable: true },
}),
],
id: 0,
nsp: '/'
nsp: "/",
};
helpers.test_bin(packet);
return helpers.test_bin(packet);
});
it('encodes ArrayBuffers deep in JSON', function() {
var packet = {
type: parser.BINARY_EVENT,
data: ['a', {a: 'hi', b: {why: new ArrayBuffer(3)}, c: {a: 'bye', b: { a: new ArrayBuffer(6)}}}],
it("encodes a TypedArray", () => {
const array = new Uint8Array(5);
for (let i = 0; i < array.length; i++) array[i] = i;
const packet = {
type: PacketType.EVENT,
data: ["a", array],
id: 0,
nsp: "/",
};
return helpers.test_bin(packet);
});
it("encodes ArrayBuffers deep in JSON", () => {
const packet = {
type: PacketType.EVENT,
data: [
"a",
{
a: "hi",
b: { why: new ArrayBuffer(3) },
c: { a: "bye", b: { a: new ArrayBuffer(6) } },
},
],
id: 999,
nsp: '/deep'
nsp: "/deep",
};
helpers.test_bin(packet);
return helpers.test_bin(packet);
});
it('encodes deep binary JSON with null values', function() {
var packet = {
type: parser.BINARY_EVENT,
data: ['a', {a: 'b', c: 4, e: {g: null}, h: new ArrayBuffer(9)}],
nsp: '/',
id: 600
it("encodes deep binary JSON with null values", () => {
const packet = {
type: PacketType.EVENT,
data: ["a", { a: "b", c: 4, e: { g: null }, h: new ArrayBuffer(9) }],
nsp: "/",
id: 600,
};
helpers.test_bin(packet);
return helpers.test_bin(packet);
});
it('cleans itself up on close', function() {
var packet = {
type: parser.BINARY_EVENT,
data: ["foo", new ArrayBuffer(2), new ArrayBuffer(3)],
id: 0,
nsp: '/'
it("should not modify the input packet", () => {
const packet = {
type: PacketType.EVENT,
nsp: "/",
data: ["a", Uint8Array.of(1, 2, 3), Uint8Array.of(4, 5, 6)],
};
encoder.encode(packet, function(encodedPackets) {
var decoder = new parser.Decoder();
decoder.on('decoded', function(packet) {
throw new Error("received a packet when not all binary data was sent.");
});
encoder.encode(packet);
decoder.add(encodedPackets[0]); // add metadata
decoder.add(encodedPackets[1]); // add first attachment
decoder.destroy(); // destroy before all data added
expect(decoder.reconstructor.buffers.length).to.be(0); // expect that buffer is clean
expect(packet).to.eql({
type: PacketType.EVENT,
nsp: "/",
data: ["a", Uint8Array.of(1, 2, 3), Uint8Array.of(4, 5, 6)],
});
});
});

View File

@@ -1,67 +1,72 @@
var parser = require('../index.js');
var helpers = require('./helpers.js');
const { PacketType } = require("..");
const helpers = require("./helpers.js");
var BlobBuilder = typeof BlobBuilder !== 'undefined' ? BlobBuilder :
typeof WebKitBlobBuilder !== 'undefined' ? WebKitBlobBuilder :
typeof MSBlobBuilder !== 'undefined' ? MSBlobBuilder :
typeof MozBlobBuilder !== 'undefined' ? MozBlobBuilder : false;
const BlobBuilderImpl =
typeof BlobBuilder !== "undefined"
? BlobBuilder
: typeof WebKitBlobBuilder !== "undefined"
? WebKitBlobBuilder
: typeof MSBlobBuilder !== "undefined"
? MSBlobBuilder
: typeof MozBlobBuilder !== "undefined"
? MozBlobBuilder
: false;
describe('parser', function() {
it('encodes a Blob', function() {
var data;
if (BlobBuilder) {
var bb = new BlobBuilder();
describe("Blob", () => {
it("encodes a Blob", () => {
let data;
if (BlobBuilderImpl) {
const bb = new BlobBuilderImpl();
bb.append(new ArrayBuffer(2));
data = bb.getBlob();
} else {
data = new Blob([new ArrayBuffer(2)]);
}
var packet = {
type: parser.BINARY_EVENT,
data: data,
const packet = {
type: PacketType.EVENT,
data: ["a", data],
id: 0,
nsp: '/'
nsp: "/",
};
helpers.test_bin(packet);
return helpers.test_bin(packet);
});
it('encodes an Blob deep in JSON', function() {
var data;
if (BlobBuilder) {
var bb = new BlobBuilder();
it("encodes an Blob deep in JSON", () => {
let data;
if (BlobBuilderImpl) {
const bb = new BlobBuilderImpl();
bb.append(new ArrayBuffer(2));
data = bb.getBlob();
} else {
data = new Blob([new ArrayBuffer(2)]);
}
var packet = {
type: parser.BINARY_EVENT,
data: {a: 'hi', b: { why: data }, c: 'bye'},
const packet = {
type: PacketType.EVENT,
data: ["a", { a: "hi", b: { why: data }, c: "bye" }],
id: 999,
nsp: '/deep'
nsp: "/deep",
};
helpers.test_bin(packet);
return helpers.test_bin(packet);
});
it('encodes a binary ack with a blob', function() {
var data;
if (BlobBuilder) {
var bb = new BlobBuilder();
it("encodes a binary ack with a blob", () => {
let data;
if (BlobBuilderImpl) {
const bb = new BlobBuilderImpl();
bb.append(new ArrayBuffer(2));
data = bb.getBlob();
} else {
data = new Blob([new ArrayBuffer(2)]);
}
var packet = {
type: parser.BINARY_ACK,
data: {a: 'hi ack', b: { why: data }, c: 'bye ack'},
const packet = {
type: PacketType.ACK,
data: [{ a: "hi ack", b: { why: data }, c: "bye ack" }],
id: 999,
nsp: '/deep'
nsp: "/deep",
};
helpers.test_bin(packet);
})
return helpers.test_bin(packet);
});
});

View File

@@ -1,66 +1,65 @@
var parser = require('../index.js');
var expect = require('expect.js');
var helpers = require('./helpers.js');
var Decoder = parser.Decoder;
const { PacketType, Decoder } = require("../");
const helpers = require("./helpers.js");
const expect = require("expect.js");
describe('parser', function() {
it('encodes a Buffer', function() {
helpers.test_bin({
type: parser.BINARY_EVENT,
data: ['a', new Buffer('abc', 'utf8')],
id: 23,
nsp: '/cool'
});
describe("Buffer", () => {
it("encodes a Buffer", () => {
return helpers.test_bin({
type: PacketType.EVENT,
data: ["a", Buffer.from("abc", "utf8")],
id: 23,
nsp: "/cool",
});
});
it("encodes a nested Buffer", function() {
helpers.test_bin({
type: parser.BINARY_EVENT,
it("encodes a nested Buffer", () => {
return helpers.test_bin({
type: PacketType.EVENT,
data: ["a", { b: ["c", Buffer.from("abc", "utf8")] }],
id: 23,
nsp: "/cool",
});
});
it('encodes a binary ack with Buffer', function() {
helpers.test_bin({
type: parser.BINARY_ACK,
data: ['a', new Buffer('xxx', 'utf8'), {}],
it("encodes a binary ack with Buffer", () => {
return helpers.test_bin({
type: PacketType.ACK,
data: ["a", Buffer.from("xxx", "utf8"), {}],
id: 127,
nsp: '/back'
})
nsp: "/back",
});
});
it("throws an error when adding an attachment with an invalid 'num' attribute (string)", function() {
var decoder = new Decoder();
it("throws an error when adding an attachment with an invalid 'num' attribute (string)", () => {
const decoder = new Decoder();
expect(function() {
expect(() => {
decoder.add('51-["hello",{"_placeholder":true,"num":"splice"}]');
decoder.add(Buffer.from("world"));
}).to.throwException(/^illegal attachments$/);
});
it("throws an error when adding an attachment with an invalid 'num' attribute (out-of-bound)", function() {
var decoder = new Decoder();
it("throws an error when adding an attachment with an invalid 'num' attribute (out-of-bound)", () => {
const decoder = new Decoder();
expect(function() {
expect(() => {
decoder.add('51-["hello",{"_placeholder":true,"num":1}]');
decoder.add(Buffer.from("world"));
}).to.throwException(/^illegal attachments$/);
});
it("throws an error when adding an attachment without header", function() {
var decoder = new Decoder();
it("throws an error when adding an attachment without header", () => {
const decoder = new Decoder();
expect(function() {
expect(() => {
decoder.add(Buffer.from("world"));
}).to.throwException(/^got binary data when not reconstructing a packet$/);
});
it("throws an error when decoding a binary event without attachments", function() {
var decoder = new Decoder();
it("throws an error when decoding a binary event without attachments", () => {
const decoder = new Decoder();
expect(function() {
expect(() => {
decoder.add('51-["hello",{"_placeholder":true,"num":0}]');
decoder.add('2["hello"]');
}).to.throwException(/^got plaintext data when reconstructing a packet$/);

View File

@@ -1,46 +1,35 @@
var parser = require('../index.js');
var expect = require('expect.js');
var encoder = new parser.Encoder();
const parser = require("..");
const expect = require("expect.js");
const encoder = new parser.Encoder();
// tests encoding and decoding a single packet
module.exports.test = function(obj){
encoder.encode(obj, function(encodedPackets) {
var decoder = new parser.Decoder();
decoder.on('decoded', function(packet) {
module.exports.test = (obj) => {
return new Promise((resolve) => {
const encodedPackets = encoder.encode(obj);
const decoder = new parser.Decoder();
decoder.on("decoded", (packet) => {
expect(packet).to.eql(obj);
resolve();
});
decoder.add(encodedPackets[0]);
});
}
};
// tests encoding of binary packets
module.exports.test_bin = function test_bin(obj) {
var originalData = obj.data;
encoder.encode(obj, function(encodedPackets) {
var decoder = new parser.Decoder();
decoder.on('decoded', function(packet) {
obj.data = originalData;
obj.attachments = undefined;
module.exports.test_bin = (obj) => {
return new Promise((resolve) => {
const encodedPackets = encoder.encode(obj);
const decoder = new parser.Decoder();
decoder.on("decoded", (packet) => {
expect(obj).to.eql(packet);
resolve();
});
for (var i = 0; i < encodedPackets.length; i++) {
for (let i = 0; i < encodedPackets.length; i++) {
decoder.add(encodedPackets[i]);
}
});
}
// array buffer's slice is native code that is not transported across
// socket.io via msgpack, so regular .eql fails
module.exports.testArrayBuffers = function(buf1, buf2) {
buf1.slice = undefined;
buf2.slice = undefined;
expect(buf1).to.eql(buf2);
}
module.exports.testPacketMetadata = function(p1, p2) {
expect(p1.type).to.eql(p2.type);
expect(p1.id).to.eql(p2.id);
expect(p1.nsp).to.eql(p2.nsp);
}
};

View File

@@ -1,33 +1,41 @@
var env = require('./support/env.js');
const env = require("./support/env.js");
var blobSupported = (function() {
const blobSupported = (function () {
try {
new Blob(['hi']);
new Blob(["hi"]);
return true;
} catch(e) {}
} catch (e) {}
return false;
})();
/**
* Create a blob builder even when vendor prefixes exist
*/
const BlobBuilderImpl =
typeof BlobBuilder !== "undefined"
? BlobBuilder
: typeof WebKitBlobBuilder !== "undefined"
? WebKitBlobBuilder
: typeof MSBlobBuilder !== "undefined"
? MSBlobBuilder
: typeof MozBlobBuilder !== "undefined"
? MozBlobBuilder
: false;
const blobBuilderSupported =
!!BlobBuilderImpl &&
!!BlobBuilderImpl.prototype.append &&
!!BlobBuilderImpl.prototype.getBlob;
var BlobBuilder = typeof BlobBuilder !== 'undefined' ? BlobBuilder :
typeof WebKitBlobBuilder !== 'undefined' ? WebKitBlobBuilder :
typeof MSBlobBuilder !== 'undefined' ? MSBlobBuilder :
typeof MozBlobBuilder !== 'undefined' ? MozBlobBuilder : false;
var blobBuilderSupported = !!BlobBuilder && !!BlobBuilder.prototype.append && !!BlobBuilder.prototype.getBlob;
require('./parser.js');
require("./parser.js");
if (!env.browser) {
require('./buffer.js');
require("./buffer.js");
}
if (typeof ArrayBuffer !== 'undefined') {
require('./arraybuffer.js');
if (typeof ArrayBuffer !== "undefined") {
require("./arraybuffer.js");
}
if (blobSupported || blobBuilderSupported) {
require('./blob.js');
require("./blob.js");
}

View File

@@ -1,126 +1,151 @@
var parser = require('../index.js');
var expect = require('expect.js');
var helpers = require('./helpers.js');
const { PacketType, Decoder, Encoder } = require("..");
const expect = require("expect.js");
const helpers = require("./helpers.js");
describe('parser', function(){
it('exposes types', function(){
expect(parser.CONNECT).to.be.a('number');
expect(parser.DISCONNECT).to.be.a('number');
expect(parser.EVENT).to.be.a('number');
expect(parser.ACK).to.be.a('number');
expect(parser.ERROR).to.be.a('number');
expect(parser.BINARY_EVENT).to.be.a('number');
expect(parser.BINARY_ACK).to.be.a('number');
describe("socket.io-parser", () => {
it("exposes types", () => {
expect(PacketType.CONNECT).to.be.a("number");
expect(PacketType.DISCONNECT).to.be.a("number");
expect(PacketType.EVENT).to.be.a("number");
expect(PacketType.ACK).to.be.a("number");
expect(PacketType.CONNECT_ERROR).to.be.a("number");
expect(PacketType.BINARY_EVENT).to.be.a("number");
expect(PacketType.BINARY_ACK).to.be.a("number");
});
it('encodes connection', function(){
helpers.test({
type: parser.CONNECT,
nsp: '/woot'
it("encodes connection", () => {
return helpers.test({
type: PacketType.CONNECT,
nsp: "/woot",
data: {
token: "123",
},
});
});
it('encodes disconnection', function(){
helpers.test({
type: parser.DISCONNECT,
nsp: '/woot'
it("encodes disconnection", () => {
return helpers.test({
type: PacketType.DISCONNECT,
nsp: "/woot",
});
});
it('encodes an event', function(){
helpers.test({
type: parser.EVENT,
data: ['a', 1, {}],
nsp: '/'
it("encodes an event", () => {
return helpers.test({
type: PacketType.EVENT,
data: ["a", 1, {}],
nsp: "/",
});
helpers.test({
type: parser.EVENT,
data: ['a', 1, {}],
});
it("encodes an event (with an integer as event name)", () => {
return helpers.test({
type: PacketType.EVENT,
data: [1, "a", {}],
nsp: "/",
});
});
it("encodes an event (with ack)", () => {
return helpers.test({
type: PacketType.EVENT,
data: ["a", 1, {}],
id: 1,
nsp: '/test'
nsp: "/test",
});
});
it('encodes an ack', function(){
helpers.test({
type: parser.ACK,
data: ['a', 1, {}],
it("encodes an ack", () => {
return helpers.test({
type: PacketType.ACK,
data: ["a", 1, {}],
id: 123,
nsp: '/'
nsp: "/",
});
});
it('encodes an error', function(){
helpers.test({
type: parser.ERROR,
data: 'Unauthorized',
nsp: '/'
it("encodes an connect error", () => {
return helpers.test({
type: PacketType.CONNECT_ERROR,
data: "Unauthorized",
nsp: "/",
});
});
it('properly handles circular objects', function() {
var a = {};
it("encodes an connect error (with object)", () => {
return helpers.test({
type: PacketType.CONNECT_ERROR,
data: {
message: "Unauthorized",
},
nsp: "/",
});
});
it("throws an error when encoding circular objects", () => {
const a = {};
a.b = a;
var data = {
type: parser.EVENT,
const data = {
type: PacketType.EVENT,
data: a,
id: 1,
nsp: '/'
}
nsp: "/",
};
var encoder = new parser.Encoder();
const encoder = new Encoder();
encoder.encode(data, function(encodedPackets) {
expect(encodedPackets[0]).to.be('4"encode error"');
});
expect(() => encoder.encode(data)).to.throwException();
});
it('decodes a bad binary packet', function(){
it("decodes a bad binary packet", () => {
try {
var decoder = new parser.Decoder();
decoder.add('5');
} catch(e){
const decoder = new Decoder();
decoder.add("5");
} catch (e) {
expect(e.message).to.match(/Illegal/);
}
});
it('returns an error packet on parsing error', function(){
function isInvalidPayload (str) {
expect(function () {
new parser.Decoder().add(str)
}).to.throwException(/^invalid payload$/);
}
it("throw an error upon parsing error", () => {
const isInvalidPayload = (str) =>
expect(() => new Decoder().add(str)).to.throwException(
/^invalid payload$/
);
isInvalidPayload('442["some","data"');
isInvalidPayload('0/admin,"invalid"');
isInvalidPayload("0[]");
isInvalidPayload("1/admin,{}");
isInvalidPayload('2/admin,"invalid');
isInvalidPayload("2/admin,{}");
isInvalidPayload('2[{"toString":"foo"}]');
isInvalidPayload('2[true,"foo"]');
isInvalidPayload('2[null,"bar"]');
isInvalidPayload('2["connect"]');
isInvalidPayload('2["disconnect","123"]');
function isInvalidAttachmentCount (str) {
expect(() => new parser.Decoder().add(str)).to.throwException(
/^Illegal attachments$/,
);
}
expect(() => new Decoder().add("999")).to.throwException(
/^unknown packet type 9$/
);
isInvalidAttachmentCount("5");
isInvalidAttachmentCount("51");
isInvalidAttachmentCount("5a-");
isInvalidAttachmentCount("51.23-");
expect(() => new Decoder().add(999)).to.throwException(
/^Unknown type: 999$/
);
});
it("throws an error when receiving too many attachments", () => {
const decoder = new parser.Decoder({ maxAttachments: 2 });
it("should resume decoding after calling destroy()", () => {
return new Promise((resolve) => {
const decoder = new Decoder();
expect(() => {
decoder.add(
'53-["hello",{"_placeholder":true,"num":0},{"_placeholder":true,"num":1},{"_placeholder":true,"num":2}]',
);
}).to.throwException(/^too many attachments$/);
decoder.on("decoded", (packet) => {
expect(packet.data).to.eql(["hello"]);
resolve();
});
decoder.add('51-["hello"]');
decoder.destroy();
decoder.add('2["hello"]');
});
});
});

View File

@@ -2,4 +2,4 @@
// 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
module.exports.browser = typeof window !== 'undefined';
module.exports.browser = typeof window !== "undefined";

12
tsconfig.esm.json Normal file
View File

@@ -0,0 +1,12 @@
{
"compilerOptions": {
"outDir": "build/esm/",
"target": "es2018",
"module": "esnext",
"moduleResolution": "node",
"declaration": true
},
"include": [
"./lib/**/*"
]
}

11
tsconfig.json Normal file
View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"outDir": "build/cjs/",
"target": "es2018", // Node.js 10 (https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping)
"module": "commonjs",
"declaration": true
},
"include": [
"./lib/**/*"
]
}

94
wdio.conf.js Normal file
View File

@@ -0,0 +1,94 @@
const BASE_SAUCE_OPTIONS = {
build: process.env.GITHUB_RUN_ID || "local",
name: "socket.io-parser",
};
const config = {
specs: ["./test/index.js"],
capabilities: [
{
browserName: "chrome",
},
],
maxInstances: 5,
logLevel: "warn",
bail: 0,
baseUrl: "http://localhost",
reporters: ["spec"],
framework: "mocha",
mochaOpts: {
ui: "bdd",
timeout: 60000,
},
};
if (process.env.CI === "true") {
config.services = ["sauce"];
config.user = process.env.SAUCE_USERNAME;
config.key = process.env.SAUCE_ACCESS_KEY;
// https://saucelabs.com/platform/platform-configurator#/
config.capabilities = [
{
browserName: "chrome",
browserVersion: "latest",
platformName: "Windows 11",
"sauce:options": BASE_SAUCE_OPTIONS,
},
{
browserName: "MicrosoftEdge",
browserVersion: "latest",
platformName: "Windows 11",
"sauce:options": BASE_SAUCE_OPTIONS,
},
{
browserName: "firefox",
browserVersion: "latest",
platformName: "Windows 11",
"sauce:options": BASE_SAUCE_OPTIONS,
},
{
browserName: "internet explorer",
browserVersion: "10",
platformName: "Windows 7",
"sauce:options": BASE_SAUCE_OPTIONS,
},
{
browserName: "safari",
browserVersion: "latest",
platformName: "macOS 12",
"sauce:options": BASE_SAUCE_OPTIONS,
},
{
platformName: "Android",
browserName: "Chrome",
"appium:deviceName": "Android GoogleAPI Emulator",
"appium:platformVersion": "latest",
"appium:automationName": "UiAutomator2",
"sauce:options": Object.assign(
{
appiumVersion: "1.22.1",
},
BASE_SAUCE_OPTIONS
),
},
{
platformName: "iOS",
browserName: "Safari",
"appium:deviceName": "iPhone Simulator",
"appium:platformVersion": "latest",
"appium:automationName": "XCUITest",
"sauce:options": Object.assign(
{
appiumVersion: "2.0.0",
},
BASE_SAUCE_OPTIONS
),
},
];
}
exports.config = config;

View File

@@ -1,39 +0,0 @@
'use strict';
var browsers = require('socket.io-browsers');
var zuulConfig = module.exports = {
ui: 'mocha-bdd',
// test on localhost by default
local: 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
};
if (process.env.CI === 'true') {
zuulConfig.local = false;
zuulConfig.tunnel = {
type: 'ngrok',
bind_tls: true
};
zuulConfig.browserify = [
{
transform: {
name: "babelify",
presets: ["@babel/preset-env"],
global: true,
only: [ /\/node_modules\/debug\// ]
}
}
]
}
var isPullRequest = process.env.TRAVIS_PULL_REQUEST && process.env.TRAVIS_PULL_REQUEST !== 'false';
zuulConfig.browsers = isPullRequest ? browsers.pullRequest : browsers.all;