Compare commits

..

22 Commits

Author SHA1 Message Date
Damien Arrachequesne
41b9a7e45d Release 0.9.19 2017-05-16 07:14:45 +02:00
Damien Arrachequesne
e4d61b1be6 Properly require EventEmitter
One must use `require('events').EventEmitter` in node.js 0.10.x
2017-05-16 07:09:38 +02:00
Damien Arrachequesne
ed74dee3b0 Release 0.9.18 2017-05-07 07:54:20 +02:00
John Chadwick
9ad1fd2771 Remove process.EventEmitter usage for Node 7.x
process.EventEmitter has been deprecated and removed from Node.
2017-05-07 07:50:00 +02:00
Guillermo Rauch
d25c5484c3 Release 0.9.17 2014-05-22 11:03:55 -07:00
Guillermo Rauch
fea676b90e Merge pull request #1371 from surespot/channelfix
Use static channels for node syncing.
2014-05-22 10:49:53 -07:00
Adam Patacchiola
386d2a9c0c don't call non existant transport onDisconnect 2013-12-13 10:02:20 -07:00
Adam Patacchiola
8b47789414 use static channels for remote syncing instead of subscribing/unsubscribing 5 channels for every connection 2013-12-13 07:22:43 -07:00
Guillermo Rauch
47b06c0fcf Merge pull request #1333 from yujiosaka/0.9
http-polling : adding 'X-XSS-Protection : 0;' to headers necessary not o...
2013-12-09 08:17:42 -08:00
Guillermo Rauch
9823325a1f Merge pull request #1346 from DeadAlready/0.9-security-fix
Use destroy buffer size on websocket transport method as well
2013-11-15 04:01:16 -08:00
Karl Düüna
a47d76b990 Use destroy buffer size on websocket transport method as well 2013-11-15 10:31:00 +02:00
yujiosaka
db3ac4b415 http-polling : adding 'X-XSS-Protection : 0;' to headers necessary not only to jsonp-polling but http-polling 2013-10-24 14:53:19 +09:00
Guillermo Rauch
b9a2804b1a Merge pull request #1323 from tico8/0.9-bug
Memory leak : If a lot of connections continue being disconnected, a memory will leak.
2013-10-09 08:32:44 -07:00
tico8
b4182a5d42 Memory leak : If a lot of connections continue being disconnected, a memory will leak. 2013-10-09 01:06:02 +09:00
Guillermo Rauch
5120a706f2 Release 0.9.16 2013-06-06 08:39:48 -07:00
Guillermo Rauch
ee078cb124 transports: added tests for htmlfile escaping/unescaping 2013-06-06 08:38:57 -07:00
Guillermo Rauch
64d8f572aa Release 0.9.15 2013-06-06 08:22:29 -07:00
Guillermo Rauch
4e1ba9f872 transports: escaping (fixes #1251) 2013-06-06 08:17:40 -07:00
Guillermo Rauch
5d93af994a Release 0.9.14 2013-03-29 14:15:52 -07:00
Guillermo Rauch
6e25c802cc manager: fix memory leak with SSL 2013-03-29 14:14:42 -07:00
Guillermo Rauch
37690f78d7 Release 0.9.13 2012-12-13 15:15:38 -03:00
Guillermo Rauch
3cbd00ca70 package: fixed base64id requirement 2012-12-13 15:15:15 -03:00
871 changed files with 18666 additions and 302299 deletions

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

@@ -1,18 +0,0 @@
### The kind of change this PR does introduce
* [x] a bug fix
* [ ] a new feature
* [ ] an update to the documentation
* [ ] a code change that improves performance
* [ ] other
### Current behavior
### New behavior
### 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,66 +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:
- 18
- 20
- 22
services:
redis:
image: redis:7
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
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,37 +0,0 @@
# reference: https://docs.npmjs.com/generating-provenance-statements
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 20
uses: actions/setup-node@v4
with:
node-version: 20
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%@*} --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

5
.gitignore vendored
View File

