mirror of
https://github.com/socketio/socket.io.git
synced 2026-01-11 16:08:24 -05:00
Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5eaeffc8e2 | ||
|
|
1b6d6de4ed | ||
|
|
0107510ba8 | ||
|
|
b25495c069 | ||
|
|
085d1de9df | ||
|
|
ac9e8ca6c7 | ||
|
|
7de2e87e88 | ||
|
|
225ade062a | ||
|
|
494c64e44f | ||
|
|
67a61e39e6 | ||
|
|
7467216e02 | ||
|
|
7247b4051f | ||
|
|
992c9380c3 | ||
|
|
8b404f424b | ||
|
|
12221f296d | ||
|
|
6f4bd7f8e7 | ||
|
|
4f2e9a716d | ||
|
|
9e8f288ca9 | ||
|
|
86eb4227b2 | ||
|
|
cf873fd831 | ||
|
|
0d10e6131b | ||
|
|
10aafbbc16 | ||
|
|
f34cfca26d | ||
|
|
d412e876b8 | ||
|
|
f05a4a6f82 | ||
|
|
2c883f5d4e | ||
|
|
161091dd4c | ||
|
|
d52532b7be | ||
|
|
6b1d7901db | ||
|
|
b55892ae80 | ||
|
|
233650c222 | ||
|
|
9925746c8e | ||
|
|
de8dffd252 | ||
|
|
f8a66fd11a | ||
|
|
752dfe3b1e | ||
|
|
bf54327421 | ||
|
|
170b739f14 | ||
|
|
230cd19164 | ||
|
|
a0a3481c64 | ||
|
|
f773b4889c | ||
|
|
292d62ea69 | ||
|
|
178e899f48 | ||
|
|
d1bfe40dbb | ||
|
|
81c1f4e819 | ||
|
|
1fba399b17 | ||
|
|
4e6d40493d | ||
|
|
28c7cc0856 | ||
|
|
06a2bd313a | ||
|
|
85ebd356e9 | ||
|
|
9b6f9711da | ||
|
|
43705d7a91 | ||
|
|
118cc686a1 | ||
|
|
c596e54343 | ||
|
|
f7e0009120 | ||
|
|
e69d0ad602 |
32
.github/ISSUE_TEMPLATE.md
vendored
32
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,32 +0,0 @@
|
||||
|
||||
**Note**: for support questions, please use one of these channels: [stackoverflow](http://stackoverflow.com/questions/tagged/socket.io) or [slack](https://socketio.slack.com)
|
||||
|
||||
For bug reports and feature requests for the **Swift client**, please open an issue [there](https://github.com/socketio/socket.io-client-swift).
|
||||
|
||||
For bug reports and feature requests for the **Java client**, please open an issue [there](https://github.com/socketio/socket.io-client-java).
|
||||
|
||||
### You want to:
|
||||
|
||||
* [x] report a *bug*
|
||||
* [ ] request a *feature*
|
||||
|
||||
### Current behaviour
|
||||
|
||||
*What is actually happening?*
|
||||
|
||||
### Steps to reproduce (if the current behaviour is a bug)
|
||||
|
||||
**Note**: the best way (and by that we mean **the only way**) to get a quick answer is to provide a failing test case by forking the following [fiddle](https://github.com/socketio/socket.io-fiddle).
|
||||
|
||||
### Expected behaviour
|
||||
|
||||
*What is expected?*
|
||||
|
||||
### Setup
|
||||
- OS:
|
||||
- browser:
|
||||
- socket.io version:
|
||||
|
||||
### Other information (e.g. stacktraces, related issues, suggestions how to fix)
|
||||
|
||||
|
||||
61
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
61
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: 'bug'
|
||||
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.
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
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
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
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.
|
||||
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -7,10 +7,10 @@
|
||||
* [ ] a code change that improves performance
|
||||
* [ ] other
|
||||
|
||||
### Current behaviour
|
||||
### Current behavior
|
||||
|
||||
|
||||
### New behaviour
|
||||
### New behavior
|
||||
|
||||
|
||||
### Other information (e.g. related issues)
|
||||
|
||||
26
.github/workflows/ci.yml
vendored
Normal file
26
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0'
|
||||
|
||||
jobs:
|
||||
test-node:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [10.x, 12.x, 14.x, 15.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
env:
|
||||
CI: true
|
||||
10
.travis.yml
10
.travis.yml
@@ -1,10 +0,0 @@
|
||||
language: node_js
|
||||
sudo: false
|
||||
node_js:
|
||||
- '10'
|
||||
- '12'
|
||||
- '14'
|
||||
notifications:
|
||||
irc: "irc.freenode.org#socket.io"
|
||||
git:
|
||||
depth: 1
|
||||
77
CHANGELOG.md
77
CHANGELOG.md
@@ -1,3 +1,80 @@
|
||||
# [4.0.0](https://github.com/socketio/socket.io/compare/3.1.2...4.0.0) (2021-03-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* make io.to(...) immutable ([ac9e8ca](https://github.com/socketio/socket.io/commit/ac9e8ca6c71e00d4af45ee03f590fe56f3951186))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add some utility methods ([b25495c](https://github.com/socketio/socket.io/commit/b25495c069031674da08e19aed68922c7c7a0e28))
|
||||
* add support for typed events ([#3822](https://github.com/socketio/socket.io/issues/3822)) ([0107510](https://github.com/socketio/socket.io/commit/0107510ba8a0f148c78029d8be8919b350feb633))
|
||||
* allow to exclude specific rooms when broadcasting ([#3789](https://github.com/socketio/socket.io/issues/3789)) ([7de2e87](https://github.com/socketio/socket.io/commit/7de2e87e888d849eb2dfc5e362af4c9e86044701))
|
||||
* allow to pass an array to io.to(...) ([085d1de](https://github.com/socketio/socket.io/commit/085d1de9df909651de8b313cc6f9f253374b702e))
|
||||
|
||||
|
||||
## [3.1.2](https://github.com/socketio/socket.io/compare/3.1.1...3.1.2) (2021-02-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* ignore packets received after disconnection ([494c64e](https://github.com/socketio/socket.io/commit/494c64e44f645cbd24c645f1186d203789e84af0))
|
||||
|
||||
|
||||
## [3.1.1](https://github.com/socketio/socket.io/compare/3.1.0...3.1.1) (2021-02-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* properly parse the CONNECT packet in v2 compatibility mode ([6f4bd7f](https://github.com/socketio/socket.io/commit/6f4bd7f8e7c41a075a8014565330a77c38b03a8d))
|
||||
* **typings:** add return types and general-case overload signatures ([#3776](https://github.com/socketio/socket.io/issues/3776)) ([9e8f288](https://github.com/socketio/socket.io/commit/9e8f288ca9f14f91064b8d3cce5946f7d23d407c))
|
||||
* **typings:** update the types of "query", "auth" and "headers" ([4f2e9a7](https://github.com/socketio/socket.io/commit/4f2e9a716d9835b550c8fd9a9b429ebf069c2895))
|
||||
|
||||
|
||||
# [3.1.0](https://github.com/socketio/socket.io/compare/3.0.5...3.1.0) (2021-01-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* confirm a weak but matching ETag ([#3485](https://github.com/socketio/socket.io/issues/3485)) ([161091d](https://github.com/socketio/socket.io/commit/161091dd4c9e1b1610ac3d45d964195e63d92b94))
|
||||
* **esm:** export the Namespace and Socket class ([#3699](https://github.com/socketio/socket.io/issues/3699)) ([233650c](https://github.com/socketio/socket.io/commit/233650c22209708b5fccc4349c38d2fa1b465d8f))
|
||||
* add support for Socket.IO v2 clients ([9925746](https://github.com/socketio/socket.io/commit/9925746c8ee3a6522bd640b5d586c83f04f2f1ba))
|
||||
* add room events ([155fa63](https://github.com/socketio/socket.io-adapter/commit/155fa6333a504036e99a33667dc0397f6aede25e))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow integers as event names ([1c220dd](https://github.com/socketio/socket.io-parser/commit/1c220ddbf45ea4b44bc8dbf6f9ae245f672ba1b9))
|
||||
|
||||
|
||||
## [3.0.5](https://github.com/socketio/socket.io/compare/3.0.4...3.0.5) (2021-01-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* properly clear timeout on connection failure ([170b739](https://github.com/socketio/socket.io/commit/170b739f147cb6c92b423729b877e242e376927d))
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* restore the socket middleware functionality ([bf54327](https://github.com/socketio/socket.io/commit/bf5432742158e4d5ba2722cff4a614967dffa5b9))
|
||||
|
||||
|
||||
## [3.0.4](https://github.com/socketio/socket.io/compare/3.0.3...3.0.4) (2020-12-07)
|
||||
|
||||
|
||||
## [3.0.3](https://github.com/socketio/socket.io/compare/3.0.2...3.0.3) (2020-11-19)
|
||||
|
||||
|
||||
## [3.0.2](https://github.com/socketio/socket.io/compare/3.0.1...3.0.2) (2020-11-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* merge Engine.IO options ([43705d7](https://github.com/socketio/socket.io/commit/43705d7a9149833afc69edc937ea7f8c9aabfeef))
|
||||
|
||||
|
||||
## [3.0.1](https://github.com/socketio/socket.io/compare/3.0.0...3.0.1) (2020-11-09)
|
||||
|
||||
|
||||
|
||||
13
Readme.md
13
Readme.md
@@ -1,8 +1,7 @@
|
||||
|
||||
# socket.io
|
||||
|
||||
[](https://repl.it/github/socketio/socket.io)
|
||||
[](#backers) [](#sponsors)
|
||||
[](https://travis-ci.org/socketio/socket.io)
|
||||
[](https://github.com/socketio/socket.io/actions)
|
||||
[](https://david-dm.org/socketio/socket.io)
|
||||
[](https://david-dm.org/socketio/socket.io#info=devDependencies)
|
||||
[](https://www.npmjs.com/package/socket.io)
|
||||
@@ -22,6 +21,8 @@ Some implementations in other languages are also available:
|
||||
- [C++](https://github.com/socketio/socket.io-client-cpp)
|
||||
- [Swift](https://github.com/socketio/socket.io-client-swift)
|
||||
- [Dart](https://github.com/rikulo/socket.io-client-dart)
|
||||
- [Python](https://github.com/miguelgrinberg/python-socketio)
|
||||
- [.Net](https://github.com/Quobject/SocketIoClientDotNet)
|
||||
|
||||
Its main features are:
|
||||
|
||||
@@ -35,7 +36,7 @@ For this purpose, it relies on [Engine.IO](https://github.com/socketio/engine.io
|
||||
|
||||
#### Auto-reconnection support
|
||||
|
||||
Unless instructed otherwise a disconnected client will try to reconnect forever, until the server is available again. Please see the available reconnection options [here](https://github.com/socketio/socket.io-client/blob/master/docs/API.md#new-managerurl-options).
|
||||
Unless instructed otherwise a disconnected client will try to reconnect forever, until the server is available again. Please see the available reconnection options [here](https://socket.io/docs/v3/client-api/#new-Manager-url-options).
|
||||
|
||||
#### Disconnection detection
|
||||
|
||||
@@ -84,7 +85,11 @@ This is a useful feature to send notifications to a group of users, or to a give
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
// with npm
|
||||
npm install socket.io
|
||||
|
||||
// with yarn
|
||||
yarn add socket.io
|
||||
```
|
||||
|
||||
## How to use
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
6
client-dist/socket.io.min.js
vendored
6
client-dist/socket.io.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
6
client-dist/socket.io.msgpack.min.js
vendored
6
client-dist/socket.io.msgpack.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
17
examples/angular-todomvc/.browserslistrc
Normal file
17
examples/angular-todomvc/.browserslistrc
Normal file
@@ -0,0 +1,17 @@
|
||||
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
|
||||
# For additional information regarding the format and rule options, please see:
|
||||
# https://github.com/browserslist/browserslist#queries
|
||||
|
||||
# For the full list of supported browsers by the Angular framework, please see:
|
||||
# https://angular.io/guide/browser-support
|
||||
|
||||
# You can see what browsers were selected by your queries by running:
|
||||
# npx browserslist
|
||||
|
||||
last 1 Chrome version
|
||||
last 1 Firefox version
|
||||
last 2 Edge major versions
|
||||
last 2 Safari major versions
|
||||
last 2 iOS major versions
|
||||
Firefox ESR
|
||||
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
|
||||
16
examples/angular-todomvc/.editorconfig
Normal file
16
examples/angular-todomvc/.editorconfig
Normal file
@@ -0,0 +1,16 @@
|
||||
# Editor configuration, see https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.ts]
|
||||
quote_type = single
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
46
examples/angular-todomvc/.gitignore
vendored
Normal file
46
examples/angular-todomvc/.gitignore
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
# Only exists if Bazel was run
|
||||
/bazel-out
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# profiling files
|
||||
chrome-profiler-events*.json
|
||||
speed-measure-plugin*.json
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
35
examples/angular-todomvc/README.md
Normal file
35
examples/angular-todomvc/README.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Angular TodoMVC + Socket.IO
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 11.0.4.
|
||||
|
||||
Inspired from the [TodoMVC](http://todomvc.com/) [angular example](https://github.com/tastejs/todomvc/tree/master/examples/angular2).
|
||||
|
||||

|
||||
|
||||
## Development server
|
||||
|
||||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
|
||||
|
||||
## Socket.IO server
|
||||
|
||||
Run `npm run start:server` to start the Socket.IO server.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Running end-to-end tests
|
||||
|
||||
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
|
||||
128
examples/angular-todomvc/angular.json
Normal file
128
examples/angular-todomvc/angular.json
Normal file
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"angular-todomvc": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:application": {
|
||||
"strict": true
|
||||
}
|
||||
},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/angular-todomvc",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"aot": true,
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"namedChunks": false,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kb",
|
||||
"maximumError": "1mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "4kb"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "angular-todomvc:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "angular-todomvc:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "angular-todomvc:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"karmaConfig": "karma.conf.js",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": []
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"tsconfig.app.json",
|
||||
"tsconfig.spec.json",
|
||||
"e2e/tsconfig.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
},
|
||||
"e2e": {
|
||||
"builder": "@angular-devkit/build-angular:protractor",
|
||||
"options": {
|
||||
"protractorConfig": "e2e/protractor.conf.js",
|
||||
"devServerTarget": "angular-todomvc:serve"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "angular-todomvc:serve:production"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "angular-todomvc"
|
||||
}
|
||||
BIN
examples/angular-todomvc/assets/demo.gif
Normal file
BIN
examples/angular-todomvc/assets/demo.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 205 KiB |
37
examples/angular-todomvc/e2e/protractor.conf.js
Normal file
37
examples/angular-todomvc/e2e/protractor.conf.js
Normal file
@@ -0,0 +1,37 @@
|
||||
// @ts-check
|
||||
// Protractor configuration file, see link for more information
|
||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
||||
|
||||
const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter');
|
||||
|
||||
/**
|
||||
* @type { import("protractor").Config }
|
||||
*/
|
||||
exports.config = {
|
||||
allScriptsTimeout: 11000,
|
||||
specs: [
|
||||
'./src/**/*.e2e-spec.ts'
|
||||
],
|
||||
capabilities: {
|
||||
browserName: 'chrome'
|
||||
},
|
||||
directConnect: true,
|
||||
SELENIUM_PROMISE_MANAGER: false,
|
||||
baseUrl: 'http://localhost:4200/',
|
||||
framework: 'jasmine',
|
||||
jasmineNodeOpts: {
|
||||
showColors: true,
|
||||
defaultTimeoutInterval: 30000,
|
||||
print: function() {}
|
||||
},
|
||||
onPrepare() {
|
||||
require('ts-node').register({
|
||||
project: require('path').join(__dirname, './tsconfig.json')
|
||||
});
|
||||
jasmine.getEnv().addReporter(new SpecReporter({
|
||||
spec: {
|
||||
displayStacktrace: StacktraceOption.PRETTY
|
||||
}
|
||||
}));
|
||||
}
|
||||
};
|
||||
23
examples/angular-todomvc/e2e/src/app.e2e-spec.ts
Normal file
23
examples/angular-todomvc/e2e/src/app.e2e-spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { AppPage } from './app.po';
|
||||
import { browser, logging } from 'protractor';
|
||||
|
||||
describe('workspace-project App', () => {
|
||||
let page: AppPage;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new AppPage();
|
||||
});
|
||||
|
||||
it('should display welcome message', async () => {
|
||||
await page.navigateTo();
|
||||
expect(await page.getTitleText()).toEqual('angular-todomvc app is running!');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Assert that there are no errors emitted from the browser
|
||||
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
|
||||
expect(logs).not.toContain(jasmine.objectContaining({
|
||||
level: logging.Level.SEVERE,
|
||||
} as logging.Entry));
|
||||
});
|
||||
});
|
||||
11
examples/angular-todomvc/e2e/src/app.po.ts
Normal file
11
examples/angular-todomvc/e2e/src/app.po.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { browser, by, element } from 'protractor';
|
||||
|
||||
export class AppPage {
|
||||
async navigateTo(): Promise<unknown> {
|
||||
return browser.get(browser.baseUrl);
|
||||
}
|
||||
|
||||
async getTitleText(): Promise<string> {
|
||||
return element(by.css('app-root .content span')).getText();
|
||||
}
|
||||
}
|
||||
13
examples/angular-todomvc/e2e/tsconfig.json
Normal file
13
examples/angular-todomvc/e2e/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/e2e",
|
||||
"module": "commonjs",
|
||||
"target": "es2018",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
||||
44
examples/angular-todomvc/karma.conf.js
Normal file
44
examples/angular-todomvc/karma.conf.js
Normal file
@@ -0,0 +1,44 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage'),
|
||||
require('@angular-devkit/build-angular/plugins/karma')
|
||||
],
|
||||
client: {
|
||||
jasmine: {
|
||||
// you can add configuration options for Jasmine here
|
||||
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
||||
// for example, you can disable the random execution with `random: false`
|
||||
// or set a specific seed with `seed: 4321`
|
||||
},
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
jasmineHtmlReporter: {
|
||||
suppressAll: true // removes the duplicated traces
|
||||
},
|
||||
coverageReporter: {
|
||||
dir: require('path').join(__dirname, './coverage/angular-todomvc'),
|
||||
subdir: '.',
|
||||
reporters: [
|
||||
{ type: 'html' },
|
||||
{ type: 'text-summary' }
|
||||
]
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false,
|
||||
restartOnFileChange: true
|
||||
});
|
||||
};
|
||||
13694
examples/angular-todomvc/package-lock.json
generated
Normal file
13694
examples/angular-todomvc/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
48
examples/angular-todomvc/package.json
Normal file
48
examples/angular-todomvc/package.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "angular-todomvc",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e",
|
||||
"start:server": "ts-node -O '{\"module\":\"commonjs\"}' server.ts"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "~11.0.4",
|
||||
"@angular/common": "~11.0.4",
|
||||
"@angular/compiler": "~11.0.4",
|
||||
"@angular/core": "~11.0.4",
|
||||
"@angular/forms": "~11.0.4",
|
||||
"@angular/platform-browser": "~11.0.4",
|
||||
"@angular/platform-browser-dynamic": "~11.0.4",
|
||||
"@angular/router": "~11.0.4",
|
||||
"rxjs": "~6.6.0",
|
||||
"socket.io": "^3.0.4",
|
||||
"socket.io-client": "^3.0.4",
|
||||
"tslib": "^2.0.0",
|
||||
"zone.js": "~0.10.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~0.1100.4",
|
||||
"@angular/cli": "~11.0.4",
|
||||
"@angular/compiler-cli": "~11.0.4",
|
||||
"@types/jasmine": "~3.6.0",
|
||||
"@types/node": "^12.11.1",
|
||||
"codelyzer": "^6.0.0",
|
||||
"jasmine-core": "~3.6.0",
|
||||
"jasmine-spec-reporter": "~5.0.0",
|
||||
"karma": "~5.1.0",
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
"karma-coverage": "~2.0.3",
|
||||
"karma-jasmine": "~4.0.0",
|
||||
"karma-jasmine-html-reporter": "^1.5.0",
|
||||
"protractor": "~7.0.0",
|
||||
"ts-node": "~8.3.0",
|
||||
"tslint": "~6.1.0",
|
||||
"typescript": "~4.0.2"
|
||||
}
|
||||
}
|
||||
28
examples/angular-todomvc/server.ts
Normal file
28
examples/angular-todomvc/server.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Server, Socket } from "socket.io";
|
||||
|
||||
const io = new Server(8080, {
|
||||
cors: {
|
||||
origin: "http://localhost:4200",
|
||||
methods: ["GET", "POST"]
|
||||
}
|
||||
});
|
||||
|
||||
interface Todo {
|
||||
completed: boolean;
|
||||
editing: boolean;
|
||||
title: string;
|
||||
}
|
||||
|
||||
let todos: Array<Todo> = [];
|
||||
|
||||
io.on("connect", (socket: Socket) => {
|
||||
socket.emit("todos", todos);
|
||||
|
||||
// note: we could also create a CRUD (create/read/update/delete) service for the todo list
|
||||
socket.on("update-store", (updatedTodos) => {
|
||||
// store it locally
|
||||
todos = updatedTodos;
|
||||
// broadcast to everyone but the sender
|
||||
socket.broadcast.emit("todos", todos);
|
||||
});
|
||||
});
|
||||
0
examples/angular-todomvc/src/app/app.component.css
Normal file
0
examples/angular-todomvc/src/app/app.component.css
Normal file
23
examples/angular-todomvc/src/app/app.component.html
Normal file
23
examples/angular-todomvc/src/app/app.component.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<section class="todoapp">
|
||||
<header class="header">
|
||||
<h1>todos</h1>
|
||||
<input class="new-todo" placeholder="What needs to be done?" autofocus="" [(ngModel)]="newTodoText" (keyup.enter)="addTodo()">
|
||||
</header>
|
||||
<section class="main" *ngIf="todoStore.todos.length > 0">
|
||||
<input id="toggle-all" class="toggle-all" type="checkbox" *ngIf="todoStore.todos.length" #toggleall [checked]="todoStore.allCompleted()" (click)="todoStore.setAllTo(toggleall.checked)">
|
||||
<ul class="todo-list">
|
||||
<li *ngFor="let todo of todoStore.todos" [class.completed]="todo.completed" [class.editing]="todo.editing">
|
||||
<div class="view">
|
||||
<input class="toggle" type="checkbox" (click)="toggleCompletion(todo)" [checked]="todo.completed">
|
||||
<label (dblclick)="editTodo(todo)">{{todo.title}}</label>
|
||||
<button class="destroy" (click)="remove(todo)"></button>
|
||||
</div>
|
||||
<input class="edit" *ngIf="todo.editing" [value]="todo.title" #editedtodo (blur)="stopEditing(todo, editedtodo.value)" (keyup.enter)="updateEditingTodo(todo, editedtodo.value)" (keyup.escape)="cancelEditingTodo(todo)">
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<footer class="footer" *ngIf="todoStore.todos.length > 0">
|
||||
<span class="todo-count"><strong>{{todoStore.getRemaining().length}}</strong> {{todoStore.getRemaining().length == 1 ? 'item' : 'items'}} left</span>
|
||||
<button class="clear-completed" *ngIf="todoStore.getCompleted().length > 0" (click)="removeCompleted()">Clear completed</button>
|
||||
</footer>
|
||||
</section>
|
||||
31
examples/angular-todomvc/src/app/app.component.spec.ts
Normal file
31
examples/angular-todomvc/src/app/app.component.spec.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have as title 'angular-todomvc'`, () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app.title).toEqual('angular-todomvc');
|
||||
});
|
||||
|
||||
it('should render title', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement;
|
||||
expect(compiled.querySelector('.content span').textContent).toContain('angular-todomvc app is running!');
|
||||
});
|
||||
});
|
||||
59
examples/angular-todomvc/src/app/app.component.ts
Normal file
59
examples/angular-todomvc/src/app/app.component.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { RemoteTodoStore, Todo } from './store';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent {
|
||||
todoStore: RemoteTodoStore;
|
||||
newTodoText = '';
|
||||
|
||||
constructor(todoStore: RemoteTodoStore) {
|
||||
this.todoStore = todoStore;
|
||||
}
|
||||
|
||||
stopEditing(todo: Todo, editedTitle: string) {
|
||||
todo.title = editedTitle;
|
||||
todo.editing = false;
|
||||
}
|
||||
|
||||
cancelEditingTodo(todo: Todo) {
|
||||
todo.editing = false;
|
||||
}
|
||||
|
||||
updateEditingTodo(todo: Todo, editedTitle: string) {
|
||||
editedTitle = editedTitle.trim();
|
||||
todo.editing = false;
|
||||
|
||||
if (editedTitle.length === 0) {
|
||||
return this.todoStore.remove(todo);
|
||||
}
|
||||
|
||||
todo.title = editedTitle;
|
||||
}
|
||||
|
||||
editTodo(todo: Todo) {
|
||||
todo.editing = true;
|
||||
}
|
||||
|
||||
removeCompleted() {
|
||||
this.todoStore.removeCompleted();
|
||||
}
|
||||
|
||||
toggleCompletion(todo: Todo) {
|
||||
this.todoStore.toggleCompletion(todo);
|
||||
}
|
||||
|
||||
remove(todo: Todo){
|
||||
this.todoStore.remove(todo);
|
||||
}
|
||||
|
||||
addTodo() {
|
||||
if (this.newTodoText.trim().length) {
|
||||
this.todoStore.add(this.newTodoText);
|
||||
this.newTodoText = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
19
examples/angular-todomvc/src/app/app.module.ts
Normal file
19
examples/angular-todomvc/src/app/app.module.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { RemoteTodoStore } from './store';
|
||||
import { FormsModule } from "@angular/forms";
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
FormsModule
|
||||
],
|
||||
providers: [RemoteTodoStore],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
||||
95
examples/angular-todomvc/src/app/store.ts
Normal file
95
examples/angular-todomvc/src/app/store.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { io, Socket } from "socket.io-client";
|
||||
|
||||
export class Todo {
|
||||
completed: boolean;
|
||||
editing: boolean;
|
||||
|
||||
private _title: String = "";
|
||||
get title() {
|
||||
return this._title;
|
||||
}
|
||||
set title(value: String) {
|
||||
this._title = value.trim();
|
||||
}
|
||||
|
||||
constructor(title: String) {
|
||||
this.completed = false;
|
||||
this.editing = false;
|
||||
this.title = title.trim();
|
||||
}
|
||||
}
|
||||
|
||||
export class TodoStore {
|
||||
todos: Array<Todo>;
|
||||
|
||||
constructor() {
|
||||
let persistedTodos = JSON.parse(localStorage.getItem('angular2-todos') || '[]');
|
||||
// Normalize back into classes
|
||||
this.todos = persistedTodos.map( (todo: {_title: String, completed: boolean}) => {
|
||||
let ret = new Todo(todo._title);
|
||||
ret.completed = todo.completed;
|
||||
return ret;
|
||||
});
|
||||
}
|
||||
|
||||
protected updateStore() {
|
||||
localStorage.setItem('angular2-todos', JSON.stringify(this.todos));
|
||||
}
|
||||
|
||||
private getWithCompleted(completed: boolean) {
|
||||
return this.todos.filter((todo: Todo) => todo.completed === completed);
|
||||
}
|
||||
|
||||
allCompleted() {
|
||||
return this.todos.length === this.getCompleted().length;
|
||||
}
|
||||
|
||||
setAllTo(completed: boolean) {
|
||||
this.todos.forEach((t: Todo) => t.completed = completed);
|
||||
this.updateStore();
|
||||
}
|
||||
|
||||
removeCompleted() {
|
||||
this.todos = this.getWithCompleted(false);
|
||||
this.updateStore();
|
||||
}
|
||||
|
||||
getRemaining() {
|
||||
return this.getWithCompleted(false);
|
||||
}
|
||||
|
||||
getCompleted() {
|
||||
return this.getWithCompleted(true);
|
||||
}
|
||||
|
||||
toggleCompletion(todo: Todo) {
|
||||
todo.completed = !todo.completed;
|
||||
this.updateStore();
|
||||
}
|
||||
|
||||
remove(todo: Todo) {
|
||||
this.todos.splice(this.todos.indexOf(todo), 1);
|
||||
this.updateStore();
|
||||
}
|
||||
|
||||
add(title: String) {
|
||||
this.todos.push(new Todo(title));
|
||||
this.updateStore();
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoteTodoStore extends TodoStore {
|
||||
private socket: Socket;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.socket = io("http://localhost:8080");
|
||||
this.socket.on("todos", (updatedTodos: Array<Todo>) => {
|
||||
this.todos = updatedTodos;
|
||||
});
|
||||
}
|
||||
|
||||
protected updateStore() {
|
||||
this.socket.emit("update-store", this.todos.map(({ title, editing, completed }) => ({ title, editing, completed })));
|
||||
}
|
||||
}
|
||||
0
examples/angular-todomvc/src/assets/.gitkeep
Normal file
0
examples/angular-todomvc/src/assets/.gitkeep
Normal file
@@ -0,0 +1,3 @@
|
||||
export const environment = {
|
||||
production: true
|
||||
};
|
||||
16
examples/angular-todomvc/src/environments/environment.ts
Normal file
16
examples/angular-todomvc/src/environments/environment.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
// This file can be replaced during build by using the `fileReplacements` array.
|
||||
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
|
||||
// The list of file replacements can be found in `angular.json`.
|
||||
|
||||
export const environment = {
|
||||
production: false
|
||||
};
|
||||
|
||||
/*
|
||||
* For easier debugging in development mode, you can import the following file
|
||||
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
|
||||
*
|
||||
* This import should be commented out in production mode because it will have a negative impact
|
||||
* on performance if an error is thrown.
|
||||
*/
|
||||
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
|
||||
BIN
examples/angular-todomvc/src/favicon.ico
Normal file
BIN
examples/angular-todomvc/src/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 948 B |
13
examples/angular-todomvc/src/index.html
Normal file
13
examples/angular-todomvc/src/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Angular Todo MVC</title>
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
12
examples/angular-todomvc/src/main.ts
Normal file
12
examples/angular-todomvc/src/main.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||
.catch(err => console.error(err));
|
||||
63
examples/angular-todomvc/src/polyfills.ts
Normal file
63
examples/angular-todomvc/src/polyfills.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* This file includes polyfills needed by Angular and is loaded before the app.
|
||||
* You can add your own extra polyfills to this file.
|
||||
*
|
||||
* This file is divided into 2 sections:
|
||||
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
|
||||
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
|
||||
* file.
|
||||
*
|
||||
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
|
||||
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
|
||||
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
|
||||
*
|
||||
* Learn more in https://angular.io/guide/browser-support
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* BROWSER POLYFILLS
|
||||
*/
|
||||
|
||||
/** IE11 requires the following for NgClass support on SVG elements */
|
||||
// import 'classlist.js'; // Run `npm install --save classlist.js`.
|
||||
|
||||
/**
|
||||
* Web Animations `@angular/platform-browser/animations`
|
||||
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
|
||||
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
|
||||
*/
|
||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
|
||||
/**
|
||||
* By default, zone.js will patch all possible macroTask and DomEvents
|
||||
* user can disable parts of macroTask/DomEvents patch by setting following flags
|
||||
* because those flags need to be set before `zone.js` being loaded, and webpack
|
||||
* will put import in the top of bundle, so user need to create a separate file
|
||||
* in this directory (for example: zone-flags.ts), and put the following flags
|
||||
* into that file, and then add the following code before importing zone.js.
|
||||
* import './zone-flags';
|
||||
*
|
||||
* The flags allowed in zone-flags.ts are listed here.
|
||||
*
|
||||
* The following flags will work for all browsers.
|
||||
*
|
||||
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
|
||||
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
|
||||
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
|
||||
*
|
||||
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
|
||||
* with the following flag, it will bypass `zone.js` patch for IE/Edge
|
||||
*
|
||||
* (window as any).__Zone_enable_cross_context_check = true;
|
||||
*
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Zone JS is required by default for Angular itself.
|
||||
*/
|
||||
import 'zone.js/dist/zone'; // Included with Angular CLI.
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
* APPLICATION IMPORTS
|
||||
*/
|
||||
381
examples/angular-todomvc/src/styles.css
Normal file
381
examples/angular-todomvc/src/styles.css
Normal file
@@ -0,0 +1,381 @@
|
||||
/* imported from node_modules/todomvc-app-css/index.css */
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
font-size: 100%;
|
||||
vertical-align: baseline;
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
color: inherit;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
line-height: 1.4em;
|
||||
background: #f5f5f5;
|
||||
color: #111111;
|
||||
min-width: 230px;
|
||||
max-width: 550px;
|
||||
margin: 0 auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.todoapp {
|
||||
background: #fff;
|
||||
margin: 130px 0 40px 0;
|
||||
position: relative;
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
|
||||
0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.todoapp input::-webkit-input-placeholder {
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.todoapp input::-moz-placeholder {
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.todoapp input::input-placeholder {
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.todoapp h1 {
|
||||
position: absolute;
|
||||
top: -140px;
|
||||
width: 100%;
|
||||
font-size: 80px;
|
||||
font-weight: 200;
|
||||
text-align: center;
|
||||
color: #b83f45;
|
||||
-webkit-text-rendering: optimizeLegibility;
|
||||
-moz-text-rendering: optimizeLegibility;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
.new-todo,
|
||||
.edit {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
font-size: 24px;
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
line-height: 1.4em;
|
||||
color: inherit;
|
||||
padding: 6px;
|
||||
border: 1px solid #999;
|
||||
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
|
||||
box-sizing: border-box;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.new-todo {
|
||||
padding: 16px 16px 16px 60px;
|
||||
border: none;
|
||||
background: rgba(0, 0, 0, 0.003);
|
||||
box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
|
||||
}
|
||||
|
||||
.main {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
border-top: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.toggle-all {
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
border: none; /* Mobile Safari */
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
right: 100%;
|
||||
bottom: 100%;
|
||||
}
|
||||
|
||||
.toggle-all + label {
|
||||
width: 60px;
|
||||
height: 34px;
|
||||
font-size: 0;
|
||||
position: absolute;
|
||||
top: -52px;
|
||||
left: -13px;
|
||||
-webkit-transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.toggle-all + label:before {
|
||||
content: '❯';
|
||||
font-size: 22px;
|
||||
color: #e6e6e6;
|
||||
padding: 10px 27px 10px 27px;
|
||||
}
|
||||
|
||||
.toggle-all:checked + label:before {
|
||||
color: #737373;
|
||||
}
|
||||
|
||||
.todo-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.todo-list li {
|
||||
position: relative;
|
||||
font-size: 24px;
|
||||
border-bottom: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.todo-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.todo-list li.editing {
|
||||
border-bottom: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.todo-list li.editing .edit {
|
||||
display: block;
|
||||
width: calc(100% - 43px);
|
||||
padding: 12px 16px;
|
||||
margin: 0 0 0 43px;
|
||||
}
|
||||
|
||||
.todo-list li.editing .view {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.todo-list li .toggle {
|
||||
text-align: center;
|
||||
width: 40px;
|
||||
/* auto, since non-WebKit browsers doesn't support input styling */
|
||||
height: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto 0;
|
||||
border: none; /* Mobile Safari */
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.todo-list li .toggle {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.todo-list li .toggle + label {
|
||||
/*
|
||||
Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
|
||||
IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/
|
||||
*/
|
||||
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center left;
|
||||
}
|
||||
|
||||
.todo-list li .toggle:checked + label {
|
||||
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');
|
||||
}
|
||||
|
||||
.todo-list li label {
|
||||
word-break: break-all;
|
||||
padding: 15px 15px 15px 60px;
|
||||
display: block;
|
||||
line-height: 1.2;
|
||||
transition: color 0.4s;
|
||||
font-weight: 400;
|
||||
color: #4d4d4d;
|
||||
}
|
||||
|
||||
.todo-list li.completed label {
|
||||
color: #cdcdcd;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.todo-list li .destroy {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 10px;
|
||||
bottom: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin: auto 0;
|
||||
font-size: 30px;
|
||||
color: #cc9a9a;
|
||||
margin-bottom: 11px;
|
||||
transition: color 0.2s ease-out;
|
||||
}
|
||||
|
||||
.todo-list li .destroy:hover {
|
||||
color: #af5b5e;
|
||||
}
|
||||
|
||||
.todo-list li .destroy:after {
|
||||
content: '×';
|
||||
}
|
||||
|
||||
.todo-list li:hover .destroy {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.todo-list li .edit {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.todo-list li.editing:last-child {
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 10px 15px;
|
||||
height: 20px;
|
||||
text-align: center;
|
||||
font-size: 15px;
|
||||
border-top: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.footer:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 50px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
|
||||
0 8px 0 -3px #f6f6f6,
|
||||
0 9px 1px -3px rgba(0, 0, 0, 0.2),
|
||||
0 16px 0 -6px #f6f6f6,
|
||||
0 17px 2px -6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.todo-count {
|
||||
float: left;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.todo-count strong {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.filters {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.filters li {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.filters li a {
|
||||
color: inherit;
|
||||
margin: 3px;
|
||||
padding: 3px 7px;
|
||||
text-decoration: none;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.filters li a:hover {
|
||||
border-color: rgba(175, 47, 47, 0.1);
|
||||
}
|
||||
|
||||
.filters li a.selected {
|
||||
border-color: rgba(175, 47, 47, 0.2);
|
||||
}
|
||||
|
||||
.clear-completed,
|
||||
html .clear-completed:active {
|
||||
float: right;
|
||||
position: relative;
|
||||
line-height: 20px;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.clear-completed:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.info {
|
||||
margin: 65px auto 0;
|
||||
color: #4d4d4d;
|
||||
font-size: 11px;
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.info p {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.info a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.info a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/*
|
||||
Hack to remove background from Mobile Safari.
|
||||
Can't use it globally since it destroys checkboxes in Firefox
|
||||
*/
|
||||
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
||||
.toggle-all,
|
||||
.todo-list li .toggle {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.todo-list li .toggle {
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 430px) {
|
||||
.footer {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.filters {
|
||||
bottom: 10px;
|
||||
}
|
||||
}
|
||||
25
examples/angular-todomvc/src/test.ts
Normal file
25
examples/angular-todomvc/src/test.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
||||
import 'zone.js/dist/zone-testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
declare const require: {
|
||||
context(path: string, deep?: boolean, filter?: RegExp): {
|
||||
keys(): string[];
|
||||
<T>(id: string): T;
|
||||
};
|
||||
};
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting()
|
||||
);
|
||||
// Then we find all the tests.
|
||||
const context = require.context('./', true, /\.spec\.ts$/);
|
||||
// And load the modules.
|
||||
context.keys().map(context);
|
||||
15
examples/angular-todomvc/tsconfig.app.json
Normal file
15
examples/angular-todomvc/tsconfig.app.json
Normal file
@@ -0,0 +1,15 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/app",
|
||||
"types": []
|
||||
},
|
||||
"files": [
|
||||
"src/main.ts",
|
||||
"src/polyfills.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
29
examples/angular-todomvc/tsconfig.json
Normal file
29
examples/angular-todomvc/tsconfig.json
Normal file
@@ -0,0 +1,29 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "./dist/out-tsc",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"downlevelIteration": true,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"target": "es2015",
|
||||
"module": "es2020",
|
||||
"lib": [
|
||||
"es2018",
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
}
|
||||
18
examples/angular-todomvc/tsconfig.spec.json
Normal file
18
examples/angular-todomvc/tsconfig.spec.json
Normal file
@@ -0,0 +1,18 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"src/test.ts",
|
||||
"src/polyfills.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
152
examples/angular-todomvc/tslint.json
Normal file
152
examples/angular-todomvc/tslint.json
Normal file
@@ -0,0 +1,152 @@
|
||||
{
|
||||
"extends": "tslint:recommended",
|
||||
"rulesDirectory": [
|
||||
"codelyzer"
|
||||
],
|
||||
"rules": {
|
||||
"align": {
|
||||
"options": [
|
||||
"parameters",
|
||||
"statements"
|
||||
]
|
||||
},
|
||||
"array-type": false,
|
||||
"arrow-return-shorthand": true,
|
||||
"curly": true,
|
||||
"deprecation": {
|
||||
"severity": "warning"
|
||||
},
|
||||
"eofline": true,
|
||||
"import-blacklist": [
|
||||
true,
|
||||
"rxjs/Rx"
|
||||
],
|
||||
"import-spacing": true,
|
||||
"indent": {
|
||||
"options": [
|
||||
"spaces"
|
||||
]
|
||||
},
|
||||
"max-classes-per-file": false,
|
||||
"max-line-length": [
|
||||
true,
|
||||
140
|
||||
],
|
||||
"member-ordering": [
|
||||
true,
|
||||
{
|
||||
"order": [
|
||||
"static-field",
|
||||
"instance-field",
|
||||
"static-method",
|
||||
"instance-method"
|
||||
]
|
||||
}
|
||||
],
|
||||
"no-console": [
|
||||
true,
|
||||
"debug",
|
||||
"info",
|
||||
"time",
|
||||
"timeEnd",
|
||||
"trace"
|
||||
],
|
||||
"no-empty": false,
|
||||
"no-inferrable-types": [
|
||||
true,
|
||||
"ignore-params"
|
||||
],
|
||||
"no-non-null-assertion": true,
|
||||
"no-redundant-jsdoc": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-var-requires": false,
|
||||
"object-literal-key-quotes": [
|
||||
true,
|
||||
"as-needed"
|
||||
],
|
||||
"quotemark": [
|
||||
true,
|
||||
"single"
|
||||
],
|
||||
"semicolon": {
|
||||
"options": [
|
||||
"always"
|
||||
]
|
||||
},
|
||||
"space-before-function-paren": {
|
||||
"options": {
|
||||
"anonymous": "never",
|
||||
"asyncArrow": "always",
|
||||
"constructor": "never",
|
||||
"method": "never",
|
||||
"named": "never"
|
||||
}
|
||||
},
|
||||
"typedef": [
|
||||
true,
|
||||
"call-signature"
|
||||
],
|
||||
"typedef-whitespace": {
|
||||
"options": [
|
||||
{
|
||||
"call-signature": "nospace",
|
||||
"index-signature": "nospace",
|
||||
"parameter": "nospace",
|
||||
"property-declaration": "nospace",
|
||||
"variable-declaration": "nospace"
|
||||
},
|
||||
{
|
||||
"call-signature": "onespace",
|
||||
"index-signature": "onespace",
|
||||
"parameter": "onespace",
|
||||
"property-declaration": "onespace",
|
||||
"variable-declaration": "onespace"
|
||||
}
|
||||
]
|
||||
},
|
||||
"variable-name": {
|
||||
"options": [
|
||||
"ban-keywords",
|
||||
"check-format",
|
||||
"allow-pascal-case"
|
||||
]
|
||||
},
|
||||
"whitespace": {
|
||||
"options": [
|
||||
"check-branch",
|
||||
"check-decl",
|
||||
"check-operator",
|
||||
"check-separator",
|
||||
"check-type",
|
||||
"check-typecast"
|
||||
]
|
||||
},
|
||||
"component-class-suffix": true,
|
||||
"contextual-lifecycle": true,
|
||||
"directive-class-suffix": true,
|
||||
"no-conflicting-lifecycle": true,
|
||||
"no-host-metadata-property": true,
|
||||
"no-input-rename": true,
|
||||
"no-inputs-metadata-property": true,
|
||||
"no-output-native": true,
|
||||
"no-output-on-prefix": true,
|
||||
"no-output-rename": true,
|
||||
"no-outputs-metadata-property": true,
|
||||
"template-banana-in-box": true,
|
||||
"template-no-negated-async": true,
|
||||
"use-lifecycle-interface": true,
|
||||
"use-pipe-transform-interface": true,
|
||||
"directive-selector": [
|
||||
true,
|
||||
"attribute",
|
||||
"app",
|
||||
"camelCase"
|
||||
],
|
||||
"component-selector": [
|
||||
true,
|
||||
"element",
|
||||
"app",
|
||||
"kebab-case"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,36 @@
|
||||
$(function() {
|
||||
var FADE_TIME = 150; // ms
|
||||
var TYPING_TIMER_LENGTH = 400; // ms
|
||||
var COLORS = [
|
||||
const FADE_TIME = 150; // ms
|
||||
const TYPING_TIMER_LENGTH = 400; // ms
|
||||
const COLORS = [
|
||||
'#e21400', '#91580f', '#f8a700', '#f78b00',
|
||||
'#58dc00', '#287b00', '#a8f07a', '#4ae8c4',
|
||||
'#3b88eb', '#3824aa', '#a700ff', '#d300e7'
|
||||
];
|
||||
|
||||
// Initialize variables
|
||||
var $window = $(window);
|
||||
var $usernameInput = $('.usernameInput'); // Input for username
|
||||
var $messages = $('.messages'); // Messages area
|
||||
var $inputMessage = $('.inputMessage'); // Input message input box
|
||||
const $window = $(window);
|
||||
const $usernameInput = $('.usernameInput'); // Input for username
|
||||
const $messages = $('.messages'); // Messages area
|
||||
const $inputMessage = $('.inputMessage'); // Input message input box
|
||||
|
||||
var $loginPage = $('.login.page'); // The login page
|
||||
var $chatPage = $('.chat.page'); // The chatroom page
|
||||
const $loginPage = $('.login.page'); // The login page
|
||||
const $chatPage = $('.chat.page'); // The chatroom page
|
||||
|
||||
const socket = io();
|
||||
|
||||
// Prompt for setting a username
|
||||
var username;
|
||||
var connected = false;
|
||||
var typing = false;
|
||||
var lastTypingTime;
|
||||
var $currentInput = $usernameInput.focus();
|
||||
|
||||
var socket = io();
|
||||
let username;
|
||||
let connected = false;
|
||||
let typing = false;
|
||||
let lastTypingTime;
|
||||
let $currentInput = $usernameInput.focus();
|
||||
|
||||
const addParticipantsMessage = (data) => {
|
||||
var message = '';
|
||||
let message = '';
|
||||
if (data.numUsers === 1) {
|
||||
message += "there's 1 participant";
|
||||
message += `there's 1 participant`;
|
||||
} else {
|
||||
message += "there are " + data.numUsers + " participants";
|
||||
message += `there are ${data.numUsers} participants`;
|
||||
}
|
||||
log(message);
|
||||
}
|
||||
@@ -53,45 +53,41 @@ $(function() {
|
||||
|
||||
// Sends a chat message
|
||||
const sendMessage = () => {
|
||||
var message = $inputMessage.val();
|
||||
let message = $inputMessage.val();
|
||||
// Prevent markup from being injected into the message
|
||||
message = cleanInput(message);
|
||||
// if there is a non-empty message and a socket connection
|
||||
if (message && connected) {
|
||||
$inputMessage.val('');
|
||||
addChatMessage({
|
||||
username: username,
|
||||
message: message
|
||||
});
|
||||
addChatMessage({ username, message });
|
||||
// tell server to execute 'new message' and send along one parameter
|
||||
socket.emit('new message', message);
|
||||
}
|
||||
}
|
||||
|
||||
// Log a message
|
||||
const log = (message, options) => {
|
||||
var $el = $('<li>').addClass('log').text(message);
|
||||
const log = (message, options) => {
|
||||
const $el = $('<li>').addClass('log').text(message);
|
||||
addMessageElement($el, options);
|
||||
}
|
||||
|
||||
// Adds the visual chat message to the message list
|
||||
const addChatMessage = (data, options) => {
|
||||
// Don't fade the message in if there is an 'X was typing'
|
||||
var $typingMessages = getTypingMessages(data);
|
||||
options = options || {};
|
||||
const $typingMessages = getTypingMessages(data);
|
||||
if ($typingMessages.length !== 0) {
|
||||
options.fade = false;
|
||||
$typingMessages.remove();
|
||||
}
|
||||
|
||||
var $usernameDiv = $('<span class="username"/>')
|
||||
const $usernameDiv = $('<span class="username"/>')
|
||||
.text(data.username)
|
||||
.css('color', getUsernameColor(data.username));
|
||||
var $messageBodyDiv = $('<span class="messageBody">')
|
||||
const $messageBodyDiv = $('<span class="messageBody">')
|
||||
.text(data.message);
|
||||
|
||||
var typingClass = data.typing ? 'typing' : '';
|
||||
var $messageDiv = $('<li class="message"/>')
|
||||
const typingClass = data.typing ? 'typing' : '';
|
||||
const $messageDiv = $('<li class="message"/>')
|
||||
.data('username', data.username)
|
||||
.addClass(typingClass)
|
||||
.append($usernameDiv, $messageBodyDiv);
|
||||
@@ -119,8 +115,7 @@ $(function() {
|
||||
// options.prepend - If the element should prepend
|
||||
// all other messages (default = false)
|
||||
const addMessageElement = (el, options) => {
|
||||
var $el = $(el);
|
||||
|
||||
const $el = $(el);
|
||||
// Setup default options
|
||||
if (!options) {
|
||||
options = {};
|
||||
@@ -141,6 +136,7 @@ $(function() {
|
||||
} else {
|
||||
$messages.append($el);
|
||||
}
|
||||
|
||||
$messages[0].scrollTop = $messages[0].scrollHeight;
|
||||
}
|
||||
|
||||
@@ -159,8 +155,8 @@ $(function() {
|
||||
lastTypingTime = (new Date()).getTime();
|
||||
|
||||
setTimeout(() => {
|
||||
var typingTimer = (new Date()).getTime();
|
||||
var timeDiff = typingTimer - lastTypingTime;
|
||||
const typingTimer = (new Date()).getTime();
|
||||
const timeDiff = typingTimer - lastTypingTime;
|
||||
if (timeDiff >= TYPING_TIMER_LENGTH && typing) {
|
||||
socket.emit('stop typing');
|
||||
typing = false;
|
||||
@@ -179,12 +175,12 @@ $(function() {
|
||||
// Gets the color of a username through our hash function
|
||||
const getUsernameColor = (username) => {
|
||||
// Compute hash code
|
||||
var hash = 7;
|
||||
for (var i = 0; i < username.length; i++) {
|
||||
hash = username.charCodeAt(i) + (hash << 5) - hash;
|
||||
let hash = 7;
|
||||
for (let i = 0; i < username.length; i++) {
|
||||
hash = username.charCodeAt(i) + (hash << 5) - hash;
|
||||
}
|
||||
// Calculate color
|
||||
var index = Math.abs(hash % COLORS.length);
|
||||
const index = Math.abs(hash % COLORS.length);
|
||||
return COLORS[index];
|
||||
}
|
||||
|
||||
@@ -229,7 +225,7 @@ $(function() {
|
||||
socket.on('login', (data) => {
|
||||
connected = true;
|
||||
// Display the welcome message
|
||||
var message = "Welcome to Socket.IO Chat – ";
|
||||
const message = 'Welcome to Socket.IO Chat – ';
|
||||
log(message, {
|
||||
prepend: true
|
||||
});
|
||||
@@ -243,13 +239,13 @@ $(function() {
|
||||
|
||||
// Whenever the server emits 'user joined', log it in the chat body
|
||||
socket.on('user joined', (data) => {
|
||||
log(data.username + ' joined');
|
||||
log(`${data.username} joined`);
|
||||
addParticipantsMessage(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'user left', log it in the chat body
|
||||
socket.on('user left', (data) => {
|
||||
log(data.username + ' left');
|
||||
log(`${data.username} left`);
|
||||
addParticipantsMessage(data);
|
||||
removeChatTyping(data);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM mhart/alpine-node:6
|
||||
FROM node:14-alpine
|
||||
|
||||
# Create app directory
|
||||
RUN mkdir -p /usr/src/app
|
||||
@@ -6,7 +6,7 @@ WORKDIR /usr/src/app
|
||||
|
||||
# Install app dependencies
|
||||
COPY package.json /usr/src/app/
|
||||
RUN npm install
|
||||
RUN npm install --prod
|
||||
|
||||
# Bundle app source
|
||||
COPY . /usr/src/app
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
"license": "BSD",
|
||||
"dependencies": {
|
||||
"express": "4.13.4",
|
||||
"socket.io": "^1.7.2",
|
||||
"socket.io-redis": "^3.0.0"
|
||||
"socket.io": "^3.1.0",
|
||||
"socket.io-redis": "^6.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
|
||||
@@ -3,6 +3,8 @@ Listen 80
|
||||
|
||||
ServerName localhost
|
||||
|
||||
LoadModule mpm_event_module modules/mod_mpm_event.so
|
||||
|
||||
LoadModule authn_file_module modules/mod_authn_file.so
|
||||
LoadModule authn_core_module modules/mod_authn_core.so
|
||||
LoadModule authz_host_module modules/mod_authz_host.so
|
||||
|
||||
@@ -6,7 +6,7 @@ WORKDIR /usr/src/app
|
||||
|
||||
# Install app dependencies
|
||||
COPY package.json /usr/src/app/
|
||||
RUN npm install
|
||||
RUN npm install --prod
|
||||
|
||||
# Bundle app source
|
||||
COPY . /usr/src/app
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
"license": "BSD",
|
||||
"dependencies": {
|
||||
"express": "4.13.4",
|
||||
"socket.io": "^1.7.2",
|
||||
"socket.io-redis": "^3.0.0"
|
||||
"socket.io": "^3.1.0",
|
||||
"socket.io-redis": "^6.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
|
||||
@@ -22,6 +22,16 @@ Each node connects to the redis backend, which will enable to broadcast to every
|
||||
$ docker-compose stop server-george
|
||||
```
|
||||
|
||||
A `client` container is included in the `docker-compose.yml` file, in order to test the routing.
|
||||
|
||||
You can create additional `client` containers with:
|
||||
|
||||
```
|
||||
$ docker-compose up -d --scale=client=10 client
|
||||
# and then
|
||||
$ docker-compose logs client
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Multiple users can join a chat room by each entering a unique username
|
||||
|
||||
15
examples/cluster-nginx/client/Dockerfile
Normal file
15
examples/cluster-nginx/client/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
FROM node:14-alpine
|
||||
|
||||
# Create app directory
|
||||
RUN mkdir -p /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Install app dependencies
|
||||
COPY package.json /usr/src/app/
|
||||
RUN npm install --prod
|
||||
|
||||
# Bundle app source
|
||||
COPY . /usr/src/app
|
||||
|
||||
EXPOSE 3000
|
||||
CMD [ "npm", "start" ]
|
||||
13
examples/cluster-nginx/client/index.js
Normal file
13
examples/cluster-nginx/client/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const socket = require('socket.io-client')('ws://nginx');
|
||||
|
||||
socket.on('connect', () => {
|
||||
console.log('connected');
|
||||
});
|
||||
|
||||
socket.on('my-name-is', (serverName) => {
|
||||
console.log(`connected to ${serverName}`);
|
||||
});
|
||||
|
||||
socket.on('disconnect', (reason) => {
|
||||
console.log(`disconnected due to ${reason}`);
|
||||
});
|
||||
15
examples/cluster-nginx/client/package.json
Normal file
15
examples/cluster-nginx/client/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "socket.io-chat",
|
||||
"version": "0.0.0",
|
||||
"description": "A simple chat client using socket.io",
|
||||
"main": "index.js",
|
||||
"author": "Grant Timmerman",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"socket.io-client": "^3.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,11 @@ server-ringo:
|
||||
environment:
|
||||
- NAME=Ringo
|
||||
|
||||
client:
|
||||
build: ./client
|
||||
links:
|
||||
- nginx
|
||||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
expose:
|
||||
|
||||
@@ -24,8 +24,12 @@ http {
|
||||
}
|
||||
|
||||
upstream nodes {
|
||||
# enable sticky session
|
||||
ip_hash;
|
||||
# enable sticky session with either "hash" (uses the complete IP address)
|
||||
hash $remote_addr consistent;
|
||||
# or "ip_hash" (uses the first three octets of the client IPv4 address, or the entire IPv6 address)
|
||||
# ip_hash;
|
||||
# or "sticky" (needs commercial subscription)
|
||||
# sticky cookie srv_id expires=1h domain=.example.com path=/;
|
||||
|
||||
server server-john:3000;
|
||||
server server-paul:3000;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM mhart/alpine-node:6
|
||||
FROM node:14-alpine
|
||||
|
||||
# Create app directory
|
||||
RUN mkdir -p /usr/src/app
|
||||
@@ -6,7 +6,7 @@ WORKDIR /usr/src/app
|
||||
|
||||
# Install app dependencies
|
||||
COPY package.json /usr/src/app/
|
||||
RUN npm install
|
||||
RUN npm install --prod
|
||||
|
||||
# Bundle app source
|
||||
COPY . /usr/src/app
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"express": "4.13.4",
|
||||
"socket.io": "^1.7.2",
|
||||
"socket.io-redis": "^3.0.0"
|
||||
"socket.io": "^3.1.0",
|
||||
"socket.io-redis": "^6.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
|
||||
22
examples/cluster-traefik/README.md
Normal file
22
examples/cluster-traefik/README.md
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
# Socket.IO Chat with traefik & [redis](https://redis.io/)
|
||||
|
||||
A simple chat demo for Socket.IO
|
||||
|
||||
## How to use
|
||||
|
||||
Install [Docker Compose](https://docs.docker.com/compose/install/), then:
|
||||
|
||||
```
|
||||
$ docker-compose up -d
|
||||
```
|
||||
|
||||
And then point your browser to `http://localhost:3000`.
|
||||
|
||||
You can then scale the server to multiple instances:
|
||||
|
||||
```
|
||||
$ docker-compose up -d --scale=server=7
|
||||
```
|
||||
|
||||
The session stickiness, which is [required](https://socket.io/docs/v3/using-multiple-nodes/) when using multiple Socket.IO server instances, is achieved with a cookie. More information [here](https://doc.traefik.io/traefik/v2.0/routing/services/#sticky-sessions).
|
||||
27
examples/cluster-traefik/docker-compose.yml
Normal file
27
examples/cluster-traefik/docker-compose.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:2.4
|
||||
volumes:
|
||||
- ./traefik.yml:/etc/traefik/traefik.yml
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
links:
|
||||
- server
|
||||
ports:
|
||||
- "3000:80"
|
||||
- "8080:8080"
|
||||
|
||||
server:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
labels:
|
||||
- "traefik.http.routers.chat.rule=PathPrefix(`/`)"
|
||||
- traefik.http.services.chat.loadBalancer.sticky.cookie.name=server_id
|
||||
- traefik.http.services.chat.loadBalancer.sticky.cookie.httpOnly=true
|
||||
|
||||
redis:
|
||||
image: redis:6-alpine
|
||||
labels:
|
||||
- traefik.enable=false
|
||||
15
examples/cluster-traefik/server/Dockerfile
Normal file
15
examples/cluster-traefik/server/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
FROM node:14-alpine
|
||||
|
||||
# Create app directory
|
||||
RUN mkdir -p /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Install app dependencies
|
||||
COPY package.json /usr/src/app/
|
||||
RUN npm install --prod
|
||||
|
||||
# Bundle app source
|
||||
COPY . /usr/src/app
|
||||
|
||||
EXPOSE 3000
|
||||
CMD [ "npm", "start" ]
|
||||
83
examples/cluster-traefik/server/index.js
Normal file
83
examples/cluster-traefik/server/index.js
Normal file
@@ -0,0 +1,83 @@
|
||||
// Setup basic express server
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
var server = require('http').createServer(app);
|
||||
var io = require('socket.io')(server);
|
||||
var redis = require('socket.io-redis');
|
||||
var port = process.env.PORT || 3000;
|
||||
var crypto = require('crypto');
|
||||
var serverName = crypto.randomBytes(3).toString('hex');
|
||||
|
||||
io.adapter(redis({ host: 'redis', port: 6379 }));
|
||||
|
||||
server.listen(port, function () {
|
||||
console.log('Server listening at port %d', port);
|
||||
console.log('Hello, I\'m %s, how can I help?', serverName);
|
||||
});
|
||||
|
||||
// Routing
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
|
||||
// Chatroom
|
||||
|
||||
var numUsers = 0;
|
||||
|
||||
io.on('connection', function (socket) {
|
||||
socket.emit('my-name-is', serverName);
|
||||
|
||||
var addedUser = false;
|
||||
|
||||
// when the client emits 'new message', this listens and executes
|
||||
socket.on('new message', function (data) {
|
||||
// we tell the client to execute 'new message'
|
||||
socket.broadcast.emit('new message', {
|
||||
username: socket.username,
|
||||
message: data
|
||||
});
|
||||
});
|
||||
|
||||
// when the client emits 'add user', this listens and executes
|
||||
socket.on('add user', function (username) {
|
||||
if (addedUser) return;
|
||||
|
||||
// we store the username in the socket session for this client
|
||||
socket.username = username;
|
||||
++numUsers;
|
||||
addedUser = true;
|
||||
socket.emit('login', {
|
||||
numUsers: numUsers
|
||||
});
|
||||
// echo globally (all clients) that a person has connected
|
||||
socket.broadcast.emit('user joined', {
|
||||
username: socket.username,
|
||||
numUsers: numUsers
|
||||
});
|
||||
});
|
||||
|
||||
// when the client emits 'typing', we broadcast it to others
|
||||
socket.on('typing', function () {
|
||||
socket.broadcast.emit('typing', {
|
||||
username: socket.username
|
||||
});
|
||||
});
|
||||
|
||||
// when the client emits 'stop typing', we broadcast it to others
|
||||
socket.on('stop typing', function () {
|
||||
socket.broadcast.emit('stop typing', {
|
||||
username: socket.username
|
||||
});
|
||||
});
|
||||
|
||||
// when the user disconnects.. perform this
|
||||
socket.on('disconnect', function () {
|
||||
if (addedUser) {
|
||||
--numUsers;
|
||||
|
||||
// echo globally that this client has left
|
||||
socket.broadcast.emit('user left', {
|
||||
username: socket.username,
|
||||
numUsers: numUsers
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
17
examples/cluster-traefik/server/package.json
Normal file
17
examples/cluster-traefik/server/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "socket.io-chat",
|
||||
"version": "0.0.0",
|
||||
"description": "A simple chat client using socket.io",
|
||||
"main": "index.js",
|
||||
"author": "Grant Timmerman",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"express": "4.13.4",
|
||||
"socket.io": "^3.1.0",
|
||||
"socket.io-redis": "^6.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
}
|
||||
}
|
||||
28
examples/cluster-traefik/server/public/index.html
Normal file
28
examples/cluster-traefik/server/public/index.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Socket.IO Chat Example</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<ul class="pages">
|
||||
<li class="chat page">
|
||||
<div class="chatArea">
|
||||
<ul class="messages"></ul>
|
||||
</div>
|
||||
<input class="inputMessage" placeholder="Type here..."/>
|
||||
</li>
|
||||
<li class="login page">
|
||||
<div class="form">
|
||||
<h3 class="title">What's your nickname?</h3>
|
||||
<input class="usernameInput" type="text" maxlength="14" />
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script src="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
286
examples/cluster-traefik/server/public/main.js
Normal file
286
examples/cluster-traefik/server/public/main.js
Normal file
@@ -0,0 +1,286 @@
|
||||
$(function() {
|
||||
var FADE_TIME = 150; // ms
|
||||
var TYPING_TIMER_LENGTH = 400; // ms
|
||||
var COLORS = [
|
||||
'#e21400', '#91580f', '#f8a700', '#f78b00',
|
||||
'#58dc00', '#287b00', '#a8f07a', '#4ae8c4',
|
||||
'#3b88eb', '#3824aa', '#a700ff', '#d300e7'
|
||||
];
|
||||
|
||||
// Initialize variables
|
||||
var $window = $(window);
|
||||
var $usernameInput = $('.usernameInput'); // Input for username
|
||||
var $messages = $('.messages'); // Messages area
|
||||
var $inputMessage = $('.inputMessage'); // Input message input box
|
||||
|
||||
var $loginPage = $('.login.page'); // The login page
|
||||
var $chatPage = $('.chat.page'); // The chatroom page
|
||||
|
||||
// Prompt for setting a username
|
||||
var username;
|
||||
var connected = false;
|
||||
var typing = false;
|
||||
var lastTypingTime;
|
||||
var $currentInput = $usernameInput.focus();
|
||||
|
||||
var socket = io();
|
||||
|
||||
function addParticipantsMessage (data) {
|
||||
var message = '';
|
||||
if (data.numUsers === 1) {
|
||||
message += "there's 1 participant";
|
||||
} else {
|
||||
message += "there are " + data.numUsers + " participants";
|
||||
}
|
||||
log(message);
|
||||
}
|
||||
|
||||
// Sets the client's username
|
||||
function setUsername () {
|
||||
username = cleanInput($usernameInput.val().trim());
|
||||
|
||||
// If the username is valid
|
||||
if (username) {
|
||||
$loginPage.fadeOut();
|
||||
$chatPage.show();
|
||||
$loginPage.off('click');
|
||||
$currentInput = $inputMessage.focus();
|
||||
|
||||
// Tell the server your username
|
||||
socket.emit('add user', username);
|
||||
}
|
||||
}
|
||||
|
||||
// Sends a chat message
|
||||
function sendMessage () {
|
||||
var message = $inputMessage.val();
|
||||
// Prevent markup from being injected into the message
|
||||
message = cleanInput(message);
|
||||
// if there is a non-empty message and a socket connection
|
||||
if (message && connected) {
|
||||
$inputMessage.val('');
|
||||
addChatMessage({
|
||||
username: username,
|
||||
message: message
|
||||
});
|
||||
// tell server to execute 'new message' and send along one parameter
|
||||
socket.emit('new message', message);
|
||||
}
|
||||
}
|
||||
|
||||
// Log a message
|
||||
function log (message, options) {
|
||||
var $el = $('<li>').addClass('log').text(message);
|
||||
addMessageElement($el, options);
|
||||
}
|
||||
|
||||
// Adds the visual chat message to the message list
|
||||
function addChatMessage (data, options) {
|
||||
// Don't fade the message in if there is an 'X was typing'
|
||||
var $typingMessages = getTypingMessages(data);
|
||||
options = options || {};
|
||||
if ($typingMessages.length !== 0) {
|
||||
options.fade = false;
|
||||
$typingMessages.remove();
|
||||
}
|
||||
|
||||
var $usernameDiv = $('<span class="username"/>')
|
||||
.text(data.username)
|
||||
.css('color', getUsernameColor(data.username));
|
||||
var $messageBodyDiv = $('<span class="messageBody">')
|
||||
.text(data.message);
|
||||
|
||||
var typingClass = data.typing ? 'typing' : '';
|
||||
var $messageDiv = $('<li class="message"/>')
|
||||
.data('username', data.username)
|
||||
.addClass(typingClass)
|
||||
.append($usernameDiv, $messageBodyDiv);
|
||||
|
||||
addMessageElement($messageDiv, options);
|
||||
}
|
||||
|
||||
// Adds the visual chat typing message
|
||||
function addChatTyping (data) {
|
||||
data.typing = true;
|
||||
data.message = 'is typing';
|
||||
addChatMessage(data);
|
||||
}
|
||||
|
||||
// Removes the visual chat typing message
|
||||
function removeChatTyping (data) {
|
||||
getTypingMessages(data).fadeOut(function () {
|
||||
$(this).remove();
|
||||
});
|
||||
}
|
||||
|
||||
// Adds a message element to the messages and scrolls to the bottom
|
||||
// el - The element to add as a message
|
||||
// options.fade - If the element should fade-in (default = true)
|
||||
// options.prepend - If the element should prepend
|
||||
// all other messages (default = false)
|
||||
function addMessageElement (el, options) {
|
||||
var $el = $(el);
|
||||
|
||||
// Setup default options
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
if (typeof options.fade === 'undefined') {
|
||||
options.fade = true;
|
||||
}
|
||||
if (typeof options.prepend === 'undefined') {
|
||||
options.prepend = false;
|
||||
}
|
||||
|
||||
// Apply options
|
||||
if (options.fade) {
|
||||
$el.hide().fadeIn(FADE_TIME);
|
||||
}
|
||||
if (options.prepend) {
|
||||
$messages.prepend($el);
|
||||
} else {
|
||||
$messages.append($el);
|
||||
}
|
||||
$messages[0].scrollTop = $messages[0].scrollHeight;
|
||||
}
|
||||
|
||||
// Prevents input from having injected markup
|
||||
function cleanInput (input) {
|
||||
return $('<div/>').text(input).text();
|
||||
}
|
||||
|
||||
// Updates the typing event
|
||||
function updateTyping () {
|
||||
if (connected) {
|
||||
if (!typing) {
|
||||
typing = true;
|
||||
socket.emit('typing');
|
||||
}
|
||||
lastTypingTime = (new Date()).getTime();
|
||||
|
||||
setTimeout(function () {
|
||||
var typingTimer = (new Date()).getTime();
|
||||
var timeDiff = typingTimer - lastTypingTime;
|
||||
if (timeDiff >= TYPING_TIMER_LENGTH && typing) {
|
||||
socket.emit('stop typing');
|
||||
typing = false;
|
||||
}
|
||||
}, TYPING_TIMER_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the 'X is typing' messages of a user
|
||||
function getTypingMessages (data) {
|
||||
return $('.typing.message').filter(function (i) {
|
||||
return $(this).data('username') === data.username;
|
||||
});
|
||||
}
|
||||
|
||||
// Gets the color of a username through our hash function
|
||||
function getUsernameColor (username) {
|
||||
// Compute hash code
|
||||
var hash = 7;
|
||||
for (var i = 0; i < username.length; i++) {
|
||||
hash = username.charCodeAt(i) + (hash << 5) - hash;
|
||||
}
|
||||
// Calculate color
|
||||
var index = Math.abs(hash % COLORS.length);
|
||||
return COLORS[index];
|
||||
}
|
||||
|
||||
// Keyboard events
|
||||
|
||||
$window.keydown(function (event) {
|
||||
// Auto-focus the current input when a key is typed
|
||||
if (!(event.ctrlKey || event.metaKey || event.altKey)) {
|
||||
$currentInput.focus();
|
||||
}
|
||||
// When the client hits ENTER on their keyboard
|
||||
if (event.which === 13) {
|
||||
if (username) {
|
||||
sendMessage();
|
||||
socket.emit('stop typing');
|
||||
typing = false;
|
||||
} else {
|
||||
setUsername();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$inputMessage.on('input', function() {
|
||||
updateTyping();
|
||||
});
|
||||
|
||||
// Click events
|
||||
|
||||
// Focus input when clicking anywhere on login page
|
||||
$loginPage.click(function () {
|
||||
$currentInput.focus();
|
||||
});
|
||||
|
||||
// Focus input when clicking on the message input's border
|
||||
$inputMessage.click(function () {
|
||||
$inputMessage.focus();
|
||||
});
|
||||
|
||||
// Socket events
|
||||
|
||||
// Whenever the server emits 'login', log the login message
|
||||
socket.on('login', function (data) {
|
||||
connected = true;
|
||||
// Display the welcome message
|
||||
var message = "Welcome to Socket.IO Chat – ";
|
||||
log(message, {
|
||||
prepend: true
|
||||
});
|
||||
addParticipantsMessage(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'new message', update the chat body
|
||||
socket.on('new message', function (data) {
|
||||
addChatMessage(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'user joined', log it in the chat body
|
||||
socket.on('user joined', function (data) {
|
||||
log(data.username + ' joined');
|
||||
addParticipantsMessage(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'user left', log it in the chat body
|
||||
socket.on('user left', function (data) {
|
||||
log(data.username + ' left');
|
||||
addParticipantsMessage(data);
|
||||
removeChatTyping(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'typing', show the typing message
|
||||
socket.on('typing', function (data) {
|
||||
addChatTyping(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'stop typing', kill the typing message
|
||||
socket.on('stop typing', function (data) {
|
||||
removeChatTyping(data);
|
||||
});
|
||||
|
||||
socket.on('disconnect', function () {
|
||||
log('you have been disconnected');
|
||||
});
|
||||
|
||||
socket.on('connect', function () {
|
||||
if (username) {
|
||||
log('you have been reconnected');
|
||||
socket.emit('add user', username);
|
||||
}
|
||||
});
|
||||
|
||||
socket.io.on('reconnect_error', function () {
|
||||
log('attempt to reconnect has failed');
|
||||
});
|
||||
|
||||
socket.on('my-name-is', function (serverName) {
|
||||
log('host is now ' + serverName);
|
||||
})
|
||||
|
||||
});
|
||||
149
examples/cluster-traefik/server/public/style.css
Normal file
149
examples/cluster-traefik/server/public/style.css
Normal file
@@ -0,0 +1,149 @@
|
||||
/* Fix user-agent */
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-weight: 300;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
html, input {
|
||||
font-family:
|
||||
"HelveticaNeue-Light",
|
||||
"Helvetica Neue Light",
|
||||
"Helvetica Neue",
|
||||
Helvetica,
|
||||
Arial,
|
||||
"Lucida Grande",
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Pages */
|
||||
|
||||
.pages {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Login Page */
|
||||
|
||||
.login.page {
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.login.page .form {
|
||||
height: 100px;
|
||||
margin-top: -100px;
|
||||
position: absolute;
|
||||
|
||||
text-align: center;
|
||||
top: 50%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login.page .form .usernameInput {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid #fff;
|
||||
outline: none;
|
||||
padding-bottom: 15px;
|
||||
text-align: center;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.login.page .title {
|
||||
font-size: 200%;
|
||||
}
|
||||
|
||||
.login.page .usernameInput {
|
||||
font-size: 200%;
|
||||
letter-spacing: 3px;
|
||||
}
|
||||
|
||||
.login.page .title, .login.page .usernameInput {
|
||||
color: #fff;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
/* Chat page */
|
||||
|
||||
.chat.page {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Font */
|
||||
|
||||
.messages {
|
||||
font-size: 150%;
|
||||
}
|
||||
|
||||
.inputMessage {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
.log {
|
||||
color: gray;
|
||||
font-size: 70%;
|
||||
margin: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Messages */
|
||||
|
||||
.chatArea {
|
||||
height: 100%;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
.messages {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow-y: scroll;
|
||||
padding: 10px 20px 10px 20px;
|
||||
}
|
||||
|
||||
.message.typing .messageBody {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
padding-right: 15px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Input */
|
||||
|
||||
.inputMessage {
|
||||
border: 10px solid #000;
|
||||
bottom: 0;
|
||||
height: 60px;
|
||||
left: 0;
|
||||
outline: none;
|
||||
padding-left: 10px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
10
examples/cluster-traefik/traefik.yml
Normal file
10
examples/cluster-traefik/traefik.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
api:
|
||||
insecure: true
|
||||
|
||||
entryPoints:
|
||||
web:
|
||||
address: ":80"
|
||||
|
||||
providers:
|
||||
docker: {}
|
||||
24
examples/private-messaging/.gitignore
vendored
Normal file
24
examples/private-messaging/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
package-lock.json
|
||||
23
examples/private-messaging/README.md
Normal file
23
examples/private-messaging/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Private messaging with Socket.IO
|
||||
|
||||
Please read the related guide:
|
||||
|
||||
- [Part I](https://socket.io/get-started/private-messaging-part-1/): initial implementation
|
||||
- [Part II](https://socket.io/get-started/private-messaging-part-2/): persistent user ID
|
||||
- [Part III](https://socket.io/get-started/private-messaging-part-3/): persistent messages
|
||||
- [Part IV](https://socket.io/get-started/private-messaging-part-4/): scaling up
|
||||
|
||||
## Running the frontend
|
||||
|
||||
```
|
||||
npm install
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Running the server
|
||||
|
||||
```
|
||||
cd server
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
5
examples/private-messaging/babel.config.js
Normal file
5
examples/private-messaging/babel.config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
||||
43
examples/private-messaging/package.json
Normal file
43
examples/private-messaging/package.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "private-messaging",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.6.5",
|
||||
"socket.io-client": "^3.1.1",
|
||||
"vue": "^2.6.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
||||
BIN
examples/private-messaging/public/favicon.ico
Normal file
BIN
examples/private-messaging/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
BIN
examples/private-messaging/public/fonts/Lato-Regular.ttf
Normal file
BIN
examples/private-messaging/public/fonts/Lato-Regular.ttf
Normal file
Binary file not shown.
17
examples/private-messaging/public/index.html
Normal file
17
examples/private-messaging/public/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title>Private messaging with Socket.IO</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but this application doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
31
examples/private-messaging/server/cluster.js
Normal file
31
examples/private-messaging/server/cluster.js
Normal file
@@ -0,0 +1,31 @@
|
||||
const cluster = require("cluster");
|
||||
const http = require("http");
|
||||
const { setupMaster } = require("@socket.io/sticky");
|
||||
|
||||
const WORKERS_COUNT = 4;
|
||||
|
||||
if (cluster.isMaster) {
|
||||
console.log(`Master ${process.pid} is running`);
|
||||
|
||||
for (let i = 0; i < WORKERS_COUNT; i++) {
|
||||
cluster.fork();
|
||||
}
|
||||
|
||||
cluster.on("exit", (worker) => {
|
||||
console.log(`Worker ${worker.process.pid} died`);
|
||||
cluster.fork();
|
||||
});
|
||||
|
||||
const httpServer = http.createServer();
|
||||
setupMaster(httpServer, {
|
||||
loadBalancingMethod: "least-connection", // either "random", "round-robin" or "least-connection"
|
||||
});
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
httpServer.listen(PORT, () =>
|
||||
console.log(`server listening at http://localhost:${PORT}`)
|
||||
);
|
||||
} else {
|
||||
console.log(`Worker ${process.pid} started`);
|
||||
require("./index");
|
||||
}
|
||||
7
examples/private-messaging/server/docker-compose.yml
Normal file
7
examples/private-messaging/server/docker-compose.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
redis:
|
||||
image: redis:5
|
||||
ports:
|
||||
- "6379:6379"
|
||||
125
examples/private-messaging/server/index.js
Normal file
125
examples/private-messaging/server/index.js
Normal file
@@ -0,0 +1,125 @@
|
||||
const httpServer = require("http").createServer();
|
||||
const Redis = require("ioredis");
|
||||
const redisClient = new Redis();
|
||||
const io = require("socket.io")(httpServer, {
|
||||
cors: {
|
||||
origin: "http://localhost:8080",
|
||||
},
|
||||
adapter: require("socket.io-redis")({
|
||||
pubClient: redisClient,
|
||||
subClient: redisClient.duplicate(),
|
||||
}),
|
||||
});
|
||||
|
||||
const { setupWorker } = require("@socket.io/sticky");
|
||||
const crypto = require("crypto");
|
||||
const randomId = () => crypto.randomBytes(8).toString("hex");
|
||||
|
||||
const { RedisSessionStore } = require("./sessionStore");
|
||||
const sessionStore = new RedisSessionStore(redisClient);
|
||||
|
||||
const { RedisMessageStore } = require("./messageStore");
|
||||
const messageStore = new RedisMessageStore(redisClient);
|
||||
|
||||
io.use(async (socket, next) => {
|
||||
const sessionID = socket.handshake.auth.sessionID;
|
||||
if (sessionID) {
|
||||
const session = await sessionStore.findSession(sessionID);
|
||||
if (session) {
|
||||
socket.sessionID = sessionID;
|
||||
socket.userID = session.userID;
|
||||
socket.username = session.username;
|
||||
return next();
|
||||
}
|
||||
}
|
||||
const username = socket.handshake.auth.username;
|
||||
if (!username) {
|
||||
return next(new Error("invalid username"));
|
||||
}
|
||||
socket.sessionID = randomId();
|
||||
socket.userID = randomId();
|
||||
socket.username = username;
|
||||
next();
|
||||
});
|
||||
|
||||
io.on("connection", async (socket) => {
|
||||
// persist session
|
||||
sessionStore.saveSession(socket.sessionID, {
|
||||
userID: socket.userID,
|
||||
username: socket.username,
|
||||
connected: true,
|
||||
});
|
||||
|
||||
// emit session details
|
||||
socket.emit("session", {
|
||||
sessionID: socket.sessionID,
|
||||
userID: socket.userID,
|
||||
});
|
||||
|
||||
// join the "userID" room
|
||||
socket.join(socket.userID);
|
||||
|
||||
// fetch existing users
|
||||
const users = [];
|
||||
const [messages, sessions] = await Promise.all([
|
||||
messageStore.findMessagesForUser(socket.userID),
|
||||
sessionStore.findAllSessions(),
|
||||
]);
|
||||
const messagesPerUser = new Map();
|
||||
messages.forEach((message) => {
|
||||
const { from, to } = message;
|
||||
const otherUser = socket.userID === from ? to : from;
|
||||
if (messagesPerUser.has(otherUser)) {
|
||||
messagesPerUser.get(otherUser).push(message);
|
||||
} else {
|
||||
messagesPerUser.set(otherUser, [message]);
|
||||
}
|
||||
});
|
||||
|
||||
sessions.forEach((session) => {
|
||||
users.push({
|
||||
userID: session.userID,
|
||||
username: session.username,
|
||||
connected: session.connected,
|
||||
messages: messagesPerUser.get(session.userID) || [],
|
||||
});
|
||||
});
|
||||
socket.emit("users", users);
|
||||
|
||||
// notify existing users
|
||||
socket.broadcast.emit("user connected", {
|
||||
userID: socket.userID,
|
||||
username: socket.username,
|
||||
connected: true,
|
||||
messages: [],
|
||||
});
|
||||
|
||||
// forward the private message to the right recipient (and to other tabs of the sender)
|
||||
socket.on("private message", ({ content, to }) => {
|
||||
const message = {
|
||||
content,
|
||||
from: socket.userID,
|
||||
to,
|
||||
};
|
||||
socket.to(to).to(socket.userID).emit("private message", message);
|
||||
messageStore.saveMessage(message);
|
||||
});
|
||||
|
||||
// notify users upon disconnection
|
||||
socket.on("disconnect", async () => {
|
||||
const matchingSockets = await io.in(socket.userID).allSockets();
|
||||
const isDisconnected = matchingSockets.size === 0;
|
||||
if (isDisconnected) {
|
||||
// notify other users
|
||||
socket.broadcast.emit("user disconnected", socket.userID);
|
||||
// update the connection status of the session
|
||||
sessionStore.saveSession(socket.sessionID, {
|
||||
userID: socket.userID,
|
||||
username: socket.username,
|
||||
connected: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
setupWorker(io);
|
||||
54
examples/private-messaging/server/messageStore.js
Normal file
54
examples/private-messaging/server/messageStore.js
Normal file
@@ -0,0 +1,54 @@
|
||||
/* abstract */ class MessageStore {
|
||||
saveMessage(message) {}
|
||||
findMessagesForUser(userID) {}
|
||||
}
|
||||
|
||||
class InMemoryMessageStore extends MessageStore {
|
||||
constructor() {
|
||||
super();
|
||||
this.messages = [];
|
||||
}
|
||||
|
||||
saveMessage(message) {
|
||||
this.messages.push(message);
|
||||
}
|
||||
|
||||
findMessagesForUser(userID) {
|
||||
return this.messages.filter(
|
||||
({ from, to }) => from === userID || to === userID
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const CONVERSATION_TTL = 24 * 60 * 60;
|
||||
|
||||
class RedisMessageStore extends MessageStore {
|
||||
constructor(redisClient) {
|
||||
super();
|
||||
this.redisClient = redisClient;
|
||||
}
|
||||
|
||||
saveMessage(message) {
|
||||
const value = JSON.stringify(message);
|
||||
this.redisClient
|
||||
.multi()
|
||||
.rpush(`messages:${message.from}`, value)
|
||||
.rpush(`messages:${message.to}`, value)
|
||||
.expire(`messages:${message.from}`, CONVERSATION_TTL)
|
||||
.expire(`messages:${message.to}`, CONVERSATION_TTL)
|
||||
.exec();
|
||||
}
|
||||
|
||||
findMessagesForUser(userID) {
|
||||
return this.redisClient
|
||||
.lrange(`messages:${userID}`, 0, -1)
|
||||
.then((results) => {
|
||||
return results.map((result) => JSON.parse(result));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
InMemoryMessageStore,
|
||||
RedisMessageStore,
|
||||
};
|
||||
17
examples/private-messaging/server/package.json
Normal file
17
examples/private-messaging/server/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "server",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node cluster.js"
|
||||
},
|
||||
"author": "Damien Arrachequesne <damien.arrachequesne@gmail.com>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@socket.io/sticky": "^1.0.0",
|
||||
"ioredis": "^4.22.0",
|
||||
"socket.io": "^3.1.1",
|
||||
"socket.io-redis": "^6.0.1"
|
||||
}
|
||||
}
|
||||
89
examples/private-messaging/server/sessionStore.js
Normal file
89
examples/private-messaging/server/sessionStore.js
Normal file
@@ -0,0 +1,89 @@
|
||||
/* abstract */ class SessionStore {
|
||||
findSession(id) {}
|
||||
saveSession(id, session) {}
|
||||
findAllSessions() {}
|
||||
}
|
||||
|
||||
class InMemorySessionStore extends SessionStore {
|
||||
constructor() {
|
||||
super();
|
||||
this.sessions = new Map();
|
||||
}
|
||||
|
||||
findSession(id) {
|
||||
return this.sessions.get(id);
|
||||
}
|
||||
|
||||
saveSession(id, session) {
|
||||
this.sessions.set(id, session);
|
||||
}
|
||||
|
||||
findAllSessions() {
|
||||
return [...this.sessions.values()];
|
||||
}
|
||||
}
|
||||
|
||||
const SESSION_TTL = 24 * 60 * 60;
|
||||
const mapSession = ([userID, username, connected]) =>
|
||||
userID ? { userID, username, connected: connected === "true" } : undefined;
|
||||
|
||||
class RedisSessionStore extends SessionStore {
|
||||
constructor(redisClient) {
|
||||
super();
|
||||
this.redisClient = redisClient;
|
||||
}
|
||||
|
||||
findSession(id) {
|
||||
return this.redisClient
|
||||
.hmget(`session:${id}`, "userID", "username", "connected")
|
||||
.then(mapSession);
|
||||
}
|
||||
|
||||
saveSession(id, { userID, username, connected }) {
|
||||
this.redisClient
|
||||
.multi()
|
||||
.hset(
|
||||
`session:${id}`,
|
||||
"userID",
|
||||
userID,
|
||||
"username",
|
||||
username,
|
||||
"connected",
|
||||
connected
|
||||
)
|
||||
.expire(`session:${id}`, SESSION_TTL)
|
||||
.exec();
|
||||
}
|
||||
|
||||
async findAllSessions() {
|
||||
const keys = new Set();
|
||||
let nextIndex = 0;
|
||||
do {
|
||||
const [nextIndexAsStr, results] = await this.redisClient.scan(
|
||||
nextIndex,
|
||||
"MATCH",
|
||||
"session:*",
|
||||
"COUNT",
|
||||
"100"
|
||||
);
|
||||
nextIndex = parseInt(nextIndexAsStr, 10);
|
||||
results.forEach((s) => keys.add(s));
|
||||
} while (nextIndex !== 0);
|
||||
const commands = [];
|
||||
keys.forEach((key) => {
|
||||
commands.push(["hmget", key, "userID", "username", "connected"]);
|
||||
});
|
||||
return this.redisClient
|
||||
.multi(commands)
|
||||
.exec()
|
||||
.then((results) => {
|
||||
return results
|
||||
.map(([err, session]) => (err ? undefined : mapSession(session)))
|
||||
.filter((v) => !!v);
|
||||
});
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
InMemorySessionStore,
|
||||
RedisSessionStore,
|
||||
};
|
||||
78
examples/private-messaging/src/App.vue
Normal file
78
examples/private-messaging/src/App.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<select-username
|
||||
v-if="!usernameAlreadySelected"
|
||||
@input="onUsernameSelection"
|
||||
/>
|
||||
<chat v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SelectUsername from "./components/SelectUsername";
|
||||
import Chat from "./components/Chat";
|
||||
import socket from "./socket";
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
components: {
|
||||
Chat,
|
||||
SelectUsername,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
usernameAlreadySelected: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onUsernameSelection(username) {
|
||||
this.usernameAlreadySelected = true;
|
||||
socket.auth = { username };
|
||||
socket.connect();
|
||||
},
|
||||
},
|
||||
created() {
|
||||
const sessionID = localStorage.getItem("sessionID");
|
||||
|
||||
if (sessionID) {
|
||||
this.usernameAlreadySelected = true;
|
||||
socket.auth = { sessionID };
|
||||
socket.connect();
|
||||
}
|
||||
|
||||
socket.on("session", ({ sessionID, userID }) => {
|
||||
// attach the session ID to the next reconnection attempts
|
||||
socket.auth = { sessionID };
|
||||
// store it in the localStorage
|
||||
localStorage.setItem("sessionID", sessionID);
|
||||
// save the ID of the user
|
||||
socket.userID = userID;
|
||||
});
|
||||
|
||||
socket.on("connect_error", (err) => {
|
||||
if (err.message === "invalid username") {
|
||||
this.usernameAlreadySelected = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
destroyed() {
|
||||
socket.off("connect_error");
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Lato;
|
||||
src: url("/fonts/Lato-Regular.ttf");
|
||||
}
|
||||
|
||||
#app {
|
||||
font-family: Lato, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
165
examples/private-messaging/src/components/Chat.vue
Normal file
165
examples/private-messaging/src/components/Chat.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="left-panel">
|
||||
<user
|
||||
v-for="user in users"
|
||||
:key="user.userID"
|
||||
:user="user"
|
||||
:selected="selectedUser === user"
|
||||
@select="onSelectUser(user)"
|
||||
/>
|
||||
</div>
|
||||
<message-panel
|
||||
v-if="selectedUser"
|
||||
:user="selectedUser"
|
||||
@input="onMessage"
|
||||
class="right-panel"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import socket from "../socket";
|
||||
import User from "./User";
|
||||
import MessagePanel from "./MessagePanel";
|
||||
|
||||
export default {
|
||||
name: "Chat",
|
||||
components: { User, MessagePanel },
|
||||
data() {
|
||||
return {
|
||||
selectedUser: null,
|
||||
users: [],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onMessage(content) {
|
||||
if (this.selectedUser) {
|
||||
socket.emit("private message", {
|
||||
content,
|
||||
to: this.selectedUser.userID,
|
||||
});
|
||||
this.selectedUser.messages.push({
|
||||
content,
|
||||
fromSelf: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
onSelectUser(user) {
|
||||
this.selectedUser = user;
|
||||
user.hasNewMessages = false;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
socket.on("connect", () => {
|
||||
this.users.forEach((user) => {
|
||||
if (user.self) {
|
||||
user.connected = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("disconnect", () => {
|
||||
this.users.forEach((user) => {
|
||||
if (user.self) {
|
||||
user.connected = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const initReactiveProperties = (user) => {
|
||||
user.hasNewMessages = false;
|
||||
};
|
||||
|
||||
socket.on("users", (users) => {
|
||||
users.forEach((user) => {
|
||||
user.messages.forEach((message) => {
|
||||
message.fromSelf = message.from === socket.userID;
|
||||
});
|
||||
for (let i = 0; i < this.users.length; i++) {
|
||||
const existingUser = this.users[i];
|
||||
if (existingUser.userID === user.userID) {
|
||||
existingUser.connected = user.connected;
|
||||
existingUser.messages = user.messages;
|
||||
return;
|
||||
}
|
||||
}
|
||||
user.self = user.userID === socket.userID;
|
||||
initReactiveProperties(user);
|
||||
this.users.push(user);
|
||||
});
|
||||
// put the current user first, and sort by username
|
||||
this.users.sort((a, b) => {
|
||||
if (a.self) return -1;
|
||||
if (b.self) return 1;
|
||||
if (a.username < b.username) return -1;
|
||||
return a.username > b.username ? 1 : 0;
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("user connected", (user) => {
|
||||
for (let i = 0; i < this.users.length; i++) {
|
||||
const existingUser = this.users[i];
|
||||
if (existingUser.userID === user.userID) {
|
||||
existingUser.connected = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
initReactiveProperties(user);
|
||||
this.users.push(user);
|
||||
});
|
||||
|
||||
socket.on("user disconnected", (id) => {
|
||||
for (let i = 0; i < this.users.length; i++) {
|
||||
const user = this.users[i];
|
||||
if (user.userID === id) {
|
||||
user.connected = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("private message", ({ content, from, to }) => {
|
||||
for (let i = 0; i < this.users.length; i++) {
|
||||
const user = this.users[i];
|
||||
const fromSelf = socket.userID === from;
|
||||
if (user.userID === (fromSelf ? to : from)) {
|
||||
user.messages.push({
|
||||
content,
|
||||
fromSelf,
|
||||
});
|
||||
if (user !== this.selectedUser) {
|
||||
user.hasNewMessages = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
destroyed() {
|
||||
socket.off("connect");
|
||||
socket.off("disconnect");
|
||||
socket.off("users");
|
||||
socket.off("user connected");
|
||||
socket.off("user disconnected");
|
||||
socket.off("private message");
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.left-panel {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 260px;
|
||||
overflow-x: hidden;
|
||||
background-color: #3f0e40;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.right-panel {
|
||||
margin-left: 260px;
|
||||
}
|
||||
</style>
|
||||
101
examples/private-messaging/src/components/MessagePanel.vue
Normal file
101
examples/private-messaging/src/components/MessagePanel.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="header">
|
||||
<status-icon :connected="user.connected" />{{ user.username }}
|
||||
</div>
|
||||
|
||||
<ul class="messages">
|
||||
<li
|
||||
v-for="(message, index) in user.messages"
|
||||
:key="index"
|
||||
class="message"
|
||||
>
|
||||
<div v-if="displaySender(message, index)" class="sender">
|
||||
{{ message.fromSelf ? "(yourself)" : user.username }}
|
||||
</div>
|
||||
{{ message.content }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<form @submit.prevent="onSubmit" class="form">
|
||||
<textarea v-model="input" placeholder="Your message..." class="input" />
|
||||
<button :disabled="!isValid" class="send-button">Send</button>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StatusIcon from "./StatusIcon";
|
||||
|
||||
export default {
|
||||
name: "MessagePanel",
|
||||
components: {
|
||||
StatusIcon,
|
||||
},
|
||||
props: {
|
||||
user: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
input: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onSubmit() {
|
||||
this.$emit("input", this.input);
|
||||
this.input = "";
|
||||
},
|
||||
displaySender(message, index) {
|
||||
return (
|
||||
index === 0 ||
|
||||
this.user.messages[index - 1].fromSelf !==
|
||||
this.user.messages[index].fromSelf
|
||||
);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isValid() {
|
||||
return this.input.length > 0;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.header {
|
||||
line-height: 40px;
|
||||
padding: 10px 20px;
|
||||
border-bottom: 1px solid #dddddd;
|
||||
}
|
||||
|
||||
.messages {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.message {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.sender {
|
||||
font-weight: bold;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.form {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 80%;
|
||||
resize: none;
|
||||
padding: 10px;
|
||||
line-height: 1.5;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #000;
|
||||
}
|
||||
|
||||
.send-button {
|
||||
vertical-align: top;
|
||||
}
|
||||
</style>
|
||||
36
examples/private-messaging/src/components/SelectUsername.vue
Normal file
36
examples/private-messaging/src/components/SelectUsername.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div class="select-username">
|
||||
<form @submit.prevent="onSubmit">
|
||||
<input v-model="username" placeholder="Your username..." />
|
||||
<button :disabled="!isValid">Send</button>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "SelectUsername",
|
||||
data() {
|
||||
return {
|
||||
username: "",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isValid() {
|
||||
return this.username.length > 2;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onSubmit() {
|
||||
this.$emit("input", this.username);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.select-username {
|
||||
width: 300px;
|
||||
margin: 200px auto 0;
|
||||
}
|
||||
</style>
|
||||
27
examples/private-messaging/src/components/StatusIcon.vue
Normal file
27
examples/private-messaging/src/components/StatusIcon.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<i class="icon" :class="{ connected: connected }"></i>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "StatusIcon",
|
||||
props: {
|
||||
connected: Boolean,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.icon {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
background-color: #e38968;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.icon.connected {
|
||||
background-color: #86bb71;
|
||||
}
|
||||
</style>
|
||||
63
examples/private-messaging/src/components/User.vue
Normal file
63
examples/private-messaging/src/components/User.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div class="user" @click="onClick" :class="{ selected: selected }">
|
||||
<div class="description">
|
||||
<div class="name">
|
||||
{{ user.username }} {{ user.self ? " (yourself)" : "" }}
|
||||
</div>
|
||||
<div class="status">
|
||||
<status-icon :connected="user.connected" />{{ status }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="user.hasNewMessages" class="new-messages">!</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StatusIcon from "./StatusIcon";
|
||||
export default {
|
||||
name: "User",
|
||||
components: { StatusIcon },
|
||||
props: {
|
||||
user: Object,
|
||||
selected: Boolean,
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
this.$emit("select");
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
status() {
|
||||
return this.user.connected ? "online" : "offline";
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.selected {
|
||||
background-color: #1164a3;
|
||||
}
|
||||
|
||||
.user {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.description {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.status {
|
||||
color: #92959e;
|
||||
}
|
||||
|
||||
.new-messages {
|
||||
color: white;
|
||||
background-color: red;
|
||||
width: 20px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
float: right;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
8
examples/private-messaging/src/main.js
Normal file
8
examples/private-messaging/src/main.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import Vue from "vue";
|
||||
import App from "./App.vue";
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
new Vue({
|
||||
render: (h) => h(App),
|
||||
}).$mount("#app");
|
||||
10
examples/private-messaging/src/socket.js
Normal file
10
examples/private-messaging/src/socket.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { io } from "socket.io-client";
|
||||
|
||||
const URL = "http://localhost:3000";
|
||||
const socket = io(URL, { autoConnect: false });
|
||||
|
||||
socket.onAny((event, ...args) => {
|
||||
console.log(event, args);
|
||||
});
|
||||
|
||||
export default socket;
|
||||
@@ -14,3 +14,7 @@ $ npm ci
|
||||
$ npm start # run expo
|
||||
$ node server.js # run the server
|
||||
```
|
||||
|
||||
You will probably need to update the IP address of your server in the `App.js` file:
|
||||
|
||||

|
||||
|
||||
BIN
examples/react-native/assets/server-ip.png
Normal file
BIN
examples/react-native/assets/server-ip.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
267
examples/react-native/package-lock.json
generated
267
examples/react-native/package-lock.json
generated
@@ -1236,6 +1236,11 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
|
||||
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
|
||||
},
|
||||
"@types/component-emitter": {
|
||||
"version": "1.2.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz",
|
||||
"integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg=="
|
||||
},
|
||||
"@types/fbemitter": {
|
||||
"version": "2.0.32",
|
||||
"resolved": "https://registry.npmjs.org/@types/fbemitter/-/fbemitter-2.0.32.tgz",
|
||||
@@ -1349,11 +1354,6 @@
|
||||
"negotiator": "0.6.2"
|
||||
}
|
||||
},
|
||||
"after": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
|
||||
"integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8="
|
||||
},
|
||||
"ansi-colors": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz",
|
||||
@@ -1486,11 +1486,6 @@
|
||||
"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
|
||||
"integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg="
|
||||
},
|
||||
"arraybuffer.slice": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
|
||||
"integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog=="
|
||||
},
|
||||
"art": {
|
||||
"version": "0.10.3",
|
||||
"resolved": "https://registry.npmjs.org/art/-/art-0.10.3.tgz",
|
||||
@@ -1671,9 +1666,9 @@
|
||||
}
|
||||
},
|
||||
"base64-arraybuffer": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
|
||||
"integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg="
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz",
|
||||
"integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI="
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "1.3.1",
|
||||
@@ -1693,14 +1688,6 @@
|
||||
"safe-buffer": "5.1.2"
|
||||
}
|
||||
},
|
||||
"better-assert": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz",
|
||||
"integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=",
|
||||
"requires": {
|
||||
"callsite": "1.0.0"
|
||||
}
|
||||
},
|
||||
"big-integer": {
|
||||
"version": "1.6.48",
|
||||
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz",
|
||||
@@ -1715,11 +1702,6 @@
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"blob": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz",
|
||||
"integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig=="
|
||||
},
|
||||
"blueimp-md5": {
|
||||
"version": "2.16.0",
|
||||
"resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.16.0.tgz",
|
||||
@@ -1862,11 +1844,6 @@
|
||||
"caller-callsite": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"callsite": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
|
||||
"integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA="
|
||||
},
|
||||
"callsites": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
|
||||
@@ -2072,11 +2049,6 @@
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
|
||||
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
|
||||
},
|
||||
"component-inherit": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
|
||||
"integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM="
|
||||
},
|
||||
"compressible": {
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
|
||||
@@ -2165,9 +2137,9 @@
|
||||
}
|
||||
},
|
||||
"cookie": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
|
||||
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
|
||||
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
|
||||
},
|
||||
"copy-descriptor": {
|
||||
"version": "0.1.1",
|
||||
@@ -2200,6 +2172,15 @@
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
},
|
||||
"cors": {
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||
"requires": {
|
||||
"object-assign": "^4",
|
||||
"vary": "^1"
|
||||
}
|
||||
},
|
||||
"cosmiconfig": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz",
|
||||
@@ -2409,64 +2390,54 @@
|
||||
}
|
||||
},
|
||||
"engine.io": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.4.2.tgz",
|
||||
"integrity": "sha512-b4Q85dFkGw+TqgytGPrGgACRUhsdKc9S9ErRAXpPGy/CXKs4tYoHDkvIRdsseAF7NjfVwjRFIn6KTnbw7LwJZg==",
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.0.2.tgz",
|
||||
"integrity": "sha512-sumdttqWLNjbuSMOSgDdL2xiEld9s5QZDk9VLyr4e28o+lzNNADhU3qpQDAY7cm2VZH0Otw/U0fL8mEjZ6kBMg==",
|
||||
"requires": {
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "2.0.0",
|
||||
"cookie": "0.3.1",
|
||||
"cookie": "~0.4.1",
|
||||
"cors": "~2.8.5",
|
||||
"debug": "~4.1.0",
|
||||
"engine.io-parser": "~2.2.0",
|
||||
"engine.io-parser": "~4.0.0",
|
||||
"ws": "^7.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"ws": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz",
|
||||
"integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w=="
|
||||
"version": "7.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.0.tgz",
|
||||
"integrity": "sha512-kyFwXuV/5ymf+IXhS6f0+eAFvydbaBW3zjpT6hUdAh/hbVjTIB5EHBGi0bPoCLSK2wcuz3BrEkB9LrYv1Nm4NQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"engine.io-client": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.3.tgz",
|
||||
"integrity": "sha512-0NGY+9hioejTEJCaSJZfWZLk4FPI9dN+1H1C4+wj2iuFba47UgZbJzfWs4aNFajnX/qAaYKbe2lLTfEEWzCmcw==",
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-4.0.3.tgz",
|
||||
"integrity": "sha512-2U6dZkOrMGpVh11l+4pMXHMDnirnzIPIYfugEUpnAl0/3wBb+GO7euu0TT3Nn8E5hgaWSpUA+XOfrch2uZdyGA==",
|
||||
"requires": {
|
||||
"base64-arraybuffer": "0.1.4",
|
||||
"component-emitter": "~1.3.0",
|
||||
"component-inherit": "0.0.3",
|
||||
"debug": "~4.1.0",
|
||||
"engine.io-parser": "~2.2.0",
|
||||
"engine.io-parser": "~4.0.1",
|
||||
"has-cors": "1.1.0",
|
||||
"indexof": "0.0.1",
|
||||
"parseqs": "0.0.5",
|
||||
"parseuri": "0.0.5",
|
||||
"ws": "~6.1.0",
|
||||
"parseqs": "0.0.6",
|
||||
"parseuri": "0.0.6",
|
||||
"ws": "~7.2.1",
|
||||
"xmlhttprequest-ssl": "~1.5.4",
|
||||
"yeast": "0.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"ws": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz",
|
||||
"integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==",
|
||||
"requires": {
|
||||
"async-limiter": "~1.0.0"
|
||||
}
|
||||
"version": "7.2.5",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.2.5.tgz",
|
||||
"integrity": "sha512-C34cIU4+DB2vMyAbmEKossWq2ZQDr6QEyuuCzWrM9zfw1sGc0mYiJ0UnG9zzNykt49C2Fi34hvr2vssFQRS6EA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"engine.io-parser": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.0.tgz",
|
||||
"integrity": "sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w==",
|
||||
"requires": {
|
||||
"after": "0.8.2",
|
||||
"arraybuffer.slice": "~0.0.7",
|
||||
"base64-arraybuffer": "0.1.5",
|
||||
"blob": "0.0.5",
|
||||
"has-binary2": "~1.0.2"
|
||||
}
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.1.tgz",
|
||||
"integrity": "sha512-v5aZK1hlckcJDGmHz3W8xvI3NUHYc9t8QtTbqdR5OaH3S9iJZilPubauOm+vLWOMMWzpE3hiq92l9lTAHamRCg=="
|
||||
},
|
||||
"envinfo": {
|
||||
"version": "7.5.1",
|
||||
@@ -3118,21 +3089,6 @@
|
||||
"resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
|
||||
"integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE="
|
||||
},
|
||||
"has-binary2": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz",
|
||||
"integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==",
|
||||
"requires": {
|
||||
"isarray": "2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
|
||||
"integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
|
||||
}
|
||||
}
|
||||
},
|
||||
"has-cors": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
|
||||
@@ -3236,11 +3192,6 @@
|
||||
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
|
||||
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
|
||||
},
|
||||
"indexof": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
|
||||
"integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10="
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
@@ -4546,11 +4497,6 @@
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
|
||||
},
|
||||
"object-component": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz",
|
||||
"integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE="
|
||||
},
|
||||
"object-copy": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
|
||||
@@ -4763,20 +4709,14 @@
|
||||
"integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA=="
|
||||
},
|
||||
"parseqs": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
|
||||
"integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=",
|
||||
"requires": {
|
||||
"better-assert": "~1.0.0"
|
||||
}
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz",
|
||||
"integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w=="
|
||||
},
|
||||
"parseuri": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz",
|
||||
"integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=",
|
||||
"requires": {
|
||||
"better-assert": "~1.0.0"
|
||||
}
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz",
|
||||
"integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow=="
|
||||
},
|
||||
"parseurl": {
|
||||
"version": "1.3.3",
|
||||
@@ -5925,101 +5865,45 @@
|
||||
}
|
||||
},
|
||||
"socket.io": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz",
|
||||
"integrity": "sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==",
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-3.0.1.tgz",
|
||||
"integrity": "sha512-oVYbCQ4sCwm4wVi+f1bsE3YFXcvd6b4JjVP8D7IZnQqBeJOKX9XrdgJWSbXqBEqUXPY3jdTqb1M3s4KFTa/IHg==",
|
||||
"requires": {
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "~2.0.0",
|
||||
"debug": "~4.1.0",
|
||||
"engine.io": "~3.4.0",
|
||||
"has-binary2": "~1.0.2",
|
||||
"socket.io-adapter": "~1.1.0",
|
||||
"socket.io-client": "2.3.0",
|
||||
"socket.io-parser": "~3.4.0"
|
||||
"engine.io": "~4.0.0",
|
||||
"socket.io-adapter": "~2.0.3",
|
||||
"socket.io-parser": "~4.0.1"
|
||||
}
|
||||
},
|
||||
"socket.io-adapter": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz",
|
||||
"integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g=="
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.0.3.tgz",
|
||||
"integrity": "sha512-2wo4EXgxOGSFueqvHAdnmi5JLZzWqMArjuP4nqC26AtLh5PoCPsaRbRdah2xhcwTAMooZfjYiNVNkkmmSMaxOQ=="
|
||||
},
|
||||
"socket.io-client": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz",
|
||||
"integrity": "sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==",
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-3.0.1.tgz",
|
||||
"integrity": "sha512-iIzWRDrF/h3KPtHjvLt5LL/1n7Euvv35zVa1r10ScRjVw40yc8DxFj7GnKrj1RNYkbtveWOwEsy2lWp3oFJO7w==",
|
||||
"requires": {
|
||||
"@types/component-emitter": "^1.2.10",
|
||||
"backo2": "1.0.2",
|
||||
"base64-arraybuffer": "0.1.5",
|
||||
"component-bind": "1.0.0",
|
||||
"component-emitter": "1.2.1",
|
||||
"component-emitter": "~1.3.0",
|
||||
"debug": "~4.1.0",
|
||||
"engine.io-client": "~3.4.0",
|
||||
"has-binary2": "~1.0.2",
|
||||
"has-cors": "1.1.0",
|
||||
"indexof": "0.0.1",
|
||||
"object-component": "0.0.3",
|
||||
"parseqs": "0.0.5",
|
||||
"parseuri": "0.0.5",
|
||||
"socket.io-parser": "~3.3.0",
|
||||
"to-array": "0.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"component-emitter": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
|
||||
"integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
|
||||
},
|
||||
"isarray": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
|
||||
"integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"socket.io-parser": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz",
|
||||
"integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==",
|
||||
"requires": {
|
||||
"component-emitter": "1.2.1",
|
||||
"debug": "~3.1.0",
|
||||
"isarray": "2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"engine.io-client": "~4.0.0",
|
||||
"parseuri": "0.0.6",
|
||||
"socket.io-parser": "~4.0.1"
|
||||
}
|
||||
},
|
||||
"socket.io-parser": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.1.tgz",
|
||||
"integrity": "sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A==",
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.1.tgz",
|
||||
"integrity": "sha512-5JfNykYptCwU2lkOI0ieoePWm+6stEhkZ2UnLDjqnE1YEjUlXXLd1lpxPZ+g+h3rtaytwWkWrLQCaJULlGqjOg==",
|
||||
"requires": {
|
||||
"component-emitter": "1.2.1",
|
||||
"debug": "~4.1.0",
|
||||
"isarray": "2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"component-emitter": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
|
||||
"integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
|
||||
},
|
||||
"isarray": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
|
||||
"integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
|
||||
}
|
||||
"component-emitter": "~1.3.0",
|
||||
"debug": "~4.1.0"
|
||||
}
|
||||
},
|
||||
"sort-keys": {
|
||||
@@ -6284,11 +6168,6 @@
|
||||
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz",
|
||||
"integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE="
|
||||
},
|
||||
"to-array": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
|
||||
"integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA="
|
||||
},
|
||||
"to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
"react-dom": "~16.9.0",
|
||||
"react-native": "https://github.com/expo/react-native/archive/sdk-37.0.1.tar.gz",
|
||||
"react-native-web": "~0.11.7",
|
||||
"socket.io": "~2.3.0",
|
||||
"socket.io-client": "~2.3.0"
|
||||
"socket.io": "^3.0.1",
|
||||
"socket.io-client": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-preset-expo": "~8.1.0",
|
||||
|
||||
6
examples/react-native/server.js
vendored
6
examples/react-native/server.js
vendored
@@ -8,7 +8,11 @@ io.on('connection', socket => {
|
||||
});
|
||||
});
|
||||
|
||||
io.listen(3000);
|
||||
io.listen(3000, {
|
||||
cors: {
|
||||
origin: ["http://localhost:19006"]
|
||||
}
|
||||
});
|
||||
|
||||
setInterval(() => {
|
||||
io.emit('message', new Date().toISOString());
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Manager } from "socket.io-client";
|
||||
import { io } from "socket.io-client";
|
||||
|
||||
const manager = new Manager("ws://localhost:8080");
|
||||
const socket = manager.socket("/");
|
||||
const socket = io("ws://localhost:8080/", {});
|
||||
|
||||
socket.on("connect", () => {
|
||||
console.log(`connect ${socket.id}`);
|
||||
|
||||
89
examples/typescript/package-lock.json
generated
89
examples/typescript/package-lock.json
generated
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "typescript",
|
||||
"name": "typescript-example",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
@@ -9,6 +9,21 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz",
|
||||
"integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg=="
|
||||
},
|
||||
"@types/cookie": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.0.tgz",
|
||||
"integrity": "sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg=="
|
||||
},
|
||||
"@types/cors": {
|
||||
"version": "2.8.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.9.tgz",
|
||||
"integrity": "sha512-zurD1ibz21BRlAOIKP8yhrxlqKx6L9VCwkB5kMiP6nZAhoF5MvC7qS1qPA7nRcr1GJolfkQC7/EAL4hdYejLtg=="
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "14.14.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.16.tgz",
|
||||
"integrity": "sha512-naXYePhweTi+BMv11TgioE2/FXU4fSl29HAH1ffxVciNsH3rYXjNP2yM8wqmSm7jS20gM8TIklKiTen+1iVncw=="
|
||||
},
|
||||
"accepts": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
|
||||
@@ -81,9 +96,9 @@
|
||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A=="
|
||||
},
|
||||
"engine.io": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.0.1.tgz",
|
||||
"integrity": "sha512-6EaSBxasBUwxRdf6B68SEYpD3tcrG80J4YTzHl/D+9Q+vM0AMHZabfYcc2WdnvEaQxZjX/UZsa+UdGoM0qQQkQ==",
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.0.5.tgz",
|
||||
"integrity": "sha512-Ri+whTNr2PKklxQkfbGjwEo+kCBUM4Qxk4wtLqLrhH+b1up2NFL9g9pjYWiCV/oazwB0rArnvF/ZmZN2ab5Hpg==",
|
||||
"requires": {
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "2.0.0",
|
||||
@@ -95,9 +110,9 @@
|
||||
}
|
||||
},
|
||||
"engine.io-client": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-4.0.1.tgz",
|
||||
"integrity": "sha512-3XXfWrEutlf1vg5PlS805bD+AgZXhRIKYAG04f1iCGOs70dWEYlZGfCZUNwPwNx05lBCKs1lIeL3SkLB0P++xw==",
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-4.0.5.tgz",
|
||||
"integrity": "sha512-1lkn0QdekHQPMTcxUh8LqIuxQHNtKV5GvqkQzmZ1rYKAvB6puMm13U7K1ps3OQZ4joE46asQiAKrcdL9weNEVw==",
|
||||
"requires": {
|
||||
"base64-arraybuffer": "0.1.4",
|
||||
"component-emitter": "~1.3.0",
|
||||
@@ -119,9 +134,12 @@
|
||||
}
|
||||
},
|
||||
"engine.io-parser": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.1.tgz",
|
||||
"integrity": "sha512-v5aZK1hlckcJDGmHz3W8xvI3NUHYc9t8QtTbqdR5OaH3S9iJZilPubauOm+vLWOMMWzpE3hiq92l9lTAHamRCg=="
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.2.tgz",
|
||||
"integrity": "sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg==",
|
||||
"requires": {
|
||||
"base64-arraybuffer": "0.1.4"
|
||||
}
|
||||
},
|
||||
"has-cors": {
|
||||
"version": "1.1.0",
|
||||
@@ -147,9 +165,9 @@
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.2",
|
||||
@@ -172,27 +190,30 @@
|
||||
"integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow=="
|
||||
},
|
||||
"socket.io": {
|
||||
"version": "3.0.0-rc3",
|
||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-3.0.0-rc3.tgz",
|
||||
"integrity": "sha512-AVIpRd7OTyhCgg5muz0AqeF06csptsnP8UP10ubLrnL7XKplJFDadUB9fEbwB3iLMpf1NT+YfqTBCVKHhpMlZA==",
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-3.0.4.tgz",
|
||||
"integrity": "sha512-Vj1jUoO75WGc9txWd311ZJJqS9Dr8QtNJJ7gk2r7dcM/yGe9sit7qOijQl3GAwhpBOz/W8CwkD7R6yob07nLbA==",
|
||||
"requires": {
|
||||
"@types/cookie": "^0.4.0",
|
||||
"@types/cors": "^2.8.8",
|
||||
"@types/node": "^14.14.7",
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "~2.0.0",
|
||||
"debug": "~4.1.0",
|
||||
"engine.io": "~4.0.0",
|
||||
"socket.io-adapter": "2.0.3-rc2",
|
||||
"socket.io-parser": "4.0.1-rc3"
|
||||
"socket.io-adapter": "~2.0.3",
|
||||
"socket.io-parser": "~4.0.1"
|
||||
}
|
||||
},
|
||||
"socket.io-adapter": {
|
||||
"version": "2.0.3-rc2",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.0.3-rc2.tgz",
|
||||
"integrity": "sha512-ic/fttwhpQ2Ry43ou6gOIeZgpGmsIUFQZ0Ww0DIpCerTKzDdLPG2+AEomG5SGXpppvtbwwesB7O9Azq0JiSs/g=="
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.0.3.tgz",
|
||||
"integrity": "sha512-2wo4EXgxOGSFueqvHAdnmi5JLZzWqMArjuP4nqC26AtLh5PoCPsaRbRdah2xhcwTAMooZfjYiNVNkkmmSMaxOQ=="
|
||||
},
|
||||
"socket.io-client": {
|
||||
"version": "3.0.0-rc3",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-3.0.0-rc3.tgz",
|
||||
"integrity": "sha512-9w67FZtTsGlDiwpobQ0l1UeLinx6LuFYEHGTcFRc5P5PyY/4M6ZI7nBFxIhjf0l8DTUfAJwllqzNxSqMW5nxhw==",
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-3.0.4.tgz",
|
||||
"integrity": "sha512-qMvBuS+W9JIN2mkfAWDCxuIt+jpIKDf8C0604zEqx1JrPaPSS6cN0F3B2GYWC83TqBeVJXW66GFxWV3KD88n0Q==",
|
||||
"requires": {
|
||||
"@types/component-emitter": "^1.2.10",
|
||||
"backo2": "1.0.2",
|
||||
@@ -201,14 +222,15 @@
|
||||
"debug": "~4.1.0",
|
||||
"engine.io-client": "~4.0.0",
|
||||
"parseuri": "0.0.6",
|
||||
"socket.io-parser": "4.0.1-rc3"
|
||||
"socket.io-parser": "~4.0.1"
|
||||
}
|
||||
},
|
||||
"socket.io-parser": {
|
||||
"version": "4.0.1-rc3",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.1-rc3.tgz",
|
||||
"integrity": "sha512-H85dvTVUOBMzquX5AojQvxRPCvH7jMkrhp2la+2gYn/uiS20sK1U8osWCMwU00oUITDUkjDTxLjw0fGkDTA76w==",
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.2.tgz",
|
||||
"integrity": "sha512-Bs3IYHDivwf+bAAuW/8xwJgIiBNtlvnjYRc4PbXgniLmcP1BrakBoq/QhO24rgtgW7VZ7uAaswRGxutUnlAK7g==",
|
||||
"requires": {
|
||||
"@types/component-emitter": "^1.2.10",
|
||||
"component-emitter": "~1.3.0",
|
||||
"debug": "~4.1.0"
|
||||
}
|
||||
@@ -239,15 +261,20 @@
|
||||
"yn": "3.1.1"
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz",
|
||||
"integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ=="
|
||||
},
|
||||
"vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
||||
},
|
||||
"ws": {
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz",
|
||||
"integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA=="
|
||||
"version": "7.4.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz",
|
||||
"integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA=="
|
||||
},
|
||||
"xmlhttprequest-ssl": {
|
||||
"version": "1.5.5",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user