Compare commits

..

4 Commits

Author SHA1 Message Date
Damien Arrachequesne
0abbd4da50 [chore] Release 1.7.4 2017-05-07 08:26:10 +02:00
Damien Arrachequesne
c89ccd9b97 [chore] Bump engine.io to version 1.8.4 2017-05-07 08:22:14 +02:00
Damien Arrachequesne
a646044f34 [chore] Release 1.7.3 2017-02-17 06:58:45 +01:00
Damien Arrachequesne
751a6fc34a [chore] Bump engine.io to version 1.8.3 2017-02-17 06:57:09 +01:00
842 changed files with 5279 additions and 137638 deletions

25
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,25 @@
*Note*: for support questions, please use one of these channels: [stackoverflow](http://stackoverflow.com/questions/tagged/socket.io) or [slack](https://socketio.slack.com)
### You want to:
* [x] report a *bug*
* [ ] request a *feature*
### Current behaviour
### Steps to reproduce (if the current behaviour is a bug)
### Expected behaviour
### Setup
- OS:
- browser:
- socket.io version:
### Other information (e.g. stacktraces, related issues, suggestions how to fix)

View File

@@ -1,61 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: 'to triage'
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Please fill the following code example:
Socket.IO server version: `x.y.z`
*Server*
```js
import { Server } from "socket.io";
const io = new Server(3000, {});
io.on("connection", (socket) => {
console.log(`connect ${socket.id}`);
socket.on("disconnect", () => {
console.log(`disconnect ${socket.id}`);
});
});
```
Socket.IO client version: `x.y.z`
*Client*
```js
import { io } from "socket.io-client";
const socket = io("ws://localhost:3000/", {});
socket.on("connect", () => {
console.log(`connect ${socket.id}`);
});
socket.on("disconnect", () => {
console.log("disconnect");
});
```
**Expected behavior**
A clear and concise description of what you expected to happen.
**Platform:**
- Device: [e.g. Samsung S8]
- OS: [e.g. Android 9.2]
**Additional context**
Add any other context about the problem here.

View File

@@ -1,5 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Ask a Question
url: https://github.com/socketio/socket.io/discussions/new?category=q-a
about: Ask the community for help

View File

@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: 'enhancement'
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -7,10 +7,10 @@
* [ ] a code change that improves performance
* [ ] other
### Current behavior
### Current behaviour
### New behavior
### New behaviour
### Other information (e.g. related issues)

View File

@@ -1,45 +0,0 @@
name: Build examples
on:
schedule:
- cron: '0 0 * * 0'
permissions:
contents: read
jobs:
build-examples:
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
example:
- custom-parsers
- typescript-example/cjs
- typescript-example/esm
- typescript-client-example/cjs
- typescript-client-example/esm
- webpack-build
- webpack-build-server
- basic-crud-application/angular-client
- basic-crud-application/vue-client
- nextjs-pages-router
- nextjs-app-router
- nuxt-example
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20
- name: Build ${{ matrix.example }}
run: |
cd examples/${{ matrix.example }}
npm install
npm run build

View File

@@ -1,41 +0,0 @@
name: CI (browser)
on:
push:
branches:
- '**'
paths:
- 'packages/engine.io-parser/**'
- 'packages/engine.io-client/**'
- 'packages/socket.io-parser/**'
- 'packages/socket.io-client/**'
permissions:
contents: read
jobs:
test-browser:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Compile each package
run: npm run compile --workspaces --if-present
- name: Run tests
run: npm test --workspace=socket.io-parser --workspace=socket.io-client
env:
BROWSERS: 1
SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}

View File

@@ -1,78 +0,0 @@
name: CI
on:
push:
branches:
- '**'
pull_request:
schedule:
- cron: '0 0 * * 0'
permissions:
contents: read
jobs:
test-node:
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
node-version:
- 20
- 22
- 24
services:
redis:
image: redis:7
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
postgres:
image: postgres:14
env:
POSTGRES_PASSWORD: changeit
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: npm ci
- name: Compile each package
run: npm run compile --workspaces --if-present
- name: Run tests
run: npm test --workspaces
- name: Run tests with uws (engine.io)
run: npm run test:uws --workspace=engine.io
if: ${{ matrix.node-version == '18' }}
- name: Run tests with fetch instead of XHR (engine.io-client)
run: npm run test:node-fetch --workspace=engine.io-client
if: ${{ matrix.node-version == '18' }}
- name: Run tests with Node.js native WebSocket (engine.io-client)
run: npm run test:node-builtin-ws --workspace=engine.io-client
if: ${{ matrix.node-version == '22' }}

View File

@@ -1,35 +0,0 @@
# reference: https://docs.npmjs.com/trusted-publishers#for-github-actions
name: Publish
on:
push:
tags:
# expected format: <package>@<version> (example: socket.io@1.2.3)
- '**@*'
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Use Node.js 24
uses: actions/setup-node@v4
with:
node-version: 24
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: npm ci
- name: Compile each package
run: npm run compile --workspaces --if-present
- name: Publish package
run: npm publish --workspace=${GITHUB_REF_NAME%@*} --access public

4
.gitignore vendored
View File

