Compare commits

..

13 Commits

Author SHA1 Message Date
Damien Arrachequesne
17bc1d65cf chore(release): socket.io-parser@3.3.5 2026-03-17 16:05:14 +01:00
Damien Arrachequesne
2ba71db5c3 ci: init publish workflow 2026-03-17 16:02:56 +01:00
Damien Arrachequesne
9d39f1f080 fix(parser): add a limit to the number of binary attachments
Backported from main: b25738c416

When a packet contains binary elements, the built-in parser does not modify them and simply sends them in their own WebSocket frame.

Example: `socket.emit("some event", Buffer.of(1,2,3))`

is encoded and transferred as:

- 1st frame: 51-["some event",{"_placeholder":true,"num":0}]
- 2nd frame: <buffer 01 02 03>

where:

- `5` is the type of the packet (binary message)
- `1` is the number of binary attachments
- `-` is the separator
- `["some event",{"_placeholder":true,"num":0}]` is the payload (including the placeholder)

On the receiving end, the parser reads the number of attachments and buffers them until they are all received.

Before this change, the built-in parser accepted any number of binary attachments, which could be exploited to make the server run out of memory.

The number of attachments is now limited to 10, which should be sufficient for most use cases.

The limit can be increased with a custom `parser`:

```js
import { Encoder, Decoder } from "socket.io-parser";

const io = new Server({
  parser: {
    Encoder,
    Decoder: class extends Decoder {
      constructor() {
        super({
          maxAttachments: 20
        });
      }
    }
  }
});
```
2026-03-17 16:00:15 +01:00
Damien Arrachequesne
6b2f875339 ci: init workflow 2026-03-17 16:00:05 +01:00
Damien Arrachequesne
1e9ebc6b7f chore(release): 3.3.4
Diff: https://github.com/Automattic/socket.io-parser/compare/3.3.3...3.3.4
2024-07-22 11:08:17 +02:00
Arnau Fugarolas Barbena
ee00660749 fix: check the format of the event name (#125)
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

Backported from 3b78117bf6
2024-07-22 11:05:42 +02:00
Damien Arrachequesne
cd11e38e1a chore(release): 3.3.3
Diff: https://github.com/Automattic/socket.io-parser/compare/3.3.2...3.3.3
2022-11-09 11:22:22 +01:00
Damien Arrachequesne
fb21e422fc 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.

Backported from b5d0cb7dc5
2022-11-09 11:21:34 +01:00
Damien Arrachequesne
3b0a3925fd chore(release): 3.3.2
Diff: https://github.com/Automattic/socket.io-parser/compare/3.3.1...3.3.2
2021-01-09 14:51:19 +01:00
bcaller
89197a05c4 fix: prevent DoS (OOM) via massive packets (#95)
When maxHttpBufferSize is large (1e8 bytes), a payload of length 100MB
can be sent like so:

99999991:422222222222222222222222222222222222222222222...

This massive packet can cause OOM via building up many many
`ConsOneByteString` objects due to concatenation:
99999989 `ConsOneByteString`s and then converting the massive integer to
a `Number`.

The performance can be improved to avoid this by using `substring`
rather than building the string via concatenation.

Below I tried one payload of length 7e7 as the 1e8 payload took so
long to process that it timed out before running out of memory.

```
==== JS stack trace =========================================

    0: ExitFrame [pc: 0x13c5b79]
Security context: 0x152fe7b808d1 <JSObject>
    1: decodeString [0x2dd385fb5d1] [/node_modules/socket.io-parser/index.js:~276] [pc=0xf59746881be](this=0x175d34c42b69 <JSGlobal Object>,0x14eccff10fe1 <Very long string[69999990]>)
    2: add [0x31fc2693da29] [/node_modules/socket.io-parser/index.js:242] [bytecode=0xa7ed6554889 offset=11](this=0x0a2881be5069 <Decoder map = 0x3ceaa8bf48c9>,0x14eccff10fe1 <Very...

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
 1: 0xa09830 node::Abort() [node]
 2: 0xa09c55 node::OnFatalError(char const*, char const*) [node]
 3: 0xb7d71e v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [node]
 4: 0xb7da99 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [node]
 5: 0xd2a1f5  [node]
 6: 0xd2a886 v8::internal::Heap::RecomputeLimits(v8::internal::GarbageCollector) [node]
 7: 0xd37105 v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [node]
 8: 0xd37fb5 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [node]
 9: 0xd3965f v8::internal::Heap::HandleGCRequest() [node]
10: 0xce8395 v8::internal::StackGuard::HandleInterrupts() [node]
11: 0x1042cb6 v8::internal::Runtime_StackGuard(int, unsigned long*, v8::internal::Isolate*) [node]
12: 0x13c5b79  [node]
```

Backported from master: dcb942d24d
2021-01-09 14:43:12 +01:00
Damien Arrachequesne
25ca624b0d chore(release): 3.3.1
Diff: https://github.com/socketio/socket.io-parser/compare/3.3.0...3.3.1
2020-09-30 02:38:02 +02:00
Damien Arrachequesne
b51b39b78d test: use Node.js 10 for the browser tests
It seems there is something wrong with newer versions (the CI seems
stuck). Let's pin the version for now.
2020-09-30 01:24:44 +02:00
Damien Arrachequesne
4184e46534 chore: bump component-emitter dependency
Subscribing/unsubscribing for a lot of different event types could lead
to a memory leak.

See aa2e57acc7

Diff: https://github.com/component/emitter/compare/1.2.1...1.3.0
2020-09-30 00:26:54 +02:00
33 changed files with 7379 additions and 15132 deletions

View File

@@ -2,9 +2,8 @@ name: CI
on:
push:
pull_request:
schedule:
- cron: '0 0 * * 0'
branches:
- 'socket.io-parser/3.3.x'
permissions:
contents: read
@@ -15,15 +14,17 @@ jobs:
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
node-version: [14, 16]
node-version:
- 16
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v6
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
@@ -32,25 +33,3 @@ 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 }}