@@ -8,8 +8,3 @@ lib-cov
*.pid
benchmarks/*.png
node_modules
coverage
.idea
.nyc_output
dist/
build/

3
.npmignore Normal file
View File

@@ -0,0 +1,3 @@
support
test
examples

6
.travis.yml Normal file
View File

@@ -0,0 +1,6 @@
language: node_js
node_js:
- 0.6
notifications:
irc: "irc.freenode.org#socket.io"

View File

@@ -1,15 +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-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) |

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 .
```

342
History.md Normal file
View File

@@ -0,0 +1,342 @@
0.9.19 / 2017-05-16
===================
* Properly require EventEmitter
0.9.18 / 2017-05-07
===================
* Remove process.EventEmitter usage for Node 7.x
0.9.17 / 2014-05-22
===================
* use static channels for remote syncing instead of subscribing/unsubscribing 5 channels for every connection
* Use destroy buffer size on websocket transport method as well
* http-polling : adding 'X-XSS-Protection : 0;' to headers necessary not only to jsonp-polling but http-polling
0.9.16 / 2013-06-06
===================
* transports: added tests for htmlfile escaping/unescaping
0.9.15 / 2013-06-06
===================
* transports: added escaping to htmlfile (fixes #1251)
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) 2011 Guillermo Rauch <guillermo@learnboost.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

31
Makefile Normal file
View File

@@ -0,0 +1,31 @@
ALL_TESTS = $(shell find test/ -name '*.test.js')
ALL_BENCH = $(shell find benchmarks -name '*.bench.js')
run-tests:
@./node_modules/.bin/expresso \
-t 3000 \
-I support \
--serial \
$(TESTFLAGS) \
$(TESTS)
test:
@$(MAKE) NODE_PATH=lib TESTS="$(ALL_TESTS)" run-tests
test-cov:
@TESTFLAGS=--cov $(MAKE) test
test-leaks:
@ls test/leaks/* | xargs node --expose_debug_as=debug --expose_gc
run-bench:
@node $(PROFILEFLAGS) benchmarks/runner.js
bench:
@$(MAKE) BENCHMARKS="$(ALL_BENCH)" run-bench
profile:
@PROFILEFLAGS='--prof --trace-opt --trace-bailout --trace-deopt' $(MAKE) bench
.PHONY: test bench profile

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)

364
Readme.md Normal file
View File

@@ -0,0 +1,364 @@
# Socket.IO
Socket.IO is a Node.JS project that makes WebSockets and realtime possible in
all browsers. It also enhances WebSockets by providing built-in multiplexing,
horizontal scalability, automatic JSON encoding/decoding, and more.
## How to Install
```bash
npm install socket.io
```
## How to use
First, require `socket.io`:
```js
var io = require('socket.io');
```
Next, attach it to a HTTP/HTTPS server. If you're using the fantastic `express`
web framework:
#### Express 3.x
```js
var app = express()
, server = require('http').createServer(app)
, io = io.listen(server);
server.listen(80);
io.sockets.on('connection', function (socket) {
socket.emit('news', { hello: 'world' });
socket.on('my other event', function (data) {
console.log(data);
});
});
```
#### Express 2.x
```js
var app = express.createServer()
, io = io.listen(app);
app.listen(80);
io.sockets.on('connection', function (socket) {
socket.emit('news', { hello: 'world' });
socket.on('my other event', function (data) {
console.log(data);
});
});
```
Finally, load it from the client side code:
```html
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('http://localhost');
socket.on('news', function (data) {
console.log(data);
socket.emit('my other event', { my: 'data' });
});
</script>
```
For more thorough examples, look at the `examples/` directory.
## Short recipes
### Sending and receiving events.
Socket.IO allows you to emit and receive custom events.
Besides `connect`, `message` and `disconnect`, you can emit custom events:
```js
// note, io.listen(<port>) will create a http server for you
var io = require('socket.io').listen(80);
io.sockets.on('connection', function (socket) {
io.sockets.emit('this', { will: 'be received by everyone' });
socket.on('private message', function (from, msg) {
console.log('I received a private message by ', from, ' saying ', msg);
});
socket.on('disconnect', function () {
io.sockets.emit('user disconnected');
});
});
```
### Storing data associated to a client
Sometimes it's necessary to store data associated with a client that's
necessary for the duration of the session.
#### Server side
```js
var io = require('socket.io').listen(80);
io.sockets.on('connection', function (socket) {
socket.on('set nickname', function (name) {
socket.set('nickname', name, function () { socket.emit('ready'); });
});
socket.on('msg', function () {
socket.get('nickname', function (err, name) {
console.log('Chat message by ', name);
});
});
});
```
#### Client side
```html
<script>
var socket = io.connect('http://localhost');
socket.on('connect', function () {
socket.emit('set nickname', prompt('What is your nickname?'));
socket.on('ready', function () {
console.log('Connected !');
socket.emit('msg', prompt('What is your message?'));
});
});
</script>
```
### Restricting yourself to a namespace
If you have control over all the messages and events emitted for a particular
application, using the default `/` namespace works.
If you want to leverage 3rd-party code, or produce code to share with others,
socket.io provides a way of namespacing a `socket`.
This has the benefit of `multiplexing` a single connection. Instead of
socket.io using two `WebSocket` connections, it'll use one.
The following example defines a socket that listens on '/chat' and one for
'/news':
#### Server side
```js
var io = require('socket.io').listen(80);
var chat = io
.of('/chat')
.on('connection', function (socket) {
socket.emit('a message', { that: 'only', '/chat': 'will get' });
chat.emit('a message', { everyone: 'in', '/chat': 'will get' });
});
var news = io
.of('/news');
.on('connection', function (socket) {
socket.emit('item', { news: 'item' });
});
```
#### Client side:
```html
<script>
var chat = io.connect('http://localhost/chat')
, news = io.connect('http://localhost/news');
chat.on('connect', function () {
chat.emit('hi!');
});
news.on('news', function () {
news.emit('woot');
});
</script>
```
### Sending volatile messages.
Sometimes certain messages can be dropped. Let's say you have an app that
shows realtime tweets for the keyword `bieber`.
If a certain client is not ready to receive messages (because of network slowness
or other issues, or because he's connected through long polling and is in the
middle of a request-response cycle), if he doesn't receive ALL the tweets related
to bieber your application won't suffer.
In that case, you might want to send those messages as volatile messages.
#### Server side
```js
var io = require('socket.io').listen(80);
io.sockets.on('connection', function (socket) {
var tweets = setInterval(function () {
getBieberTweet(function (tweet) {
socket.volatile.emit('bieber tweet', tweet);
});
}, 100);
socket.on('disconnect', function () {
clearInterval(tweets);
});
});
```
#### Client side
In the client side, messages are received the same way whether they're volatile
or not.
### Getting acknowledgements
Sometimes, you might want to get a callback when the client confirmed the message
reception.
To do this, simply pass a function as the last parameter of `.send` or `.emit`.
What's more, when you use `.emit`, the acknowledgement is done by you, which
means you can also pass data along:
#### Server side
```js
var io = require('socket.io').listen(80);
io.sockets.on('connection', function (socket) {
socket.on('ferret', function (name, fn) {
fn('woot');
});
});
```
#### Client side
```html
<script>
var socket = io.connect(); // TIP: .connect with no args does auto-discovery
socket.on('connect', function () { // TIP: you can avoid listening on `connect` and listen on events directly too!
socket.emit('ferret', 'tobi', function (data) {
console.log(data); // data will be 'woot'
});
});
</script>
```
### Broadcasting messages
To broadcast, simply add a `broadcast` flag to `emit` and `send` method calls.
Broadcasting means sending a message to everyone else except for the socket
that starts it.
#### Server side
```js
var io = require('socket.io').listen(80);
io.sockets.on('connection', function (socket) {
socket.broadcast.emit('user connected');
socket.broadcast.json.send({ a: 'message' });
});
```
### Rooms
Sometimes you want to put certain sockets in the same room, so that it's easy
to broadcast to all of them together.
Think of this as built-in channels for sockets. Sockets `join` and `leave`
rooms in each socket.
#### Server side
```js
var io = require('socket.io').listen(80);
io.sockets.on('connection', function (socket) {
socket.join('justin bieber fans');
socket.broadcast.to('justin bieber fans').emit('new fan');
io.sockets.in('rammstein fans').emit('new non-fan');
});
```
### Using it just as a cross-browser WebSocket
If you just want the WebSocket semantics, you can do that too.
Simply leverage `send` and listen on the `message` event:
#### Server side
```js
var io = require('socket.io').listen(80);
io.sockets.on('connection', function (socket) {
socket.on('message', function () { });
socket.on('disconnect', function () { });
});
```
#### Client side
```html
<script>
var socket = io.connect('http://localhost/');
socket.on('connect', function () {
socket.send('hi');
socket.on('message', function (msg) {
// my msg
});
});
</script>
```
### Changing configuration
Configuration in socket.io is TJ-style:
#### Server side
```js
var io = require('socket.io').listen(80);
io.configure(function () {
io.set('transports', ['websocket', 'flashsocket', 'xhr-polling']);
});
io.configure('development', function () {
io.set('transports', ['websocket', 'xhr-polling']);
io.enable('log');
});
```
## License
(The MIT License)
Copyright (c) 2011 Guillermo Rauch &lt;guillermo@learnboost.com&gt;
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

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

@@ -0,0 +1,64 @@
/**
* Module dependencies.
*/
var benchmark = require('benchmark')
, colors = require('colors')
, io = require('../')
, parser = io.parser
, suite = new benchmark.Suite('Decode packet');
suite.add('string', function () {
parser.decodePacket('4:::"2"');
});
suite.add('event', function () {
parser.decodePacket('5:::{"name":"woot"}');
});
suite.add('event+ack', function () {
parser.decodePacket('5:1+::{"name":"tobi"}');
});
suite.add('event+data', function () {
parser.decodePacket('5:::{"name":"edwald","args":[{"a": "b"},2,"3"]}');
});
suite.add('heartbeat', function () {
parser.decodePacket('2:::');
});
suite.add('error', function () {
parser.decodePacket('7:::2+0');
});
var payload = parser.encodePayload([
parser.encodePacket({ type: 'message', data: '5', endpoint: '' })
, parser.encodePacket({ type: 'message', data: '53d', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbazfoobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' })
]);
suite.add('payload', function () {
parser.decodePayload(payload);
});
suite.on('cycle', function (bench, details) {
console.log('\n' + suite.name.grey, details.name.white.bold);
console.log([
details.hz.toFixed(2).cyan + ' ops/sec'.grey
, details.count.toString().white + ' times executed'.grey
, 'benchmark took '.grey + details.times.elapsed.toString().white + ' sec.'.grey
,
].join(', '.grey));
});
if (!module.parent) {
suite.run();
} else {
module.exports = suite;
}

View File

@@ -0,0 +1,90 @@
/**
* Module dependencies.
*/
var benchmark = require('benchmark')
, colors = require('colors')
, io = require('../')
, parser = io.parser
, suite = new benchmark.Suite('Encode packet');
suite.add('string', function () {
parser.encodePacket({
type: 'json'
, endpoint: ''
, data: '2'
});
});
suite.add('event', function () {
parser.encodePacket({
type: 'event'
, name: 'woot'
, endpoint: ''
, args: []
});
});
suite.add('event+ack', function () {
parser.encodePacket({
type: 'json'
, id: 1
, ack: 'data'
, endpoint: ''
, data: { a: 'b' }
});
});
suite.add('event+data', function () {
parser.encodePacket({
type: 'event'
, name: 'edwald'
, endpoint: ''
, args: [{a: 'b'}, 2, '3']
});
});
suite.add('heartbeat', function () {
parser.encodePacket({
type: 'heartbeat'
, endpoint: ''
})
});
suite.add('error', function () {
parser.encodePacket({
type: 'error'
, reason: 'unauthorized'
, advice: 'reconnect'
, endpoint: ''
})
})
suite.add('payload', function () {
parser.encodePayload([
parser.encodePacket({ type: 'message', data: '5', endpoint: '' })
, parser.encodePacket({ type: 'message', data: '53d', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbazfoobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' })
]);
});
suite.on('cycle', function (bench, details) {
console.log('\n' + suite.name.grey, details.name.white.bold);
console.log([
details.hz.toFixed(2).cyan + ' ops/sec'.grey
, details.count.toString().white + ' times executed'.grey
, 'benchmark took '.grey + details.times.elapsed.toString().white + ' sec.'.grey
,
].join(', '.grey));
});
if (!module.parent) {
suite.run();
} else {
module.exports = suite;
}

55
benchmarks/runner.js Normal file
View File

@@ -0,0 +1,55 @@
/**
* Benchmark runner dependencies
*/
var colors = require('colors')
, path = require('path');
/**
* Find all the benchmarks
*/
var benchmarks_files = process.env.BENCHMARKS.split(' ')
, all = [].concat(benchmarks_files)
, first = all.shift()
, benchmarks = {};
// find the benchmarks and load them all in our obj
benchmarks_files.forEach(function (file) {
benchmarks[file] = require(path.join(__dirname, '..', file));
});
// setup the complete listeners
benchmarks_files.forEach(function (file) {
var benchmark = benchmarks[file]
, next_file = all.shift()
, next = benchmarks[next_file];
/**
* Generate a oncomplete function for the tests, either we are done or we
* have more benchmarks to process.
*/
function complete () {
if (!next) {
console.log(
'\n\nBenchmark completed in'.grey
, (Date.now() - start).toString().green + ' ms'.grey
);
} else {
console.log('\nStarting benchmark '.grey + next_file.yellow);
next.run();
}
}
// attach the listener
benchmark.on('complete', complete);
});
/**
* Start the benchmark
*/
var start = Date.now();
console.log('Starting benchmark '.grey + first.yellow);
benchmarks[first].run();

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,569 +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 }
);
});
}
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 = new WebSocket(
`${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 = 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=4`);
if (isNodejs) {
socket.on("error", () => {});
}
waitFor(socket, "close");
const socket2 = new WebSocket(
`${WS_URL}/engine.io/?EIO=4&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=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);
});
});
describe("WebSocket", () => {
it("sends and receives a plain text packet", async () => {
const socket = new WebSocket(
`${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 = new WebSocket(
`${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 = new WebSocket(
`${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 = new WebSocket(
`${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 = new WebSocket(
`${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 = new WebSocket(
`${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 = new WebSocket(
`${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 = new WebSocket(
`${WS_URL}/engine.io/?EIO=4&transport=websocket&sid=${sid}`
);
await waitFor(socket, "open");
socket.send("2probe");
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 = new WebSocket(
`${WS_URL}/engine.io/?EIO=4&transport=websocket&sid=${sid}`
);
await waitFor(socket, "open");
socket.send("2probe");
socket.send("5");
const socket2 = new WebSocket(
`${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,636 +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 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 = new WebSocket(
`${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 = new WebSocket(
`${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 = new WebSocket(
`${WS_URL}/socket.io/?transport=websocket`
);
if (isNodejs) {
socket.on("error", () => {});
}
waitFor(socket, "close");
const socket2 = new WebSocket(
`${WS_URL}/socket.io/?EIO=abc&transport=websocket`
);
if (isNodejs) {
socket2.on("error", () => {});
}
waitFor(socket2, "close");
});
it("should fail with an invalid 'transport' query parameter", async () => {
const socket = new WebSocket(`${WS_URL}/socket.io/?EIO=4`);
if (isNodejs) {
socket.on("error", () => {});
}
waitFor(socket, "close");
const socket2 = new WebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=abc`
);
if (isNodejs) {
socket2.on("error", () => {});
}
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 = new WebSocket(
`${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 = new WebSocket(
`${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 = new WebSocket(
`${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 = new WebSocket(
`${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 = new WebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket&sid=${sid}`
);
await waitFor(socket, "open");
socket.send("2probe");
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 = new WebSocket(
`${WS_URL}/socket.io/?EIO=4&transport=websocket&sid=${sid}`
);
await waitFor(socket, "open");
socket.send("2probe");
socket.send("5");
const socket2 = new WebSocket(
`${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 = new WebSocket(
`${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 = new WebSocket(
`${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 = new WebSocket(
`${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 = new WebSocket(
`${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 = new WebSocket(
`${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 = new WebSocket(
`${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 = new WebSocket(
`${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',
};

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