@@ -10,6 +10,4 @@ benchmarks/*.png
node_modules
coverage
.idea
.nyc_output
dist/
build/
dist

28
.travis.yml Normal file
View File

@@ -0,0 +1,28 @@
sudo: false
before_install:
- npm install -g npm@'>=1.4.3'
language: node_js
node_js:
- "0.10"
- "0.12"
- "4"
- "node"
git:
depth: 1
matrix:
include:
- node_js: '0.10'
env: TEST_VERSION=compat
- node_js: '0.12'
env: TEST_VERSION=compat
- node_js: '4'
env: TEST_VERSION=compat
#matrix:
#fast_finish: true
#allow_failures:
#- node_js: "0.11"
notifications:
irc: "irc.freenode.org#socket.io"

View File

@@ -1,18 +0,0 @@
# Changelog
Here are the detailed changelogs for each package in this monorepo:
| Package | Changelog |
|------------------------------------|----------------------------------------------------------------|
| `engine.io` | [link](packages/engine.io/CHANGELOG.md) |
| `engine.io-client` | [link](packages/engine.io-client/CHANGELOG.md) |
| `engine.io-parser` | [link](packages/engine.io-parser/CHANGELOG.md) |
| `socket.io` | [link](packages/socket.io/CHANGELOG.md) |
| `socket.io-adapter` | [link](packages/socket.io-adapter/CHANGELOG.md) |
| `socket.io-client` | [link](packages/socket.io-client/CHANGELOG.md) |
| `@socket.io/cluster-adapter` | [link](packages/socket.io-cluster-adapter/CHANGELOG.md) |
| `@socket.io/cluster-engine` | [link](packages/socket.io-cluster-engine/CHANGELOG.md) |
| `@socket.io/component-emitter` | [link](packages/socket.io-component-emitter/History.md) |
| `socket.io-parser` | [link](packages/socket.io-parser/CHANGELOG.md) |
| `@socket.io/postgres-emitter` | [link](packages/socket.io-postgres-emitter/CHANGELOG.md) |
| `@socket.io/redis-streams-emitter` | [link](/packages/socket.io-redis-streams-emitter/CHANGELOG.md) |

View File

@@ -1,167 +0,0 @@
# Socket.IO Contributing Guide
Thanks a lot for your interest in contributing to Socket.IO!
Before submitting your contribution, please make sure to take a moment and read through the following guidelines:
<!-- TOC -->
* [Before you start](#before-you-start)
* [Guidelines for reporting a bug](#guidelines-for-reporting-a-bug)
* [Guidelines for requesting a feature](#guidelines-for-requesting-a-feature)
* [Guidelines for creating a pull request](#guidelines-for-creating-a-pull-request)
* [Bug fix](#bug-fix)
* [New feature](#new-feature)
* [Project structure](#project-structure)
* [Development setup](#development-setup)
* [Commands](#commands)
* [Compile with TypeScript](#compile-with-typescript)
* [Apply formatting](#apply-formatting)
* [Run the tests](#run-the-tests)
<!-- TOC -->
## Before you start
Our [issues list](https://github.com/socketio/socket.io/issues) is exclusively reserved for bug reports and feature requests. For usage questions, please use the following resources:
- read the [docs](https://socket.io/docs/v4/)
- check the [troubleshooting guide](https://socket.io/docs/v4/troubleshooting-connection-issues/)
- look for/ask questions on [Stack Overflow](https://stackoverflow.com/questions/tagged/socket.io)
- create a new [discussion](https://github.com/socketio/socket.io/discussions/new?category=q-a)
## Guidelines for reporting a bug
If you think that you have found a security vulnerability in our project, please do not create an issue in this GitHub repository, but rather refer to our [security policy](./SECURITY.md).
Please make sure that the bug hasn't already been reported in our [issues list](https://github.com/socketio/socket.io/issues?q=label%3Abug+), as it may already have been fixed in a recent version. However, if the bug was reported in an old, closed issue but persists, you should open a new issue instead of commenting on the old issue.
After these checks, please [create a new bug report](https://github.com/socketio/socket.io/issues/new/choose) with all the necessary details:
- package versions
- platform (device, browser, operating system)
- a minimal reproduction (you can fork [this repository](https://github.com/socketio/socket.io-fiddle))
Without a clear way to reproduce the bug, we unfortunately won't be able to help you.
## Guidelines for requesting a feature
Please make sure that the feature hasn't already been requested in our [issues list](https://github.com/socketio/socket.io/labels/enhancement).
After these checks, please [create a new feature request](https://github.com/socketio/socket.io/issues/new/choose) with all the necessary details:
- what the problem is
- what you want to happen
- any alternative solutions or features you have considered
## Guidelines for creating a pull request
### Bug fix
- if you fix a bug which is described in our [issues list](https://github.com/socketio/socket.io/issues), please add a reference to it in the description of your pull request. Otherwise, please provide all necessary details to reproduce the bug, as described [above](#guidelines-for-reporting-a-bug).
- add one or more test cases, in order to avoid any regression in the future
- make sure existing tests still pass
### New feature
- we strongly suggest that you first open a [feature request](#guidelines-for-requesting-a-feature) and have it approved before working on it. In that case, please add a reference to it in the description of your pull request.
- add one or more test cases, in order to avoid any regression in the future
- make sure existing tests still pass
## Project structure
This repository is a [monorepo](https://en.wikipedia.org/wiki/Monorepo) which contains the source of the following packages:
| Package | Description |
|--------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|
| `engine.io` | The server-side implementation of the low-level communication layer. |
| `engine.io-client` | The client-side implementation of the low-level communication layer. |
| `engine.io-parser` | The parser responsible for encoding and decoding Engine.IO packets, used by both the `engine.io` and `engine.io-client` packages. |
| `socket.io` | The server-side implementation of the bidirectional channel, built on top on the `engine.io` package. |
| `socket.io-adapter` | An extensible component responsible for broadcasting a packet to all connected clients, used by the `socket.io` package. |
| `socket.io-client` | The client-side implementation of the bidirectional channel, built on top on the `engine.io-client` package. |
| `@socket.io/cluster-engine` | A cluster-friendly engine to share load between multiple Node.js processes (without sticky sessions) |
| `@socket.io/component-emitter` | An `EventEmitter` implementation, similar to the one provided by [Node.js](https://nodejs.org/api/events.html) but for all platforms. |
| `socket.io-parser` | The parser responsible for encoding and decoding Socket.IO packets, used by both the `socket.io` and `socket.io-client` packages. |
## Development setup
You will need [Node.js](https://nodejs.org) **version 18+**, and [`npm`](https://docs.npmjs.com/about-npm) **version 7+**, as we make use of npm's [workspaces feature](https://docs.npmjs.com/cli/v10/using-npm/workspaces).
After cloning the repository, please run:
```bash
npm ci
```
to install all dependencies.
Here is the list of tools that we use:
- [TypeScript](https://www.typescriptlang.org/) as the development language
- [Rollup](https://rollupjs.org/) for production bundling
- [Prettier](https://prettier.io/) for code formatting
- [Mocha](https://mochajs.org/) for testing
- [WebdriverIO](https://webdriver.io/) for browser and mobile testing
## Commands
Each npm workspace corresponds to a package. You can run the command:
- on all workspaces with the `--workspace` command-line argument (abbreviated `-ws`)
- on a specific workspace with the `--workspace=<some-workspace>` command-line argument
### Compile with TypeScript
For all workspaces:
```bash
npm run compile -ws --if-present
```
For a specific workspace:
```bash
npm run compile --workspace=socket.io
```
### Apply formatting
For all workspaces:
```bash
npm run format:fix -ws
```
For a specific workspace:
```bash
npm run format:fix --workspace=socket.io
```
### Run the tests
For all workspaces:
```bash
npm test -ws
```
For a specific workspace:
```bash
npm test --workspace=socket.io
```
### Generate the changelog
Install the [`conventional-changelog-cli`](https://www.npmjs.com/package/conventional-changelog-cli) package:
```bash
npm i -g conventional-changelog-cli
```
Then run:
```bash
cd packages/engine.io-client
conventional-changelog -p angular --tag-prefix "engine.io-client@" --commit-path .
```

648
History.md Normal file
View File

@@ -0,0 +1,648 @@
1.7.4 / 2017-05-07
===================
* [chore] Bump engine.io to version 1.8.4
1.7.3 / 2017-02-17
===================
* [chore] Bump engine.io to version 1.8.3
1.7.2 / 2016-12-11
===================
* [chore] Bump engine.io to version 1.8.2 (#2782)
* [fix] Fixes socket.use error packet (#2772)
1.7.1 / 2016-11-28
===================
1.7.0 / 2016-11-27
===================
* [docs] Comment connected socket availability for adapters (#2081)
* [docs] Fixed grammar issues in the README.md (#2159)
* [feature] serve sourcemap for socket.io-client (#2482)
* [feature] Add a `local` flag (#2628)
* [chore] Bump engine.io to version 1.8.1 (#2765)
* [chore] Update client location and serve minified file (#2766)
1.6.0 / 2016-11-20
==================
* [fix] Make ETag header comply with standard. (#2603)
* [feature] Loading client script on demand. (#2567)
* [test] Fix leaking clientSocket (#2721)
* [feature] Add support for all event emitter methods (#2601)
* [chore] Update year to 2016 (#2456)
* [feature] Add support for socket middleware (#2306)
* [feature] add support for Server#close(callback) (#2748)
* [fix] Don't drop query variables on handshake (#2745)
* [example] Add disconnection/reconnection logs to the chat example (#2675)
* [perf] Minor code optimizations (#2219)
* [chore] Bump debug to version 2.3.3 (#2754)
* [chore] Bump engine.io to version 1.8.0 (#2755)
* [chore] Bump socket.io-adapter to version 0.5.0 (#2756)
1.5.1 / 2016-10-24
==================
* [fix] Avoid swallowing exceptions thrown by user event handlers (#2682)
* [test] Use client function to unify `client` in test script (#2731)
* [docs] Add link to LICENSE (#2221)
* [docs] Fix JSDoc of optional parameters (#2465)
* [docs] Fix typo (#2724)
* [docs] Link readme npm package badge to npm registry page (#2612)
* [docs] Minor fixes (#2526)
* [chore] Bump socket.io-parser to 2.3.0 (#2730)
* [chore] Add Github issue and PR templates (#2733)
* [chore] Bump engine.io to 1.7.2 (#2729)
* [chore] Bump socket.io-parser to 2.3.1 (#2734)
1.5.0 / 2016-10-06
==================
* [feature] stop append /# before id when no namespace (#2508)
* [feature] Add a 'disconnecting' event to access to socket.rooms upon disconnection (#2332)
* [fix] Fix query string management (#2422)
* [fix] add quote to exec paths, prevent error when spaces in path (#2508)
* [docs] Prevent mixup for new programmers (#2599)
* [example] Fix chat display in Firefox (#2477)
* [chore] Add gulp & babel in the build process (#2471)
* [chore] Bump engine.io to 1.7.0 (#2707)
* [chore] Remove unused zuul-ngrok dependency (#2708)
* [chore] Point towards current master of socket.io-client (#2710)
* [chore] Restrict files included in npm package (#2709)
* [chore] Link build badge to master branch (#2549)
1.4.8 / 2016-06-23
==================
* package: bump `engine.io`
1.4.7 / 2016-06-23
==================
* package: bump `engine.io`
1.4.6 / 2016-05-02
==================
* package: bump engine.io
1.4.5 / 2016-01-26
==================
* fix closing the underlying `http.Server`
1.4.4 / 2016-01-10
==================
* package: bump `engine.io`
1.4.3 / 2016-01-08
==================
* bump `socket.io-client`
1.4.2 / 2016-01-07
==================
* bump `engine.io`
1.4.1 / 2016-01-07
==================
* version bump
1.4.0 / 2015-11-28
==================
* socket.io: increase large binary data test timeout
* package: bump `engine.io` for release
* trigger callback even when joining an already joined room
* package: bump parser
* namespace: clear rooms flag after a clients call (fixes #1978)
* package: bump `socket.io-parser`
* fixed tests with large data
* fixed a typo in the example code
* package: bump mocha
* package: bump `has-binary` and `zuul-ngrok`
* package: bump `engine.io` and `socket.io-client`
* README: clarified documentation of Socket.in
* README: fixed up legacy repo links
* test: better timeout for stress test
* socket: don't set request property which has a getter
* removed proxy index file
* support flags on namespace
* improve Socket#packet and Client#packet
* socket: warn node_redis-style about missing `error`
* test: added failing test
* test: increase timeout for large binary data test
* package: bump `has-binary` to work with all objects (fixes #1955)
* fix origin verification default https port [evanlucas]
* support compression [nkzawa]
* changed type of `Client#sockets`, `Namespace#sockets` and `Socket#rooms` to maps (instead of arrays)
1.3.7 / 2015-09-21
==================
* package: bump `socket.io-client` for node4 compatibility
* package: bump `engine.io` for node4 compatibility
1.3.6 / 2015-07-14
==================
* package: bump `engine.io` to fix build on windows
1.3.5 / 2015-03-03
==================
* package: bump `socket.io-parser`
1.3.4 / 2015-02-14
==================
* package: bump `socket.io-client`
1.3.3 / 2015-02-03
==================
* socket: warn node_redis-style about missing `error`
* package: bump parser to better handle bad binary packets
1.3.2 / 2015-01-19
==================
* no change on this release
1.3.1 / 2015-01-19
==================
* no change on this release
* package: bump `engine.io`
1.3.0 / 2015-01-19
==================
* package: bump `engine.io`
* add test for reconnection after server restarts [rase-]
* update license with up-to-date year range [fay-jai]
* fix leaving unknown rooms [defunctzombie]
* allow null origins when allowed origins is a function [drewblaisdell]
* fix tests on node 0.11
* package: fix `npm test` to run on windows
* package: bump `debug` v2.1.0 [coderaiser]
* added tests for volatile [rase-]
1.2.1 / 2014-11-21
==================
* fix protocol violations and improve error handling (GH-1880)
* package: bump `engine.io` for websocket leak fix [3rd-Eden]
* style tweaks
1.2.0 / 2014-10-27
==================
* package: bump `engine.io`
* downloads badge
* add test to check that empty rooms are autopruned
* added Server#origins(v:Function) description for dynamic CORS
* added test coverage for Server#origins(function) for dynamic CORS
* added optional Server#origins(function) for dynamic CORS
* fix usage example for Server#close
* package: fix main file for example application 'chat'
* package: bump `socket.io-parser`
* update README http ctor to createServer()
* bump adapter with a lot of fixes for room bookkeeping
1.1.0 / 2014-09-04
==================
* examples: minor fix of escaping
* testing for equivalence of namespaces starting with / or without
* update index.js
* added relevant tests
* take "" and "/" as equivalent namespaces on server
* use svg instead of png to get better image quality in readme
* make CI build faster
* fix splice arguments and `socket.rooms` value update in `socket.leaveAll`.
* client cannot connect to non-existing namespaces
* bump engine.io version to get the cached IP address
* fixed handshake object address property and made the test case more strict.
* package: bump `engine.io`
* fixed the failing test where server crashes on disconnect involving connectBuffer
* npmignore: ignore `.gitignore` (fixes #1607)
* test: added failing case for `socket.disconnect` and nsps
* fix repo in package.json
* improve Close documentation
* use ephemeral ports
* fix: We should use the standard http protocol to handler the etag header.
* override default browser font-family for inputs
* update has-binary-data to 1.0.3
* add close specs
* add ability to stop the http server even if not created inside socket.io
* make sure server gets close
* Add test case for checking that reconnect_failed is fired only once upon failure
* package: bump `socket.io-parser` for `component-emitter` dep fix
1.0.6 / 2014-06-19
==================
* package: bump `socket.io-client`
1.0.5 / 2014-06-16
==================
* package: bump `engine.io` to fix jsonp `\n` bug and CORS warnings
* index: fix typo [yanatan16]
* add `removeListener` to blacklisted events
* examples: clearer instructions to install chat example
* index: fix namespace `connectBuffer` issue
1.0.4 / 2014-06-02
==================
* package: bump socket.io-client
1.0.3 / 2014-05-31
==================
* package: bump `socket.io-client`
* package: bump `socket.io-parser` for binary ACK fix
* package: bump `engine.io` for binary UTF8 fix
* example: fix XSS in chat example
1.0.2 / 2014-05-28
==================
* package: bump `socket.io-parser` for windows fix
1.0.1 / 2014-05-28
==================
* bump due to bad npm tag
1.0.0 / 2014-05-28
==================
* stable release
1.0.0-pre5 / 2014-05-22
=======================
* package: bump `socket.io-client` for parser fixes
* package: bump `engine.io`
1.0.0-pre4 / 2014-05-19
=======================
* package: bump client
1.0.0-pre3 / 2014-05-17
=======================
* package: bump parser
* package: bump engine.io
1.0.0-pre2 / 2014-04-27
=======================
* package: bump `engine.io`
* added backwards compatible of engine.io maxHttpBufferSize
* added test that server and client using same protocol
* added support for setting allowed origins
* added information about logging
* the set function in server can be used to set some attributes for BC
* fix error in callback call 'done' instead of 'next' in docs
* package: bump `socket.io-parser`
* package: bump `expect.js`
* added some new tests, including binary with acks
1.0.0-pre / 2014-03-14
======================
* implemented `engine.io`
* implemented `socket.io-adapter`
* implemented `socket.io-protocol`
* implemented `debug` and improved instrumentation
* added binary support
* added new `require('io')(srv)` signature
* simplified `socket.io-client` serving
0.9.14 / 2013-03-29
===================
* manager: fix memory leak with SSL [jpallen]
0.9.13 / 2012-12-13
===================
* package: fixed `base64id` requirement
0.9.12 / 2012-12-13
===================
* manager: fix for latest node which is returning a clone with `listeners` [viirya]
0.9.11 / 2012-11-02
===================
* package: move redis to optionalDependenices [3rd-Eden]
* bumped client
0.9.10 / 2012-08-10
===================
* Don't lowercase log messages
* Always set the HTTP response in case an error should be returned to the client
* Create or destroy the flash policy server on configuration change
* Honour configuration to disable flash policy server
* Add express 3.0 instructions on Readme.md
* Bump client
0.9.9 / 2012-08-01
==================
* Fixed sync disconnect xhrs handling
* Put license text in its own file (#965)
* Add warning to .listen() to ease the migration to Express 3.x
* Restored compatibility with node 0.4.x
0.9.8 / 2012-07-24
==================
* Bumped client.
0.9.7 / 2012-07-24
==================
* Prevent crash when socket leaves a room twice.
* Corrects unsafe usage of for..in
* Fix for node 0.8 with `gzip compression` [vadimi]
* Update redis to support Node 0.8.x
* Made ID generation securely random
* Fix Redis Store race condition in manager onOpen unsubscribe callback
* Fix for EventEmitters always reusing the same Array instance for listeners
0.9.6 / 2012-04-17
==================
* Fixed XSS in jsonp-polling.
0.9.5 / 2012-04-05
==================
* Added test for polling and socket close.
* Ensure close upon request close.
* Fix disconnection reason being lost for polling transports.
* Ensure that polling transports work with Connection: close.
* Log disconnection reason.
0.9.4 / 2012-04-01
==================
* Disconnecting from namespace improvement (#795) [DanielBaulig]
* Bumped client with polling reconnection loop (#438)
0.9.3 / 2012-03-28
==================
* Fix "Syntax error" on FF Web Console with XHR Polling [mikito]
0.9.2 / 2012-03-13
==================
* More sensible close `timeout default` (fixes disconnect issue)
0.9.1-1 / 2012-03-02
====================
* Bumped client with NPM dependency fix.
0.9.1 / 2012-03-02
==================
* Changed heartbeat timeout and interval defaults (60 and 25 seconds)
* Make tests work both on 0.4 and 0.6
* Updated client (improvements + bug fixes).
0.9.0 / 2012-02-26
==================
* Make it possible to use a regexp to match the socket.io resource URL.
We need this because we have to prefix the socket.io URL with a variable ID.
* Supplemental fix to gavinuhma/authfix, it looks like the same Access-Control-Origin logic is needed in the http and xhr-polling transports
* Updated express dep for windows compatibility.
* Combine two substr calls into one in decodePayload to improve performance
* Minor documentation fix
* Minor. Conform to style of other files.
* Switching setting to 'match origin protocol'
* Revert "Fixes leaking Redis subscriptions for #663. The local flag was not getting passed through onClientDisconnect()."
* Revert "Handle leaked dispatch:[id] subscription."
* Merge pull request #667 from dshaw/patch/redis-disconnect
* Handle leaked dispatch:[id] subscription.
* Fixes leaking Redis subscriptions for #663. The local flag was not getting passed through onClientDisconnect().
* Prevent memory leaking on uncompleted requests & add max post size limitation
* Fix for testcase
* Set Access-Control-Allow-Credentials true, regardless of cookie
* Remove assertvarnish from package as it breaks on 0.6
* Correct irc channel
* Added proper return after reserved field error
* Fixes manager.js failure to close connection after transport error has happened
* Added implicit port 80 for origin checks. fixes #638
* Fixed bug #432 in 0.8.7
* Set Access-Control-Allow-Origin header to origin to enable withCredentials
* Adding configuration variable matchOriginProtocol
* Fixes location mismatch error in Safari.
* Use tty to detect if we should add colors or not by default.
* Updated the package location.
0.8.7 / 2011-11-05
==================
* Fixed memory leaks in closed clients.
* Fixed memory leaks in namespaces.
* Fixed websocket handling for malformed requests from proxies. [einaros]
* Node 0.6 compatibility. [einaros] [3rd-Eden]
* Adapted tests and examples.
0.8.6 / 2011-10-27
==================
* Added JSON decoding on jsonp-polling transport.
* Fixed README example.
* Major speed optimizations [3rd-Eden] [einaros] [visionmedia]
* Added decode/encode benchmarks [visionmedia]
* Added support for black-listing client sent events.
* Fixed logging options, closes #540 [3rd-Eden]
* Added vary header for gzip [3rd-Eden]
* Properly cleaned up async websocket / flashsocket tests, after patching node-websocket-client
* Patched to properly shut down when a finishClose call is made during connection establishment
* Added support for socket.io version on url and far-future Expires [3rd-Eden] [getify]
* Began IE10 compatibility [einaros] [tbranyen]
* Misc WebSocket fixes [einaros]
* Added UTF8 to respone headers for htmlfile [3rd-Eden]
0.8.5 / 2011-10-07
==================
* Added websocket draft HyBi-16 support. [einaros]
* Fixed websocket continuation bugs. [einaros]
* Fixed flashsocket transport name.
* Fixed websocket tests.
* Ensured `parser#decodePayload` doesn't choke.
* Added http referrer verification to manager verifyOrigin.
* Added access control for cross domain xhr handshakes [3rd-Eden]
* Added support for automatic generation of socket.io files [3rd-Eden]
* Added websocket binary support [einaros]
* Added gzip support for socket.io.js [3rd-Eden]
* Expose socket.transport [3rd-Eden]
* Updated client.
0.8.4 / 2011-09-06
==================
* Client build
0.8.3 / 2011-09-03
==================
* Fixed `\n` parsing for non-JSON packets (fixes #479).
* Fixed parsing of certain unicode characters (fixes #451).
* Fixed transport message packet logging.
* Fixed emission of `error` event resulting in an uncaught exception if unhandled (fixes #476).
* Fixed; allow for falsy values as the configuration value of `log level` (fixes #491).
* Fixed repository URI in `package.json`. Fixes #504.
* Added text/plain content-type to handshake responses [einaros]
* Improved single byte writes [einaros]
* Updated socket.io-flashsocket default port from 843 to 10843 [3rd-Eden]
* Updated client.
0.8.2 / 2011-08-29
==================
* Updated client.
0.8.1 / 2011-08-29
==================
* Fixed utf8 bug in send framing in websocket [einaros]
* Fixed typo in docs [Znarkus]
* Fixed bug in send framing for over 64kB of data in websocket [einaros]
* Corrected ping handling in websocket transport [einaros]
0.8.0 / 2011-08-28
==================
* Updated to work with two-level websocket versioning. [einaros]
* Added hybi07 support. [einaros]
* Added hybi10 support. [einaros]
* Added http referrer verification to manager.js verifyOrigin. [einaors]
0.7.11 / 2011-08-27
===================
* Updated socket.io-client.
0.7.10 / 2011-08-27
===================
* Updated socket.io-client.
0.7.9 / 2011-08-12
==================
* Updated socket.io-client.
* Make sure we only do garbage collection when the server we receive is actually run.
0.7.8 / 2011-08-08
==================
* Changed; make sure sio#listen passes options to both HTTP server and socket.io manager.
* Added docs for sio#listen.
* Added options parameter support for Manager constructor.
* Added memory leaks tests and test-leaks Makefile task.
* Removed auto npm-linking from make test.
* Make sure that you can disable heartbeats. [3rd-Eden]
* Fixed rooms memory leak [3rd-Eden]
* Send response once we got all POST data, not immediately [Pita]
* Fixed onLeave behavior with missing clientsk [3rd-Eden]
* Prevent duplicate references in rooms.
* Added alias for `to` to `in` and `in` to `to`.
* Fixed roomClients definition.
* Removed dependency on redis for installation without npm [3rd-Eden]
* Expose path and querystring in handshakeData [3rd-Eden]
0.7.7 / 2011-07-12
==================
* Fixed double dispatch handling with emit to closed clients.
* Added test for emitting to closed clients to prevent regression.
* Fixed race condition in redis test.
* Changed Transport#end instrumentation.
* Leveraged $emit instead of emit internally.
* Made tests faster.
* Fixed double disconnect events.
* Fixed disconnect logic
* Simplified remote events handling in Socket.
* Increased testcase timeout.
* Fixed unknown room emitting (GH-291). [3rd-Eden]
* Fixed `address` in handshakeData. [3rd-Eden]
* Removed transports definition in chat example.
* Fixed room cleanup
* Fixed; make sure the client is cleaned up after booting.
* Make sure to mark the client as non-open if the connection is closed.
* Removed unneeded `buffer` declarations.
* Fixed; make sure to clear socket handlers and subscriptions upon transport close.
0.7.6 / 2011-06-30
==================
* Fixed general dispatching when a client has closed.
0.7.5 / 2011-06-30
==================
* Fixed dispatching to clients that are disconnected.
0.7.4 / 2011-06-30
==================
* Fixed; only clear handlers if they were set. [level09]
0.7.3 / 2011-06-30
==================
* Exposed handshake data to clients.
* Refactored dispatcher interface.
* Changed; Moved id generation method into the manager.
* Added sub-namespace authorization. [3rd-Eden]
* Changed; normalized SocketNamespace local eventing [dvv]
* Changed; Use packet.reason or default to 'packet' [3rd-Eden]
* Changed console.error to console.log.
* Fixed; bind both servers at the same time do that the test never times out.
* Added 304 support.
* Removed `Transport#name` for abstract interface.
* Changed; lazily require http and https module only when needed. [3rd-Eden]
0.7.2 / 2011-06-22
==================
* Make sure to write a packet (of type `noop`) when closing a poll.
This solves a problem with cross-domain requests being flagged as aborted and
reconnection being triggered.
* Added `noop` message type.
0.7.1 / 2011-06-21
==================
* Fixed cross-domain XHR.
* Added CORS test to xhr-polling suite.
0.7.0 / 2010-06-21
==================
* http://socket.io/announcement.html

View File

@@ -1,6 +1,6 @@
(The MIT License)
Copyright (c) 2014-present Guillermo Rauch and Socket.IO contributors
Copyright (c) 2014-2016 Automattic <dev@cloudup.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

8
Makefile Normal file
View File

@@ -0,0 +1,8 @@
test:
@./node_modules/.bin/gulp test
test-cov:
@./node_modules/.bin/gulp test-cov
.PHONY: test

View File

@@ -1,34 +0,0 @@
# socket.io
[![Latest NPM version](https://img.shields.io/npm/v/socket.io.svg)](https://www.npmjs.com/package/socket.io)
[![Build status](https://github.com/socketio/socket.io/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/socketio/socket.io/actions/workflows/ci.yml)
[![Downloads per month](https://img.shields.io/npm/dm/socket.io.svg)]((https://www.npmjs.com/package/socket.io))
## Getting Started
Please check our documentation [here](https://socket.io).
## Questions
Our [issues list](https://github.com/socketio/socket.io/issues) is exclusively reserved for bug reports and feature requests. For usage questions, please use the following resources:
- read our [documentation](https://socket.io/docs/v4/)
- check our [troubleshooting guide](https://socket.io/docs/v4/troubleshooting-connection-issues/)
- look for/ask questions on [Stack Overflow](https://stackoverflow.com/questions/tagged/socket.io)
- create a new [discussion](https://github.com/socketio/socket.io/discussions/new?category=q-a)
## Security
If you think that you have found a security vulnerability in our project, please do not create an issue in this GitHub repository, but rather refer to our [Security Policy](./SECURITY.md).
## Issues and contribution
Please make sure to read our [Contributing Guide](./CONTRIBUTING.md) before creating an issue or making a pull request.
Thanks to everyone who has already contributed to Socket.IO!
<a href="https://github.com/socketio/socket.io/graphs/contributors"><img src="https://opencollective.com/socketio/contributors.svg?width=890" /></a>
## License
[MIT](https://opensource.org/licenses/MIT)

489
Readme.md Normal file
View File

@@ -0,0 +1,489 @@
# socket.io
[![Build Status](https://secure.travis-ci.org/socketio/socket.io.svg?branch=master)](https://travis-ci.org/socketio/socket.io)
[![Dependency Status](https://david-dm.org/socketio/socket.io.svg)](https://david-dm.org/socketio/socket.io)
[![devDependency Status](https://david-dm.org/socketio/socket.io/dev-status.svg)](https://david-dm.org/socketio/socket.io#info=devDependencies)
[![NPM version](https://badge.fury.io/js/socket.io.svg)](https://www.npmjs.com/package/socket.io)
![Downloads](https://img.shields.io/npm/dm/socket.io.svg?style=flat)
[![](http://slack.socket.io/badge.svg?)](http://slack.socket.io)
## How to use
The following example attaches socket.io to a plain Node.JS
HTTP server listening on port `3000`.
```js
var server = require('http').createServer();
var io = require('socket.io')(server);
io.on('connection', function(client){
client.on('event', function(data){});
client.on('disconnect', function(){});
});
server.listen(3000);
```
### Standalone
```js
var io = require('socket.io')();
io.on('connection', function(client){});
io.listen(3000);
```
### In conjunction with Express
Starting with **3.0**, express applications have become request handler
functions that you pass to `http` or `http` `Server` instances. You need
to pass the `Server` to `socket.io`, and not the express application
function.
```js
var app = require('express')();
var server = require('http').createServer(app);
var io = require('socket.io')(server);
io.on('connection', function(){ /* … */ });
server.listen(3000);
```
### In conjunction with Koa
Like Express.JS, Koa works by exposing an application as a request
handler function, but only by calling the `callback` method.
```js
var app = require('koa')();
var server = require('http').createServer(app.callback());
var io = require('socket.io')(server);
io.on('connection', function(){ /* … */ });
server.listen(3000);
```
## API
### Server
Exposed by `require('socket.io')`.
### Server()
Creates a new `Server`. Works with and without `new`:
```js
var io = require('socket.io')();
// or
var Server = require('socket.io');
var io = new Server();
```
### Server(opts:Object)
Optionally, the first or second argument (see below) of the `Server`
constructor can be an options object.
The following options are supported:
- `serveClient` sets the value for Server#serveClient()
- `path` sets the value for Server#path()
The same options passed to socket.io are always passed to
the `engine.io` `Server` that gets created. See engine.io
[options](https://github.com/socketio/engine.io#methods-1)
as reference.
### Server(srv:http#Server, opts:Object)
Creates a new `Server` and attaches it to the given `srv`. Optionally
`opts` can be passed.
### Server(port:Number, opts:Object)
Binds socket.io to a new `http.Server` that listens on `port`.
### Server#serveClient(v:Boolean):Server
If `v` is `true` the attached server (see `Server#attach`) will serve
the client files. Defaults to `true`.
This method has no effect after `attach` is called.
```js
// pass a server and the `serveClient` option
var io = require('socket.io')(http, { serveClient: false });
// or pass no server and then you can call the method
var io = require('socket.io')();
io.serveClient(false);
io.attach(http);
```
If no arguments are supplied this method returns the current value.
### Server#path(v:String):Server
Sets the path `v` under which `engine.io` and the static files will be
served. Defaults to `/socket.io`.
If no arguments are supplied this method returns the current value.
### Server#adapter(v:Adapter):Server
Sets the adapter `v`. Defaults to an instance of the `Adapter` that
ships with socket.io which is memory based. See
[socket.io-adapter](https://github.com/socketio/socket.io-adapter).
If no arguments are supplied this method returns the current value.
### Server#origins(v:String):Server
Sets the allowed origins `v`. Defaults to any origins being allowed.
If no arguments are supplied this method returns the current value.
### Server#origins(v:Function):Server
Sets the allowed origins as dynamic function. Function takes two arguments `origin:String` and `callback(error, success)`, where `success` is a boolean value indicating whether origin is allowed or not.
__Potential drawbacks__:
* in some situations, when it is not possible to determine `origin` it may have value of `*`
* As this function will be executed for every request, it is advised to make this function work as fast as possible
* If `socket.io` is used together with `Express`, the CORS headers will be affected only for `socket.io` requests. For Express can use [cors](https://github.com/expressjs/cors).
### Server#sockets:Namespace
The default (`/`) namespace.
### Server#attach(srv:http#Server, opts:Object):Server
Attaches the `Server` to an engine.io instance on `srv` with the
supplied `opts` (optionally).
### Server#attach(port:Number, opts:Object):Server
Attaches the `Server` to an engine.io instance that is bound to `port`
with the given `opts` (optionally).
### Server#listen
Synonym of `Server#attach`.
### Server#bind(srv:engine#Server):Server
Advanced use only. Binds the server to a specific engine.io `Server`
(or compatible API) instance.
### Server#onconnection(socket:engine#Socket):Server
Advanced use only. Creates a new `socket.io` client from the incoming
engine.io (or compatible API) `socket`.
### Server#of(nsp:String):Namespace
Initializes and retrieves the given `Namespace` by its pathname
identifier `nsp`.
If the namespace was already initialized it returns it immediately.
### Server#emit
Emits an event to all connected clients. The following two are
equivalent:
```js
var io = require('socket.io')();
io.sockets.emit('an event sent to all connected clients');
io.emit('an event sent to all connected clients');
```
For other available methods, see `Namespace` below.
### Server#close([fn:Function])
Closes socket.io server.
The optional `fn` is passed to the `server.close([callback])` method of the
core `net` module and is called on error or when all connections are closed.
The callback is expected to implement the common single argument `err`
signature (if any).
```js
var Server = require('socket.io');
var PORT = 3030;
var server = require('http').Server();
var io = Server(PORT);
io.close(); // Close current server
server.listen(PORT); // PORT is free to use
io = Server(server);
```
### Server#use
See `Namespace#use` below.
### Namespace
Represents a pool of sockets connected under a given scope identified
by a pathname (eg: `/chat`).
By default the client always connects to `/`.
#### Events
- `connection` / `connect`. Fired upon a connection.
Parameters:
- `Socket` the incoming socket.
### Namespace#name:String
The namespace identifier property.
### Namespace#connected:Object<Socket>
Hash of `Socket` objects that are connected to this namespace indexed
by `id`.
### Namespace#clients(fn:Function)
Gets a list of client IDs connected to this namespace (across all nodes if applicable).
An example to get all clients in a namespace:
```js
var io = require('socket.io')();
io.of('/chat').clients(function(error, clients){
if (error) throw error;
console.log(clients); // => [PZDoMHjiu8PYfRiKAAAF, Anw2LatarvGVVXEIAAAD]
});
```
An example to get all clients in namespace's room:
```js
var io = require('socket.io')();
io.of('/chat').in('general').clients(function(error, clients){
if (error) throw error;
console.log(clients); // => [Anw2LatarvGVVXEIAAAD]
});
```
As with broadcasting, the default is all clients from the default namespace ('/'):
```js
var io = require('socket.io')();
io.clients(function(error, clients){
if (error) throw error;
console.log(clients); // => [6em3d4TJP8Et9EMNAAAA, G5p55dHhGgUnLUctAAAB]
});
```
### Namespace#use(fn:Function):Namespace
Registers a middleware, which is a function that gets executed for
every incoming `Socket`, and receives as parameters the socket and a
function to optionally defer execution to the next registered
middleware.
```js
var io = require('socket.io')();
io.use(function(socket, next){
if (socket.request.headers.cookie) return next();
next(new Error('Authentication error'));
});
```
Errors passed to middleware callbacks are sent as special `error`
packets to clients.
### Socket
A `Socket` is the fundamental class for interacting with browser
clients. A `Socket` belongs to a certain `Namespace` (by default `/`)
and uses an underlying `Client` to communicate.
It should be noted the `Socket` doesn't relate directly to the actual
underlying TCP/IP `socket` and it is only the name of the class.
### Socket#use(fn:Function):Socket
Registers a middleware, which is a function that gets executed for
every incoming `Packet` and receives as parameter the packet and a
function to optionally defer execution to the next registered
middleware.
```js
var io = require('socket.io')();
io.on('connection', function(socket){
socket.use(function(packet, next){
if (packet.doge === true) return next();
next(new Error('Not a doge error'));
});
```
Errors passed to middleware callbacks are sent as special `error`
packets to clients.
### Socket#rooms:Object
A hash of strings identifying the rooms this client is in, indexed by
room name.
### Socket#client:Client
A reference to the underlying `Client` object.
### Socket#conn:Socket
A reference to the underlying `Client` transport connection (engine.io
`Socket` object). This allows access to the IO transport layer, which
still (mostly) abstracts the actual TCP/IP socket.
### Socket#request:Request
A getter proxy that returns the reference to the `request` that
originated the underlying engine.io `Client`. Useful for accessing
request headers such as `Cookie` or `User-Agent`.
### Socket#id:String
A unique identifier for the session, that comes from the
underlying `Client`.
### Socket#emit(name:String[, …]):Socket
Emits an event identified by the string `name` to the client.
Any other parameters can be included.
All datastructures are supported, including `Buffer`. JavaScript
functions can't be serialized/deserialized.
```js
var io = require('socket.io')();
io.on('connection', function(client){
client.emit('an event', { some: 'data' });
});
```
### Socket#join(name:String[, fn:Function]):Socket
Adds the client to the `room`, and fires optionally a callback `fn`
with `err` signature (if any).
The client is automatically a member of a room identified with its
session id (see `Socket#id`).
The mechanics of joining rooms are handled by the `Adapter`
that has been configured (see `Server#adapter` above), defaulting to
[socket.io-adapter](https://github.com/socketio/socket.io-adapter).
### Socket#leave(name:String[, fn:Function]):Socket
Removes the client from `room`, and fires optionally a callback `fn`
with `err` signature (if any).
**Rooms are left automatically upon disconnection**.
The mechanics of leaving rooms are handled by the `Adapter`
that has been configured (see `Server#adapter` above), defaulting to
[socket.io-adapter](https://github.com/socketio/socket.io-adapter).
### Socket#to(room:String):Socket
Sets a modifier for a subsequent event emission that the event will
only be _broadcasted_ to clients that have joined the given `room`.
To emit to multiple rooms, you can call `to` several times.
```js
var io = require('socket.io')();
io.on('connection', function(client){
client.to('others').emit('an event', { some: 'data' });
});
```
### Socket#in(room:String):Socket
Same as `Socket#to`
### Socket#compress(v:Boolean):Socket
Sets a modifier for a subsequent event emission that the event data will
only be _compressed_ if the value is `true`. Defaults to `true` when you don't call the method.
```js
var io = require('socket.io')();
io.on('connection', function(client){
client.compress(false).emit('an event', { some: 'data' });
});
```
### Socket#disconnect(close:Boolean):Socket
Disconnects this client. If value of close is `true`, closes the underlying connection.
Otherwise, it just disconnects the namespace.
#### Events
- `disconnect`
- Fired upon disconnection.
- **Arguments**
- `String`: the reason of the disconnection (either client or server-side)
- `error`
- Fired when an error occurs.
- **Arguments**
- `Object`: error data
- `disconnecting`
- Fired when the client is going to be disconnected (but hasn't left its `rooms` yet).
- **Arguments**
- `String`: the reason of the disconnection (either client or server-side)
These are reserved events (along with `connect`, `newListener` and `removeListener`) which cannot be used as event names.
### Client
The `Client` class represents an incoming transport (engine.io)
connection. A `Client` can be associated with many multiplexed `Socket`s
that belong to different `Namespace`s.
### Client#conn
A reference to the underlying `engine.io` `Socket` connection.
### Client#request
A getter proxy that returns the reference to the `request` that
originated the engine.io connection. Useful for accessing
request headers such as `Cookie` or `User-Agent`.
## Debug / logging
Socket.IO is powered by [debug](https://github.com/visionmedia/debug).
In order to see all the debug output, run your app with the environment variable
`DEBUG` including the desired scope.
To see the output from all of Socket.IO's debugging scopes you can use:
```
DEBUG=socket.io* node myapp
```
## Testing
```
npm test
```
This runs the `gulp` task `test`. By default the test will be run with the source code in `lib` directory.
Set the environmental variable `TEST_VERSION` to `compat` to test the transpiled es5-compat version of the code.
The `gulp` task `test` will always transpile the source code into es5 and export to `dist` first before running the test.
## License
[MIT](LICENSE)

View File

@@ -1,69 +0,0 @@
# Security Policy
<!-- TOC -->
* [Supported Versions](#supported-versions)
* [Reporting a Vulnerability](#reporting-a-vulnerability)
* [History](#history)
* [For the `socket.io` package](#for-the-socketio-package)
* [For the `socket.io-client` package](#for-the-socketio-client-package)
<!-- TOC -->
## Supported Versions
| Version | Supported |
|---------|--------------------|
| 4.x | :white_check_mark: |
| 3.x | :white_check_mark: |
| 2.4.x | :white_check_mark: |
| < 2.4.0 | :x: |
## Reporting a Vulnerability
To report a security vulnerability in this package, please send an email to [@darrachequesne](https://github.com/darrachequesne) (see address in profile) describing the vulnerability and how to reproduce it.
We will get back to you as soon as possible and publish a fix if necessary.
:warning: IMPORTANT :warning: please do not create an issue in this repository, as attackers might take advantage of it. Thank you in advance for your responsible disclosure.
## History
### For the `socket.io` package
| Date | Description | CVE number | Affected versions | Patched versions |
|--------------|------------------------------------------------------------------------------|------------------|-------------------------------------|-----------------------|
| July 2012 | [Insecure randomness](https://github.com/advisories/GHSA-qv2v-m59f-v5fw) | `CVE-2017-16031` | `<= 0.9.6` | `0.9.7` |
| January 2021 | [CORS misconfiguration](https://github.com/advisories/GHSA-fxwf-4rqh-v8g3) | `CVE-2020-28481` | `< 2.4.0` | `2.4.0` |
| June 2024 | [Unhandled 'error' event](https://github.com/advisories/GHSA-25hc-qcg6-38wj) | `CVE-2024-38355` | `< 2.5.1` <br/> `>= 3.0.0, < 4.6.2` | `2.5.1` <br/> `4.6.2` |
From the transitive dependencies:
| Date | Dependency | Description | CVE number |
|---------------|--------------------|---------------------------------------------------------------------------------------------------------------|------------------|
| January 2016 | `ws` | [Buffer vulnerability](https://github.com/advisories/GHSA-2mhh-w6q8-5hxw) | `CVE-2016-10518` |
| January 2016 | `ws` | [DoS due to excessively large websocket message](https://github.com/advisories/GHSA-6663-c963-2gqg) | `CVE-2016-10542` |
| November 2017 | `ws` | [DoS in the `Sec-Websocket-Extensions` header parser](https://github.com/advisories/GHSA-5v72-xg48-5rpm) | `-` |
| February 2020 | `engine.io` | [Resource exhaustion](https://github.com/advisories/GHSA-j4f2-536g-r55m) | `CVE-2020-36048` |
| January 2021 | `socket.io-parser` | [Resource exhaustion](https://github.com/advisories/GHSA-xfhh-g9f5-x4m4) | `CVE-2020-36049` |
| May 2021 | `ws` | [ReDoS in `Sec-Websocket-Protocol` header](https://github.com/advisories/GHSA-6fc8-4gx4-v693) | `CVE-2021-32640` |
| January 2022 | `engine.io` | [Uncaught exception](https://github.com/advisories/GHSA-273r-mgr4-v34f) | `CVE-2022-21676` |
| October 2022 | `socket.io-parser` | [Insufficient validation when decoding a Socket.IO packet](https://github.com/advisories/GHSA-qm95-pgcg-qqfq) | `CVE-2022-2421` |
| November 2022 | `engine.io` | [Uncaught exception](https://github.com/advisories/GHSA-r7qp-cfhv-p84w) | `CVE-2022-41940` |
| May 2023 | `engine.io` | [Uncaught exception](https://github.com/advisories/GHSA-q9mw-68c2-j6m5) | `CVE-2023-31125` |
| May 2023 | `socket.io-parser` | [Insufficient validation when decoding a Socket.IO packet](https://github.com/advisories/GHSA-cqmj-92xf-r6r9) | `CVE-2023-32695` |
| June 2024 | `ws` | [DoS when handling a request with many HTTP headers](https://github.com/advisories/GHSA-3h5v-q93c-6h6q) | `CVE-2024-37890` |
### For the `socket.io-client` package
From the transitive dependencies:
| Date | Dependency | Description | CVE number |
|---------------|--------------------|---------------------------------------------------------------------------------------------------------------|------------------|
| January 2016 | `ws` | [Buffer vulnerability](https://github.com/advisories/GHSA-2mhh-w6q8-5hxw) | `CVE-2016-10518` |
| January 2016 | `ws` | [DoS due to excessively large websocket message](https://github.com/advisories/GHSA-6663-c963-2gqg) | `CVE-2016-10542` |
| October 2016 | `engine.io-client` | [Insecure Defaults Allow MITM Over TLS](https://github.com/advisories/GHSA-4r4m-hjwj-43p8) | `CVE-2016-10536` |
| November 2017 | `ws` | [DoS in the `Sec-Websocket-Extensions` header parser](https://github.com/advisories/GHSA-5v72-xg48-5rpm) | `-` |
| January 2021 | `socket.io-parser` | [Resource exhaustion](https://github.com/advisories/GHSA-xfhh-g9f5-x4m4) | `CVE-2020-36049` |
| May 2021 | `ws` | [ReDoS in `Sec-Websocket-Protocol` header](https://github.com/advisories/GHSA-6fc8-4gx4-v693) | `CVE-2021-32640` |
| October 2022 | `socket.io-parser` | [Insufficient validation when decoding a Socket.IO packet](https://github.com/advisories/GHSA-qm95-pgcg-qqfq) | `CVE-2022-2421` |
| May 2023 | `socket.io-parser` | [Insufficient validation when decoding a Socket.IO packet](https://github.com/advisories/GHSA-cqmj-92xf-r6r9) | `CVE-2023-32695` |
| June 2024 | `ws` | [DoS when handling a request with many HTTP headers](https://github.com/advisories/GHSA-3h5v-q93c-6h6q) | `CVE-2024-37890` |

View File

@@ -1,2 +0,0 @@
The documentation has been moved to the website [here](https://socket.io/docs/).

View File

@@ -1 +0,0 @@
node_modules

View File

@@ -1,30 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Test suite for the Engine.IO protocol</title>
<link rel="stylesheet" href="https://unpkg.com/mocha@9/mocha.css" />
</head>
<body>
<div id="mocha"></div>
<script src="https://unpkg.com/mocha@9/mocha.js"></script>
<script src="https://unpkg.com/chai@4/chai.js" ></script>
<script src="https://unpkg.com/chai-string@1/chai-string.js" ></script>
<script class="mocha-init">
mocha.setup("bdd");
mocha.checkLeaks();
</script>
<script type="module" src="test-suite.js"></script>
<script class="mocha-exec">
mocha.run();
</script>
</body>
</html>

View File

@@ -1,10 +0,0 @@
import fetch from "node-fetch";
import { WebSocket } from "ws";
import chai from "chai";
import chaiString from "chai-string";
chai.use(chaiString);
globalThis.fetch = fetch;
globalThis.WebSocket = WebSocket;
globalThis.chai = chai;

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +0,0 @@
{
"name": "engine.io-protocol-test-suite",
"version": "0.0.1",
"private": true,
"type": "module",
"scripts": {
"format": "prettier -w *.js",
"test": "mocha test-suite.js"
},
"devDependencies": {
"chai": "^4.3.6",
"chai-string": "^1.5.0",
"mocha": "^9.2.1",
"node-fetch": "^3.2.0",
"prettier": "^2.5.1",
"ws": "^8.5.0"
}
}

View File

@@ -1,595 +0,0 @@
const isNodejs = typeof window === "undefined";
if (isNodejs) {
// make the tests runnable in both the browser and Node.js
await import("./node-imports.js");
}
const { expect } = chai;
const URL = "http://localhost:3000";
const WS_URL = URL.replace("http", "ws");
const PING_INTERVAL = 300;
const PING_TIMEOUT = 200;
function sleep(delay) {
return new Promise((resolve) => setTimeout(resolve, delay));
}
function waitFor(socket, eventType) {
return new Promise((resolve) => {
socket.addEventListener(
eventType,
(event) => {
resolve(event);
},
{ once: true }
);
});
}
function decodePayload(payload) {
const firstColonIndex = payload.indexOf(":");
const length = payload.substring(0, firstColonIndex);
const packet = payload.substring(firstColonIndex + 1);
return [length, packet];
}
async function initLongPollingSession(supportsBinary = false) {
const response = await fetch(`${URL}/engine.io/?EIO=3&transport=polling` + (supportsBinary ? "" : "&b64=1"));
const text = await response.text();
const [, content] = decodePayload(text);
return JSON.parse(content.substring(1)).sid;
}
describe("Engine.IO protocol", () => {
describe("handshake", () => {
describe("HTTP long-polling", () => {
it("successfully opens a session", async () => {
const response = await fetch(
`${URL}/engine.io/?EIO=3&transport=polling`
);
expect(response.status).to.eql(200);
const text = await response.text();
const [length, content] = decodePayload(text);
expect(length).to.eql(content.length.toString());
expect(content).to.startsWith("0");
const value = JSON.parse(content.substring(1));
expect(value.sid).to.be.a("string");
expect(value.upgrades).to.eql(["websocket"]);
expect(value.pingInterval).to.eql(PING_INTERVAL);
expect(value.pingTimeout).to.eql(PING_TIMEOUT);
expect(value.maxPayload).to.be.oneOf([undefined, 1000000]);
});
it("fails with an invalid 'transport' query parameter", async () => {
const response = await fetch(`${URL}/engine.io/?EIO=3`);
expect(response.status).to.eql(400);
const response2 = await fetch(`${URL}/engine.io/?EIO=3&transport=abc`);
expect(response2.status).to.eql(400);
});
it("fails with an invalid request method", async () => {
const response = await fetch(
`${URL}/engine.io/?EIO=3&transport=polling`,
{
method: "post",
}
);
expect(response.status).to.eql(400);
});
});
describe("WebSocket", () => {
it("successfully opens a session", async () => {
const socket = new WebSocket(
`${WS_URL}/engine.io/?EIO=3&transport=websocket`
);
const { data } = await waitFor(socket, "message");
expect(data).to.startsWith("0");
const value = JSON.parse(data.substring(1));
expect(value.sid).to.be.a("string");
expect(value.upgrades).to.eql([]);
expect(value.pingInterval).to.eql(PING_INTERVAL);
expect(value.pingTimeout).to.eql(PING_TIMEOUT);
expect(value.maxPayload).to.be.oneOf([undefined, 1000000]);
socket.close();
});
it("fails with an invalid 'EIO' query parameter", async () => {
const socket = new WebSocket(
`${WS_URL}/engine.io/?transport=websocket`
);
if (isNodejs) {
socket.on("error", () => {});
}
waitFor(socket, "close");
const socket2 = new WebSocket(
`${WS_URL}/engine.io/?EIO=abc&transport=websocket`
);
if (isNodejs) {
socket2.on("error", () => {});
}
waitFor(socket2, "close");
});
it("fails with an invalid 'transport' query parameter", async () => {
const socket = new WebSocket(`${WS_URL}/engine.io/?EIO=3`);
if (isNodejs) {
socket.on("error", () => {});
}
waitFor(socket, "close");
const socket2 = new WebSocket(
`${WS_URL}/engine.io/?EIO=3&transport=abc`
);
if (isNodejs) {
socket2.on("error", () => {});
}
waitFor(socket2, "close");
});
});
});
describe("message", () => {
describe("HTTP long-polling", () => {
it("sends and receives a payload containing one plain text packet", async () => {
const sid = await initLongPollingSession();
const pushResponse = await fetch(
`${URL}/engine.io/?EIO=3&transport=polling&sid=${sid}`,
{
method: "post",
body: "6:4hello",
}
);
expect(pushResponse.status).to.eql(200);
const postContent = await pushResponse.text();
expect(postContent).to.eql("ok");
const pollResponse = await fetch(
`${URL}/engine.io/?EIO=3&transport=polling&sid=${sid}`
);
expect(pollResponse.status).to.eql(200);
const pollContent = await pollResponse.text();
expect(pollContent).to.eql("6:4hello");
});
it("sends and receives a payload containing several plain text packets", async () => {
const sid = await initLongPollingSession();
const pushResponse = await fetch(
`${URL}/engine.io/?EIO=3&transport=polling&sid=${sid}`,
{
method: "post",
body: "6:4test16:4test26:4test3",
}
);
expect(pushResponse.status).to.eql(200);
const postContent = await pushResponse.text();
expect(postContent).to.eql("ok");
const pollResponse = await fetch(
`${URL}/engine.io/?EIO=3&transport=polling&sid=${sid}`
);
expect(pollResponse.status).to.eql(200);
const pollContent = await pollResponse.text();
expect(pollContent).to.eql("6:4test16:4test26:4test3");
});
it("sends and receives a payload containing plain text and binary packets (base64 encoded)", async () => {
const sid = await initLongPollingSession();
const pushResponse = await fetch(
`${URL}/engine.io/?EIO=3&transport=polling&sid=${sid}`,
{
method: "post",
body: "6:4hello10:b4AQIDBA==",
}
);
expect(pushResponse.status).to.eql(200);
const postContent = await pushResponse.text();
expect(postContent).to.eql("ok");
const pollResponse = await fetch(
`${URL}/engine.io/?EIO=3&transport=polling&sid=${sid}`
);
expect(pollResponse.status).to.eql(200);
const pollContent = await pollResponse.text();
expect(pollContent).to.eql("6:4hello10:b4AQIDBA==");
});
it("sends and receives a payload containing plain text and binary packets (binary)", async () => {
const sid = await initLongPollingSession(true);
const pushResponse = await fetch(
`${URL}/engine.io/?EIO=3&transport=polling&sid=${sid}`,
{
method: "post",
body: "6:4hello10:b4AQIDBA==",
}
);
expect(pushResponse.status).to.eql(200);
const postContent = await pushResponse.text();
expect(postContent).to.eql("ok");
const pollResponse = await fetch(
`${URL}/engine.io/?EIO=3&transport=polling&sid=${sid}`
);
expect(pollResponse.status).to.eql(200);
const buffer = await pollResponse.arrayBuffer();
// 0 => string
// 6 => byte length
// 255 => delimiter
// 52 => 4 (MESSAGE packet type)
// 104 101 108 108 111 => "hello"
// 1 => binary
// 5 => byte length
// 255 => delimiter
// 4 => 4 (MESSAGE packet type)
// 1 2 3 4 => binary message
expect(buffer).to.eql(Uint8Array.from([0, 6, 255, 52, 104, 101, 108, 108, 111, 1, 5, 255, 4, 1, 2, 3, 4]).buffer);
});
it("closes the session upon invalid packet format", async () => {
const sid = await initLongPollingSession();
try {
const pushResponse = await fetch(
`${URL}/engine.io/?EIO=3&transport=polling&sid=${sid}`,
{
method: "post",
body: "abc",
}
);
expect(pushResponse.status).to.eql(400);
} catch (e) {
// node-fetch throws when the request is closed abnormally
}
const pollResponse = await fetch(
`${URL}/engine.io/?EIO=3&transport=polling&sid=${sid}`
);
expect(pollResponse.status).to.eql(400);
});
// FIXME CORS error
it.skip("closes the session upon duplicate poll requests", async () => {
const sid = await initLongPollingSession();
const pollResponses = await Promise.all([
fetch(`${URL}/engine.io/?EIO=3&transport=polling&sid=${sid}`),
sleep(5).then(() => fetch(`${URL}/engine.io/?EIO=3&transport=polling&sid=${sid}&t=burst`)),
]);
expect(pollResponses[0].status).to.eql(200);
const content = await pollResponses[0].text();
expect(content).to.eql("1:1");
// the Node.js implementation uses HTTP 500 (Internal Server Error), but HTTP 400 seems more suitable
expect(pollResponses[1].status).to.be.oneOf([400, 500]);
const pollResponse = await fetch(
`${URL}/engine.io/?EIO=3&transport=polling&sid=${sid}`
);
expect(pollResponse.status).to.eql(500);
});
});
describe("WebSocket", () => {
it("sends and receives a plain text packet", async () => {
const socket = new WebSocket(
`${WS_URL}/engine.io/?EIO=3&transport=websocket`
);
await waitFor(socket, "open");
await waitFor(socket, "message"); // handshake
socket.send("4hello");
const { data } = await waitFor(socket, "message");
expect(data).to.eql("4hello");
socket.close();
});
it("sends and receives a binary packet", async () => {
const socket = new WebSocket(
`${WS_URL}/engine.io/?EIO=3&transport=websocket`
);
socket.binaryType = "arraybuffer";
await waitFor(socket, "message"); // handshake
socket.send(Uint8Array.from([4, 1, 2, 3, 4]));
const { data } = await waitFor(socket, "message");
expect(data).to.eql(Uint8Array.from([4, 1, 2, 3, 4]).buffer);
socket.close();
});
it("closes the session upon invalid packet format", async () => {
const socket = new WebSocket(
`${WS_URL}/engine.io/?EIO=3&transport=websocket`
);
await waitFor(socket, "message"); // handshake
socket.send("abc");
await waitFor(socket, "close");
socket.close();
});
});
});
describe("heartbeat", function () {
this.timeout(5000);
describe("HTTP long-polling", () => {
it("sends ping/pong packets", async () => {
const sid = await initLongPollingSession();
for (let i = 0; i < 3; i++) {
const pushResponse = await fetch(
`${URL}/engine.io/?EIO=3&transport=polling&sid=${sid}`,
{
method: "post",
body: "1:2",
}
);
expect(pushResponse.status).to.eql(200);
const pollResponse = await fetch(
`${URL}/engine.io/?EIO=3&transport=polling&sid=${sid}`
);
expect(pollResponse.status).to.eql(200);
const pollContent = await pollResponse.text();
expect(pollContent).to.eql("1:3");
}
});
it("closes the session upon ping timeout", async () => {
const sid = await initLongPollingSession();
await sleep(PING_INTERVAL + PING_TIMEOUT);
const pushResponse = await fetch(
`${URL}/engine.io/?EIO=3&transport=polling&sid=${sid}`,
{
method: "post",
body: "1:2",
}
);
expect(pushResponse.status).to.eql(400);
});
});
describe("WebSocket", () => {
it("sends ping/pong packets", async () => {
const socket = new WebSocket(
`${WS_URL}/engine.io/?EIO=3&transport=websocket`
);
const x = await waitFor(socket, "message"); // handshake
for (let i = 0; i < 3; i++) {
socket.send("2");
const { data } = await waitFor(socket, "message");
expect(data).to.eql("3");
}
socket.close();
});
it("closes the session upon ping timeout", async () => {
const socket = new WebSocket(
`${WS_URL}/engine.io/?EIO=3&transport=websocket`
);
await waitFor(socket, "close"); // handshake
});
});
});
describe("close", () => {
describe("HTTP long-polling", () => {
it("forcefully closes the session", async () => {
const sid = await initLongPollingSession();
const [pollResponse] = await Promise.all([
fetch(`${URL}/engine.io/?EIO=3&transport=polling&sid=${sid}`),
fetch(`${URL}/engine.io/?EIO=3&transport=polling&sid=${sid}`, {
method: "post",
body: "1:1",
}),
]);
expect(pollResponse.status).to.eql(200);
const pullContent = await pollResponse.text();
expect(pullContent).to.eql("1:6");
const pollResponse2 = await fetch(
`${URL}/engine.io/?EIO=3&transport=polling&sid=${sid}`
);
expect(pollResponse2.status).to.eql(400);
});
});
describe("WebSocket", () => {
it("forcefully closes the session", async () => {
const socket = new WebSocket(
`${WS_URL}/engine.io/?EIO=3&transport=websocket`
);
await waitFor(socket, "message"); // handshake
socket.send("1");
await waitFor(socket, "close");
});
});
});
describe("upgrade", () => {
it("successfully upgrades from HTTP long-polling to WebSocket", async () => {
const sid = await initLongPollingSession();
const socket = new WebSocket(
`${WS_URL}/engine.io/?EIO=3&transport=websocket&sid=${sid}`
);
await waitFor(socket, "open");
// send probe
socket.send("2probe");
const probeResponse = await waitFor(socket, "message");
expect(probeResponse.data).to.eql("3probe");
const pollResponse = await fetch(
`${URL}/engine.io/?EIO=3&transport=polling&sid=${sid}`
);
expect(pollResponse.status).to.eql(200);
const pollContent = await pollResponse.text();
expect(pollContent).to.eql("1:6"); // "noop" packet to cleanly end the HTTP long-polling request
// complete upgrade
socket.send("5");
socket.send("4hello");
const { data } = await waitFor(socket, "message");
expect(data).to.eql("4hello");
});
it("ignores HTTP requests with same sid after upgrade", async () => {
const sid = await initLongPollingSession();
const socket = new WebSocket(
`${WS_URL}/engine.io/?EIO=3&transport=websocket&sid=${sid}`
);
await waitFor(socket, "open");
socket.send("2probe");
const res = await waitFor(socket, "message");
expect(res.data).to.eql("3probe");
socket.send("5");
const pollResponse = await fetch(
`${URL}/engine.io/?EIO=3&transport=polling&sid=${sid}`
);
expect(pollResponse.status).to.eql(400);
socket.send("4hello");
const { data } = await waitFor(socket, "message");
expect(data).to.eql("4hello");
});
it("ignores WebSocket connection with same sid after upgrade", async () => {
const sid = await initLongPollingSession();
const socket = new WebSocket(
`${WS_URL}/engine.io/?EIO=3&transport=websocket&sid=${sid}`
);
await waitFor(socket, "open");
socket.send("2probe");
const res = await waitFor(socket, "message");
expect(res.data).to.eql("3probe");
socket.send("5");
const socket2 = new WebSocket(
`${WS_URL}/engine.io/?EIO=3&transport=websocket&sid=${sid}`
);
await waitFor(socket2, "close");
socket.send("4hello");
const { data } = await waitFor(socket, "message");
expect(data).to.eql("4hello");
});
});
});

View File

@@ -1,545 +0,0 @@
# Engine.IO Protocol
This document describes the Engine.IO protocol. For a reference JavaScript
implementation, take a look at
[engine.io-parser](https://github.com/learnboost/engine.io-parser),
[engine.io-client](https://github.com/learnboost/engine.io-client)
and [engine.io](https://github.com/learnboost/engine.io).
Table of Contents:
- [Revision](#revision)
- [Anatomy of an Engine.IO session](#anatomy-of-an-engineio-session)
- [Sample session](#sample-session)
- [Sample session with WebSocket only](#sample-session-with-websocket-only)
- [URLs](#urls)
- [Encoding](#encoding)
- [Packet](#packet)
- [0 open](#0-open)
- [1 close](#1-close)
- [2 ping](#2-ping)
- [3 pong](#3-pong)
- [4 message](#4-message)
- [5 upgrade](#5-upgrade)
- [6 noop](#6-noop)
- [Payload](#payload)
- [Transports](#transports)
- [Polling](#polling)
- [XHR](#xhr)
- [JSONP](#jsonp)
- [WebSocket](#websocket)
- [Transport upgrading](#transport-upgrading)
- [Timeouts](#timeouts)
- [Difference between v2 and v3](#difference-between-v2-and-v3)
- [Test suite](#test-suite)
## Revision
This is revision **3** of the Engine.IO protocol.
The revision 2 can be found here: https://github.com/socketio/engine.io-protocol/tree/v2
## Anatomy of an Engine.IO session
1. Transport establishes a connection to the Engine.IO URL .
2. Server responds with an `open` packet with JSON-encoded handshake data:
- `sid` session id (`String`)
- `upgrades` possible transport upgrades (`Array` of `String`)
- `pingTimeout` server configured ping timeout, used for the client
to detect that the server is unresponsive (`Number`)
- `pingInterval` server configured ping interval, used for the client
to detect that the server is unresponsive (`Number`)
3. Server must respond to periodic `ping` packets sent by the client
with `pong` packets.
4. Client and server can exchange `message` packets at will.
5. Polling transports can send a `close` packet to close the socket, since
they're expected to be "opening" and "closing" all the time.
### Sample session
- Request n°1 (open packet)
```
GET /engine.io/?EIO=3&transport=polling&t=N8hyd6w
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=UTF-8
96:0{"sid":"lv_VI97HAXpY6yYWAAAC","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":5000}
```
Details:
```
96 => number of characters (not bytes)
: => separator
0 => "open" packet type
{"sid":... => the handshake data
```
Note: the `t` query param is used to ensure that the request is not cached by the browser.
- Request n°2 (message in):
`socket.send('hey')` is executed on the server:
```
GET /engine.io/?EIO=3&transport=polling&t=N8hyd7H&sid=lv_VI97HAXpY6yYWAAAC
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=UTF-8
4:4hey
```
Details:
```
4 => number of characters
: => separator
4 => "message" packet type
hey => the actual message
```
- Request n°3 (message out)
`socket.send('hello'); socket.send('world');` is executed on the client:
```
POST /engine.io/?EIO=3&transport=polling&t=N8hzxke&sid=lv_VI97HAXpY6yYWAAAC
> Content-Type: text/plain; charset=UTF-8
6:4hello6:4world
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=UTF-8
ok
```
Details:
```
6 => number of characters of the 1st packet
: => separator
4 => "message" packet type
hello => the 1st message
6 => number of characters of the 2nd packet
: => separator
4 => "message" message type
world => the 2nd message
```
- Request n°4 (WebSocket upgrade)
```
GET /engine.io/?EIO=3&transport=websocket&sid=lv_VI97HAXpY6yYWAAAC
< HTTP/1.1 101 Switching Protocols
```
WebSocket frames:
```
< 2probe => probe request
> 3probe => probe response
> 5 => "upgrade" packet type
> 4hello => message (not concatenated)
> 4world
> 2 => "ping" packet type
< 3 => "pong" packet type
> 1 => "close" packet type
```
### Sample session with WebSocket only
In that case, the client only enables WebSocket (without HTTP polling).
```
GET /engine.io/?EIO=3&transport=websocket
< HTTP/1.1 101 Switching Protocols
```
WebSocket frames:
```
< 0{"sid":"lv_VI97HAXpY6yYWAAAC","pingInterval":25000,"pingTimeout":5000} => handshake
< 4hey
> 4hello => message (not concatenated)
> 4world
< 2 => "ping" packet type
> 3 => "pong" packet type
> 1 => "close" packet type
```
## URLs
An Engine.IO url is composed as follows:
```
/engine.io/[?<query string>]
```
- The `engine.io` pathname should only be changed by higher-level
frameworks whose protocol sits on top of engine's.
- The query string is optional and has six reserved keys:
- `transport`: indicates the transport name. Supported ones by default are
`polling`, `websocket`.
- `j`: if the transport is `polling` but a JSONP response is required, `j`
must be set with the JSONP response index.
- `sid`: if the client has been given a session id, it must be included
in the querystring.
- `b64`: if the client doesn't support XHR2, `b64=1` is sent in the query string
to signal the server that all binary data should be sent base64 encoded.
- `EIO`: the version of the protocol
- `t`: a hashed-timestamp used for cache-busting
*FAQ:* Is the `/engine.io` portion modifiable?
Provided the server is customized to intercept requests under a different
path segment, yes.
*FAQ:* What determines whether an option is going to be part of the path
versus being encoded as part of the query string? In other words, why
is the `transport` not part of the URL?
It's convention that the path segments remain *only* that which allows to
disambiguate whether a request should be handled by a given Engine.IO
server instance or not. As it stands, it's only the Engine.IO prefix
(`/engine.io`) and the resource (`default` by default).
## Encoding
There's two distinct types of encodings
- packet
- payload
### Packet
An encoded packet can be UTF-8 string or binary data. The packet encoding format for a string is as follows
```
<packet type id>[<data>]
```
example:
```
2probe
```
For binary data the encoding is identical. When sending binary data, the packet
type id is sent in the first byte of the binary contents, followed by the
actual packet data. Example:
```
4|0|1|2|3|4|5
```
In the above example each byte is separated by a pipe character and shown as an
integer. So the above packet is of type message (see below), and contains
binary data that corresponds to an array of integers with values 0, 1, 2, 3, 4
and 5.
The packet type id is an integer. The following are the accepted packet
types.
#### 0 open
Sent from the server when a new transport is opened (recheck)
#### 1 close
Request the close of this transport but does not shutdown the connection itself.
#### 2 ping
Sent by the client. Server should answer with a pong packet containing the same data
example
1. client sends: ```2probe```
2. server sends: ```3probe```
#### 3 pong
Sent by the server to respond to ping packets.
#### 4 message
actual message, client and server should call their callbacks with the data.
##### example 1
1. server sends: ```4HelloWorld```
2. client receives and calls callback ```socket.on('message', function (data) { console.log(data); });```
##### example 2
1. client sends: ```4HelloWorld```
2. server receives and calls callback ```socket.on('message', function (data) { console.log(data); });```
#### 5 upgrade
Before engine.io switches a transport, it tests, if server and client can communicate over this transport.
If this test succeed, the client sends an upgrade packets which requests the server to flush its cache on
the old transport and switch to the new transport.
#### 6 noop
A noop packet. Used primarily to force a poll cycle when an incoming websocket connection is received.
##### example
1. client connects through new transport
2. client sends ```2probe```
3. server receives and sends ```3probe```
4. client receives and sends ```5```
5. server flushes and closes old transport and switches to new.
### Payload
A payload is a series of encoded packets tied together. The payload encoding format is as follows when only strings are sent and XHR2 is not supported:
```
<length1>:<packet1>[<length2>:<packet2>[...]]
```
* length: length of the packet in __characters__
* packet: actual packets as descriped above
When XHR2 is not supported, the same encoding principle is used also when
binary data is sent, but it is sent as base64 encoded strings. For the purposes of decoding, an identifier `b` is
put before a packet encoding that contains binary data. A combination of any
number of strings and base64 encoded strings can be sent. Here is an example of
base 64 encoded messages:
```
<length of base64 representation of the data + 1 (for packet type)>:b<packet1 type><packet1 data in b64>[...]
```
When XHR2 is supported, a similar principle is used, but everything is encoded
directly into binary, so that it can be sent as binary over XHR. The format is
the following:
```
<0 for string data, 1 for binary data><Any number of numbers between 0 and 9><The number 255><packet1 (first type,
then data)>[...]
```
If a combination of UTF-8 strings and binary data is sent, the string values
are represented so that each character is written as a character code into a
byte.
The payload is used for transports which do not support framing, as the polling protocol for example.
- Example without binary:
```
[
{
"type": "message",
"data": "hello"
},
{
"type": "message",
"data": "€"
}
]
```
is encoded to:
```
6:4hello2:4€
```
Please note that we are not counting bytes, but characters, hence 2 (1 + 1) instead of 4 (1 + 3).
- Example with binary (both the client and the transport support binary):
```
[
{
"type": "message",
"data": "€"
},
{
"type": "message",
"data": buffer <01 02 03 04>
}
]
```
is encoded to:
```
buffer <00 04 ff 34 e2 82 ac 01 04 ff 01 02 03 04>
with:
00 => string header
04 => string length in bytes
ff => separator
34 => "message" packet type ("4")
e2 82 ac => "€"
01 => binary header
04 => buffer length in bytes
ff => separator
01 02 03 04 => buffer content
```
- Example with binary (either the client or the transport does not support binary):
```
[
{
"type": "message",
"data": "€"
},
{
"type": "message",
"data": buffer <01 02 03 04>
}
]
```
is encoded to:
```
2:4€10:b4AQIDBA==
with
2 => number of characters of the 1st packet
: => separator
4 => "message" packet type
10 => number of characters of the 2nd packet
: => separator
b => indicates a base64 packet
4 => "message" packet type
AQIDBA== => buffer content encoded in base64
```
## Transports
An engine.io server must support three transports:
- websocket
- polling
- jsonp
- xhr
### Polling
The polling transport consists of recurring GET requests by the client
to the server to get data, and POST requests with payloads from the
client to the server to send data.
#### XHR
The server must support CORS responses.
#### JSONP
The server implementation must respond with valid JavaScript. The URL
contains a query string parameter `j` that must be used in the response.
`j` is an integer.
The format of a JSONP packet.
```
`___eio[` <j> `]("` <encoded payload> `");`
```
To ensure that the payload gets processed correctly, it must be escaped
in such a way that the response is still valid JavaScript. Passing the
encoded payload through a JSON encoder is a good way to escape it.
Example JSONP frame returned by the server:
```
___eio[4]("packet data");
```
##### Posting data
The client posts data through a hidden iframe. The data gets to the server
in the URI encoded format as follows:
```
d=<escaped packet payload>
```
In addition to the regular qs escaping, in order to prevent
inconsistencies with `\n` handling by browsers, `\n` gets escaped as `\\n`
prior to being POSTd.
### WebSocket
Encoding payloads _should not_ be used for WebSocket, as the protocol
already has a lightweight framing mechanism.
In order to send a payload of messages, encode packets individually
and `send()` them in succession.
## Transport upgrading
A connection always starts with polling (either XHR or JSONP). WebSocket
gets tested on the side by sending a probe. If the probe is responded
from the server, an upgrade packet is sent.
To ensure no messages are lost, the upgrade packet will only be sent
once all the buffers of the existing transport are flushed and the
transport is considered _paused_.
When the server receives the upgrade packet, it must assume this is the
new transport channel and send all existing buffers (if any) to it.
The probe sent by the client is a `ping` packet with `probe` sent as data.
The probe sent by the server is a `pong` packet with `probe` sent as data.
Moving forward, upgrades other than just `polling -> x` are being considered.
## Timeouts
The client must use the `pingTimeout` and the `pingInterval` sent as part
of the handshake (with the `open` packet) to determine whether the server
is unresponsive.
The client sends a `ping` packet. If no packet type is received within
`pingTimeout`, the client considers the socket disconnected. If a `pong`
packet is actually received, the client will wait `pingInterval` before
sending a `ping` packet again.
Since the two values are shared between the server and the client, the server
will also be able to detect whether the client becomes unresponsive when it
does not receive any data within `pingTimeout + pingInterval`.
## Difference between v2 and v3
- add support for binary data
v2 is included in Socket.IO v0.9, while v3 is included in Socket.IO v1/v2.
## Test suite
The test suite in the `test-suite/` directory lets you check the compliance of a server implementation.
Usage:
- in Node.js: `npm ci && npm test`
- in a browser: simply open the `index.html` file in your browser
For reference, here is expected configuration for the JavaScript server to pass all tests:
```js
import { listen } from "engine.io";
const server = listen(3000, {
pingInterval: 300,
pingTimeout: 200,
allowEIO3: true,
maxPayload: 1e6,
cors: {
origin: "*"
}
});
server.on("connection", socket => {
socket.on("data", (...args) => {
socket.send(...args);
});
});
```

View File

@@ -1,422 +0,0 @@
# Engine.IO Protocol
This document describes the 4th version of the Engine.IO protocol.
**Table of content**
- [Introduction](#introduction)
- [Transports](#transports)
- [HTTP long-polling](#http-long-polling)
- [Request path](#request-path)
- [Query parameters](#query-parameters)
- [Headers](#headers)
- [Sending and receiving data](#sending-and-receiving-data)
- [Sending data](#sending-data)
- [Receiving data](#receiving-data)
- [WebSocket](#websocket)
- [Protocol](#protocol)
- [Handshake](#handshake)
- [Heartbeat](#heartbeat)
- [Upgrade](#upgrade)
- [Message](#message)
- [Packet encoding](#packet-encoding)
- [HTTP long-polling](#http-long-polling-1)
- [WebSocket](#websocket-1)
- [History](#history)
- [From v2 to v3](#from-v2-to-v3)
- [From v3 to v4](#from-v3-to-v4)
- [Test suite](#test-suite)
## Introduction
The Engine.IO protocol enables [full-duplex](https://en.wikipedia.org/wiki/Duplex_(telecommunications)#FULL-DUPLEX) and low-overhead communication between a client and a server.
It is based on the [WebSocket protocol](https://en.wikipedia.org/wiki/WebSocket) and uses [HTTP long-polling](https://en.wikipedia.org/wiki/Push_technology#Long_polling) as fallback if the WebSocket connection can't be established.
The reference implementation is written in [TypeScript](https://www.typescriptlang.org/):
- server: https://github.com/socketio/engine.io
- client: https://github.com/socketio/engine.io-client
The [Socket.IO protocol](https://github.com/socketio/socket.io-protocol) is built on top of these foundations, bringing additional features over the communication channel provided by the Engine.IO protocol.
## Transports
The connection between an Engine.IO client and an Engine.IO server can be established with:
- [HTTP long-polling](#http-long-polling)
- [WebSocket](#websocket)
### HTTP long-polling
The HTTP long-polling transport (also simply referred as "polling") consists of successive HTTP requests:
- long-running `GET` requests, for receiving data from the server
- short-running `POST` requests, for sending data to the server
#### Request path
The path of the HTTP requests is `/engine.io/` by default.
It might be updated by libraries built on top of the protocol (for example, the Socket.IO protocol uses `/socket.io/`).
#### Query parameters
The following query parameters are used:
| Name | Value | Description |
|-------------|-----------|--------------------------------------------------------------------|
| `EIO` | `4` | Mandatory, the version of the protocol. |
| `transport` | `polling` | Mandatory, the name of the transport. |
| `sid` | `<sid>` | Mandatory once the session is established, the session identifier. |
If a mandatory query parameter is missing, then the server MUST respond with an HTTP 400 error status.
#### Headers
When sending binary data, the sender (client or server) MUST include a `Content-Type: application/octet-stream` header.
Without an explicit `Content-Type` header, the receiver SHOULD infer that the data is plaintext.
Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type
#### Sending and receiving data
##### Sending data
To send some packets, a client MUST create an HTTP `POST` request with the packets encoded in the request body:
```
CLIENT SERVER
│ │
│ POST /engine.io/?EIO=4&transport=polling&sid=... │
│ ───────────────────────────────────────────────────► │
│ ◄──────────────────────────────────────────────────┘ │
│ HTTP 200 │
│ │
```
The server MUST return an HTTP 400 response if the session ID (from the `sid` query parameter) is not known.
To indicate success, the server MUST return an HTTP 200 response, with the string `ok` in the response body.
To ensure packet ordering, a client MUST NOT have more than one active `POST` request. Should it happen, the server MUST return an HTTP 400 error status and close the session.
##### Receiving data
To receive some packets, a client MUST create an HTTP `GET` request:
```
CLIENT SERVER
│ GET /engine.io/?EIO=4&transport=polling&sid=... │
│ ──────────────────────────────────────────────────► │
│ . │
│ . │
│ . │
│ . │
│ ◄─────────────────────────────────────────────────┘ │
│ HTTP 200 │
```
The server MUST return an HTTP 400 response if the session ID (from the `sid` query parameter) is not known.
The server MAY not respond right away if there are no packets buffered for the given session. Once there are some packets to be sent, the server SHOULD encode them (see [Packet encoding](#packet-encoding)) and send them in the response body of the HTTP request.
To ensure packet ordering, a client MUST NOT have more than one active `GET` request. Should it happen, the server MUST return an HTTP 400 error status and close the session.
### WebSocket
The WebSocket transport consists of a [WebSocket connection](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API), which provides a bidirectional and low-latency communication channel between the server and the client.
The following query parameters are used:
| Name | Value | Description |
|-------------|-------------|-------------------------------------------------------------------------------|
| `EIO` | `4` | Mandatory, the version of the protocol. |
| `transport` | `websocket` | Mandatory, the name of the transport. |
| `sid` | `<sid>` | Optional, depending on whether it's an upgrade from HTTP long-polling or not. |
If a mandatory query parameter is missing, then the server MUST close the WebSocket connection.
Each packet (read or write) is sent its own [WebSocket frame](https://datatracker.ietf.org/doc/html/rfc6455#section-5).
A client MUST NOT open more than one WebSocket connection per session. Should it happen, the server MUST close the WebSocket connection.
## Protocol
An Engine.IO packet consists of:
- a packet type
- an optional packet payload
Here is the list of available packet types:
| Type | ID | Usage |
|---------|-----|--------------------------------------------------|
| open | 0 | Used during the [handshake](#handshake). |
| close | 1 | Used to indicate that a transport can be closed. |
| ping | 2 | Used in the [heartbeat mechanism](#heartbeat). |
| pong | 3 | Used in the [heartbeat mechanism](#heartbeat). |
| message | 4 | Used to send a payload to the other side. |
| upgrade | 5 | Used during the [upgrade process](#upgrade). |
| noop | 6 | Used during the [upgrade process](#upgrade). |
### Handshake
To establish a connection, the client MUST send an HTTP `GET` request to the server:
- HTTP long-polling first (by default)
```
CLIENT SERVER
│ │
│ GET /engine.io/?EIO=4&transport=polling │
│ ───────────────────────────────────────────────────────► │
│ ◄──────────────────────────────────────────────────────┘ │
│ HTTP 200 │
│ │
```
- WebSocket-only session
```
CLIENT SERVER
│ │
│ GET /engine.io/?EIO=4&transport=websocket │
│ ───────────────────────────────────────────────────────► │
│ ◄──────────────────────────────────────────────────────┘ │
│ HTTP 101 │
│ │
```
If the server accepts the connection, then it MUST respond with an `open` packet with the following JSON-encoded payload:
| Key | Type | Description |
|----------------|------------|-------------------------------------------------------------------------------------------------------------------|
| `sid` | `string` | The session ID. |
| `upgrades` | `string[]` | The list of available [transport upgrades](#upgrade). |
| `pingInterval` | `number` | The ping interval, used in the [heartbeat mechanism](#heartbeat) (in milliseconds). |
| `pingTimeout` | `number` | The ping timeout, used in the [heartbeat mechanism](#heartbeat) (in milliseconds). |
| `maxPayload` | `number` | The maximum number of bytes per chunk, used by the client to aggregate packets into [payloads](#packet-encoding). |
Example:
```json
{
"sid": "lv_VI97HAXpY6yYWAAAC",
"upgrades": ["websocket"],
"pingInterval": 25000,
"pingTimeout": 20000,
"maxPayload": 1000000
}
```
The client MUST send the `sid` value in the query parameters of all subsequent requests.
### Heartbeat
Once the [handshake](#handshake) is completed, a heartbeat mechanism is started to check the liveness of the connection:
```
CLIENT SERVER
│ *** Handshake *** │
│ │
│ ◄───────────────────────────────────────────────── │
│ 2 │ (ping packet)
│ ─────────────────────────────────────────────────► │
│ 3 │ (pong packet)
```
At a given interval (the `pingInterval` value sent in the handshake) the server sends a `ping` packet and the client has a few seconds (the `pingTimeout` value) to send a `pong` packet back.
If the server does not receive a `pong` packet back, then it SHOULD consider that the connection is closed.
Conversely, if the client does not receive a `ping` packet within `pingInterval + pingTimeout`, then it SHOULD consider that the connection is closed.
### Upgrade
By default, the client SHOULD create an HTTP long-polling connection, and then upgrade to better transports if available.
To upgrade to WebSocket, the client MUST:
- pause the HTTP long-polling transport (no more HTTP request gets sent), to ensure that no packet gets lost
- open a WebSocket connection with the same session ID
- send a `ping` packet with the string `probe` in the payload
The server MUST:
- send a `noop` packet to any pending `GET` request (if applicable) to cleanly close HTTP long-polling transport
- respond with a `pong` packet with the string `probe` in the payload
Finally, the client MUST send a `upgrade` packet to complete the upgrade:
```
CLIENT SERVER
│ │
│ GET /engine.io/?EIO=4&transport=websocket&sid=... │
│ ───────────────────────────────────────────────────► │
│ ◄─────────────────────────────────────────────────┘ │
│ HTTP 101 (WebSocket handshake) │
│ │
│ ----- WebSocket frames ----- │
│ ─────────────────────────────────────────────────► │
│ 2probe │ (ping packet)
│ ◄───────────────────────────────────────────────── │
│ 3probe │ (pong packet)
│ ─────────────────────────────────────────────────► │
│ 5 │ (upgrade packet)
│ │
```
### Message
Once the [handshake](#handshake) is completed, the client and the server can exchange data by including it in a `message` packet.
## Packet encoding
The serialization of an Engine.IO packet depends on the type of the payload (plaintext or binary) and on the transport.
The character encoding is UTF-8 for plain text and for base64-encoded binary payloads.
### HTTP long-polling
Due to the nature of the HTTP long-polling transport, multiple packets might be concatenated in a single payload in order to increase throughput.
Format:
```
<packet type>[<data>]<separator><packet type>[<data>]<separator><packet type>[<data>][...]
```
Example:
```
4hello\x1e2\x1e4world
with:
4 => message packet type
hello => message payload
\x1e => separator
2 => ping packet type
\x1e => separator
4 => message packet type
world => message payload
```
The packets are separated by the [record separator character](https://en.wikipedia.org/wiki/C0_and_C1_control_codes#Field_separators): `\x1e`
Binary payloads MUST be base64-encoded and prefixed with a `b` character:
Example:
```
4hello\x1ebAQIDBA==
with:
4 => message packet type
hello => message payload
\x1e => separator
b => binary prefix
AQIDBA== => buffer <01 02 03 04> encoded as base64
```
The client SHOULD use the `maxPayload` value sent during the [handshake](#handshake) to decide how many packets should be concatenated.
### WebSocket
Each Engine.IO packet is sent in its own [WebSocket frame](https://datatracker.ietf.org/doc/html/rfc6455#section-5).
Format:
```
<packet type>[<data>]
```
Example:
```
4hello
with:
4 => message packet type
hello => message payload (UTF-8 encoded)
```
Binary payloads are sent as is, without modification.
## History
### From v2 to v3
- add support for binary data
The [2nd version](https://github.com/socketio/engine.io-protocol/tree/v2) of the protocol is used in Socket.IO `v0.9` and below.
The [3rd version](https://github.com/socketio/engine.io-protocol/tree/v3) of the protocol is used in Socket.IO `v1` and `v2`.
### From v3 to v4
- reverse ping/pong mechanism
The ping packets are now sent by the server, because the timers set in the browsers are not reliable enough. We
suspect that a lot of timeout problems came from timers being delayed on the client-side.
- always use base64 when encoding a payload with binary data
This change allows to treat all payloads (with or without binary) the same way, without having to take in account
whether the client or the current transport supports binary data or not.
Please note that this only applies to HTTP long-polling. Binary data is sent in WebSocket frames with no additional transformation.
- use a record separator (`\x1e`) instead of counting of characters
Counting characters prevented (or at least makes harder) to implement the protocol in other languages, which may not use
the UTF-16 encoding.
For example, `€` was encoded to `2:4€`, though `Buffer.byteLength('€') === 3`.
Note: this assumes the record separator is not used in the data.
The 4th version (current) is included in Socket.IO `v3` and above.
## Test suite
The test suite in the `test-suite/` directory lets you check the compliance of a server implementation.
Usage:
- in Node.js: `npm ci && npm test`
- in a browser: simply open the `index.html` file in your browser
For reference, here is expected configuration for the JavaScript server to pass all tests:
```js
import { listen } from "engine.io";
const server = listen(3000, {
pingInterval: 300,
pingTimeout: 200,
maxPayload: 1e6,
cors: {
origin: "*"
}
});
server.on("connection", socket => {
socket.on("data", (...args) => {
socket.send(...args);
});
});
```

View File

@@ -1 +0,0 @@
node_modules

View File

@@ -1,30 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Test suite for the Engine.IO protocol</title>
<link rel="stylesheet" href="https://unpkg.com/mocha@9/mocha.css" />
</head>
<body>
<div id="mocha"></div>
<script src="https://unpkg.com/mocha@9/mocha.js"></script>
<script src="https://unpkg.com/chai@4/chai.js" ></script>
<script src="https://unpkg.com/chai-string@1/chai-string.js" ></script>
<script class="mocha-init">
mocha.setup("bdd");
mocha.checkLeaks();
</script>
<script type="module" src="test-suite.js"></script>
<script class="mocha-exec">
mocha.run();
</script>
</body>
</html>

View File

@@ -1,10 +0,0 @@
import fetch from "node-fetch";
import { WebSocket } from "ws";
import chai from "chai";
import chaiString from "chai-string";
chai.use(chaiString);
globalThis.fetch = fetch;
globalThis.WebSocket = WebSocket;
globalThis.chai = chai;

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +0,0 @@
{
"name": "engine.io-protocol-test-suite",
"version": "0.0.1",
"private": true,
"type": "module",
"scripts": {
"format": "prettier -w *.js",
"test": "mocha test-suite.js"
},
"devDependencies": {
"chai": "^4.3.6",
"chai-string": "^1.5.0",
"mocha": "^9.2.1",
"node-fetch": "^3.2.0",
"prettier": "^2.5.1",
"ws": "^8.5.0"
}
}

View File

@@ -1,609 +0,0 @@
const isNodejs = typeof window === "undefined";
if (isNodejs) {
// make the tests runnable in both the browser and Node.js
await import("./node-imports.js");
}
const { expect } = chai;
const URL = "http://localhost:3000";
const WS_URL = URL.replace("http", "ws");
const PING_INTERVAL = 300;
const PING_TIMEOUT = 200;
function sleep(delay) {
return new Promise((resolve) => setTimeout(resolve, delay));
}
function createWebSocket(url) {
const socket = new WebSocket(url);
socket._eventBuffer = {};
socket._pendingPromises = {};
for (const eventType of ["open", "close", "message"]) {
socket._eventBuffer[eventType] = [];
socket._pendingPromises[eventType] = [];
socket.addEventListener(eventType, (event) => {
if (socket._pendingPromises[eventType].length) {
socket._pendingPromises[eventType].shift()(event);
} else {
socket._eventBuffer[eventType].push(event);
}
});
}
return socket;
}
function waitFor(socket, eventType) {
if (socket._eventBuffer[eventType].length) {
return Promise.resolve(socket._eventBuffer[eventType].shift());
} else {
return new Promise((resolve) => {
socket._pendingPromises[eventType].push(resolve);
});
}
}
async function initLongPollingSession() {
const response = await fetch(`${URL}/engine.io/?EIO=4&transport=polling`);
const content = await response.text();
return JSON.parse(content.substring(1)).sid;
}
describe("Engine.IO protocol", () => {
describe("handshake", () => {
describe("HTTP long-polling", () => {
it("successfully opens a session", async () => {
const response = await fetch(
`${URL}/engine.io/?EIO=4&transport=polling`
);
expect(response.status).to.eql(200);
const content = await response.text();
expect(content).to.startsWith("0");
const value = JSON.parse(content.substring(1));
expect(value).to.have.all.keys(
"sid",
"upgrades",
"pingInterval",
"pingTimeout",
"maxPayload"
);
expect(value.sid).to.be.a("string");
expect(value.upgrades).to.eql(["websocket"]);
expect(value.pingInterval).to.eql(PING_INTERVAL);
expect(value.pingTimeout).to.eql(PING_TIMEOUT);
expect(value.maxPayload).to.eql(1000000);
});
it("fails with an invalid 'EIO' query parameter", async () => {
const response = await fetch(`${URL}/engine.io/?transport=polling`);
expect(response.status).to.eql(400);
const response2 = await fetch(
`${URL}/engine.io/?EIO=abc&transport=polling`
);
expect(response2.status).to.eql(400);
});
it("fails with an invalid 'transport' query parameter", async () => {
const response = await fetch(`${URL}/engine.io/?EIO=4`);
expect(response.status).to.eql(400);
const response2 = await fetch(`${URL}/engine.io/?EIO=4&transport=abc`);
expect(response2.status).to.eql(400);
});
it("fails with an invalid request method", async () => {
const response = await fetch(
`${URL}/engine.io/?EIO=4&transport=polling`,
{
method: "post",
}
);
expect(response.status).to.eql(400);
const response2 = await fetch(
`${URL}/engine.io/?EIO=4&transport=polling`,
{
method: "put",
}
);
expect(response2.status).to.eql(400);
});
});
describe("WebSocket", () => {
it("successfully opens a session", async () => {
const socket = createWebSocket(
`${WS_URL}/engine.io/?EIO=4&transport=websocket`
);
const { data } = await waitFor(socket, "message");
expect(data).to.startsWith("0");
const value = JSON.parse(data.substring(1));
expect(value).to.have.all.keys(
"sid",
"upgrades",
"pingInterval",
"pingTimeout",
"maxPayload"
);
expect(value.sid).to.be.a("string");
expect(value.upgrades).to.eql([]);
expect(value.pingInterval).to.eql(PING_INTERVAL);
expect(value.pingTimeout).to.eql(PING_TIMEOUT);
expect(value.maxPayload).to.eql(1000000);
socket.close();
});
it("fails with an invalid 'EIO' query parameter", async () => {
const socket = createWebSocket(
`${WS_URL}/engine.io/?transport=websocket`
);
if (isNodejs) {
socket.on("error", () => {});
}
await waitFor(socket, "close");
const socket2 = createWebSocket(
`${WS_URL}/engine.io/?EIO=abc&transport=websocket`
);
if (isNodejs) {
socket2.on("error", () => {});
}
await waitFor(socket2, "close");
});
it("fails with an invalid 'transport' query parameter", async () => {
const socket = createWebSocket(`${WS_URL}/engine.io/?EIO=4`);
if (isNodejs) {
socket.on("error", () => {});
}
await waitFor(socket, "close");
const socket2 = createWebSocket(
`${WS_URL}/engine.io/?EIO=4&transport=abc`
);
if (isNodejs) {
socket2.on("error", () => {});
}
await waitFor(socket2, "close");
});
});
});
describe("message", () => {
describe("HTTP long-polling", () => {
it("sends and receives a payload containing one plain text packet", async () => {
const sid = await initLongPollingSession();
const pushResponse = await fetch(
`${URL}/engine.io/?EIO=4&transport=polling&sid=${sid}`,
{
method: "post",
body: "4hello",
}
);
expect(pushResponse.status).to.eql(200);
const postContent = await pushResponse.text();
expect(postContent).to.eql("ok");
const pollResponse = await fetch(
`${URL}/engine.io/?EIO=4&transport=polling&sid=${sid}`
);
expect(pollResponse.status).to.eql(200);
const pollContent = await pollResponse.text();
expect(pollContent).to.eql("4hello");
});
it("sends and receives a payload containing several plain text packets", async () => {
const sid = await initLongPollingSession();
const pushResponse = await fetch(
`${URL}/engine.io/?EIO=4&transport=polling&sid=${sid}`,
{
method: "post",
body: "4test1\x1e4test2\x1e4test3",
}
);
expect(pushResponse.status).to.eql(200);
const postContent = await pushResponse.text();
expect(postContent).to.eql("ok");
const pollResponse = await fetch(
`${URL}/engine.io/?EIO=4&transport=polling&sid=${sid}`
);
expect(pollResponse.status).to.eql(200);
const pollContent = await pollResponse.text();
expect(pollContent).to.eql("4test1\x1e4test2\x1e4test3");
});
it("sends and receives a payload containing plain text and binary packets", async () => {
const sid = await initLongPollingSession();
const pushResponse = await fetch(
`${URL}/engine.io/?EIO=4&transport=polling&sid=${sid}`,
{
method: "post",
body: "4hello\x1ebAQIDBA==",
}
);
expect(pushResponse.status).to.eql(200);
const postContent = await pushResponse.text();
expect(postContent).to.eql("ok");
const pollResponse = await fetch(
`${URL}/engine.io/?EIO=4&transport=polling&sid=${sid}`
);
expect(pollResponse.status).to.eql(200);
const pollContent = await pollResponse.text();
expect(pollContent).to.eql("4hello\x1ebAQIDBA==");
});
it("closes the session upon invalid packet format", async () => {
const sid = await initLongPollingSession();
try {
const pushResponse = await fetch(
`${URL}/engine.io/?EIO=4&transport=polling&sid=${sid}`,
{
method: "post",
body: "abc",
}
);
expect(pushResponse.status).to.eql(400);
} catch (e) {
// node-fetch throws when the request is closed abnormally
}
const pollResponse = await fetch(
`${URL}/engine.io/?EIO=4&transport=polling&sid=${sid}`
);
expect(pollResponse.status).to.eql(400);
});
it("closes the session upon duplicate poll requests", async () => {
const sid = await initLongPollingSession();
const pollResponses = await Promise.all([
fetch(`${URL}/engine.io/?EIO=4&transport=polling&sid=${sid}`),
sleep(5).then(() => fetch(`${URL}/engine.io/?EIO=4&transport=polling&sid=${sid}&t=burst`)),
]);
expect(pollResponses[0].status).to.eql(200);
const content = await pollResponses[0].text();
expect(content).to.eql("1");
// the Node.js implementation uses HTTP 500 (Internal Server Error), but HTTP 400 seems more suitable
expect(pollResponses[1].status).to.be.oneOf([400, 500]);
const pollResponse = await fetch(
`${URL}/engine.io/?EIO=4&transport=polling&sid=${sid}`
);
expect(pollResponse.status).to.eql(400);
});
it("closes the session upon cancelled polling request", async () => {
const sid = await initLongPollingSession();
const controller = new AbortController();
fetch(`${URL}/engine.io/?EIO=4&transport=polling&sid=${sid}`, {
signal: controller.signal,
}).catch(() => {});
await sleep(5);
controller.abort();
const pollResponse = await fetch(
`${URL}/engine.io/?EIO=4&transport=polling&sid=${sid}`,
);
expect(pollResponse.status).to.eql(400);
});
});
describe("WebSocket", () => {
it("sends and receives a plain text packet", async () => {
const socket = createWebSocket(
`${WS_URL}/engine.io/?EIO=4&transport=websocket`
);
await waitFor(socket, "open");
await waitFor(socket, "message"); // handshake
socket.send("4hello");
const { data } = await waitFor(socket, "message");
expect(data).to.eql("4hello");
socket.close();
});
it("sends and receives a binary packet", async () => {
const socket = createWebSocket(
`${WS_URL}/engine.io/?EIO=4&transport=websocket`
);
socket.binaryType = "arraybuffer";
await waitFor(socket, "message"); // handshake
socket.send(Uint8Array.from([1, 2, 3, 4]));
const { data } = await waitFor(socket, "message");
expect(data).to.eql(Uint8Array.from([1, 2, 3, 4]).buffer);
socket.close();
});
it("closes the session upon invalid packet format", async () => {
const socket = createWebSocket(
`${WS_URL}/engine.io/?EIO=4&transport=websocket`
);
await waitFor(socket, "message"); // handshake
socket.send("abc");
await waitFor(socket, "close");
socket.close();
});
});
});
describe("heartbeat", function () {
this.timeout(5000);
describe("HTTP long-polling", () => {
it("sends ping/pong packets", async () => {
const sid = await initLongPollingSession();
for (let i = 0; i < 3; i++) {
const pollResponse = await fetch(
`${URL}/engine.io/?EIO=4&transport=polling&sid=${sid}`
);
expect(pollResponse.status).to.eql(200);
const pollContent = await pollResponse.text();
expect(pollContent).to.eql("2");
const pushResponse = await fetch(
`${URL}/engine.io/?EIO=4&transport=polling&sid=${sid}`,
{
method: "post",
body: "3",
}
);
expect(pushResponse.status).to.eql(200);
}
});
it("closes the session upon ping timeout", async () => {
const sid = await initLongPollingSession();
await sleep(PING_INTERVAL + PING_TIMEOUT);
const pollResponse = await fetch(
`${URL}/engine.io/?EIO=4&transport=polling&sid=${sid}`
);
expect(pollResponse.status).to.eql(400);
});
});
describe("WebSocket", () => {
it("sends ping/pong packets", async () => {
const socket = createWebSocket(
`${WS_URL}/engine.io/?EIO=4&transport=websocket`
);
await waitFor(socket, "message"); // handshake
for (let i = 0; i < 3; i++) {
const { data } = await waitFor(socket, "message");
expect(data).to.eql("2");
socket.send("3");
}
socket.close();
});
it("closes the session upon ping timeout", async () => {
const socket = createWebSocket(
`${WS_URL}/engine.io/?EIO=4&transport=websocket`
);
await waitFor(socket, "close"); // handshake
});
});
});
describe("close", () => {
describe("HTTP long-polling", () => {
it("forcefully closes the session", async () => {
const sid = await initLongPollingSession();
const [pollResponse] = await Promise.all([
fetch(`${URL}/engine.io/?EIO=4&transport=polling&sid=${sid}`),
fetch(`${URL}/engine.io/?EIO=4&transport=polling&sid=${sid}`, {
method: "post",
body: "1",
}),
]);
expect(pollResponse.status).to.eql(200);
const pullContent = await pollResponse.text();
expect(pullContent).to.eql("6");
const pollResponse2 = await fetch(
`${URL}/engine.io/?EIO=4&transport=polling&sid=${sid}`
);
expect(pollResponse2.status).to.eql(400);
});
});
describe("WebSocket", () => {
it("forcefully closes the session", async () => {
const socket = createWebSocket(
`${WS_URL}/engine.io/?EIO=4&transport=websocket`
);
await waitFor(socket, "message"); // handshake
socket.send("1");
await waitFor(socket, "close");
});
});
});
describe("upgrade", () => {
it("successfully upgrades from HTTP long-polling to WebSocket", async () => {
const sid = await initLongPollingSession();
const socket = createWebSocket(
`${WS_URL}/engine.io/?EIO=4&transport=websocket&sid=${sid}`
);
await waitFor(socket, "open");
// send probe
socket.send("2probe");
const probeResponse = await waitFor(socket, "message");
expect(probeResponse.data).to.eql("3probe");
const pollResponse = await fetch(
`${URL}/engine.io/?EIO=4&transport=polling&sid=${sid}`
);
expect(pollResponse.status).to.eql(200);
const pollContent = await pollResponse.text();
expect(pollContent).to.eql("6"); // "noop" packet to cleanly end the HTTP long-polling request
// complete upgrade
socket.send("5");
socket.send("4hello");
const { data } = await waitFor(socket, "message");
expect(data).to.eql("4hello");
});
it("ignores HTTP requests with same sid after upgrade", async () => {
const sid = await initLongPollingSession();
const socket = createWebSocket(
`${WS_URL}/engine.io/?EIO=4&transport=websocket&sid=${sid}`
);
await waitFor(socket, "open");
socket.send("2probe");
await waitFor(socket, "message"); // "3probe"
socket.send("5");
const pollResponse = await fetch(
`${URL}/engine.io/?EIO=4&transport=polling&sid=${sid}`
);
expect(pollResponse.status).to.eql(400);
socket.send("4hello");
const { data } = await waitFor(socket, "message");
expect(data).to.eql("4hello");
});
it("ignores WebSocket connection with same sid after upgrade", async () => {
const sid = await initLongPollingSession();
const socket = createWebSocket(
`${WS_URL}/engine.io/?EIO=4&transport=websocket&sid=${sid}`
);
await waitFor(socket, "open");
socket.send("2probe");
await waitFor(socket, "message"); // "3probe"
socket.send("5");
const socket2 = createWebSocket(
`${WS_URL}/engine.io/?EIO=4&transport=websocket&sid=${sid}`
);
await waitFor(socket2, "close");
socket.send("4hello");
const { data } = await waitFor(socket, "message");
expect(data).to.eql("4hello");
});
});
});

View File

@@ -1,411 +0,0 @@
# socket.io-protocol
This document describes the Socket.IO protocol. For a reference JavaScript
implementation, take a look at
[socket.io-parser](https://github.com/socketio/socket.io-parser),
[socket.io-client](https://github.com/socketio/socket.io-client)
and [socket.io](https://github.com/socketio/socket.io).
## Table of Contents
- [Protocol version](#protocol-version)
- [Packet format](#packet-format)
- [Packet types](#packet-types)
- [CONNECT](#0---connect)
- [DISCONNECT](#1---disconnect)
- [EVENT](#2---event)
- [ACK](#3---ack)
- [ERROR](#4---error)
- [BINARY_EVENT](#5---binary_event)
- [Packet encoding](#packet-encoding)
- [Encoding format](#encoding-format)
- [Examples](#examples)
- [Exchange protocol](#exchange-protocol)
- [Connection to the default namespace](#connection-to-the-default-namespace)
- [Connection to a non-default namespace](#connection-to-a-non-default-namespace)
- [Disconnection from a non-default namespace](#disconnection-from-a-non-default-namespace)
- [Acknowledgement](#acknowledgement)
- [History](#history)
- [Difference between v3 and v2](#difference-between-v3-and-v2)
- [Difference between v2 and v1](#difference-between-v2-and-v1)
- [Initial revision](#initial-revision)
## Protocol version
This is the revision **3** of the Socket.IO protocol, included in ̀`socket.io@1.0.0...1.0.2`.
The 4th revision (included in ̀`socket.io@1.0.3...latest`) can be found here: https://github.com/socketio/socket.io-protocol/tree/master
Both the 1st and the 2nd revisions were part of the work towards Socket.IO 1.0 but were never included in a Socket.IO
release.
It is built on top of the [3rd](https://github.com/socketio/engine.io-protocol/tree/v3) revision of the Engine.IO
protocol.
While the Engine.IO protocol describes the low-level plumbing with WebSocket and HTTP long-polling, the Socket.IO
protocol adds another layer above in order to provide the following features:
- multiplexing (what we call [Namespace](https://socket.io/docs/namespaces/))
Example of the Javascript API:
```js
// server-side
const nsp = io.of("/admin");
nsp.on("connect", socket => {});
// client-side
const socket1 = io(); // default namespace
const socket2 = io("/admin");
socket2.on("connect", () => {});
```
- acknowledgement of packets
Example of the Javascript API:
```js
// on one side
socket.emit("hello", 1, () => { console.log("received"); });
// on the other side
socket.on("hello", (a, cb) => { cb(); });
```
## Packet format
A packet contains the following fields:
- a type (integer, see [below](#packet-types))
- a namespace (string)
- optionally, a payload (string | Array)
- optionally, an acknowledgment id (integer)
## Packet types
### 0 - CONNECT
This event is sent:
- by the client when requesting access to a namespace
- by the server when accepting the connection to a namespace
It does not contain any payload nor acknowledgement id.
Example:
```json
{
"type": 0,
"nsp": "/admin"
}
```
The client may include additional information (i.e. for authentication purpose) in the namespace field. Example:
```json
{
"type": 0,
"nsp": "/admin?token=1234&uid=abcd"
}
```
#### 1 - DISCONNECT
This event is used when one side wants to disconnect from a namespace.
It does not contain any payload nor acknowledgement id.
Example:
```json
{
"type": 1,
"nsp": "/admin"
}
```
#### 2 - EVENT
This event is used when one side wants to transmit some data (without binary) to the other side.
It does contain a payload, and an optional acknowledgement id.
Example:
```json
{
"type": 2,
"nsp": "/",
"data": ["hello", 1]
}
```
With an acknowledgment id:
```json
{
"type": 2,
"nsp": "/admin",
"data": ["project:delete", 123],
"id": 456
}
```
#### 3 - ACK
This event is used when one side has received an EVENT or a BINARY_EVENT with an acknowledgement id.
It contains the acknowledgement id received in the previous packet, and may contain a payload (without binary).
```json
{
"type": 3,
"nsp": "/admin",
"data": [],
"id": 456
}
```
#### 4 - ERROR
This event is sent by the server when the connection to a namespace is refused.
It may contain a payload indicating the reason of the refusal.
Example:
```json
{
"type": 4,
"nsp": "/admin",
"data": "Not authorized"
}
```
#### 5 - BINARY_EVENT
This event is used when one side wants to transmit some data (including binary) to the other side.
It does contain a payload, and an optional acknowledgement id.
Example:
```
{
"type": 5,
"nsp": "/",
"data": ["hello", <Buffer 01 02 03>]
}
```
With an acknowledgment id:
```
{
"type": 5,
"nsp": "/admin",
"data": ["project:delete", <Buffer 01 02 03>],
"id": 456
}
```
## Packet encoding
This section details the encoding used by the default parser which is included in Socket.IO server and client, and
whose source can be found [here](https://github.com/socketio/socket.io-parser).
The JS server and client implementations also supports custom parsers, which have different tradeoffs and may benefit to
certain kind of applications. Please see [socket.io-json-parser](https://github.com/darrachequesne/socket.io-json-parser)
or [socket.io-msgpack-parser](https://github.com/darrachequesne/socket.io-msgpack-parser) for example.
Please also note that each Socket.IO packet is sent as a Engine.IO `message` packet (more information [here](https://github.com/socketio/engine.io-protocol)),
so the encoded result will be prefixed by `4` when sent over the wire (in the request/response body with HTTP
long-polling, or in the WebSocket frame).
### Encoding format
```
<packet type>[<# of binary attachments>-][<namespace>,][<acknowledgment id>][JSON-stringified payload without binary]
+ binary attachments extracted
```
Note:
- the namespace is only included if it is different from the default namespace (`/`)
### Examples
- `CONNECT` packet for the default namespace
```json
{
"type": 0,
"nsp": "/"
}
```
is encoded to `0`
- `CONNECT` packet for the `/admin` namespace
```json
{
"type": 0,
"nsp": "/admin"
}
```
is encoded to `0/admin`
- `DISCONNECT` packet for the `/admin` namespace
```json
{
"type": 1,
"nsp": "/admin"
}
```
is encoded to `1/admin`
- `EVENT` packet
```json
{
"type": 2,
"nsp": "/",
"data": ["hello", 1]
}
```
is encoded to `2["hello",1]`
- `EVENT` packet with an acknowledgement id
```json
{
"type": 2,
"nsp": "/admin",
"data": ["project:delete", 123],
"id": 456
}
```
is encoded to `2/admin,456["project:delete",123]`
- `ACK` packet
```json
{
"type": 3,
"nsp": "/admin",
"data": [],
"id": 456
}
```
is encoded to `3/admin,456[]`
- `ERROR` packet
```json
{
"type": 4,
"nsp": "/admin",
"data": "Not authorized"
}
```
is encoded to `4/admin,"Not authorized"`
- `BINARY_EVENT` packet
```
{
"type": 5,
"nsp": "/",
"data": ["hello", <Buffer 01 02 03>]
}
```
is encoded to `51-["hello",{"_placeholder":true,"num":0}]` + `<Buffer 01 02 03>`
- `BINARY_EVENT` packet with an acknowledgement id
```
{
"type": 5,
"nsp": "/admin",
"data": ["project:delete", <Buffer 01 02 03>],
"id": 456
}
```
is encoded to `51-/admin,456["project:delete",{"_placeholder":true,"num":0}]` + `<Buffer 01 02 03>`
## Exchange protocol
### Connection to the default namespace
The server always send a `CONNECT` packet for the default namespace (`/`) when the connection is established.
That is, even if the client requests access to a non-default namespace, it will receive a `CONNECT` packet for the
default namespace first.
```
Server > { type: CONNECT, nsp: "/" }
```
No response is expected from the client.
### Connection to a non-default namespace
```
Client > { type: CONNECT, nsp: "/admin" }
Server > { type: CONNECT, nsp: "/admin" } (if the connection is successful)
or
Server > { type: ERROR, nsp: "/admin", data: "Not authorized" }
```
### Disconnection from a non-default namespace
```
Client > { type: DISCONNECT, nsp: "/admin" }
```
And vice versa. No response is expected from the other-side.
### Acknowledgement
```
Client > { type: EVENT, nsp: "/admin", data: ["hello"], id: 456 }
Server > { type: ACK, nsp: "/admin", data: [], id: 456 }
```
And vice versa.
## History
### Difference between v3 and v2
- remove the usage of msgpack to encode packets containing binary objects (see also [299849b](https://github.com/socketio/socket.io-parser/commit/299849b00294c3bc95817572441f3aca8ffb1f65))
### Difference between v2 and v1
- add a BINARY_EVENT packet type
This was added during the work towards Socket.IO 1.0, in order to add support for binary objects. The BINARY_EVENT
packets were encoded with [msgpack](https://msgpack.org/).
### Initial revision
This first revision was the result of the split between the Engine.IO protocol (low-level plumbing with WebSocket / HTTP
long-polling, heartbeat) and the Socket.IO protocol. It was never included in a Socket.IO release, but paved the way for
the next iterations.
## License
MIT

View File

@@ -1,556 +0,0 @@
# socket.io-protocol
This document describes the Socket.IO protocol. For a reference JavaScript
implementation, take a look at
[socket.io-parser](https://github.com/socketio/socket.io-parser),
[socket.io-client](https://github.com/socketio/socket.io-client)
and [socket.io](https://github.com/socketio/socket.io).
## Table of Contents
- [Protocol version](#protocol-version)
- [Packet format](#packet-format)
- [Packet types](#packet-types)
- [CONNECT](#0---connect)
- [DISCONNECT](#1---disconnect)
- [EVENT](#2---event)
- [ACK](#3---ack)
- [ERROR](#4---error)
- [BINARY_EVENT](#5---binary_event)
- [BINARY_ACK](#6---binary_ack)
- [Packet encoding](#packet-encoding)
- [Encoding format](#encoding-format)
- [Examples](#examples)
- [Exchange protocol](#exchange-protocol)
- [Connection to the default namespace](#connection-to-the-default-namespace)
- [Connection to a non-default namespace](#connection-to-a-non-default-namespace)
- [Disconnection from a non-default namespace](#disconnection-from-a-non-default-namespace)
- [Acknowledgement](#acknowledgement)
- [Sample session](#sample-session)
- [History](#history)
- [Difference between v4 and v3](#difference-between-v4-and-v3)
- [Difference between v3 and v2](#difference-between-v3-and-v2)
- [Difference between v2 and v1](#difference-between-v2-and-v1)
- [Initial revision](#initial-revision)
## Protocol version
This is the revision **4** of the Socket.IO protocol, included in `socket.io@1.0.3...latest`.
The 3rd revision (included in `socket.io@1.0.0...1.0.2`) can be found here: https://github.com/socketio/socket.io-protocol/tree/v3
Both the 1st and the 2nd revisions were part of the work towards Socket.IO 1.0 but were never included in a Socket.IO
release.
It is built on top of the [3rd](https://github.com/socketio/engine.io-protocol/tree/v3) revision of the Engine.IO
protocol.
While the Engine.IO protocol describes the low-level plumbing with WebSocket and HTTP long-polling, the Socket.IO
protocol adds another layer above in order to provide the following features:
- multiplexing (what we call [Namespace](https://socket.io/docs/namespaces/))
Example of the Javascript API:
```js
// server-side
const nsp = io.of("/admin");
nsp.on("connect", socket => {});
// client-side
const socket1 = io(); // default namespace
const socket2 = io("/admin");
socket2.on("connect", () => {});
```
- acknowledgement of packets
Example of the Javascript API:
```js
// on one side
socket.emit("hello", 1, () => { console.log("received"); });
// on the other side
socket.on("hello", (a, cb) => { cb(); });
```
## Packet format
A packet contains the following fields:
- a type (integer, see [below](#packet-types))
- a namespace (string)
- optionally, a payload (string | Array)
- optionally, an acknowledgment id (integer)
## Packet types
### 0 - CONNECT
This event is sent:
- by the client when requesting access to a namespace
- by the server when accepting the connection to a namespace
It does not contain any payload nor acknowledgement id.
Example:
```json
{
"type": 0,
"nsp": "/admin"
}
```
The client may include additional information (i.e. for authentication purpose) in the namespace field. Example:
```json
{
"type": 0,
"nsp": "/admin?token=1234&uid=abcd"
}
```
#### 1 - DISCONNECT
This event is used when one side wants to disconnect from a namespace.
It does not contain any payload nor acknowledgement id.
Example:
```json
{
"type": 1,
"nsp": "/admin"
}
```
#### 2 - EVENT
This event is used when one side wants to transmit some data (without binary) to the other side.
It does contain a payload, and an optional acknowledgement id.
Example:
```json
{
"type": 2,
"nsp": "/",
"data": ["hello", 1]
}
```
With an acknowledgment id:
```json
{
"type": 2,
"nsp": "/admin",
"data": ["project:delete", 123],
"id": 456
}
```
#### 3 - ACK
This event is used when one side has received an EVENT or a BINARY_EVENT with an acknowledgement id.
It contains the acknowledgement id received in the previous packet, and may contain a payload (without binary).
```json
{
"type": 3,
"nsp": "/admin",
"data": [],
"id": 456
}
```
#### 4 - ERROR
This event is sent by the server when the connection to a namespace is refused.
It may contain a payload indicating the reason of the refusal.
Example:
```json
{
"type": 4,
"nsp": "/admin",
"data": "Not authorized"
}
```
#### 5 - BINARY_EVENT
This event is used when one side wants to transmit some data (including binary) to the other side.
It does contain a payload, and an optional acknowledgement id.
Example:
```
{
"type": 5,
"nsp": "/",
"data": ["hello", <Buffer 01 02 03>]
}
```
With an acknowledgment id:
```
{
"type": 5,
"nsp": "/admin",
"data": ["project:delete", <Buffer 01 02 03>],
"id": 456
}
```
#### 6 - BINARY_ACK
This event is used when one side has received an EVENT or a BINARY_EVENT with an acknowledgement id.
It contains the acknowledgement id received in the previous packet, and contain a payload including binary.
Example:
```
{
"type": 6,
"nsp": "/admin",
"data": [<Buffer 03 02 01>],
"id": 456
}
```
## Packet encoding
This section details the encoding used by the default parser which is included in Socket.IO server and client, and
whose source can be found [here](https://github.com/socketio/socket.io-parser).
The JS server and client implementations also supports custom parsers, which have different tradeoffs and may benefit to
certain kind of applications. Please see [socket.io-json-parser](https://github.com/darrachequesne/socket.io-json-parser)
or [socket.io-msgpack-parser](https://github.com/darrachequesne/socket.io-msgpack-parser) for example.
Please also note that each Socket.IO packet is sent as a Engine.IO `message` packet (more information [here](https://github.com/socketio/engine.io-protocol)),
so the encoded result will be prefixed by `4` when sent over the wire (in the request/response body with HTTP
long-polling, or in the WebSocket frame).
### Encoding format
```
<packet type>[<# of binary attachments>-][<namespace>,][<acknowledgment id>][JSON-stringified payload without binary]
+ binary attachments extracted
```
Note:
- the namespace is only included if it is different from the default namespace (`/`)
### Examples
- `CONNECT` packet for the default namespace
```json
{
"type": 0,
"nsp": "/"
}
```
is encoded to `0`
- `CONNECT` packet for the `/admin` namespace
```json
{
"type": 0,
"nsp": "/admin"
}
```
is encoded to `0/admin,`
- `DISCONNECT` packet for the `/admin` namespace
```json
{
"type": 1,
"nsp": "/admin"
}
```
is encoded to `1/admin,`
- `EVENT` packet
```json
{
"type": 2,
"nsp": "/",
"data": ["hello", 1]
}
```
is encoded to `2["hello",1]`
- `EVENT` packet with an acknowledgement id
```json
{
"type": 2,
"nsp": "/admin",
"data": ["project:delete", 123],
"id": 456
}
```
is encoded to `2/admin,456["project:delete",123]`
- `ACK` packet
```json
{
"type": 3,
"nsp": "/admin",
"data": [],
"id": 456
}
```
is encoded to `3/admin,456[]`
- `ERROR` packet
```json
{
"type": 4,
"nsp": "/admin",
"data": "Not authorized"
}
```
is encoded to `4/admin,"Not authorized"`
- `BINARY_EVENT` packet
```
{
"type": 5,
"nsp": "/",
"data": ["hello", <Buffer 01 02 03>]
}
```
is encoded to `51-["hello",{"_placeholder":true,"num":0}]` + `<Buffer 01 02 03>`
- `BINARY_EVENT` packet with an acknowledgement id
```
{
"type": 5,
"nsp": "/admin",
"data": ["project:delete", <Buffer 01 02 03>],
"id": 456
}
```
is encoded to `51-/admin,456["project:delete",{"_placeholder":true,"num":0}]` + `<Buffer 01 02 03>`
- `BINARY_ACK` packet
```
{
"type": 6,
"nsp": "/admin",
"data": [<Buffer 03 02 01>],
"id": 456
}
```
is encoded to `61-/admin,456[{"_placeholder":true,"num":0}]` + `<Buffer 03 02 01>`
## Exchange protocol
### Connection to the default namespace
The server always send a `CONNECT` packet for the default namespace (`/`) when the connection is established.
That is, even if the client requests access to a non-default namespace, it will receive a `CONNECT` packet for the
default namespace first.
```
Server > { type: CONNECT, nsp: "/" }
```
No response is expected from the client.
### Connection to a non-default namespace
```
Client > { type: CONNECT, nsp: "/admin" }
Server > { type: CONNECT, nsp: "/admin" } (if the connection is successful)
or
Server > { type: ERROR, nsp: "/admin", data: "Not authorized" }
```
### Disconnection from a non-default namespace
```
Client > { type: DISCONNECT, nsp: "/admin" }
```
And vice versa. No response is expected from the other-side.
### Acknowledgement
```
Client > { type: EVENT, nsp: "/admin", data: ["hello"], id: 456 }
Server > { type: ACK, nsp: "/admin", data: [], id: 456 }
or
Server > { type: BINARY_ACK, nsp: "/admin", data: [ <Buffer 01 02 03> ], id: 456 }
```
And vice versa.
## Sample session
Here is an example of what is sent over the wire when combining both the Engine.IO and the Socket.IO protocols.
- Request n°1 (open packet)
```
GET /socket.io/?EIO=3&transport=polling&t=N8hyd6w
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=UTF-8
96:0{"sid":"lv_VI97HAXpY6yYWAAAC","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":5000}2:40
```
Details:
```
96 => number of characters (not bytes) of the first message
: => separator
0 => Engine.IO "open" packet type
{"sid":... => the Engine.IO handshake data
2 => number of characters of the 2nd message
: => separator
4 => Engine.IO "message" packet type
0 => Socket.IO "CONNECT" packet type
```
Note: the `t` query param is used to ensure that the request is not cached by the browser.
- Request n°2 (message in):
`socket.emit('hey', 'Jude')` is executed on the server:
```
GET /socket.io/?EIO=3&transport=polling&t=N8hyd7H&sid=lv_VI97HAXpY6yYWAAAC
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=UTF-8
16:42["hey","Jude"]
```
Details:
```
16 => number of characters
: => separator
4 => Engine.IO "message" packet type
2 => Socket.IO "EVENT" packet type
[...] => content
```
- Request n°3 (message out)
`socket.emit('hello'); socket.emit('world');` is executed on the client:
```
POST /socket.io/?EIO=3&transport=polling&t=N8hzxke&sid=lv_VI97HAXpY6yYWAAAC
> Content-Type: text/plain; charset=UTF-8
11:42["hello"]11:42["world"]
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=UTF-8
ok
```
Details:
```
11 => number of characters of the 1st packet
: => separator
4 => Engine.IO "message" packet type
2 => Socket.IO "EVENT" packet type
["hello"] => the 1st content
11 => number of characters of the 2nd packet
: => separator
4 => Engine.IO "message" packet type
2 => Socket.IO "EVENT" packet type
["world"] => the 2nd content
```
- Request n°4 (WebSocket upgrade)
```
GET /socket.io/?EIO=3&transport=websocket&sid=lv_VI97HAXpY6yYWAAAC
< HTTP/1.1 101 Switching Protocols
```
WebSocket frames:
```
< 2probe => Engine.IO probe request
> 3probe => Engine.IO probe response
> 5 => Engine.IO "upgrade" packet type
> 42["hello"]
> 42["world"]
> 40/admin, => request access to the admin namespace (Socket.IO "CONNECT" packet)
< 40/admin, => grant access to the admin namespace
> 42/admin,1["tellme"] => Socket.IO "EVENT" packet with acknowledgement
< 461-/admin,1[{"_placeholder":true,"num":0}] => Socket.IO "BINARY_ACK" packet with a placeholder
< <binary> => the binary attachment (sent in the following frame)
... after a while without message
> 2 => Engine.IO "ping" packet type
< 3 => Engine.IO "pong" packet type
> 1 => Engine.IO "close" packet type
```
## History
### Difference between v4 and v3
- add a `BINARY_ACK` packet type
Previously, an `ACK` packet was always treated as if it may contain binary objects, with recursive search for such
objects, which could hurt performance.
### Difference between v3 and v2
- remove the usage of msgpack to encode packets containing binary objects (see also [299849b](https://github.com/socketio/socket.io-parser/commit/299849b00294c3bc95817572441f3aca8ffb1f65))
### Difference between v2 and v1
- add a `BINARY_EVENT` packet type
This was added during the work towards Socket.IO 1.0, in order to add support for binary objects. The `BINARY_EVENT`
packets were encoded with [msgpack](https://msgpack.org/).
### Initial revision
This first revision was the result of the split between the Engine.IO protocol (low-level plumbing with WebSocket / HTTP
long-polling, heartbeat) and the Socket.IO protocol. It was never included in a Socket.IO release, but paved the way for
the next iterations.
## License
MIT

View File

@@ -1,733 +0,0 @@
# Socket.IO Protocol
This document describes the 5th version of the Socket.IO protocol.
**Table of content**
- [Introduction](#introduction)
- [Exchange protocol](#exchange-protocol)
- [Connection to a namespace](#connection-to-a-namespace)
- [Sending and receiving data](#sending-and-receiving-data)
- [Acknowledgement](#acknowledgement)
- [Disconnection from a namespace](#disconnection-from-a-namespace)
- [Packet encoding](#packet-encoding)
- [Format](#format)
- [Examples](#examples)
- [Connection to a namespace](#connection-to-a-namespace-1)
- [Sending and receiving data](#sending-and-receiving-data-1)
- [Acknowledgement](#acknowledgement-1)
- [Disconnection from a namespace](#disconnection-from-a-namespace-1)
- [Sample session](#sample-session)
- [History](#history)
- [Difference between v5 and v4](#difference-between-v5-and-v4)
- [Difference between v4 and v3](#difference-between-v4-and-v3)
- [Difference between v3 and v2](#difference-between-v3-and-v2)
- [Difference between v2 and v1](#difference-between-v2-and-v1)
- [Initial revision](#initial-revision)
- [Test suite](#test-suite)
## Introduction
The Socket.IO protocol enables [full-duplex](https://en.wikipedia.org/wiki/Duplex_(telecommunications)#FULL-DUPLEX) and low-overhead communication between a client and a server.
It is built on top of [the Engine.IO protocol](https://github.com/socketio/engine.io-protocol), which handles the low-level plumbing with WebSocket and HTTP long-polling.
The Socket.IO protocol adds the following features:
- multiplexing (referred as ["namespace"](https://socket.io/docs/v4/namespaces) in the Socket.IO jargon)
Example with the JavaScript API:
*Server*
```js
// declare the namespace
const namespace = io.of("/admin");
// handle the connection to the namespace
namespace.on("connection", (socket) => {
// ...
});
```
*Client*
```js
// reach the main namespace
const socket1 = io();
// reach the "/admin" namespace (with the same underlying WebSocket connection)
const socket2 = io("/admin");
// handle the connection to the namespace
socket2.on("connect", () => {
// ...
});
```
- acknowledgement of packets
Example with the JavaScript API:
```js
// on one side
socket.emit("hello", "foo", (arg) => {
console.log("received", arg);
});
// on the other side
socket.on("hello", (arg, ack) => {
ack("bar");
});
```
The reference implementation is written in [TypeScript](https://www.typescriptlang.org/):
- server: https://github.com/socketio/socket.io
- client: https://github.com/socketio/socket.io-client
## Exchange protocol
A Socket.IO packet contains the following fields:
- a packet type (integer)
- a namespace (string)
- optionally, a payload (Object | Array)
- optionally, an acknowledgment id (integer)
Here is the list of available packet types:
| Type | ID | Usage |
|---------------|-----|---------------------------------------------------------------------------------------|
| CONNECT | 0 | Used during the [connection to a namespace](#connection-to-a-namespace). |
| DISCONNECT | 1 | Used when [disconnecting from a namespace](#disconnection-from-a-namespace). |
| EVENT | 2 | Used to [send data](#sending-and-receiving-data) to the other side. |
| ACK | 3 | Used to [acknowledge](#acknowledgement) an event. |
| CONNECT_ERROR | 4 | Used during the [connection to a namespace](#connection-to-a-namespace). |
| BINARY_EVENT | 5 | Used to [send binary data](#sending-and-receiving-data) to the other side. |
| BINARY_ACK | 6 | Used to [acknowledge](#acknowledgement) an event (the response includes binary data). |
### Connection to a namespace
At the beginning of a Socket.IO session, the client MUST send a `CONNECT` packet:
The server MUST respond with either:
- a `CONNECT` packet if the connection is successful, with the session ID in the payload
- or a `CONNECT_ERROR` packet if the connection is not allowed
```
CLIENT SERVER
│ ───────────────────────────────────────────────────────► │
│ { type: CONNECT, namespace: "/" } │
│ ◄─────────────────────────────────────────────────────── │
│ { type: CONNECT, namespace: "/", data: { sid: "..." } } │
```
If the server does not receive a `CONNECT` packet first, then it MUST close the connection immediately.
A client MAY be connected to multiple namespaces at the same time, with the same underlying WebSocket connection.
Examples:
- with the main namespace (named `"/"`)
```
Client > { type: CONNECT, namespace: "/" }
Server > { type: CONNECT, namespace: "/", data: { sid: "wZX3oN0bSVIhsaknAAAI" } }
```
- with a custom namespace
```
Client > { type: CONNECT, namespace: "/admin" }
Server > { type: CONNECT, namespace: "/admin", data: { sid: "oSO0OpakMV_3jnilAAAA" } }
```
- with an additional payload
```
Client > { type: CONNECT, namespace: "/admin", data: { "token": "123" } }
Server > { type: CONNECT, namespace: "/admin", data: { sid: "iLnRaVGHY4B75TeVAAAB" } }
```
- in case the connection is refused
```
Client > { type: CONNECT, namespace: "/" }
Server > { type: CONNECT_ERROR, namespace: "/", data: { message: "Not authorized" } }
```
### Sending and receiving data
Once the [connection to a namespace](#connection-to-a-namespace) is established, the client and the server can begin exchanging data:
```
CLIENT SERVER
│ ───────────────────────────────────────────────────────► │
│ { type: EVENT, namespace: "/", data: ["foo"] } │
│ │
│ ◄─────────────────────────────────────────────────────── │
│ { type: EVENT, namespace: "/", data: ["bar"] } │
```
The payload is mandatory and MUST be a non-empty array. If that's not the case, then the receiver MUST close the connection.
Examples:
- with the main namespace
```
Client > { type: EVENT, namespace: "/", data: ["foo"] }
```
- with a custom namespace
```
Server > { type: EVENT, namespace: "/admin", data: ["bar"] }
```
- with binary data
```
Client > { type: BINARY_EVENT, namespace: "/", data: ["baz", <Buffer <01 02 03 04>> ] }
```
### Acknowledgement
The sender MAY include an event ID in order to request an acknowledgement from the receiver:
```
CLIENT SERVER
│ ───────────────────────────────────────────────────────► │
│ { type: EVENT, namespace: "/", data: ["foo"], id: 12 } │
│ ◄─────────────────────────────────────────────────────── │
│ { type: ACK, namespace: "/", data: ["bar"], id: 12 } │
```
The receiver MUST respond with an `ACK` packet with the same event ID.
The payload is mandatory and MUST be an array (possibly empty).
Examples:
- with the main namespace
```
Client > { type: EVENT, namespace: "/", data: ["foo"], id: 12 }
Server > { type: ACK, namespace: "/", data: [], id: 12 }
```
- with a custom namespace
```
Server > { type: EVENT, namespace: "/admin", data: ["foo"], id: 13 }
Client > { type: ACK, namespace: "/admin", data: ["bar"], id: 13 }
```
- with binary data
```
Client > { type: BINARY_EVENT, namespace: "/", data: ["foo", <buffer <01 02 03 04> ], id: 14 }
Server > { type: ACK, namespace: "/", data: ["bar"], id: 14 }
or
Server > { type: EVENT, namespace: "/", data: ["foo" ], id: 15 }
Client > { type: BINARY_ACK, namespace: "/", data: ["bar", <buffer <01 02 03 04>], id: 15 }
```
### Disconnection from a namespace
At any time, one side can end the connection to a namespace by sending a `DISCONNECT` packet:
```
CLIENT SERVER
│ ───────────────────────────────────────────────────────► │
│ { type: DISCONNECT, namespace: "/" } │
```
No response is expected from the other side. The low-level connection MAY be kept alive if the client is connected to another namespace.
## Packet encoding
This section details the encoding used by the default parser which is included in Socket.IO server and client, and
whose source can be found [here](https://github.com/socketio/socket.io-parser).
The JavaScript server and client implementations also supports custom parsers, which have different tradeoffs and may benefit to
certain kind of applications. Please see [socket.io-json-parser](https://github.com/socketio/socket.io-json-parser)
or [socket.io-msgpack-parser](https://github.com/socketio/socket.io-msgpack-parser) for example.
Please also note that each Socket.IO packet is sent as a Engine.IO `message` packet (more information [here](https://github.com/socketio/engine.io-protocol)),
so the encoded result will be prefixed by the character `"4"` when sent over the wire (in the request/response body with HTTP
long-polling, or in the WebSocket frame).
### Format
```
<packet type>[<# of binary attachments>-][<namespace>,][<acknowledgment id>][JSON-stringified payload without binary]
+ binary attachments extracted
```
Note: the namespace is only included if it is different from the main namespace (`/`)
### Examples
#### Connection to a namespace
- with the main namespace
*Packet*
```
{ type: CONNECT, namespace: "/" }
```
*Encoded*
```
0
```
- with a custom namespace
*Packet*
```
{ type: CONNECT, namespace: "/admin", data: { sid: "oSO0OpakMV_3jnilAAAA" } }
```
*Encoded*
```
0/admin,{"sid":"oSO0OpakMV_3jnilAAAA"}
```
- in case the connection is refused
*Packet*
```
{ type: CONNECT_ERROR, namespace: "/", data: { message: "Not authorized" } }
```
*Encoded*
```
4{"message":"Not authorized"}
```
#### Sending and receiving data
- with the main namespace
*Packet*
```
{ type: EVENT, namespace: "/", data: ["foo"] }
```
*Encoded*
```
2["foo"]
```
- with a custom namespace
*Packet*
```
{ type: EVENT, namespace: "/admin", data: ["bar"] }
```
*Encoded*
```
2/admin,["bar"]
```
- with binary data
*Packet*
```
{ type: BINARY_EVENT, namespace: "/", data: ["baz", <Buffer <01 02 03 04>> ] }
```
*Encoded*
```
51-["baz",{"_placeholder":true,"num":0}]
+ <Buffer <01 02 03 04>>
```
- with multiple attachments
*Packet*
```
{ type: BINARY_EVENT, namespace: "/admin", data: ["baz", <Buffer <01 02>>, <Buffer <03 04>> ] }
```
*Encoded*
```
52-/admin,["baz",{"_placeholder":true,"num":0},{"_placeholder":true,"num":1}]
+ <Buffer <01 02>>
+ <Buffer <03 04>>
```
Please remember that each Socket.IO packet is wrapped in a Engine.IO `message` packet, so they will be prefixed by the character `"4"` when sent over the wire.
Example: `{ type: EVENT, namespace: "/", data: ["foo"] }` will be sent as `42["foo"]`
#### Acknowledgement
- with the main namespace
*Packet*
```
{ type: EVENT, namespace: "/", data: ["foo"], id: 12 }
```
*Encoded*
```
212["foo"]
```
- with a custom namespace
*Packet*
```
{ type: ACK, namespace: "/admin", data: ["bar"], id: 13 }
```
*Encoded*
```
3/admin,13["bar"]`
```
- with binary data
*Packet*
```
{ type: BINARY_ACK, namespace: "/", data: ["bar", <Buffer <01 02 03 04>>], id: 15 }
```
*Encoded*
```
61-15["bar",{"_placeholder":true,"num":0}]
+ <Buffer <01 02 03 04>>
```
#### Disconnection from a namespace
- with the main namespace
*Packet*
```
{ type: DISCONNECT, namespace: "/" }
```
*Encoded*
```
1
```
- with a custom namespace
```
{ type: DISCONNECT, namespace: "/admin" }
```
*Encoded*
```
1/admin,
```
## Sample session
Here is an example of what is sent over the wire when combining both the Engine.IO and the Socket.IO protocols.
- Request n°1 (open packet)
```
GET /socket.io/?EIO=4&transport=polling&t=N8hyd6w
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=UTF-8
0{"sid":"lv_VI97HAXpY6yYWAAAC","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":5000,"maxPayload":1000000}
```
Details:
```
0 => Engine.IO "open" packet type
{"sid":... => the Engine.IO handshake data
```
Note: the `t` query param is used to ensure that the request is not cached by the browser.
- Request n°2 (namespace connection request):
```
POST /socket.io/?EIO=4&transport=polling&t=N8hyd7H&sid=lv_VI97HAXpY6yYWAAAC
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=UTF-8
40
```
Details:
```
4 => Engine.IO "message" packet type
0 => Socket.IO "CONNECT" packet type
```
- Request n°3 (namespace connection approval)
```
GET /socket.io/?EIO=4&transport=polling&t=N8hyd7H&sid=lv_VI97HAXpY6yYWAAAC
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=UTF-8
40{"sid":"wZX3oN0bSVIhsaknAAAI"}
```
- Request n°4
`socket.emit('hey', 'Jude')` is executed on the server:
```
GET /socket.io/?EIO=4&transport=polling&t=N8hyd7H&sid=lv_VI97HAXpY6yYWAAAC
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=UTF-8
42["hey","Jude"]
```
Details:
```
4 => Engine.IO "message" packet type
2 => Socket.IO "EVENT" packet type
[...] => content
```
- Request n°5 (message out)
`socket.emit('hello'); socket.emit('world');` is executed on the client:
```
POST /socket.io/?EIO=4&transport=polling&t=N8hzxke&sid=lv_VI97HAXpY6yYWAAAC
> Content-Type: text/plain; charset=UTF-8
42["hello"]\x1e42["world"]
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=UTF-8
ok
```
Details:
```
4 => Engine.IO "message" packet type
2 => Socket.IO "EVENT" packet type
["hello"] => the 1st content
\x1e => separator
4 => Engine.IO "message" packet type
2 => Socket.IO "EVENT" packet type
["world"] => the 2nd content
```
- Request n°6 (WebSocket upgrade)
```
GET /socket.io/?EIO=4&transport=websocket&sid=lv_VI97HAXpY6yYWAAAC
< HTTP/1.1 101 Switching Protocols
```
WebSocket frames:
```
< 2probe => Engine.IO probe request
> 3probe => Engine.IO probe response
> 5 => Engine.IO "upgrade" packet type
> 42["hello"]
> 42["world"]
> 40/admin, => request access to the admin namespace (Socket.IO "CONNECT" packet)
< 40/admin,{"sid":"-G5j-67EZFp-q59rADQM"} => grant access to the admin namespace
> 42/admin,1["tellme"] => Socket.IO "EVENT" packet with acknowledgement
< 461-/admin,1[{"_placeholder":true,"num":0}] => Socket.IO "BINARY_ACK" packet with a placeholder
< <binary> => the binary attachment (sent in the following frame)
... after a while without message
> 2 => Engine.IO "ping" packet type
< 3 => Engine.IO "pong" packet type
> 1 => Engine.IO "close" packet type
```
## History
### Difference between v5 and v4
The 5th revision (current) of the Socket.IO protocol is used in Socket.IO v3 and above (`v3.0.0` was released in November 2020).
It is built on top of the 4th revision of [the Engine.IO protocol](https://github.com/socketio/engine.io-protocol) (hence the `EIO=4` query parameter).
List of changes:
- remove the implicit connection to the default namespace
In previous versions, a client was always connected to the default namespace, even if it requested access to another namespace.
This is not the case anymore, the client must send a `CONNECT` packet in any case.
Commits: [09b6f23](https://github.com/socketio/socket.io/commit/09b6f2333950b8afc8c1400b504b01ad757876bd) (server) and [249e0be](https://github.com/socketio/socket.io-client/commit/249e0bef9071e7afd785485961c4eef0094254e8) (client)
- rename `ERROR` to `CONNECT_ERROR`
The meaning and the code number (4) are not modified: this packet type is still used by the server when the connection to a namespace is refused. But we feel the name is more self-descriptive.
Commits: [d16c035](https://github.com/socketio/socket.io/commit/d16c035d258b8deb138f71801cb5aeedcdb3f002) (server) and [13e1db7c](https://github.com/socketio/socket.io-client/commit/13e1db7c94291c583d843beaa9e06ee041ae4f26) (client).
- the `CONNECT` packet now can contain a payload
The client can send a payload for authentication/authorization purposes. Example:
```json
{
"type": 0,
"nsp": "/admin",
"data": {
"token": "123"
}
}
```
In case of success, the server responds with a payload contain the ID of the Socket. Example:
```json
{
"type": 0,
"nsp": "/admin",
"data": {
"sid": "CjdVH4TQvovi1VvgAC5Z"
}
}
```
This change means that the ID of the Socket.IO connection will now be different from the ID of the underlying Engine.IO connection (the one that is found in the query parameters of the HTTP requests).
Commits: [2875d2c](https://github.com/socketio/socket.io/commit/2875d2cfdfa463e64cb520099749f543bbc4eb15) (server) and [bbe94ad](https://github.com/socketio/socket.io-client/commit/bbe94adb822a306c6272e977d394e3e203cae25d) (client)
- the payload `CONNECT_ERROR` packet is now an object instead of a plain string
Commits: [54bf4a4](https://github.com/socketio/socket.io/commit/54bf4a44e9e896dfb64764ee7bd4e8823eb7dc7b) (server) and [0939395](https://github.com/socketio/socket.io-client/commit/09393952e3397a0c71f239ea983f8ec1623b7c21) (client)
### Difference between v4 and v3
The 4th revision of the Socket.IO protocol is used in Socket.IO v1 (`v1.0.3` was released in June 2014) and v2 (`v2.0.0` was released in May 2017).
The details of the revision can be found here: https://github.com/socketio/socket.io-protocol/tree/v4
It is built on top of the 3rd revision of [the Engine.IO protocol](https://github.com/socketio/engine.io-protocol) (hence the `EIO=3` query parameter).
List of changes:
- add a `BINARY_ACK` packet type
Previously, an `ACK` packet was always treated as if it may contain binary objects, with recursive search for such
objects, which could hurt performance.
Reference: https://github.com/socketio/socket.io-parser/commit/ca4f42a922ba7078e840b1bc09fe3ad618acc065
### Difference between v3 and v2
The 3rd revision of the Socket.IO protocol is used in early Socket.IO v1 versions (`socket.io@1.0.0...1.0.2`) (released in May 2014).
The details of the revision can be found here: https://github.com/socketio/socket.io-protocol/tree/v3
List of changes:
- remove the usage of msgpack to encode packets containing binary objects (see also [299849b](https://github.com/socketio/socket.io-parser/commit/299849b00294c3bc95817572441f3aca8ffb1f65))
### Difference between v2 and v1
List of changes:
- add a `BINARY_EVENT` packet type
This was added during the work towards Socket.IO 1.0, in order to add support for binary objects. The `BINARY_EVENT`
packets were encoded with [msgpack](https://msgpack.org/).
### Initial revision
This first revision was the result of the split between the Engine.IO protocol (low-level plumbing with WebSocket / HTTP
long-polling, heartbeat) and the Socket.IO protocol. It was never included in a Socket.IO release, but paved the way for
the next iterations.
## Test suite
The test suite in the [`test-suite/`](https://github.com/socketio/socket.io-protocol/tree/main/test-suite) directory lets you check the compliance of a server implementation.
Usage:
- in Node.js: `npm ci && npm test`
- in a browser: simply open the `index.html` file in your browser
For reference, here is expected configuration for the JavaScript server to pass all tests:
```js
import { Server } from "socket.io";
const io = new Server(3000, {
pingInterval: 300,
pingTimeout: 200,
maxPayload: 1000000,
connectTimeout: 1000,
cors: {
origin: "*"
}
});
io.on("connection", (socket) => {
socket.emit("auth", socket.handshake.auth);
socket.on("message", (...args) => {
socket.emit.apply(socket, ["message-back", ...args]);
});
socket.on("message-with-ack", (...args) => {
const ack = args.pop();
ack(...args);
})
});
io.of("/custom").on("connection", (socket) => {
socket.emit("auth", socket.handshake.auth);
});
```
## License
MIT

View File

@@ -1 +0,0 @@
node_modules

View File

@@ -1,30 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Test suite for the Socket.IO protocol</title>
<link rel="stylesheet" href="https://unpkg.com/mocha@9/mocha.css" />
</head>
<body>
<div id="mocha"></div>
<script src="https://unpkg.com/mocha@9/mocha.js"></script>
<script src="https://unpkg.com/chai@4/chai.js" ></script>
<script src="https://unpkg.com/chai-string@1/chai-string.js" ></script>
<script class="mocha-init">
mocha.setup("bdd");
mocha.checkLeaks();
</script>
<script type="module" src="test-suite.js"></script>
<script class="mocha-exec">
mocha.run();
</script>
</body>
</html>

View File

@@ -1,10 +0,0 @@
import fetch from "node-fetch";
import { WebSocket } from "ws";
import chai from "chai";
import chaiString from "chai-string";
chai.use(chaiString);
globalThis.fetch = fetch;
globalThis.WebSocket = WebSocket;
globalThis.chai = chai;

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +0,0 @@
{
"name": "socket.io-protocol-test-suite",
"version": "0.0.1",
"private": true,
"type": "module",
"scripts": {
"format": "prettier -w *.js",
"test": "mocha test-suite.js"
},
"devDependencies": {
"chai": "^4.3.6",
"chai-string": "^1.5.0",
"mocha": "^9.2.1",
"node-fetch": "^3.2.0",
"prettier": "^2.5.1",
"ws": "^8.5.0"
}
}

View File

@@ -1,656 +0,0 @@
const isNodejs = typeof window === "undefined";
if (isNodejs) {
// make the tests runnable in both the browser and Node.js
await import("./node-imports.js");
}
const { expect } = chai;
const URL = "http://localhost:3000";
const WS_URL = URL.replace("http", "ws");
const PING_INTERVAL = 300;
const PING_TIMEOUT = 200;
function sleep(delay) {
return new Promise((resolve) => setTimeout(resolve, delay));
}
function createWebSocket(url) {
const socket = new WebSocket(url);
socket._eventBuffer = {};
socket._pendingPromises = {};
for (const eventType of ["open", "close", "message"]) {
socket._eventBuffer[eventType] = [];
socket._pendingPromises[eventType] = [];
socket.addEventListener(eventType, (event) => {
if (socket._pendingPromises[eventType].length) {
socket._pendingPromises[eventType].shift()(event);
} else {
socket._eventBuffer[eventType].push(event);
}
});
}
return socket;
}
function waitFor(socket, eventType) {
if (socket._eventBuffer[eventType].length) {
return Promise.resolve(socket._eventBuffer[eventType].shift());
} else {
return new Promise((resolve) => {
socket._pendingPromises[eventType].push(resolve);
});
}
}
function waitForPackets(socket, count) {
const packets = [];
return new Promise((resolve) => {
const handler = (event) => {
if (event.data === "2") {
// ignore PING packets
return;
}
packets.push(event.data);
if (packets.length === count) {
socket.removeEventListener("message", handler);
resolve(packets);
}
};
socket.addEventListener("message", handler);
});
}
async function initLongPollingSession() {
const response = await fetch(`${URL}/socket.io/?EIO=4&transport=polling`);
const content = await response.text();
return JSON.parse(content.substring(1)).sid;
}
async function initSocketIOConnection() {
const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket`
);
socket.binaryType = "arraybuffer";
await waitFor(socket, "message"); // Engine.IO handshake
socket.send("40");
await waitFor(socket, "message"); // Socket.IO handshake
await waitFor(socket, "message"); // "auth" packet
return socket;
}
describe("Engine.IO protocol", () => {
describe("handshake", () => {
describe("HTTP long-polling", () => {
it("should successfully open a session", async () => {
const response = await fetch(
`${URL}/socket.io/?EIO=4&transport=polling`
);
expect(response.status).to.eql(200);
const content = await response.text();
expect(content).to.startsWith("0");
const value = JSON.parse(content.substring(1));
expect(value).to.have.all.keys(
"sid",
"upgrades",
"pingInterval",
"pingTimeout",
"maxPayload"
);
expect(value.sid).to.be.a("string");
expect(value.upgrades).to.eql(["websocket"]);
expect(value.pingInterval).to.eql(PING_INTERVAL);
expect(value.pingTimeout).to.eql(PING_TIMEOUT);
expect(value.maxPayload).to.eql(1000000);
});
it("should fail with an invalid 'EIO' query parameter", async () => {
const response = await fetch(`${URL}/socket.io/?transport=polling`);
expect(response.status).to.eql(400);
const response2 = await fetch(
`${URL}/socket.io/?EIO=abc&transport=polling`
);
expect(response2.status).to.eql(400);
});
it("should fail with an invalid 'transport' query parameter", async () => {
const response = await fetch(`${URL}/socket.io/?EIO=4`);
expect(response.status).to.eql(400);
const response2 = await fetch(`${URL}/socket.io/?EIO=4&transport=abc`);
expect(response2.status).to.eql(400);
});
it("should fail with an invalid request method", async () => {
const response = await fetch(
`${URL}/socket.io/?EIO=4&transport=polling`,
{
method: "post",
}
);
expect(response.status).to.eql(400);
const response2 = await fetch(
`${URL}/socket.io/?EIO=4&transport=polling`,
{
method: "put",
}
);
expect(response2.status).to.eql(400);
});
});
describe("WebSocket", () => {
it("should successfully open a session", async () => {
const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket`
);
const { data } = await waitFor(socket, "message");
expect(data).to.startsWith("0");
const value = JSON.parse(data.substring(1));
expect(value).to.have.all.keys(
"sid",
"upgrades",
"pingInterval",
"pingTimeout",
"maxPayload"
);
expect(value.sid).to.be.a("string");
expect(value.upgrades).to.eql([]);
expect(value.pingInterval).to.eql(PING_INTERVAL);
expect(value.pingTimeout).to.eql(PING_TIMEOUT);
expect(value.maxPayload).to.eql(1000000);
socket.close();
});
it("should fail with an invalid 'EIO' query parameter", async () => {
const socket = createWebSocket(
`${WS_URL}/socket.io/?transport=websocket`
);
if (isNodejs) {
socket.on("error", () => {});
}
await waitFor(socket, "close");
const socket2 = createWebSocket(
`${WS_URL}/socket.io/?EIO=abc&transport=websocket`
);
if (isNodejs) {
socket2.on("error", () => {});
}
await waitFor(socket2, "close");
});
it("should fail with an invalid 'transport' query parameter", async () => {
const socket = createWebSocket(`${WS_URL}/socket.io/?EIO=4`);
if (isNodejs) {
socket.on("error", () => {});
}
await waitFor(socket, "close");
const socket2 = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=abc`
);
if (isNodejs) {
socket2.on("error", () => {});
}
await waitFor(socket2, "close");
});
});
});
describe("heartbeat", function () {
this.timeout(5000);
describe("HTTP long-polling", () => {
it("should send ping/pong packets", async () => {
const sid = await initLongPollingSession();
for (let i = 0; i < 3; i++) {
const pollResponse = await fetch(
`${URL}/socket.io/?EIO=4&transport=polling&sid=${sid}`
);
expect(pollResponse.status).to.eql(200);
const pollContent = await pollResponse.text();
expect(pollContent).to.eql("2");
const pushResponse = await fetch(
`${URL}/socket.io/?EIO=4&transport=polling&sid=${sid}`,
{
method: "post",
body: "3",
}
);
expect(pushResponse.status).to.eql(200);
}
});
it("should close the session upon ping timeout", async () => {
const sid = await initLongPollingSession();
await sleep(PING_INTERVAL + PING_TIMEOUT);
const pollResponse = await fetch(
`${URL}/socket.io/?EIO=4&transport=polling&sid=${sid}`
);
expect(pollResponse.status).to.eql(400);
});
});
describe("WebSocket", () => {
it("should send ping/pong packets", async () => {
const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket`
);
await waitFor(socket, "message"); // handshake
for (let i = 0; i < 3; i++) {
const { data } = await waitFor(socket, "message");
expect(data).to.eql("2");
socket.send("3");
}
socket.close();
});
it("should close the session upon ping timeout", async () => {
const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket`
);
await waitFor(socket, "close"); // handshake
});
});
});
describe("close", () => {
describe("HTTP long-polling", () => {
it("should forcefully close the session", async () => {
const sid = await initLongPollingSession();
const [pollResponse] = await Promise.all([
fetch(`${URL}/socket.io/?EIO=4&transport=polling&sid=${sid}`),
fetch(`${URL}/socket.io/?EIO=4&transport=polling&sid=${sid}`, {
method: "post",
body: "1",
}),
]);
expect(pollResponse.status).to.eql(200);
const pullContent = await pollResponse.text();
expect(pullContent).to.eql("6");
const pollResponse2 = await fetch(
`${URL}/socket.io/?EIO=4&transport=polling&sid=${sid}`
);
expect(pollResponse2.status).to.eql(400);
});
});
describe("WebSocket", () => {
it("should forcefully close the session", async () => {
const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket`
);
await waitFor(socket, "message"); // handshake
socket.send("1");
await waitFor(socket, "close");
});
});
});
describe("upgrade", () => {
it("should successfully upgrade from HTTP long-polling to WebSocket", async () => {
const sid = await initLongPollingSession();
const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket&sid=${sid}`
);
await waitFor(socket, "open");
// send probe
socket.send("2probe");
const probeResponse = await waitFor(socket, "message");
expect(probeResponse.data).to.eql("3probe");
// complete upgrade
socket.send("5");
});
it("should ignore HTTP requests with same sid after upgrade", async () => {
const sid = await initLongPollingSession();
const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket&sid=${sid}`
);
await waitFor(socket, "open");
socket.send("2probe");
await waitFor(socket, "message"); // "3probe"
socket.send("5");
const pollResponse = await fetch(
`${URL}/socket.io/?EIO=4&transport=polling&sid=${sid}`
);
expect(pollResponse.status).to.eql(400);
});
it("should ignore WebSocket connection with same sid after upgrade", async () => {
const sid = await initLongPollingSession();
const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket&sid=${sid}`
);
await waitFor(socket, "open");
socket.send("2probe");
await waitFor(socket, "message"); // "3probe"
socket.send("5");
const socket2 = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket&sid=${sid}`
);
await waitFor(socket2, "close");
});
});
});
describe("Socket.IO protocol", () => {
describe("connect", () => {
it("should allow connection to the main namespace", async () => {
const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket`
);
await waitFor(socket, "message"); // Engine.IO handshake
socket.send("40");
const { data } = await waitFor(socket, "message");
expect(data).to.startsWith("40");
const handshake = JSON.parse(data.substring(2));
expect(handshake).to.have.all.keys("sid");
expect(handshake.sid).to.be.a("string");
const authPacket = await waitFor(socket, "message");
expect(authPacket.data).to.eql('42["auth",{}]');
});
it("should allow connection to the main namespace with a payload", async () => {
const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket`
);
await waitFor(socket, "message"); // Engine.IO handshake
socket.send('40{"token":"123"}');
const { data } = await waitFor(socket, "message");
expect(data).to.startsWith("40");
const handshake = JSON.parse(data.substring(2));
expect(handshake).to.have.all.keys("sid");
expect(handshake.sid).to.be.a("string");
const authPacket = await waitFor(socket, "message");
expect(authPacket.data).to.eql('42["auth",{"token":"123"}]');
});
it("should allow connection to a custom namespace", async () => {
const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket`
);
await waitFor(socket, "message"); // Engine.IO handshake
socket.send("40/custom,");
const { data } = await waitFor(socket, "message");
expect(data).to.startsWith("40/custom,");
const handshake = JSON.parse(data.substring(10));
expect(handshake).to.have.all.keys("sid");
expect(handshake.sid).to.be.a("string");
const authPacket = await waitFor(socket, "message");
expect(authPacket.data).to.eql('42/custom,["auth",{}]');
});
it("should allow connection to a custom namespace with a payload", async () => {
const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket`
);
await waitFor(socket, "message"); // Engine.IO handshake
socket.send('40/custom,{"token":"abc"}');
const { data } = await waitFor(socket, "message");
expect(data).to.startsWith("40/custom,");
const handshake = JSON.parse(data.substring(10));
expect(handshake).to.have.all.keys("sid");
expect(handshake.sid).to.be.a("string");
const authPacket = await waitFor(socket, "message");
expect(authPacket.data).to.eql('42/custom,["auth",{"token":"abc"}]');
});
it("should disallow connection to an unknown namespace", async () => {
const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket`
);
await waitFor(socket, "message"); // Engine.IO handshake
socket.send("40/random");
const { data } = await waitFor(socket, "message");
expect(data).to.eql('44/random,{"message":"Invalid namespace"}');
});
it("should disallow connection with an invalid handshake", async () => {
const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket`
);
await waitFor(socket, "message"); // Engine.IO handshake
socket.send("4abc");
await waitFor(socket, "close");
});
it("should close the connection if no handshake is received", async () => {
const socket = createWebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket`
);
await waitFor(socket, "close");
});
});
describe("disconnect", () => {
it("should disconnect from the main namespace", async () => {
const socket = await initSocketIOConnection();
socket.send("41");
const { data } = await waitFor(socket, "message");
expect(data).to.eql("2");
});
it("should connect then disconnect from a custom namespace", async () => {
const socket = await initSocketIOConnection();
await waitFor(socket, "message"); // ping
socket.send("40/custom");
await waitFor(socket, "message"); // Socket.IO handshake
await waitFor(socket, "message"); // auth packet
socket.send("41/custom");
socket.send('42["message","message to main namespace"]');
const { data } = await waitFor(socket, "message");
expect(data).to.eql('42["message-back","message to main namespace"]');
});
});
describe("message", () => {
it("should send a plain-text packet", async () => {
const socket = await initSocketIOConnection();
socket.send('42["message",1,"2",{"3":[true]}]');
const { data } = await waitFor(socket, "message");
expect(data).to.eql('42["message-back",1,"2",{"3":[true]}]');
});
it("should send a packet with binary attachments", async () => {
const socket = await initSocketIOConnection();
socket.send(
'452-["message",{"_placeholder":true,"num":0},{"_placeholder":true,"num":1}]'
);
socket.send(Uint8Array.from([1, 2, 3]));
socket.send(Uint8Array.from([4, 5, 6]));
const packets = await waitForPackets(socket, 3);
expect(packets[0]).to.eql(
'452-["message-back",{"_placeholder":true,"num":0},{"_placeholder":true,"num":1}]'
);
expect(packets[1]).to.eql(Uint8Array.from([1, 2, 3]).buffer);
expect(packets[2]).to.eql(Uint8Array.from([4, 5, 6]).buffer);
socket.close();
});
it("should send a plain-text packet with an ack", async () => {
const socket = await initSocketIOConnection();
socket.send('42456["message-with-ack",1,"2",{"3":[false]}]');
const { data } = await waitFor(socket, "message");
expect(data).to.eql('43456[1,"2",{"3":[false]}]');
});
it("should send a packet with binary attachments and an ack", async () => {
const socket = await initSocketIOConnection();
socket.send(
'452-789["message-with-ack",{"_placeholder":true,"num":0},{"_placeholder":true,"num":1}]'
);
socket.send(Uint8Array.from([1, 2, 3]));
socket.send(Uint8Array.from([4, 5, 6]));
const packets = await waitForPackets(socket, 3);
expect(packets[0]).to.eql(
'462-789[{"_placeholder":true,"num":0},{"_placeholder":true,"num":1}]'
);
expect(packets[1]).to.eql(Uint8Array.from([1, 2, 3]).buffer);
expect(packets[2]).to.eql(Uint8Array.from([4, 5, 6]).buffer);
socket.close();
});
it("should close the connection upon invalid format (unknown packet type)", async () => {
const socket = await initSocketIOConnection();
socket.send("4abc");
await waitFor(socket, "close");
});
it("should close the connection upon invalid format (invalid payload format)", async () => {
const socket = await initSocketIOConnection();
socket.send("42{}");
await waitFor(socket, "close");
});
it("should close the connection upon invalid format (invalid ack id)", async () => {
const socket = await initSocketIOConnection();
socket.send('42abc["message-with-ack",1,"2",{"3":[false]}]');
await waitFor(socket, "close");
});
});
});

1
examples/.gitignore vendored
View File

@@ -1 +0,0 @@
package-lock.json

View File

@@ -1,2 +0,0 @@
BUNDLE_PATH: "vendor/bundle"
BUNDLE_FORCE_RUBY_PLATFORM: 1

View File

@@ -1,4 +0,0 @@
module.exports = {
root: true,
extends: '@react-native',
};

View File

@@ -1,66 +0,0 @@
# OSX
#
.DS_Store
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
ios/.xcode.env.local
# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
*.hprof
.cxx/
*.keystore
!debug.keystore
# node.js
#
node_modules/
npm-debug.log
yarn-error.log
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/
**/fastlane/report.xml
**/fastlane/Preview.html
**/fastlane/screenshots
**/fastlane/test_output
# Bundle artifact
*.jsbundle
# Ruby / CocoaPods
/ios/Pods/
/vendor/bundle/
# Temporary files created by Metro to check the health of the file watcher
.metro-health-check*
# testing
/coverage

View File

@@ -1,7 +0,0 @@
module.exports = {
arrowParens: 'avoid',
bracketSameLine: true,
bracketSpacing: false,
singleQuote: true,
trailingComma: 'all',
};

View File

@@ -1 +0,0 @@
{}

View File

@@ -1,119 +0,0 @@
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
*/
import React from 'react';
import type {PropsWithChildren} from 'react';
import {
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
useColorScheme,
View,
} from 'react-native';
import { socket } from './socket';
import { useEffect, useState } from 'react';
import {
Colors,
DebugInstructions,
Header,
LearnMoreLinks,
ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';
type SectionProps = PropsWithChildren<{
title: string;
}>;
function Section({children, title}: SectionProps): React.JSX.Element {
const isDarkMode = useColorScheme() === 'dark';
return (
<View style={styles.sectionContainer}>
<Text
style={[
styles.sectionTitle,
{
color: isDarkMode ? Colors.white : Colors.black,
},
]}>
{title}
</Text>
<Text
style={[
styles.sectionDescription,
{
color: isDarkMode ? Colors.light : Colors.dark,
},
]}>
{children}
</Text>
</View>
);
}
function App(): React.JSX.Element {
const [isConnected, setIsConnected] = useState(false);
const [transport, setTransport] = useState('N/A');
useEffect(() => {
if (socket.connected) {
onConnect();
}
function onConnect() {
setIsConnected(true);
setTransport(socket.io.engine.transport.name);
socket.io.engine.on('upgrade', (transport) => {
setTransport(transport.name);
});
}
function onDisconnect() {
setIsConnected(false);
setTransport('N/A');
}
socket.on('connect', onConnect);
socket.on('disconnect', onDisconnect);
return () => {
socket.off('connect', onConnect);
socket.off('disconnect', onDisconnect);
};
}, []);
return (
<View style={styles.container}>
<Text>Status: { isConnected ? 'connected' : 'disconnected' }</Text>
<Text>Transport: { transport }</Text>
</View>
);
}
const styles = StyleSheet.create({
sectionContainer: {
marginTop: 32,
paddingHorizontal: 24,
},
sectionTitle: {
fontSize: 24,
fontWeight: '600',
},
sectionDescription: {
marginTop: 8,
fontSize: 18,
fontWeight: '400',
},
highlight: {
fontWeight: '700',
},
});
export default App;

View File

@@ -1,9 +0,0 @@
source 'https://rubygems.org'
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
ruby ">= 2.6.10"
# Cocoapods 1.15 introduced a bug which break the build. We will remove the upper
# bound in the template on Cocoapods with next React Native release.
gem 'cocoapods', '>= 1.13', '< 1.15'
gem 'activesupport', '>= 6.1.7.5', '< 7.1.0'

View File

@@ -1,89 +0,0 @@
This is a new [**React Native**](https://reactnative.dev) project, bootstrapped using [`@react-native-community/cli`](https://github.com/react-native-community/cli).
# Getting Started
>**Note**: Make sure you have completed the [React Native - Environment Setup](https://reactnative.dev/docs/environment-setup) instructions till "Creating a new application" step, before proceeding.
## Step 1: Start the Metro Server
First, you will need to start **Metro**, the JavaScript _bundler_ that ships _with_ React Native.
To start Metro, run the following command from the _root_ of your React Native project:
```bash
# using npm
npm start
# OR using Yarn
yarn start
```
## Step 2: Start your Application
Let Metro Bundler run in its _own_ terminal. Open a _new_ terminal from the _root_ of your React Native project. Run the following command to start your _Android_ or _iOS_ app:
### For Android
```bash
# using npm
npm run android
# OR using Yarn
yarn android
```
### For iOS
```bash
# using npm
npm run ios
# OR using Yarn
yarn ios
```
If everything is set up _correctly_, you should see your new app running in your _Android Emulator_ or _iOS Simulator_ shortly provided you have set up your emulator/simulator correctly.
This is one way to run your app — you can also run it directly from within Android Studio and Xcode respectively.
## Step 3: Start the Socket.IO server
```bash
cd server
npm install
npm start
```
## Step 4: Modifying your App
Now that you have successfully run the app, let's modify it.
1. Open `App.tsx` in your text editor of choice and edit some lines.
2. For **Android**: Press the <kbd>R</kbd> key twice or select **"Reload"** from the **Developer Menu** (<kbd>Ctrl</kbd> + <kbd>M</kbd> (on Window and Linux) or <kbd>Cmd ⌘</kbd> + <kbd>M</kbd> (on macOS)) to see your changes!
For **iOS**: Hit <kbd>Cmd ⌘</kbd> + <kbd>R</kbd> in your iOS Simulator to reload the app and see your changes!
## Congratulations! :tada:
You've successfully run and modified your React Native App. :partying_face:
### Now what?
- If you want to add this new React Native code to an existing application, check out the [Integration guide](https://reactnative.dev/docs/integration-with-existing-apps).
- If you're curious to learn more about React Native, check out the [Introduction to React Native](https://reactnative.dev/docs/getting-started).
# Troubleshooting
If you can't get this to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page.
# Learn More
To learn more about React Native, take a look at the following resources:
- [React Native Website](https://reactnative.dev) - learn more about React Native.
- [Getting Started](https://reactnative.dev/docs/environment-setup) - an **overview** of React Native and how setup your environment.
- [Learn the Basics](https://reactnative.dev/docs/getting-started) - a **guided tour** of the React Native **basics**.
- [Blog](https://reactnative.dev/blog) - read the latest official React Native **Blog** posts.
- [`@facebook/react-native`](https://github.com/facebook/react-native) - the Open Source; GitHub **repository** for React Native.

View File

@@ -1,17 +0,0 @@
/**
* @format
*/
import 'react-native';
import React from 'react';
import App from '../App';
// Note: import explicitly to use the types shipped with jest.
import {it} from '@jest/globals';
// Note: test renderer must be required after react-native.
import renderer from 'react-test-renderer';
it('renders correctly', () => {
renderer.create(<App />);
});

View File

@@ -1,119 +0,0 @@
apply plugin: "com.android.application"
apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "com.facebook.react"
/**
* This is the configuration block to customize your React Native Android app.
* By default you don't need to apply any configuration, just uncomment the lines you need.
*/
react {
/* Folders */
// The root of your project, i.e. where "package.json" lives. Default is '..'
// root = file("../")
// The folder where the react-native NPM package is. Default is ../node_modules/react-native
// reactNativeDir = file("../node_modules/react-native")
// The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen
// codegenDir = file("../node_modules/@react-native/codegen")
// The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js
// cliFile = file("../node_modules/react-native/cli.js")
/* Variants */
// The list of variants to that are debuggable. For those we're going to
// skip the bundling of the JS bundle and the assets. By default is just 'debug'.
// If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
// debuggableVariants = ["liteDebug", "prodDebug"]
/* Bundling */
// A list containing the node command and its flags. Default is just 'node'.
// nodeExecutableAndArgs = ["node"]
//
// The command to run when bundling. By default is 'bundle'
// bundleCommand = "ram-bundle"
//
// The path to the CLI configuration file. Default is empty.
// bundleConfig = file(../rn-cli.config.js)
//
// The name of the generated asset file containing your JS bundle
// bundleAssetName = "MyApplication.android.bundle"
//
// The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
// entryFile = file("../js/MyApplication.android.js")
//
// A list of extra flags to pass to the 'bundle' commands.
// See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
// extraPackagerArgs = []
/* Hermes Commands */
// The hermes compiler command to run. By default it is 'hermesc'
// hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
//
// The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
// hermesFlags = ["-O", "-output-source-map"]
}
/**
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
*/
def enableProguardInReleaseBuilds = false
/**
* The preferred build flavor of JavaScriptCore (JSC)
*
* For example, to use the international variant, you can use:
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
*
* The international variant includes ICU i18n library and necessary data
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
* give correct results when using with locales other than en-US. Note that
* this variant is about 6MiB larger per architecture than default.
*/
def jscFlavor = 'org.webkit:android-jsc:+'
android {
ndkVersion rootProject.ext.ndkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileSdk rootProject.ext.compileSdkVersion
namespace "com.reactnativeexample"
defaultConfig {
applicationId "com.reactnativeexample"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
}
signingConfigs {
debug {
storeFile file('debug.keystore')
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
// Caution! In production, you need to generate your own keystore file.
// see https://reactnative.dev/docs/signed-apk-android.
signingConfig signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
}
dependencies {
// The version of react-native is set by the React Native Gradle Plugin
implementation("com.facebook.react:react-android")
implementation("com.facebook.react:flipper-integration")
if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
} else {
implementation jscFlavor
}
}
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)

View File

@@ -1,10 +0,0 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:usesCleartextTraffic="true"
tools:targetApi="28"
tools:ignore="GoogleAppIndexingWarning"/>
</manifest>

View File

@@ -1,25 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".MainApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -1,22 +0,0 @@
package com.reactnativeexample
import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
import com.facebook.react.defaults.DefaultReactActivityDelegate
class MainActivity : ReactActivity() {
/**
* Returns the name of the main component registered from JavaScript. This is used to schedule
* rendering of the component.
*/
override fun getMainComponentName(): String = "ReactNativeExample"
/**
* Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
* which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
*/
override fun createReactActivityDelegate(): ReactActivityDelegate =
DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
}

View File

@@ -1,45 +0,0 @@
package com.reactnativeexample
import android.app.Application
import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactHost
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.defaults.DefaultReactNativeHost
import com.facebook.react.flipper.ReactNativeFlipper
import com.facebook.soloader.SoLoader
class MainApplication : Application(), ReactApplication {
override val reactNativeHost: ReactNativeHost =
object : DefaultReactNativeHost(this) {
override fun getPackages(): List<ReactPackage> =
PackageList(this).packages.apply {
// Packages that cannot be autolinked yet can be added manually here, for example:
// add(MyReactNativePackage())
}
override fun getJSMainModuleName(): String = "index"
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
}
override val reactHost: ReactHost
get() = getDefaultReactHost(this.applicationContext, reactNativeHost)
override fun onCreate() {
super.onCreate()
SoLoader.init(this, false)
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
// If you opted-in for the New Architecture, we load the native entry point for this app.
load()
}
ReactNativeFlipper.initializeFlipper(this, reactNativeHost.reactInstanceManager)
}
}

View File

@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2014 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetLeft="@dimen/abc_edit_text_inset_horizontal_material"
android:insetRight="@dimen/abc_edit_text_inset_horizontal_material"
android:insetTop="@dimen/abc_edit_text_inset_top_material"
android:insetBottom="@dimen/abc_edit_text_inset_bottom_material">
<selector>
<!--
This file is a copy of abc_edit_text_material (https://bit.ly/3k8fX7I).
The item below with state_pressed="false" and state_focused="false" causes a NullPointerException.
NullPointerException:tempt to invoke virtual method 'android.graphics.drawable.Drawable android.graphics.drawable.Drawable$ConstantState.newDrawable(android.content.res.Resources)'
<item android:state_pressed="false" android:state_focused="false" android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
For more info, see https://bit.ly/3CdLStv (react-native/pull/29452) and https://bit.ly/3nxOMoR.
-->
<item android:state_enabled="false" android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
<item android:drawable="@drawable/abc_textfield_activated_mtrl_alpha"/>
</selector>
</inset>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,3 +0,0 @@
<resources>
<string name="app_name">ReactNativeExample</string>
</resources>

View File

@@ -1,9 +0,0 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<!-- Customize your theme here. -->
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
</style>
</resources>

View File

@@ -1,21 +0,0 @@
buildscript {
ext {
buildToolsVersion = "34.0.0"
minSdkVersion = 21
compileSdkVersion = 34
targetSdkVersion = 34
ndkVersion = "25.1.8937393"
kotlinVersion = "1.8.0"
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle")
classpath("com.facebook.react:react-native-gradle-plugin")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")
}
}
apply plugin: "com.facebook.react.rootproject"

View File

@@ -1,41 +0,0 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Use this property to specify which architecture you want to build.
# You can also override it from the CLI using
# ./gradlew <task> -PreactNativeArchitectures=x86_64
reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
# Use this property to enable support to the new architecture.
# This will allow you to use TurboModules and the Fabric render in
# your application. You should enable this flag either if you want
# to write custom TurboModules/Fabric components OR use libraries that
# are providing them.
newArchEnabled=false
# Use this property to enable or disable the Hermes JS engine.
# If set to false, you will be using JSC instead.
hermesEnabled=true

View File

@@ -1,7 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,249 +0,0 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View File

@@ -1,92 +0,0 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -1,4 +0,0 @@
rootProject.name = 'ReactNativeExample'
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
include ':app'
includeBuild('../node_modules/@react-native/gradle-plugin')

View File

@@ -1,4 +0,0 @@
{
"name": "ReactNativeExample",
"displayName": "ReactNativeExample"
}

View File

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

View File

@@ -1,9 +0,0 @@
/**
* @format
*/
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
AppRegistry.registerComponent(appName, () => App);

View File

@@ -1,11 +0,0 @@
# This `.xcode.env` file is versioned and is used to source the environment
# used when running script phases inside Xcode.
# To customize your local environment, you can create an `.xcode.env.local`
# file that is not versioned.
# NODE_BINARY variable contains the PATH to the node executable.
#
# Customize the NODE_BINARY variable here.
# For example, to use nvm with brew, add the following line
# . "$(brew --prefix nvm)/nvm.sh" --no-use
export NODE_BINARY=$(command -v node)

View File

@@ -1,55 +0,0 @@
# Resolve react_native_pods.rb with node to allow for hoisting
require Pod::Executable.execute_command('node', ['-p',
'require.resolve(
"react-native/scripts/react_native_pods.rb",
{paths: [process.argv[1]]},
)', __dir__]).strip
platform :ios, min_ios_version_supported
prepare_react_native_project!
# If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set.
# because `react-native-flipper` depends on (FlipperKit,...) that will be excluded
#
# To fix this you can also exclude `react-native-flipper` using a `react-native.config.js`
# ```js
# module.exports = {
# dependencies: {
# ...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}),
# ```
flipper_config = ENV['NO_FLIPPER'] == "1" ? FlipperConfiguration.disabled : FlipperConfiguration.enabled
linkage = ENV['USE_FRAMEWORKS']
if linkage != nil
Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
use_frameworks! :linkage => linkage.to_sym
end
target 'ReactNativeExample' do
config = use_native_modules!
use_react_native!(
:path => config[:reactNativePath],
# Enables Flipper.
#
# Note that if you have use_frameworks! enabled, Flipper will not work and
# you should disable the next line.
:flipper_configuration => flipper_config,
# An absolute path to your application root.
:app_path => "#{Pod::Config.instance.installation_root}/.."
)
target 'ReactNativeExampleTests' do
inherit! :complete
# Pods for testing
end
post_install do |installer|
# https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202
react_native_post_install(
installer,
config[:reactNativePath],
:mac_catalyst_enabled => false
)
end
end

View File

@@ -1,684 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
00E356F31AD99517003FC87E /* ReactNativeExampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* ReactNativeExampleTests.m */; };
0C80B921A6F3F58F76C31292 /* libPods-ReactNativeExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-ReactNativeExample.a */; };
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
7699B88040F8A987B510C191 /* libPods-ReactNativeExample-ReactNativeExampleTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 19F6CBCC0A4E27FBF8BF4A61 /* libPods-ReactNativeExample-ReactNativeExampleTests.a */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 13B07F861A680F5B00A75B9A;
remoteInfo = ReactNativeExample;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
00E356EE1AD99517003FC87E /* ReactNativeExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactNativeExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
00E356F21AD99517003FC87E /* ReactNativeExampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ReactNativeExampleTests.m; sourceTree = "<group>"; };
13B07F961A680F5B00A75B9A /* ReactNativeExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ReactNativeExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = ReactNativeExample/AppDelegate.h; sourceTree = "<group>"; };
13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = ReactNativeExample/AppDelegate.mm; sourceTree = "<group>"; };
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = ReactNativeExample/Images.xcassets; sourceTree = "<group>"; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ReactNativeExample/Info.plist; sourceTree = "<group>"; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = ReactNativeExample/main.m; sourceTree = "<group>"; };
19F6CBCC0A4E27FBF8BF4A61 /* libPods-ReactNativeExample-ReactNativeExampleTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeExample-ReactNativeExampleTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
3B4392A12AC88292D35C810B /* Pods-ReactNativeExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeExample.debug.xcconfig"; path = "Target Support Files/Pods-ReactNativeExample/Pods-ReactNativeExample.debug.xcconfig"; sourceTree = "<group>"; };
5709B34CF0A7D63546082F79 /* Pods-ReactNativeExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeExample.release.xcconfig"; path = "Target Support Files/Pods-ReactNativeExample/Pods-ReactNativeExample.release.xcconfig"; sourceTree = "<group>"; };
5B7EB9410499542E8C5724F5 /* Pods-ReactNativeExample-ReactNativeExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeExample-ReactNativeExampleTests.debug.xcconfig"; path = "Target Support Files/Pods-ReactNativeExample-ReactNativeExampleTests/Pods-ReactNativeExample-ReactNativeExampleTests.debug.xcconfig"; sourceTree = "<group>"; };
5DCACB8F33CDC322A6C60F78 /* libPods-ReactNativeExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeExample.a"; sourceTree = BUILT_PRODUCTS_DIR; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = ReactNativeExample/LaunchScreen.storyboard; sourceTree = "<group>"; };
89C6BE57DB24E9ADA2F236DE /* Pods-ReactNativeExample-ReactNativeExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeExample-ReactNativeExampleTests.release.xcconfig"; path = "Target Support Files/Pods-ReactNativeExample-ReactNativeExampleTests/Pods-ReactNativeExample-ReactNativeExampleTests.release.xcconfig"; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
00E356EB1AD99517003FC87E /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
7699B88040F8A987B510C191 /* libPods-ReactNativeExample-ReactNativeExampleTests.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
0C80B921A6F3F58F76C31292 /* libPods-ReactNativeExample.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
00E356EF1AD99517003FC87E /* ReactNativeExampleTests */ = {
isa = PBXGroup;
children = (
00E356F21AD99517003FC87E /* ReactNativeExampleTests.m */,
00E356F01AD99517003FC87E /* Supporting Files */,
);
path = ReactNativeExampleTests;
sourceTree = "<group>";
};
00E356F01AD99517003FC87E /* Supporting Files */ = {
isa = PBXGroup;
children = (
00E356F11AD99517003FC87E /* Info.plist */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
13B07FAE1A68108700A75B9A /* ReactNativeExample */ = {
isa = PBXGroup;
children = (
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
13B07FB01A68108700A75B9A /* AppDelegate.mm */,
13B07FB51A68108700A75B9A /* Images.xcassets */,
13B07FB61A68108700A75B9A /* Info.plist */,
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,
13B07FB71A68108700A75B9A /* main.m */,
);
name = ReactNativeExample;
sourceTree = "<group>";
};
2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
isa = PBXGroup;
children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
5DCACB8F33CDC322A6C60F78 /* libPods-ReactNativeExample.a */,
19F6CBCC0A4E27FBF8BF4A61 /* libPods-ReactNativeExample-ReactNativeExampleTests.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
isa = PBXGroup;
children = (
);
name = Libraries;
sourceTree = "<group>";
};
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
13B07FAE1A68108700A75B9A /* ReactNativeExample */,
832341AE1AAA6A7D00B99B32 /* Libraries */,
00E356EF1AD99517003FC87E /* ReactNativeExampleTests */,
83CBBA001A601CBA00E9B192 /* Products */,
2D16E6871FA4F8E400B85C8A /* Frameworks */,
BBD78D7AC51CEA395F1C20DB /* Pods */,
);
indentWidth = 2;
sourceTree = "<group>";
tabWidth = 2;
usesTabs = 0;
};
83CBBA001A601CBA00E9B192 /* Products */ = {
isa = PBXGroup;
children = (
13B07F961A680F5B00A75B9A /* ReactNativeExample.app */,
00E356EE1AD99517003FC87E /* ReactNativeExampleTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
BBD78D7AC51CEA395F1C20DB /* Pods */ = {
isa = PBXGroup;
children = (
3B4392A12AC88292D35C810B /* Pods-ReactNativeExample.debug.xcconfig */,
5709B34CF0A7D63546082F79 /* Pods-ReactNativeExample.release.xcconfig */,
5B7EB9410499542E8C5724F5 /* Pods-ReactNativeExample-ReactNativeExampleTests.debug.xcconfig */,
89C6BE57DB24E9ADA2F236DE /* Pods-ReactNativeExample-ReactNativeExampleTests.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
00E356ED1AD99517003FC87E /* ReactNativeExampleTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "ReactNativeExampleTests" */;
buildPhases = (
A55EABD7B0C7F3A422A6CC61 /* [CP] Check Pods Manifest.lock */,
00E356EA1AD99517003FC87E /* Sources */,
00E356EB1AD99517003FC87E /* Frameworks */,
00E356EC1AD99517003FC87E /* Resources */,
C59DA0FBD6956966B86A3779 /* [CP] Embed Pods Frameworks */,
F6A41C54EA430FDDC6A6ED99 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
00E356F51AD99517003FC87E /* PBXTargetDependency */,
);
name = ReactNativeExampleTests;
productName = ReactNativeExampleTests;
productReference = 00E356EE1AD99517003FC87E /* ReactNativeExampleTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
13B07F861A680F5B00A75B9A /* ReactNativeExample */ = {
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "ReactNativeExample" */;
buildPhases = (
C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */,
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */,
E235C05ADACE081382539298 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
);
name = ReactNativeExample;
productName = ReactNativeExample;
productReference = 13B07F961A680F5B00A75B9A /* ReactNativeExample.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
83CBB9F71A601CBA00E9B192 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1210;
TargetAttributes = {
00E356ED1AD99517003FC87E = {
CreatedOnToolsVersion = 6.2;
TestTargetID = 13B07F861A680F5B00A75B9A;
};
13B07F861A680F5B00A75B9A = {
LastSwiftMigration = 1120;
};
};
};
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "ReactNativeExample" */;
compatibilityVersion = "Xcode 12.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 83CBB9F61A601CBA00E9B192;
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
13B07F861A680F5B00A75B9A /* ReactNativeExample */,
00E356ED1AD99517003FC87E /* ReactNativeExampleTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
00E356EC1AD99517003FC87E /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
13B07F8E1A680F5B00A75B9A /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"$(SRCROOT)/.xcode.env.local",
"$(SRCROOT)/.xcode.env",
);
name = "Bundle React Native code and images";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "set -e\n\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n";
};
00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ReactNativeExample/Pods-ReactNativeExample-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ReactNativeExample/Pods-ReactNativeExample-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeExample/Pods-ReactNativeExample-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
A55EABD7B0C7F3A422A6CC61 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-ReactNativeExample-ReactNativeExampleTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-ReactNativeExample-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
C59DA0FBD6956966B86A3779 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ReactNativeExample-ReactNativeExampleTests/Pods-ReactNativeExample-ReactNativeExampleTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ReactNativeExample-ReactNativeExampleTests/Pods-ReactNativeExample-ReactNativeExampleTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeExample-ReactNativeExampleTests/Pods-ReactNativeExample-ReactNativeExampleTests-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
E235C05ADACE081382539298 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ReactNativeExample/Pods-ReactNativeExample-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ReactNativeExample/Pods-ReactNativeExample-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeExample/Pods-ReactNativeExample-resources.sh\"\n";
showEnvVarsInLog = 0;
};
F6A41C54EA430FDDC6A6ED99 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ReactNativeExample-ReactNativeExampleTests/Pods-ReactNativeExample-ReactNativeExampleTests-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ReactNativeExample-ReactNativeExampleTests/Pods-ReactNativeExample-ReactNativeExampleTests-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeExample-ReactNativeExampleTests/Pods-ReactNativeExample-ReactNativeExampleTests-resources.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
00E356EA1AD99517003FC87E /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
00E356F31AD99517003FC87E /* ReactNativeExampleTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
13B07F871A680F5B00A75B9A /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
00E356F51AD99517003FC87E /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 13B07F861A680F5B00A75B9A /* ReactNativeExample */;
targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
00E356F61AD99517003FC87E /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 5B7EB9410499542E8C5724F5 /* Pods-ReactNativeExample-ReactNativeExampleTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
INFOPLIST_FILE = ReactNativeExampleTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
OTHER_LDFLAGS = (
"-ObjC",
"-lc++",
"$(inherited)",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ReactNativeExample.app/ReactNativeExample";
};
name = Debug;
};
00E356F71AD99517003FC87E /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 89C6BE57DB24E9ADA2F236DE /* Pods-ReactNativeExample-ReactNativeExampleTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
COPY_PHASE_STRIP = NO;
INFOPLIST_FILE = ReactNativeExampleTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
OTHER_LDFLAGS = (
"-ObjC",
"-lc++",
"$(inherited)",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ReactNativeExample.app/ReactNativeExample";
};
name = Release;
};
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 3B4392A12AC88292D35C810B /* Pods-ReactNativeExample.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = ReactNativeExample/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = ReactNativeExample;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 5709B34CF0A7D63546082F79 /* Pods-ReactNativeExample.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
INFOPLIST_FILE = ReactNativeExample/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = ReactNativeExample;
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
83CBBA201A601CBA00E9B192 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
LD_RUNPATH_SEARCH_PATHS = (
/usr/lib/swift,
"$(inherited)",
);
LIBRARY_SEARCH_PATHS = (
"\"$(SDKROOT)/usr/lib/swift\"",
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
"\"$(inherited)\"",
);
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
"-DFOLLY_NO_CONFIG",
"-DFOLLY_MOBILE=1",
"-DFOLLY_USE_LIBCPP=1",
"-DFOLLY_CFG_NO_COROUTINES=1",
);
SDKROOT = iphoneos;
};
name = Debug;
};
83CBBA211A601CBA00E9B192 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
LD_RUNPATH_SEARCH_PATHS = (
/usr/lib/swift,
"$(inherited)",
);
LIBRARY_SEARCH_PATHS = (
"\"$(SDKROOT)/usr/lib/swift\"",
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
"\"$(inherited)\"",
);
MTL_ENABLE_DEBUG_INFO = NO;
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
"-DFOLLY_NO_CONFIG",
"-DFOLLY_MOBILE=1",
"-DFOLLY_USE_LIBCPP=1",
"-DFOLLY_CFG_NO_COROUTINES=1",
);
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "ReactNativeExampleTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
00E356F61AD99517003FC87E /* Debug */,
00E356F71AD99517003FC87E /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "ReactNativeExample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
13B07F941A680F5B00A75B9A /* Debug */,
13B07F951A680F5B00A75B9A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "ReactNativeExample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
83CBBA201A601CBA00E9B192 /* Debug */,
83CBBA211A601CBA00E9B192 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
}

View File

@@ -1,88 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1210"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "ReactNativeExample.app"
BlueprintName = "ReactNativeExample"
ReferencedContainer = "container:ReactNativeExample.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "00E356ED1AD99517003FC87E"
BuildableName = "ReactNativeExampleTests.xctest"
BlueprintName = "ReactNativeExampleTests"
ReferencedContainer = "container:ReactNativeExample.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "ReactNativeExample.app"
BlueprintName = "ReactNativeExample"
ReferencedContainer = "container:ReactNativeExample.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "ReactNativeExample.app"
BlueprintName = "ReactNativeExample"
ReferencedContainer = "container:ReactNativeExample.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -1,6 +0,0 @@
#import <RCTAppDelegate.h>
#import <UIKit/UIKit.h>
@interface AppDelegate : RCTAppDelegate
@end

View File

@@ -1,31 +0,0 @@
#import "AppDelegate.h"
#import <React/RCTBundleURLProvider.h>
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.moduleName = @"ReactNativeExample";
// You can add your custom initial props in the dictionary below.
// They will be passed down to the ViewController used by React Native.
self.initialProps = @{};
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
return [self getBundleURL];
}
- (NSURL *)getBundleURL
{
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}
@end

View File

@@ -1,53 +0,0 @@
{
"images" : [
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1,6 +0,0 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@@ -1,52 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>ReactNativeExample</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<!-- Do not change NSAllowsArbitraryLoads to true, or you will risk app rejection! -->
<key>NSAllowsArbitraryLoads</key>
<false/>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>

View File

@@ -1,47 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="ReactNativeExample" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="GJd-Yh-RWb">
<rect key="frame" x="0.0" y="202" width="375" height="43"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Powered by React Native" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="MN2-I3-ftu">
<rect key="frame" x="0.0" y="626" width="375" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="Bcu-3y-fUS" firstAttribute="bottom" secondItem="MN2-I3-ftu" secondAttribute="bottom" constant="20" id="OZV-Vh-mqD"/>
<constraint firstItem="Bcu-3y-fUS" firstAttribute="centerX" secondItem="GJd-Yh-RWb" secondAttribute="centerX" id="Q3B-4B-g5h"/>
<constraint firstItem="MN2-I3-ftu" firstAttribute="centerX" secondItem="Bcu-3y-fUS" secondAttribute="centerX" id="akx-eg-2ui"/>
<constraint firstItem="MN2-I3-ftu" firstAttribute="leading" secondItem="Bcu-3y-fUS" secondAttribute="leading" id="i1E-0Y-4RG"/>
<constraint firstItem="GJd-Yh-RWb" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="bottom" multiplier="1/3" constant="1" id="moa-c2-u7t"/>
<constraint firstItem="GJd-Yh-RWb" firstAttribute="leading" secondItem="Bcu-3y-fUS" secondAttribute="leading" symbolic="YES" id="x7j-FC-K8j"/>
</constraints>
<viewLayoutGuide key="safeArea" id="Bcu-3y-fUS"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="52.173913043478265" y="375"/>
</scene>
</scenes>
</document>

View File

@@ -1,10 +0,0 @@
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char *argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

View File

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View File

@@ -1,66 +0,0 @@
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import <React/RCTLog.h>
#import <React/RCTRootView.h>
#define TIMEOUT_SECONDS 600
#define TEXT_TO_LOOK_FOR @"Welcome to React"
@interface ReactNativeExampleTests : XCTestCase
@end
@implementation ReactNativeExampleTests
- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL (^)(UIView *view))test
{
if (test(view)) {
return YES;
}
for (UIView *subview in [view subviews]) {
if ([self findSubviewInView:subview matching:test]) {
return YES;
}
}
return NO;
}
- (void)testRendersWelcomeScreen
{
UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController];
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
BOOL foundElement = NO;
__block NSString *redboxError = nil;
#ifdef DEBUG
RCTSetLogFunction(
^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
if (level >= RCTLogLevelError) {
redboxError = message;
}
});
#endif
while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
foundElement = [self findSubviewInView:vc.view
matching:^BOOL(UIView *view) {
if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) {
return YES;
}
return NO;
}];
}
#ifdef DEBUG
RCTSetLogFunction(RCTDefaultLogFunction);
#endif
XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
}
@end

View File

@@ -1,3 +0,0 @@
module.exports = {
preset: 'react-native',
};

View File

@@ -1,11 +0,0 @@
const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
/**
* Metro configuration
* https://facebook.github.io/metro/docs/configuration
*
* @type {import('metro-config').MetroConfig}
*/
const config = {};
module.exports = mergeConfig(getDefaultConfig(__dirname), config);

View File

@@ -1,37 +0,0 @@
{
"name": "ReactNativeExample",
"version": "0.0.1",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"lint": "eslint .",
"start": "react-native start",
"test": "jest"
},
"dependencies": {
"react": "18.2.0",
"react-native": "0.73.6",
"socket.io-client": "^4.7.5"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@babel/preset-env": "^7.20.0",
"@babel/runtime": "^7.20.0",
"@react-native/babel-preset": "0.73.21",
"@react-native/eslint-config": "0.73.2",
"@react-native/metro-config": "0.73.5",
"@react-native/typescript-config": "0.73.1",
"@types/react": "^18.2.6",
"@types/react-test-renderer": "^18.0.0",
"babel-jest": "^29.6.3",
"eslint": "^8.19.0",
"jest": "^29.6.3",
"prettier": "2.8.8",
"react-test-renderer": "18.2.0",
"typescript": "5.0.4"
},
"engines": {
"node": ">=18"
}
}

View File

@@ -1,13 +0,0 @@
import { Server } from 'socket.io';
const io = new Server();
io.on('connection', (socket) => {
console.log(`connect: ${socket.id}`, socket.request.headers);
socket.on('disconnect', () => {
console.log(`disconnect: ${socket.id}`);
});
});
io.listen(3000);

Some files were not shown because too many files have changed in this diff Show More