31
.github/workflows/publish.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
# 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
dist/
build/
build
components

22
.travis.yml Normal file
View File

@@ -0,0 +1,22 @@
language: node_js
sudo: false
node_js:
- '8'
- '10'
git:
depth: 1
matrix:
include:
- node_js: 10
env: BROWSERS=1
cache:
directories:
- node_modules
env:
global:
- secure: >-
Ea4P/R9UlWzDlHSP5ynmLiD/YgLjecIvCviOcRTle9mV3P1j2k94Ay1LVu1Jw4whlNmWLq2Z/p8M63L92ODPMlarPsuME8HlP4zGr41whFhRbFdda4k3zrHfUhZBlnhY1MVWXTtVm/l7DOzpBrNh+wKecxZB3yyyEaA+PSG3qcQ=
- secure: >-
JmPf38qx5Rb6K+WYOMwb5YmESkDmVJ6tgggiJIuyRfHsgQVOO7XBwZuspIKGTSFolUIMaqwQe79Kd+Ehs2ZZ/0lUyF2/6xW3FqFnASUusYJcZdfRjypmBFWs6BRdtEORM8HL0dgBx4O4u/e4ZvtygumbPahjQbMDaqN+MvlpjD0=
- secure: >-
c3pnLhy3VDJqMl16ABA+8vt3I623aNa2wkLceLXb2V1Dc6eiZeulDH2ekwmdVo/r2WwGIKP3Y6B0mq/xP4W0hg4uT+xWh0AmFHclVyM/yp/AqfXrDUv17Vm0vB7OIgp332OiAlK6Dr13YDbWW8iZxmID41O2+2qohLGPn5JMncg=

View File

@@ -1,84 +1,18 @@
# 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)
## [3.3.5](https://github.com/socketio/socket.io-parser/compare/3.3.4...3.3.5) (2026-03-17)
### Bug Fixes
* 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))
* add a limit to the number of binary attachments ([9d39f1f](https://github.com/socketio/socket.io/commit/9d39f1f080510f036782f2177fac701cc041faaf))
## [3.4.3](https://github.com/socketio/socket.io-parser/compare/3.4.2...3.4.3) (2023-05-22)
## [3.3.4](https://github.com/Automattic/socket.io-parser/compare/3.3.3...3.3.4) (2024-07-22)
### Bug Fixes
* check the format of the event name ([2dc3c92](https://github.com/socketio/socket.io-parser/commit/2dc3c92622dad113b8676be06f23b1ed46b02ced))
## [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))
* check the format of the event name ([#125](https://github.com/Automattic/socket.io-parser/issues/125)) ([ee00660](https://github.com/Automattic/socket.io-parser/commit/ee006607495eca4ec7262ad080dd3a91439a5ba4))
@@ -91,71 +25,6 @@
## [3.4.2](https://github.com/socketio/socket.io-parser/compare/3.4.1...3.4.2) (2022-11-09)
### Bug Fixes
* check the format of the index of each attachment ([04d23ce](https://github.com/socketio/socket.io-parser/commit/04d23cecafe1b859fb03e0cbf6ba3b74dff56d14))
## [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)
@@ -164,95 +33,5 @@
* 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))

14
Makefile Normal file
View File

@@ -0,0 +1,14 @@
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,22 +1,14 @@
# socket.io-parser
[![Build Status](https://github.com/socketio/socket.io-parser/workflows/CI/badge.svg)](https://github.com/socketio/socket.io-parser/actions)
[![Build Status](https://secure.travis-ci.org/socketio/socket.io-parser.svg?branch=master)](http://travis-ci.org/socketio/socket.io-parser)
[![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 `5`
A socket.io encoder and decoder written in JavaScript complying with version `3`
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
@@ -56,7 +48,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) {

View File

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

10
bench/bench.js Normal file
View File

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

View File

@@ -1,4 +0,0 @@
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 Normal file
View File

@@ -0,0 +1,149 @@
/*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 Normal file
View File

@@ -0,0 +1,455 @@
/**
* 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);
}
};
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);
}
}
/**
* 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;
}
}
/**
* 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
);
};

20
is-buffer.js Normal file
View File

@@ -0,0 +1,20 @@
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)));
}

View File

@@ -1,86 +0,0 @@
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;
}

View File

@@ -1,361 +0,0 @@
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 = [];
}
}

View File

@@ -1,66 +0,0 @@
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;
}

20202
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,58 +1,31 @@
{
"name": "socket.io-parser",
"version": "4.2.4",
"version": "3.3.5",
"description": "socket.io protocol parser",
"repository": {
"type": "git",
"url": "https://github.com/socketio/socket.io-parser.git"
"url": "git+https://github.com/socketio/socket.io.git"
},
"files": [
"build/"
"binary.js",
"index.js",
"is-buffer.js"
],
"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": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
"component-emitter": "~1.3.0",
"debug": "~3.1.0",
"isarray": "2.0.1"
},
"devDependencies": {
"@babel/core": "~7.9.6",
"@babel/preset-env": "~7.9.6",
"@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": "^10.1.0",
"prettier": "^2.1.2",
"rimraf": "^3.0.2",
"typescript": "^4.0.3",
"wdio-geckodriver-service": "^4.0.0"
"mocha": "3.2.0",
"socket.io-browsers": "^1.0.0",
"zuul": "3.11.1",
"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 --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"
"test": "make test"
},
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
"license": "MIT"
}

View File

@@ -1,8 +0,0 @@
#!/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

View File

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

View File

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

View File

@@ -1,87 +1,70 @@
const { PacketType, Decoder, Encoder } = require("..");
const expect = require("expect.js");
const helpers = require("./helpers.js");
const encoder = new Encoder();
var parser = require('../index.js');
var expect = require('expect.js');
var helpers = require('./helpers.js');
var encoder = new parser.Encoder();
describe("ArrayBuffer", () => {
it("encodes an ArrayBuffer", () => {
const packet = {
type: PacketType.EVENT,
data: ["a", new ArrayBuffer(2)],
describe('parser', function() {
it('encodes an ArrayBuffer', function() {
var packet = {
type: parser.BINARY_EVENT,
data: ['a', new ArrayBuffer(2)],
id: 0,
nsp: "/",
nsp: '/'
};
return helpers.test_bin(packet);
helpers.test_bin(packet);
});
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 },
}),
],
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],
id: 0,
nsp: "/",
nsp: '/'
};
return helpers.test_bin(packet);
helpers.test_bin(packet);
});
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) } },
},
],
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)}}}],
id: 999,
nsp: "/deep",
nsp: '/deep'
};
return helpers.test_bin(packet);
helpers.test_bin(packet);
});
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,
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
};
return helpers.test_bin(packet);
helpers.test_bin(packet);
});
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)],
it('cleans itself up on close', function() {
var packet = {
type: parser.BINARY_EVENT,
data: ["foo", new ArrayBuffer(2), new ArrayBuffer(3)],
id: 0,
nsp: '/'
};
encoder.encode(packet);
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.");
});
expect(packet).to.eql({
type: PacketType.EVENT,
nsp: "/",
data: ["a", Uint8Array.of(1, 2, 3), Uint8Array.of(4, 5, 6)],
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
});
});
});

View File

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

View File

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

View File

@@ -1,35 +1,46 @@
const parser = require("..");
const expect = require("expect.js");
const encoder = new parser.Encoder();
var parser = require('../index.js');
var expect = require('expect.js');
var encoder = new parser.Encoder();
// tests encoding and decoding a single packet
module.exports.test = (obj) => {
return new Promise((resolve) => {
const encodedPackets = encoder.encode(obj);
const decoder = new parser.Decoder();
decoder.on("decoded", (packet) => {
module.exports.test = function(obj){
encoder.encode(obj, function(encodedPackets) {
var decoder = new parser.Decoder();
decoder.on('decoded', function(packet) {
expect(packet).to.eql(obj);
resolve();
});
decoder.add(encodedPackets[0]);
});
};
}
// tests encoding of binary packets
module.exports.test_bin = (obj) => {
return new Promise((resolve) => {
const encodedPackets = encoder.encode(obj);
const decoder = new parser.Decoder();
decoder.on("decoded", (packet) => {
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;
expect(obj).to.eql(packet);
resolve();
});
for (let i = 0; i < encodedPackets.length; i++) {
for (var 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,41 +1,33 @@
const env = require("./support/env.js");
var env = require('./support/env.js');
const blobSupported = (function () {
var 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;
require("./parser.js");
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');
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,151 +1,126 @@
const { PacketType, Decoder, Encoder } = require("..");
const expect = require("expect.js");
const helpers = require("./helpers.js");
var parser = require('../index.js');
var expect = require('expect.js');
var helpers = require('./helpers.js');
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");
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');
});
it("encodes connection", () => {
return helpers.test({
type: PacketType.CONNECT,
nsp: "/woot",
data: {
token: "123",
},
it('encodes connection', function(){
helpers.test({
type: parser.CONNECT,
nsp: '/woot'
});
});
it("encodes disconnection", () => {
return helpers.test({
type: PacketType.DISCONNECT,
nsp: "/woot",
it('encodes disconnection', function(){
helpers.test({
type: parser.DISCONNECT,
nsp: '/woot'
});
});
it("encodes an event", () => {
return helpers.test({
type: PacketType.EVENT,
data: ["a", 1, {}],
nsp: "/",
it('encodes an event', function(){
helpers.test({
type: parser.EVENT,
data: ['a', 1, {}],
nsp: '/'
});
});
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, {}],
helpers.test({
type: parser.EVENT,
data: ['a', 1, {}],
id: 1,
nsp: "/test",
nsp: '/test'
});
});
it("encodes an ack", () => {
return helpers.test({
type: PacketType.ACK,
data: ["a", 1, {}],
it('encodes an ack', function(){
helpers.test({
type: parser.ACK,
data: ['a', 1, {}],
id: 123,
nsp: "/",
nsp: '/'
});
});
it("encodes an connect error", () => {
return helpers.test({
type: PacketType.CONNECT_ERROR,
data: "Unauthorized",
nsp: "/",
it('encodes an error', function(){
helpers.test({
type: parser.ERROR,
data: 'Unauthorized',
nsp: '/'
});
});
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 = {};
it('properly handles circular objects', function() {
var a = {};
a.b = a;
const data = {
type: PacketType.EVENT,
var data = {
type: parser.EVENT,
data: a,
id: 1,
nsp: "/",
};
nsp: '/'
}
const encoder = new Encoder();
var encoder = new parser.Encoder();
expect(() => encoder.encode(data)).to.throwException();
encoder.encode(data, function(encodedPackets) {
expect(encodedPackets[0]).to.be('4"encode error"');
});
});
it("decodes a bad binary packet", () => {
it('decodes a bad binary packet', function(){
try {
const decoder = new Decoder();
decoder.add("5");
} catch (e) {
var decoder = new parser.Decoder();
decoder.add('5');
} catch(e){
expect(e.message).to.match(/Illegal/);
}
});
it("throw an error upon parsing error", () => {
const isInvalidPayload = (str) =>
expect(() => new Decoder().add(str)).to.throwException(
/^invalid payload$/
);
it('returns an error packet on parsing error', function(){
function isInvalidPayload (str) {
expect(function () {
new parser.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"]');
expect(() => new Decoder().add("999")).to.throwException(
/^unknown packet type 9$/
);
function isInvalidAttachmentCount (str) {
expect(() => new parser.Decoder().add(str)).to.throwException(
/^Illegal attachments$/,
);
}
expect(() => new Decoder().add(999)).to.throwException(
/^Unknown type: 999$/
);
isInvalidAttachmentCount("5");
isInvalidAttachmentCount("51");
isInvalidAttachmentCount("5a-");
isInvalidAttachmentCount("51.23-");
});
it("should resume decoding after calling destroy()", () => {
return new Promise((resolve) => {
const decoder = new Decoder();
it("throws an error when receiving too many attachments", () => {
const decoder = new parser.Decoder({ maxAttachments: 2 });
decoder.on("decoded", (packet) => {
expect(packet.data).to.eql(["hello"]);
resolve();
});
decoder.add('51-["hello"]');
decoder.destroy();
decoder.add('2["hello"]');
});
expect(() => {
decoder.add(
'53-["hello",{"_placeholder":true,"num":0},{"_placeholder":true,"num":1},{"_placeholder":true,"num":2}]',
);
}).to.throwException(/^too many attachments$/);
});
});

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';

View File

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

View File

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

View File

@@ -1,94 +0,0 @@
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;

29
zuul.config.js Normal file
View File

@@ -0,0 +1,29 @@
'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
};
}
var isPullRequest = process.env.TRAVIS_PULL_REQUEST && process.env.TRAVIS_PULL_REQUEST !== 'false';
zuulConfig.browsers = isPullRequest ? browsers.pullRequest : browsers.all;