Compare commits

..

16 Commits
4.3.2 ... 2.x

Author SHA1 Message Date
Damien Arrachequesne
88b2cdb6ab chore(release): 2.5.1
Release notes: https://github.com/socketio/socket.io/releases/tag/2.5.1
Diff: https://github.com/socketio/socket.io/compare/2.5.0...2.5.1
2024-06-19 10:48:24 +02:00
Damien Arrachequesne
d30630ba10 fix: add a noop handler for the error event
Backported from main: 15af22fc22
2024-06-19 10:46:29 +02:00
Damien Arrachequesne
f927ba29ef test: fix tests on Node.js > 18
Reference: https://nodejs.org/api/buffer.html#class-blob
2024-06-19 10:44:04 +02:00
Damien Arrachequesne
baa6804440 chore(release): 2.5.0
Release notes: https://github.com/socketio/socket.io/releases/tag/2.5.0
Diff: https://github.com/socketio/socket.io/compare/2.4.1...2.5.0
2022-06-26 09:49:21 +02:00
Damien Arrachequesne
f223178eb6 fix: prevent the socket from joining a room after disconnection
Calling `socket.join()` after disconnection would lead to a memory
leak, because the room was never removed from the memory:

```js
io.on("connection", (socket) => {
  socket.disconnect();
  socket.join("room1"); // leak
});
```

Related:

- https://github.com/socketio/socket.io/issues/4067
- https://github.com/socketio/socket.io/issues/4380

Backported from 18f3fdab12
2022-06-26 08:54:51 +02:00
Damien Arrachequesne
226cc16165 fix: only set 'connected' to true after middleware execution
The Socket instance is only considered connected when the "connection"
event is emitted, and not during the middleware(s) execution.

```js
io.use((socket, next) => {
  console.log(socket.connected); // prints "false"
  next();
});

io.on("connection", (socket) => {
  console.log(socket.connected); // prints "true"
});
```

Related: https://github.com/socketio/socket.io/issues/4129

Backported from 02b0f73e2c
2022-06-26 08:46:28 +02:00
Damien Arrachequesne
05e1278cfa fix: fix race condition in dynamic namespaces
Using an async operation with `io.use()` could lead to the creation of
several instances of a same namespace, each of them overriding the
previous one.

Example:

```js
io.use(async (nsp, auth, next) => {
  await anOperationThatTakesSomeTime();
  next();
});
```

Related: https://github.com/socketio/socket.io/issues/4136

Backported from 9d86397243
2022-06-26 08:41:16 +02:00
Damien Arrachequesne
22d4bdf00d fix: ignore packet received after disconnection
Related: https://github.com/socketio/socket.io/issues/3095

Backported from 494c64e44f
2022-06-26 08:35:42 +02:00
Damien Arrachequesne
dfded53593 chore: update engine.io version to 3.6.0
Release notes: https://github.com/socketio/engine.io/releases/tag/3.6.0
Diff: https://github.com/socketio/engine.io/compare/3.5.0...3.6.0
2022-06-26 08:13:06 +02:00
Damien Arrachequesne
e6b869738c chore(release): 2.4.1
Diff: https://github.com/socketio/socket.io/compare/2.4.0...2.4.1
2021-01-07 10:59:46 +01:00
Damien Arrachequesne
a169050947 revert: fix(security): do not allow all origins by default
This reverts commit f78a575f66.

This commit contains a breaking change which deviates from semver,
which we try to follow as closely as possible. That's why this change
is reverted and we will rather suggest users to upgrade to v3.

Related: https://github.com/socketio/socket.io/discussions/3741
2021-01-07 10:51:55 +01:00
Damien Arrachequesne
873fdc55ed chore(release): 2.4.0
Diff: https://github.com/socketio/socket.io/compare/2.3.0...2.4.0
2021-01-05 00:27:13 +01:00
Damien Arrachequesne
f78a575f66 fix(security): do not allow all origins by default
BREAKING CHANGE: previously, all origins were allowed by default, which
meant that a Socket.IO server sent the necessary CORS headers
(`Access-Control-Allow-xxx`) to any domain by default.

Please note that you are not impacted if:

- you are using Socket.IO v2 and the `origins` option to restrict the list of allowed domains
- you are using Socket.IO v3 (disabled by default)

This commit also removes the support for '*' matchers and protocol-less
URL:

```
io.origins('https://example.com:443'); => io.origins(['https://example.com']);
io.origins('localhost:3000');          => io.origins(['http://localhost:3000']);
io.origins('http://localhost:*');      => io.origins(['http://localhost:3000']);
io.origins('*:3000');                  => io.origins(['http://localhost:3000']);
```

To restore the previous behavior (please use with caution):

```js
io.origins((_, callback) => {
  callback(null, true);
});
```

See also:

- https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
- https://socket.io/docs/v3/handling-cors/
- https://socket.io/docs/v3/migrating-from-2-x-to-3-0/#CORS-handling

Thanks a lot to https://github.com/ni8walk3r for the security report.
2021-01-04 22:34:09 +01:00
Sebastiaan Marynissen
d33a619905 fix: properly overwrite the query sent in the handshake
The `query` option of the Manager had the priority over the one of the
Socket instance, which meant updating the Socket#query object on the
client-side was not reflected in the Socket#handshake object on the
server-side.

Please note that the behavior of the `query` option is still a bit
weird in Socket.IO v2, as it only applies to non-default namespace.
This is fixed in v3:

- https://socket.io/docs/v3/migrating-from-2-x-to-3-0/#Add-a-clear-distinction-between-the-Manager-query-option-and-the-Socket-query-option
- https://socket.io/docs/v3/middlewares/#Sending-credentials

Fixes https://github.com/socketio/socket.io/issues/3495
2021-01-04 11:34:24 +01:00
Damien Arrachequesne
3951a79359 chore: bump engine.io version
Diff: https://github.com/socketio/engine.io/compare/3.4.2...3.5.0
2021-01-04 10:50:13 +01:00
Damien Arrachequesne
6fa026fc94 ci: migrate to GitHub Actions
Due to the recent changes to the Travis CI platform (see [1]), we will
now use GitHub Actions to run the tests.

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

[1]: https://blog.travis-ci.com/2020-11-02-travis-ci-new-billing
2021-01-04 10:46:44 +01:00
224 changed files with 8244 additions and 31072 deletions

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

@@ -0,0 +1,32 @@
**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)

View File

@@ -1,61 +0,0 @@
---
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.

View File

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

View File

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

View File

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

View File

@@ -3,8 +3,6 @@ name: CI
on:
push:
pull_request:
schedule:
- cron: '0 0 * * 0'
jobs:
test-node:
@@ -12,12 +10,12 @@ jobs:
strategy:
matrix:
node-version: [12, 14, 16]
node-version: [10.x, 12.x, 14.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci

4
.gitignore vendored
View File

@@ -10,5 +10,5 @@ benchmarks/*.png
node_modules
coverage
.idea
.nyc_output
dist/
dist
.nyc_output

View File

@@ -1,2 +0,0 @@
language = "nodejs"
run = "npm start"

View File

@@ -1,452 +1,36 @@
## [4.3.2](https://github.com/socketio/socket.io/compare/4.3.1...4.3.2) (2021-11-08)
## [2.5.1](https://github.com/socketio/socket.io/compare/2.5.0...2.5.1) (2024-06-19)
### Bug Fixes
* fix race condition in dynamic namespaces ([#4137](https://github.com/socketio/socket.io/issues/4137)) ([9d86397](https://github.com/socketio/socket.io/commit/9d86397243bcbb5775a29d96e5ef03e17148a8e7))
* add a noop handler for the error event ([d30630b](https://github.com/socketio/socket.io/commit/d30630ba10562bf987f4d2b42440fc41a828119c))
## [4.3.1](https://github.com/socketio/socket.io/compare/4.3.0...4.3.1) (2021-10-16)
# [2.5.0](https://github.com/socketio/socket.io/compare/2.4.1...2.5.0) (2022-06-26)
### Bug Fixes
* fix server attachment ([#4127](https://github.com/socketio/socket.io/issues/4127)) ([0ef2a4d](https://github.com/socketio/socket.io/commit/0ef2a4d02c9350aff163df9cb61aece89c4dac0f))
* fix race condition in dynamic namespaces ([05e1278](https://github.com/socketio/socket.io/commit/05e1278cfa99f3ecf3f8f0531ffe57d850e9a05b))
* ignore packet received after disconnection ([22d4bdf](https://github.com/socketio/socket.io/commit/22d4bdf00d1a03885dc0171125faddfaef730066))
* only set 'connected' to true after middleware execution ([226cc16](https://github.com/socketio/socket.io/commit/226cc16165f9fe60f16ff4d295fb91c8971cde35))
* prevent the socket from joining a room after disconnection ([f223178](https://github.com/socketio/socket.io/commit/f223178eb655a7713303b21a78f9ef9e161d6458))
# [4.3.0](https://github.com/socketio/socket.io/compare/4.2.0...4.3.0) (2021-10-14)
### Bug Fixes
* **typings:** add name field to cookie option ([#4099](https://github.com/socketio/socket.io/issues/4099)) ([033c5d3](https://github.com/socketio/socket.io/commit/033c5d399a2b985afad32c1e4b0c16d764e248cd))
* send volatile packets with binary attachments ([dc81fcf](https://github.com/socketio/socket.io/commit/dc81fcf461cfdbb5b34b1a5a96b84373754047d5))
### Features
* serve ESM bundle ([60edecb](https://github.com/socketio/socket.io/commit/60edecb3bd33801803cdcba0aefbafa381a2abb3))
# [4.2.0](https://github.com/socketio/socket.io/compare/4.1.3...4.2.0) (2021-08-30)
### Bug Fixes
* **typings:** allow async listener in typed events ([ccfd8ca](https://github.com/socketio/socket.io/commit/ccfd8caba6d38b7ba6c5114bd8179346ed07671c))
### Features
* ignore the query string when serving client JavaScript ([#4024](https://github.com/socketio/socket.io/issues/4024)) ([24fee27](https://github.com/socketio/socket.io/commit/24fee27ba36485308f8e995879c10931532c814e))
## [4.1.3](https://github.com/socketio/socket.io/compare/4.1.2...4.1.3) (2021-07-10)
### Bug Fixes
* fix io.except() method ([94e27cd](https://github.com/socketio/socket.io/commit/94e27cd072c8a4eeb9636f6ffbb7a21d382f36b0))
* remove x-sourcemap header ([a4dffc6](https://github.com/socketio/socket.io/commit/a4dffc6527f412d51a786ae5bf2e9080fe1ca63c))
## [4.1.2](https://github.com/socketio/socket.io/compare/4.1.1...4.1.2) (2021-05-17)
### Bug Fixes
* **typings:** ensure compatibility with TypeScript 3.x ([0cb6ac9](https://github.com/socketio/socket.io/commit/0cb6ac95b49a27483b6f1b6402fa54b35f82e36f))
* ensure compatibility with previous versions of the adapter ([a2cf248](https://github.com/socketio/socket.io/commit/a2cf2486c366cb62293101c10520c57f6984a3fc))
## [4.1.1](https://github.com/socketio/socket.io/compare/4.1.0...4.1.1) (2021-05-11)
### Bug Fixes
* **typings:** properly type server-side events ([b84ed1e](https://github.com/socketio/socket.io/commit/b84ed1e41c9053792caf58974c5de9395bfd509f))
* **typings:** properly type the adapter attribute ([891b187](https://github.com/socketio/socket.io/commit/891b1870e92d1ec38910f03bb839817e2d6be65a))
# [4.1.0](https://github.com/socketio/socket.io/compare/4.0.2...4.1.0) (2021-05-11)
### Features
* add support for inter-server communication ([93cce05](https://github.com/socketio/socket.io/commit/93cce05fb3faf91f21fa71212275c776aa161107))
* notify upon namespace creation ([499c892](https://github.com/socketio/socket.io/commit/499c89250d2db1ab7725ab2b74840e188c267c46))
* add a "connection_error" event ([7096e98](https://github.com/socketio/engine.io/commit/7096e98a02295a62c8ea2aa56461d4875887092d), from `engine.io`)
* add the "initial_headers" and "headers" events ([2527543](https://github.com/socketio/engine.io/commit/252754353a0e88eb036ebb3082e9d6a9a5f497db), from `engine.io`)
### Performance Improvements
* add support for the "wsPreEncoded" writing option ([dc381b7](https://github.com/socketio/socket.io/commit/dc381b72c6b2f8172001dedd84116122e4cc95b3))
## [4.0.2](https://github.com/socketio/socket.io/compare/4.0.1...4.0.2) (2021-05-06)
### Bug Fixes
* **typings:** make "engine" attribute public ([b81ce4c](https://github.com/socketio/socket.io/commit/b81ce4c9d0b00666361498e2ba5e0d007d5860b8))
* properly export the Socket class ([d65b6ee](https://github.com/socketio/socket.io/commit/d65b6ee84c8e91deb61c3c1385eb19afa196a909))
## [4.0.1](https://github.com/socketio/socket.io/compare/4.0.0...4.0.1) (2021-03-31)
### Bug Fixes
* **typings:** add fallback to untyped event listener ([#3834](https://github.com/socketio/socket.io/issues/3834)) ([a11152f](https://github.com/socketio/socket.io/commit/a11152f42b281df83409313962f60f230239c79e))
* **typings:** update return type from emit ([#3843](https://github.com/socketio/socket.io/issues/3843)) ([1a72ae4](https://github.com/socketio/socket.io/commit/1a72ae4fe27a14cf60916f991a2c94da91d9e54a))
# [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))
## [2.4.1](https://github.com/socketio/socket.io/compare/2.4.0...2.4.1) (2021-01-07)
### Reverts
* restore the socket middleware functionality ([bf54327](https://github.com/socketio/socket.io/commit/bf5432742158e4d5ba2722cff4a614967dffa5b9))
* fix(security): do not allow all origins by default ([a169050](https://github.com/socketio/socket.io/commit/a1690509470e9dd5559cec4e60908ca6c23e9ba0))
## [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)
# [2.4.0](https://github.com/socketio/socket.io/compare/2.3.0...2.4.0) (2021-01-04)
### 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)
### Bug Fixes
* export ServerOptions and Namespace types ([#3684](https://github.com/socketio/socket.io/issues/3684)) ([f62f180](https://github.com/socketio/socket.io/commit/f62f180edafdd56d8a8a277e092bc66df0c5f07f))
* **typings:** update the signature of the emit method ([50671d9](https://github.com/socketio/socket.io/commit/50671d984a81535a6a15c704546ca7465e2ea295))
# [3.0.0](https://github.com/socketio/socket.io/compare/2.3.0...3.0.0) (2020-11-05)
### Bug Fixes
* close clients with no namespace ([91cd255](https://github.com/socketio/socket.io/commit/91cd255ba76ff6a780c62740f9f5cd3a76f5d7c7))
### Features
* emit an Error object upon middleware error ([54bf4a4](https://github.com/socketio/socket.io/commit/54bf4a44e9e896dfb64764ee7bd4e8823eb7dc7b))
* serve msgpack bundle ([aa7574f](https://github.com/socketio/socket.io/commit/aa7574f88471aa30ae472a5cddf1000a8baa70fd))
* add support for catch-all listeners ([5c73733](https://github.com/socketio/socket.io/commit/5c737339858d59eab4b5ee2dd6feff0e82c4fe5a))
* make Socket#join() and Socket#leave() synchronous ([129c641](https://github.com/socketio/socket.io/commit/129c6417bd818bc8b4e1b831644323876e627c13))
* remove prod dependency to socket.io-client ([7603da7](https://github.com/socketio/socket.io/commit/7603da71a535481e3fc60e38b013abf78516d322))
* move binary detection back to the parser ([669592d](https://github.com/socketio/socket.io/commit/669592d120409a5cf00f128070dee6d22259ba4f))
* add ES6 module export ([8b6b100](https://github.com/socketio/socket.io/commit/8b6b100c284ccce7d85e55659e3397f533916847))
* do not reuse the Engine.IO id ([2875d2c](https://github.com/socketio/socket.io/commit/2875d2cfdfa463e64cb520099749f543bbc4eb15))
* remove Server#set() method ([029f478](https://github.com/socketio/socket.io/commit/029f478992f59b1eb5226453db46363a570eea46))
* remove Socket#rooms object ([1507b41](https://github.com/socketio/socket.io/commit/1507b416d584381554d1ed23c9aaf3b650540071))
* remove the 'origins' option ([a8c0600](https://github.com/socketio/socket.io/commit/a8c06006098b512ba1b8b8df82777349db486f41))
* remove the implicit connection to the default namespace ([3289f7e](https://github.com/socketio/socket.io/commit/3289f7ec376e9ec88c2f90e2735c8ca8d01c0e97))
* throw upon reserved event names ([4bd5b23](https://github.com/socketio/socket.io/commit/4bd5b2339a66a5a675e20f689fff2e70ff12d236))
### BREAKING CHANGES
* the Socket#use() method is removed (see [5c73733](https://github.com/socketio/socket.io/commit/5c737339858d59eab4b5ee2dd6feff0e82c4fe5a))
* Socket#join() and Socket#leave() do not accept a callback argument anymore.
Before:
```js
socket.join("room1", () => {
io.to("room1").emit("hello");
});
```
After:
```js
socket.join("room1");
io.to("room1").emit("hello");
// or await socket.join("room1"); for custom adapters
```
* the "connected" map is renamed to "sockets"
* the Socket#binary() method is removed, as this use case is now covered by the ability to provide your own parser.
* the 'origins' option is removed
Before:
```js
new Server(3000, {
origins: ["https://example.com"]
});
```
The 'origins' option was used in the allowRequest method, in order to
determine whether the request should pass or not. And the Engine.IO
server would implicitly add the necessary Access-Control-Allow-xxx
headers.
After:
```js
new Server(3000, {
cors: {
origin: "https://example.com",
methods: ["GET", "POST"],
allowedHeaders: ["content-type"]
}
});
```
The already existing 'allowRequest' option can be used for validation:
```js
new Server(3000, {
allowRequest: (req, callback) => {
callback(null, req.headers.referer.startsWith("https://example.com"));
}
});
```
* Socket#rooms is now a Set instead of an object
* Namespace#connected is now a Map instead of an object
* there is no more implicit connection to the default namespace:
```js
// client-side
const socket = io("/admin");
// server-side
io.on("connect", socket => {
// not triggered anymore
})
io.use((socket, next) => {
// not triggered anymore
});
io.of("/admin").use((socket, next) => {
// triggered
});
```
* the Server#set() method was removed
This method was kept for backward-compatibility with pre-1.0 versions.
# [3.0.0-rc4](https://github.com/socketio/socket.io/compare/3.0.0-rc3...3.0.0-rc4) (2020-10-30)
### Features
* emit an Error object upon middleware error ([54bf4a4](https://github.com/socketio/socket.io/commit/54bf4a44e9e896dfb64764ee7bd4e8823eb7dc7b))
* serve msgpack bundle ([aa7574f](https://github.com/socketio/socket.io/commit/aa7574f88471aa30ae472a5cddf1000a8baa70fd))
# [3.0.0-rc3](https://github.com/socketio/socket.io/compare/3.0.0-rc2...3.0.0-rc3) (2020-10-26)
### Features
* add support for catch-all listeners ([5c73733](https://github.com/socketio/socket.io/commit/5c737339858d59eab4b5ee2dd6feff0e82c4fe5a))
* make Socket#join() and Socket#leave() synchronous ([129c641](https://github.com/socketio/socket.io/commit/129c6417bd818bc8b4e1b831644323876e627c13))
* remove prod dependency to socket.io-client ([7603da7](https://github.com/socketio/socket.io/commit/7603da71a535481e3fc60e38b013abf78516d322))
### BREAKING CHANGES
* the Socket#use() method is removed (see [5c73733](https://github.com/socketio/socket.io/commit/5c737339858d59eab4b5ee2dd6feff0e82c4fe5a))
* Socket#join() and Socket#leave() do not accept a callback argument anymore.
Before:
```js
socket.join("room1", () => {
io.to("room1").emit("hello");
});
```
After:
```js
socket.join("room1");
io.to("room1").emit("hello");
// or await socket.join("room1"); for custom adapters
```
# [3.0.0-rc2](https://github.com/socketio/socket.io/compare/3.0.0-rc1...3.0.0-rc2) (2020-10-15)
### Bug Fixes
* close clients with no namespace ([91cd255](https://github.com/socketio/socket.io/commit/91cd255ba76ff6a780c62740f9f5cd3a76f5d7c7))
### Code Refactoring
* remove duplicate _sockets map ([8a5db7f](https://github.com/socketio/socket.io/commit/8a5db7fa36a075da75cde43cd4fb6382b7659953))
### Features
* move binary detection back to the parser ([669592d](https://github.com/socketio/socket.io/commit/669592d120409a5cf00f128070dee6d22259ba4f))
### BREAKING CHANGES
* the "connected" map is renamed to "sockets"
* the Socket#binary() method is removed, as this use case is now covered by the ability to provide your own parser.
# [3.0.0-rc1](https://github.com/socketio/socket.io/compare/2.3.0...3.0.0-rc1) (2020-10-13)
### Features
* add ES6 module export ([8b6b100](https://github.com/socketio/socket.io/commit/8b6b100c284ccce7d85e55659e3397f533916847))
* do not reuse the Engine.IO id ([2875d2c](https://github.com/socketio/socket.io/commit/2875d2cfdfa463e64cb520099749f543bbc4eb15))
* remove Server#set() method ([029f478](https://github.com/socketio/socket.io/commit/029f478992f59b1eb5226453db46363a570eea46))
* remove Socket#rooms object ([1507b41](https://github.com/socketio/socket.io/commit/1507b416d584381554d1ed23c9aaf3b650540071))
* remove the 'origins' option ([a8c0600](https://github.com/socketio/socket.io/commit/a8c06006098b512ba1b8b8df82777349db486f41))
* remove the implicit connection to the default namespace ([3289f7e](https://github.com/socketio/socket.io/commit/3289f7ec376e9ec88c2f90e2735c8ca8d01c0e97))
* throw upon reserved event names ([4bd5b23](https://github.com/socketio/socket.io/commit/4bd5b2339a66a5a675e20f689fff2e70ff12d236))
### BREAKING CHANGES
* the 'origins' option is removed
Before:
```js
new Server(3000, {
origins: ["https://example.com"]
});
```
The 'origins' option was used in the allowRequest method, in order to
determine whether the request should pass or not. And the Engine.IO
server would implicitly add the necessary Access-Control-Allow-xxx
headers.
After:
```js
new Server(3000, {
cors: {
origin: "https://example.com",
methods: ["GET", "POST"],
allowedHeaders: ["content-type"]
}
});
```
The already existing 'allowRequest' option can be used for validation:
```js
new Server(3000, {
allowRequest: (req, callback) => {
callback(null, req.headers.referer.startsWith("https://example.com"));
}
});
```
* Socket#rooms is now a Set instead of an object
* Namespace#connected is now a Map instead of an object
* there is no more implicit connection to the default namespace:
```js
// client-side
const socket = io("/admin");
// server-side
io.on("connect", socket => {
// not triggered anymore
})
io.use((socket, next) => {
// not triggered anymore
});
io.of("/admin").use((socket, next) => {
// triggered
});
```
* the Server#set() method was removed
This method was kept for backward-compatibility with pre-1.0 versions.
* **security:** do not allow all origins by default ([f78a575](https://github.com/socketio/socket.io/commit/f78a575f66ab693c3ea96ea88429ddb1a44c86c7))
* properly overwrite the query sent in the handshake ([d33a619](https://github.com/socketio/socket.io/commit/d33a619905a4905c153d4fec337c74da5b533a9e))

View File

@@ -1,5 +1,6 @@
# socket.io
[![Run on Repl.it](https://repl.it/badge/github/socketio/socket.io)](https://replit.com/@socketio/socketio-minimal-example)
[![Backers on Open Collective](https://opencollective.com/socketio/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/socketio/sponsors/badge.svg)](#sponsors)
[![Build Status](https://github.com/socketio/socket.io/workflows/CI/badge.svg)](https://github.com/socketio/socket.io/actions)
[![Dependency Status](https://david-dm.org/socketio/socket.io.svg)](https://david-dm.org/socketio/socket.io)
@@ -21,8 +22,6 @@ 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/doghappy/socket.io-client-csharp)
Its main features are:
@@ -36,7 +35,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://socket.io/docs/v3/client-api/#new-Manager-url-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://github.com/socketio/socket.io-client/blob/master/docs/API.md#new-managerurl-options).
#### Disconnection detection
@@ -65,7 +64,7 @@ io.on('connection', socket => {
#### Cross-browser
Browser support is tested in Sauce Labs:
Browser support is tested in Saucelabs:
[![Sauce Test Status](https://saucelabs.com/browser-matrix/socket.svg)](https://saucelabs.com/u/socket)
@@ -85,11 +84,7 @@ 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
@@ -143,24 +138,9 @@ io.on('connection', () => { /* … */ });
server.listen(3000);
```
### In conjunction with Fastify
To integrate Socket.io in your Fastify application you just need to
register `fastify-socket.io` plugin. It will create a `decorator`
called `io`.
```js
const app = require('fastify')();
app.register(require('fastify-socket.io'));
app.io.on('connection', () => { /* … */ });
app.listen(3000);
```
## Documentation
Please see the documentation [here](https://socket.io/docs/).
The source code of the website can be found [here](https://github.com/socketio/socket.io-website). Contributions are welcome!
Please see the documentation [here](/docs/README.md). Contributions are welcome!
## Debug / logging

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

880
docs/API.md Normal file
View File

@@ -0,0 +1,880 @@
## Table of Contents
- [Class: Server](#server)
- [new Server(httpServer[, options])](#new-serverhttpserver-options)
- [new Server(port[, options])](#new-serverport-options)
- [new Server(options)](#new-serveroptions)
- [server.sockets](#serversockets)
- [server.engine.generateId](#serverenginegenerateid)
- [server.serveClient([value])](#serverserveclientvalue)
- [server.path([value])](#serverpathvalue)
- [server.adapter([value])](#serveradaptervalue)
- [server.origins([value])](#serveroriginsvalue)
- [server.origins(fn)](#serveroriginsfn)
- [server.attach(httpServer[, options])](#serverattachhttpserver-options)
- [server.attach(port[, options])](#serverattachport-options)
- [server.listen(httpServer[, options])](#serverlistenhttpserver-options)
- [server.listen(port[, options])](#serverlistenport-options)
- [server.bind(engine)](#serverbindengine)
- [server.onconnection(socket)](#serveronconnectionsocket)
- [server.of(nsp)](#serverofnsp)
- [server.close([callback])](#serverclosecallback)
- [Class: Namespace](#namespace)
- [namespace.name](#namespacename)
- [namespace.connected](#namespaceconnected)
- [namespace.adapter](#namespaceadapter)
- [namespace.to(room)](#namespacetoroom)
- [namespace.in(room)](#namespaceinroom)
- [namespace.emit(eventName[, ...args])](#namespaceemiteventname-args)
- [namespace.clients(callback)](#namespaceclientscallback)
- [namespace.use(fn)](#namespaceusefn)
- [Event: 'connect'](#event-connect)
- [Event: 'connection'](#event-connect)
- [Flag: 'volatile'](#flag-volatile)
- [Flag: 'local'](#flag-local)
- [Flag: 'binary'](#flag-binary)
- [Class: Socket](#socket)
- [socket.id](#socketid)
- [socket.rooms](#socketrooms)
- [socket.client](#socketclient)
- [socket.conn](#socketconn)
- [socket.request](#socketrequest)
- [socket.handshake](#sockethandshake)
- [socket.use(fn)](#socketusefn)
- [socket.send([...args][, ack])](#socketsendargs-ack)
- [socket.emit(eventName[, ...args][, ack])](#socketemiteventname-args-ack)
- [socket.on(eventName, callback)](#socketoneventname-callback)
- [socket.once(eventName, listener)](#socketonceeventname-listener)
- [socket.removeListener(eventName, listener)](#socketremovelistenereventname-listener)
- [socket.removeAllListeners([eventName])](#socketremovealllistenerseventname)
- [socket.eventNames()](#socketeventnames)
- [socket.join(room[, callback])](#socketjoinroom-callback)
- [socket.join(rooms[, callback])](#socketjoinrooms-callback)
- [socket.leave(room[, callback])](#socketleaveroom-callback)
- [socket.to(room)](#sockettoroom)
- [socket.in(room)](#socketinroom)
- [socket.compress(value)](#socketcompressvalue)
- [socket.disconnect(close)](#socketdisconnectclose)
- [Flag: 'broadcast'](#flag-broadcast)
- [Flag: 'volatile'](#flag-volatile-1)
- [Flag: 'binary'](#flag-binary-1)
- [Event: 'disconnect'](#event-disconnect)
- [Event: 'error'](#event-error)
- [Event: 'disconnecting'](#event-disconnecting)
- [Class: Client](#client)
- [client.conn](#clientconn)
- [client.request](#clientrequest)
### Server
Exposed by `require('socket.io')`.
#### new Server(httpServer[, options])
- `httpServer` _(http.Server)_ the server to bind to.
- `options` _(Object)_
- `path` _(String)_: name of the path to capture (`/socket.io`)
- `serveClient` _(Boolean)_: whether to serve the client files (`true`)
- `adapter` _(Adapter)_: the adapter to use. Defaults to an instance of the `Adapter` that ships with socket.io which is memory based. See [socket.io-adapter](https://github.com/socketio/socket.io-adapter)
- `origins` _(String)_: the allowed origins (`*:*`)
- `parser` _(Parser)_: the parser to use. Defaults to an instance of the `Parser` that ships with socket.io. See [socket.io-parser](https://github.com/socketio/socket.io-parser).
Works with and without `new`:
```js
const io = require('socket.io')();
// or
const Server = require('socket.io');
const io = new Server();
```
The same options passed to socket.io are always passed to the `engine.io` `Server` that gets created. See engine.io [options](https://github.com/socketio/engine.io#methods-1) as reference.
Among those options:
- `pingTimeout` _(Number)_: how many ms without a pong packet to consider the connection closed (`60000`)
- `pingInterval` _(Number)_: how many ms before sending a new ping packet (`25000`).
Those two parameters will impact the delay before a client knows the server is not available anymore. For example, if the underlying TCP connection is not closed properly due to a network issue, a client may have to wait up to `pingTimeout + pingInterval` ms before getting a `disconnect` event.
- `transports` _(Array<String>)_: transports to allow connections to (`['polling', 'websocket']`).
**Note:** The order is important. By default, a long-polling connection is established first, and then upgraded to WebSocket if possible. Using `['websocket']` means there will be no fallback if a WebSocket connection cannot be opened.
```js
const server = require('http').createServer();
const io = require('socket.io')(server, {
path: '/test',
serveClient: false,
// below are engine.IO options
pingInterval: 10000,
pingTimeout: 5000,
cookie: false
});
server.listen(3000);
```
#### new Server(port[, options])
- `port` _(Number)_ a port to listen to (a new `http.Server` will be created)
- `options` _(Object)_
See [above](#new-serverhttpserver-options) for available options.
```js
const server = require('http').createServer();
const io = require('socket.io')(3000, {
path: '/test',
serveClient: false,
// below are engine.IO options
pingInterval: 10000,
pingTimeout: 5000,
cookie: false
});
```
#### new Server(options)
- `options` _(Object)_
See [above](#new-serverhttpserver-options) for available options.
```js
const io = require('socket.io')({
path: '/test',
serveClient: false,
});
// either
const server = require('http').createServer();
io.attach(server, {
pingInterval: 10000,
pingTimeout: 5000,
cookie: false
});
server.listen(3000);
// or
io.attach(3000, {
pingInterval: 10000,
pingTimeout: 5000,
cookie: false
});
```
#### server.sockets
* _(Namespace)_
The default (`/`) namespace.
#### server.serveClient([value])
- `value` _(Boolean)_
- **Returns** `Server|Boolean`
If `value` is `true` the attached server (see `Server#attach`) will serve the client files. Defaults to `true`. This method has no effect after `attach` is called. If no arguments are supplied this method returns the current value.
```js
// pass a server and the `serveClient` option
const io = require('socket.io')(http, { serveClient: false });
// or pass no server and then you can call the method
const io = require('socket.io')();
io.serveClient(false);
io.attach(http);
```
#### server.path([value])
- `value` _(String)_
- **Returns** `Server|String`
Sets the path `value` under which `engine.io` and the static files will be served. Defaults to `/socket.io`. If no arguments are supplied this method returns the current value.
```js
const io = require('socket.io')();
io.path('/myownpath');
// client-side
const socket = io({
path: '/myownpath'
});
```
#### server.adapter([value])
- `value` _(Adapter)_
- **Returns** `Server|Adapter`
Sets the adapter `value`. Defaults to an instance of the `Adapter` that ships with socket.io which is memory based. See [socket.io-adapter](https://github.com/socketio/socket.io-adapter). If no arguments are supplied this method returns the current value.
```js
const io = require('socket.io')(3000);
const redis = require('socket.io-redis');
io.adapter(redis({ host: 'localhost', port: 6379 }));
```
#### server.origins([value])
- `value` _(String|String[])_
- **Returns** `Server|String`
Sets the allowed origins `value`. Defaults to any origins being allowed. If no arguments are supplied this method returns the current value.
```js
io.origins(['https://foo.example.com:443']);
```
#### server.origins(fn)
- `fn` _(Function)_
- **Returns** `Server`
Provides a function taking two arguments `origin:String` and `callback(error, success)`, where `success` is a boolean value indicating whether origin is allowed or not. If `success` is set to `false`, `error` must be provided as a string value that will be appended to the server response, e.g. "Origin not allowed".
__Potential drawbacks__:
* in some situations, when it is not possible to determine `origin` it may have value of `*`
* As this function will be executed for every request, it is advised to make this function work as fast as possible
* If `socket.io` is used together with `Express`, the CORS headers will be affected only for `socket.io` requests. For Express you can use [cors](https://github.com/expressjs/cors).
```js
io.origins((origin, callback) => {
if (origin !== 'https://foo.example.com') {
return callback('origin not allowed', false);
}
callback(null, true);
});
```
#### server.attach(httpServer[, options])
- `httpServer` _(http.Server)_ the server to attach to
- `options` _(Object)_
Attaches the `Server` to an engine.io instance on `httpServer` with the supplied `options` (optionally).
#### server.attach(port[, options])
- `port` _(Number)_ the port to listen on
- `options` _(Object)_
Attaches the `Server` to an engine.io instance on a new http.Server with the supplied `options` (optionally).
#### server.listen(httpServer[, options])
Synonym of [server.attach(httpServer[, options])](#serverattachhttpserver-options).
#### server.listen(port[, options])
Synonym of [server.attach(port[, options])](#serverattachport-options).
#### server.bind(engine)
- `engine` _(engine.Server)_
- **Returns** `Server`
Advanced use only. Binds the server to a specific engine.io `Server` (or compatible API) instance.
#### server.onconnection(socket)
- `socket` _(engine.Socket)_
- **Returns** `Server`
Advanced use only. Creates a new `socket.io` client from the incoming engine.io (or compatible API) `Socket`.
#### server.of(nsp)
- `nsp` _(String|RegExp|Function)_
- **Returns** `Namespace`
Initializes and retrieves the given `Namespace` by its pathname identifier `nsp`. If the namespace was already initialized it returns it immediately.
```js
const adminNamespace = io.of('/admin');
```
A regex or a function can also be provided, in order to create namespace in a dynamic way:
```js
const dynamicNsp = io.of(/^\/dynamic-\d+$/).on('connect', (socket) => {
const newNamespace = socket.nsp; // newNamespace.name === '/dynamic-101'
// broadcast to all clients in the given sub-namespace
newNamespace.emit('hello');
});
// client-side
const socket = io('/dynamic-101');
// broadcast to all clients in each sub-namespace
dynamicNsp.emit('hello');
// use a middleware for each sub-namespace
dynamicNsp.use((socket, next) => { /* ... */ });
```
With a function:
```js
io.of((name, query, next) => {
next(null, checkToken(query.token));
}).on('connect', (socket) => { /* ... */ });
```
#### server.close([callback])
- `callback` _(Function)_
Closes the socket.io server. The `callback` argument is optional and will be called when all connections are closed.
```js
const Server = require('socket.io');
const PORT = 3030;
const server = require('http').Server();
const io = Server(PORT);
io.close(); // Close current server
server.listen(PORT); // PORT is free to use
io = Server(server);
```
#### server.engine.generateId
Overwrites the default method to generate your custom socket id.
The function is called with a node request object (`http.IncomingMessage`) as first parameter.
```js
io.engine.generateId = (req) => {
return "custom:id:" + custom_id++; // custom id must be unique
}
```
### Namespace
Represents a pool of sockets connected under a given scope identified
by a pathname (eg: `/chat`).
A client always connects to `/` (the main namespace), then potentially connect to other namespaces (while using the same underlying connection).
#### namespace.name
* _(String)_
The namespace identifier property.
#### namespace.connected
* _(Object<Socket>)_
The hash of `Socket` objects that are connected to this namespace, indexed by `id`.
#### namespace.adapter
* _(Adapter)_
The `Adapter` used for the namespace. Useful when using the `Adapter` based on [Redis](https://github.com/socketio/socket.io-redis), as it exposes methods to manage sockets and rooms accross your cluster.
**Note:** the adapter of the main namespace can be accessed with `io.of('/').adapter`.
#### namespace.to(room)
- `room` _(String)_
- **Returns** `Namespace` for chaining
Sets a modifier for a subsequent event emission that the event will only be _broadcasted_ to clients that have joined the given `room`.
To emit to multiple rooms, you can call `to` several times.
```js
const io = require('socket.io')();
const adminNamespace = io.of('/admin');
adminNamespace.to('level1').emit('an event', { some: 'data' });
```
#### namespace.in(room)
Synonym of [namespace.to(room)](#namespacetoroom).
#### namespace.emit(eventName[, ...args])
- `eventName` _(String)_
- `args`
Emits an event to all connected clients. The following two are equivalent:
```js
const io = require('socket.io')();
io.emit('an event sent to all connected clients'); // main namespace
const chat = io.of('/chat');
chat.emit('an event sent to all connected clients in chat namespace');
```
**Note:** acknowledgements are not supported when emitting from namespace.
#### namespace.clients(callback)
- `callback` _(Function)_
Gets a list of client IDs connected to this namespace (across all nodes if applicable).
```js
const io = require('socket.io')();
io.of('/chat').clients((error, clients) => {
if (error) throw error;
console.log(clients); // => [PZDoMHjiu8PYfRiKAAAF, Anw2LatarvGVVXEIAAAD]
});
```
An example to get all clients in namespace's room:
```js
io.of('/chat').in('general').clients((error, clients) => {
if (error) throw error;
console.log(clients); // => [Anw2LatarvGVVXEIAAAD]
});
```
As with broadcasting, the default is all clients from the default namespace ('/'):
```js
io.clients((error, clients) => {
if (error) throw error;
console.log(clients); // => [6em3d4TJP8Et9EMNAAAA, G5p55dHhGgUnLUctAAAB]
});
```
#### namespace.use(fn)
- `fn` _(Function)_
Registers a middleware, which is a function that gets executed for every incoming `Socket`, and receives as parameters the socket and a function to optionally defer execution to the next registered middleware.
Errors passed to middleware callbacks are sent as special `error` packets to clients.
```js
io.use((socket, next) => {
if (socket.request.headers.cookie) return next();
next(new Error('Authentication error'));
});
```
#### Event: 'connect'
- `socket` _(Socket)_ socket connection with client
Fired upon a connection from client.
```js
io.on('connect', (socket) => {
// ...
});
io.of('/admin').on('connect', (socket) => {
// ...
});
```
#### Event: 'connection'
Synonym of [Event: 'connect'](#event-connect).
#### Flag: 'volatile'
Sets a modifier for a subsequent event emission that the event data may be lost if the clients are not ready to receive messages (because of network slowness or other issues, or because theyre connected through long polling and is in the middle of a request-response cycle).
```js
io.volatile.emit('an event', { some: 'data' }); // the clients may or may not receive it
```
#### Flag: 'binary'
Specifies whether there is binary data in the emitted data. Increases performance when specified. Can be `true` or `false`.
```js
io.binary(false).emit('an event', { some: 'data' });
```
#### Flag: 'local'
Sets a modifier for a subsequent event emission that the event data will only be _broadcast_ to the current node (when the [Redis adapter](https://github.com/socketio/socket.io-redis) is used).
```js
io.local.emit('an event', { some: 'data' });
```
### Socket
A `Socket` is the fundamental class for interacting with browser clients. A `Socket` belongs to a certain `Namespace` (by default `/`) and uses an underlying `Client` to communicate.
It should be noted the `Socket` doesn't relate directly to the actual underlying TCP/IP `socket` and it is only the name of the class.
Within each `Namespace`, you can also define arbitrary channels (called `room`) that the `Socket` can join and leave. That provides a convenient way to broadcast to a group of `Socket`s (see `Socket#to` below).
The `Socket` class inherits from [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter). The `Socket` class overrides the `emit` method, and does not modify any other `EventEmitter` method. All methods documented here which also appear as `EventEmitter` methods (apart from `emit`) are implemented by `EventEmitter`, and documentation for `EventEmitter` applies.
#### socket.id
* _(String)_
A unique identifier for the session, that comes from the underlying `Client`.
#### socket.rooms
* _(Object)_
A hash of strings identifying the rooms this client is in, indexed by room name.
```js
io.on('connection', (socket) => {
socket.join('room 237', () => {
let rooms = Object.keys(socket.rooms);
console.log(rooms); // [ <socket.id>, 'room 237' ]
});
});
```
#### socket.client
* _(Client)_
A reference to the underlying `Client` object.
#### socket.conn
* _(engine.Socket)_
A reference to the underlying `Client` transport connection (engine.io `Socket` object). This allows access to the IO transport layer, which still (mostly) abstracts the actual TCP/IP socket.
#### socket.request
* _(Request)_
A getter proxy that returns the reference to the `request` that originated the underlying engine.io `Client`. Useful for accessing request headers such as `Cookie` or `User-Agent`.
#### socket.handshake
* _(Object)_
The handshake details:
```js
{
headers: /* the headers sent as part of the handshake */,
time: /* the date of creation (as string) */,
address: /* the ip of the client */,
xdomain: /* whether the connection is cross-domain */,
secure: /* whether the connection is secure */,
issued: /* the date of creation (as unix timestamp) */,
url: /* the request URL string */,
query: /* the query object */
}
```
Usage:
```js
io.use((socket, next) => {
let handshake = socket.handshake;
// ...
});
io.on('connection', (socket) => {
let handshake = socket.handshake;
// ...
});
```
#### socket.use(fn)
- `fn` _(Function)_
Registers a middleware, which is a function that gets executed for every incoming `Packet` and receives as parameter the packet and a function to optionally defer execution to the next registered middleware.
Errors passed to middleware callbacks are sent as special `error` packets to clients.
```js
io.on('connection', (socket) => {
socket.use((packet, next) => {
if (packet.doge === true) return next();
next(new Error('Not a doge error'));
});
});
```
#### socket.send([...args][, ack])
- `args`
- `ack` _(Function)_
- **Returns** `Socket`
Sends a `message` event. See [socket.emit(eventName[, ...args][, ack])](#socketemiteventname-args-ack).
#### socket.emit(eventName[, ...args][, ack])
*(overrides `EventEmitter.emit`)*
- `eventName` _(String)_
- `args`
- `ack` _(Function)_
- **Returns** `Socket`
Emits an event to the socket identified by the string name. Any other parameters can be included. All serializable datastructures are supported, including `Buffer`.
```js
socket.emit('hello', 'world');
socket.emit('with-binary', 1, '2', { 3: '4', 5: Buffer.alloc(6) });
```
The `ack` argument is optional and will be called with the client's answer.
```js
io.on('connection', (socket) => {
socket.emit('an event', { some: 'data' });
socket.emit('ferret', 'tobi', (data) => {
console.log(data); // data will be 'woot'
});
// the client code
// client.on('ferret', (name, fn) => {
// fn('woot');
// });
});
```
#### socket.on(eventName, callback)
*(inherited from `EventEmitter`)*
- `eventName` _(String)_
- `callback` _(Function)_
- **Returns** `Socket`
Register a new handler for the given event.
```js
socket.on('news', (data) => {
console.log(data);
});
// with several arguments
socket.on('news', (arg1, arg2, arg3) => {
// ...
});
// or with acknowledgement
socket.on('news', (data, callback) => {
callback(0);
});
```
#### socket.once(eventName, listener)
#### socket.removeListener(eventName, listener)
#### socket.removeAllListeners([eventName])
#### socket.eventNames()
Inherited from `EventEmitter` (along with other methods not mentioned here). See Node.js documentation for the `events` module.
#### socket.join(room[, callback])
- `room` _(String)_
- `callback` _(Function)_
- **Returns** `Socket` for chaining
Adds the client to the `room`, and fires optionally a callback with `err` signature (if any).
```js
io.on('connection', (socket) => {
socket.join('room 237', () => {
let rooms = Object.keys(socket.rooms);
console.log(rooms); // [ <socket.id>, 'room 237' ]
io.to('room 237').emit('a new user has joined the room'); // broadcast to everyone in the room
});
});
```
The mechanics of joining rooms are handled by the `Adapter` that has been configured (see `Server#adapter` above), defaulting to [socket.io-adapter](https://github.com/socketio/socket.io-adapter).
For your convenience, each socket automatically joins a room identified by its id (see `Socket#id`). This makes it easy to broadcast messages to other sockets:
```js
io.on('connection', (socket) => {
socket.on('say to someone', (id, msg) => {
// send a private message to the socket with the given id
socket.to(id).emit('my message', msg);
});
});
```
#### socket.join(rooms[, callback])
- `rooms` _(Array)_
- `callback` _(Function)_
- **Returns** `Socket` for chaining
Adds the client to the list of room, and fires optionally a callback with `err` signature (if any).
#### socket.leave(room[, callback])
- `room` _(String)_
- `callback` _(Function)_
- **Returns** `Socket` for chaining
Removes the client from `room`, and fires optionally a callback with `err` signature (if any).
**Rooms are left automatically upon disconnection**.
#### socket.to(room)
- `room` _(String)_
- **Returns** `Socket` for chaining
Sets a modifier for a subsequent event emission that the event will only be _broadcasted_ to clients that have joined the given `room` (the socket itself being excluded).
To emit to multiple rooms, you can call `to` several times.
```js
io.on('connection', (socket) => {
// to one room
socket.to('others').emit('an event', { some: 'data' });
// to multiple rooms
socket.to('room1').to('room2').emit('hello');
// a private message to another socket
socket.to(/* another socket id */).emit('hey');
});
```
**Note:** acknowledgements are not supported when broadcasting.
#### socket.in(room)
Synonym of [socket.to(room)](#sockettoroom).
#### socket.compress(value)
- `value` _(Boolean)_ whether to following packet will be compressed
- **Returns** `Socket` for chaining
Sets a modifier for a subsequent event emission that the event data will only be _compressed_ if the value is `true`. Defaults to `true` when you don't call the method.
```js
io.on('connection', (socket) => {
socket.compress(false).emit('uncompressed', "that's rough");
});
```
#### socket.disconnect(close)
- `close` _(Boolean)_ whether to close the underlying connection
- **Returns** `Socket`
Disconnects this client. If value of close is `true`, closes the underlying connection. Otherwise, it just disconnects the namespace.
```js
io.on('connection', (socket) => {
setTimeout(() => socket.disconnect(true), 5000);
});
```
#### Flag: 'broadcast'
Sets a modifier for a subsequent event emission that the event data will only be _broadcast_ to every sockets but the sender.
```js
io.on('connection', (socket) => {
socket.broadcast.emit('an event', { some: 'data' }); // everyone gets it but the sender
});
```
#### Flag: 'volatile'
Sets a modifier for a subsequent event emission that the event data may be lost if the client is not ready to receive messages (because of network slowness or other issues, or because theyre connected through long polling and is in the middle of a request-response cycle).
```js
io.on('connection', (socket) => {
socket.volatile.emit('an event', { some: 'data' }); // the client may or may not receive it
});
```
#### Flag: 'binary'
Specifies whether there is binary data in the emitted data. Increases performance when specified. Can be `true` or `false`.
```js
var io = require('socket.io')();
io.on('connection', function(socket){
socket.binary(false).emit('an event', { some: 'data' }); // The data to send has no binary data
});
```
#### Event: 'disconnect'
- `reason` _(String)_ the reason of the disconnection (either client or server-side)
Fired upon disconnection.
```js
io.on('connection', (socket) => {
socket.on('disconnect', (reason) => {
// ...
});
});
```
#### Event: 'error'
- `error` _(Object)_ error object
Fired when an error occurs.
```js
io.on('connection', (socket) => {
socket.on('error', (error) => {
// ...
});
});
```
#### Event: 'disconnecting'
- `reason` _(String)_ the reason of the disconnection (either client or server-side)
Fired when the client is going to be disconnected (but hasn't left its `rooms` yet).
```js
io.on('connection', (socket) => {
socket.on('disconnecting', (reason) => {
let rooms = Object.keys(socket.rooms);
// ...
});
});
```
These are reserved events (along with `connect`, `newListener` and `removeListener`) which cannot be used as event names.
### Client
The `Client` class represents an incoming transport (engine.io) connection. A `Client` can be associated with many multiplexed `Socket`s that belong to different `Namespace`s.
#### client.conn
* _(engine.Socket)_
A reference to the underlying `engine.io` `Socket` connection.
#### client.request
* _(Request)_
A getter proxy that returns the reference to the `request` that originated the engine.io connection. Useful for accessing request headers such as `Cookie` or `User-Agent`.

View File

@@ -1,2 +1,15 @@
The documentation has been moved to the website [here](https://socket.io/docs/).
## Table of Contents
#### Getting started
- [Write a chat application](http://socket.io/get-started/chat/)
#### API Reference
- [Server API](API.md)
- [Client API](https://github.com/socketio/socket.io-client/blob/master/docs/API.md)
#### Advanced topics
- [Emit cheatsheet](emit.md)

64
docs/emit.md Normal file
View File

@@ -0,0 +1,64 @@
## Emit cheatsheet
```js
io.on('connect', onConnect);
function onConnect(socket){
// sending to the client
socket.emit('hello', 'can you hear me?', 1, 2, 'abc');
// sending to all clients except sender
socket.broadcast.emit('broadcast', 'hello friends!');
// sending to all clients in 'game' room except sender
socket.to('game').emit('nice game', "let's play a game");
// sending to all clients in 'game1' and/or in 'game2' room, except sender
socket.to('game1').to('game2').emit('nice game', "let's play a game (too)");
// sending to all clients in 'game' room, including sender
io.in('game').emit('big-announcement', 'the game will start soon');
// sending to all clients in namespace 'myNamespace', including sender
io.of('myNamespace').emit('bigger-announcement', 'the tournament will start soon');
// sending to a specific room in a specific namespace, including sender
io.of('myNamespace').to('room').emit('event', 'message');
// sending to individual socketid (private message)
io.to(<socketid>).emit('hey', 'I just met you');
// sending with acknowledgement
socket.emit('question', 'do you think so?', function (answer) {});
// sending without compression
socket.compress(false).emit('uncompressed', "that's rough");
// sending a message that might be dropped if the client is not ready to receive messages
socket.volatile.emit('maybe', 'do you really need it?');
// specifying whether the data to send has binary data
socket.binary(false).emit('what', 'I have no binaries!');
// sending to all clients on this node (when using multiple nodes)
io.local.emit('hi', 'my lovely babies');
// sending to all connected clients
io.emit('an event sent to all connected clients');
};
```
**Note:** The following events are reserved and should not be used as event names by your application:
- `error`
- `connect`
- `disconnect`
- `disconnecting`
- `newListener`
- `removeListener`
- `ping`
- `pong`

1
examples/.gitignore vendored
View File

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

View File

@@ -1,17 +0,0 @@
# 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.

View File

@@ -1,16 +0,0 @@
# 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

View File

@@ -1,46 +0,0 @@
# 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

View File

@@ -1,35 +0,0 @@
# 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).
![demo](assets/demo.gif)
## 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.

View File

@@ -1,128 +0,0 @@
{
"$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"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 KiB

View File

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

View File

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

View File

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

View File

@@ -1,13 +0,0 @@
/* 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"
]
}
}

View File

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

View File

@@ -1,48 +0,0 @@
{
"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": "^4.0.0",
"socket.io-client": "^4.0.0",
"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"
}
}

View File

@@ -1,28 +0,0 @@
import { Server } 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.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);
});
});

View File

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

View File

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

View File

@@ -1,59 +0,0 @@
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 = '';
}
}
}

View File

@@ -1,19 +0,0 @@
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 { }

View File

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

View File

@@ -1,3 +0,0 @@
export const environment = {
production: true
};

View File

@@ -1,16 +0,0 @@
// 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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 948 B

View File

@@ -1,13 +0,0 @@
<!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>

View File

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

View File

@@ -1,63 +0,0 @@
/**
* 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
*/

View File

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

View File

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

View File

@@ -1,15 +0,0 @@
/* 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"
]
}

View File

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

View File

@@ -1,18 +0,0 @@
/* 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"
]
}

View File

@@ -1,152 +0,0 @@
{
"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"
]
}
}

View File

@@ -1,19 +0,0 @@
# Basic CRUD application with Socket.IO
Please read the related [guide](https://socket.io/get-started/basic-crud-application/).
## Running the frontend
```
cd angular-client
npm install
npm start
```
### Running the server
```
cd server
npm install
npm start
```

View File

@@ -1,17 +0,0 @@
# 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.

View File

@@ -1,16 +0,0 @@
# 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

View File

@@ -1,46 +0,0 @@
# 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

View File

@@ -1,31 +0,0 @@
# 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).
![demo](assets/demo.gif)
## 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.
## 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.

View File

@@ -1,128 +0,0 @@
{
"$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"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 KiB

View File

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

View File

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

View File

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

View File

@@ -1,13 +0,0 @@
/* 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"
]
}
}

View File

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

View File

@@ -1,46 +0,0 @@
{
"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"
},
"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-client": "^4.0.0",
"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"
}
}

View File

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

View File

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

View File

@@ -1,59 +0,0 @@
import { Component } from '@angular/core';
import { TodoStore, Todo } from './store';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
todoStore: TodoStore;
newTodoText = '';
constructor(todoStore: TodoStore) {
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 = '';
}
}
}

View File

@@ -1,19 +0,0 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { TodoStore } from './store';
import { FormsModule } from "@angular/forms";
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule
],
providers: [TodoStore],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@@ -1,140 +0,0 @@
import { io, Socket } from "socket.io-client";
import { ClientEvents, ServerEvents } from "../../../server/lib/events";
import { environment } from '../environments/environment';
export interface Todo {
id: string,
title: string,
completed: boolean,
editing: boolean,
synced: boolean
}
const mapTodo = (todo: any) => {
return {
...todo,
editing: false,
synced: true
}
}
export class TodoStore {
public todos: Array<Todo> = [];
private socket: Socket<ServerEvents, ClientEvents>;
constructor() {
this.socket = io(environment.serverUrl);
this.socket.on("connect", () => {
this.socket.emit("todo:list", (res) => {
if ("error" in res) {
// handle the error
return;
}
this.todos = res.data.map(mapTodo);
});
});
this.socket.on("todo:created", (todo) => {
this.todos.push(mapTodo(todo));
});
this.socket.on("todo:updated", (todo) => {
const existingTodo = this.todos.find(t => {
return t.id === todo.id
});
if (existingTodo) {
existingTodo.title = todo.title;
existingTodo.completed = todo.completed;
}
});
this.socket.on("todo:deleted", (id) => {
const index = this.todos.findIndex(t => {
return t.id === id
});
if (index !== -1) {
this.todos.splice(index, 1);
}
})
}
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(todo => {
todo.completed = completed;
todo.synced = false;
this.socket.emit("todo:update", todo, (res) => {
if (res && "error" in res) {
// handle the error
return;
}
todo.synced = true;
});
});
}
removeCompleted() {
this.getCompleted().forEach((todo) => {
this.socket.emit("todo:delete", todo.id, (res) => {
if (res && "error" in res) {
// handle the error
}
});
})
this.todos = this.getRemaining();
}
getRemaining() {
return this.getWithCompleted(false);
}
getCompleted() {
return this.getWithCompleted(true);
}
toggleCompletion(todo: Todo) {
todo.completed = !todo.completed;
todo.synced = false;
this.socket.emit("todo:update", todo, (res) => {
if (res && "error" in res) {
// handle the error
return;
}
todo.synced = true;
})
}
remove(todo: Todo) {
this.todos.splice(this.todos.indexOf(todo), 1);
this.socket.emit("todo:delete", todo.id, (res) => {
if (res && "error" in res) {
// handle the error
}
});
}
add(title: string) {
this.socket.emit("todo:create", { title, completed: false }, (res) => {
if ("error" in res) {
// handle the error
return;
}
this.todos.push({
id: res.data,
title,
completed: false,
editing: false,
synced: true
});
});
}
}

View File

@@ -1,4 +0,0 @@
export const environment = {
production: true,
serverUrl: "https://my-custom-domain.com"
};

View File

@@ -1,17 +0,0 @@
// 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,
serverUrl: "http://localhost:3000"
};
/*
* 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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 948 B

View File

@@ -1,13 +0,0 @@
<!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>

View File

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

View File

@@ -1,63 +0,0 @@
/**
* 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
*/

View File

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

View File

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

View File

@@ -1,15 +0,0 @@
/* 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"
]
}

View File

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

View File

@@ -1,18 +0,0 @@
/* 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"
]
}

View File

@@ -1,152 +0,0 @@
{
"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"
]
}
}

View File

@@ -1,35 +0,0 @@
import { Server as HttpServer } from "http";
import { Server, ServerOptions } from "socket.io";
import { ClientEvents, ServerEvents } from "./events";
import { TodoRepository } from "./todo-management/todo.repository";
import createTodoHandlers from "./todo-management/todo.handlers";
export interface Components {
todoRepository: TodoRepository;
}
export function createApplication(
httpServer: HttpServer,
components: Components,
serverOptions: Partial<ServerOptions> = {}
): Server<ClientEvents, ServerEvents> {
const io = new Server<ClientEvents, ServerEvents>(httpServer, serverOptions);
const {
createTodo,
readTodo,
updateTodo,
deleteTodo,
listTodo,
} = createTodoHandlers(components);
io.on("connection", (socket) => {
socket.on("todo:create", createTodo);
socket.on("todo:read", readTodo);
socket.on("todo:update", updateTodo);
socket.on("todo:delete", deleteTodo);
socket.on("todo:list", listTodo);
});
return io;
}

View File

@@ -1,37 +0,0 @@
import { Todo, TodoID } from "./todo-management/todo.repository";
import { ValidationErrorItem } from "joi";
interface Error {
error: string;
errorDetails?: ValidationErrorItem[];
}
interface Success<T> {
data: T;
}
export type Response<T> = Error | Success<T>;
export interface ServerEvents {
"todo:created": (todo: Todo) => void;
"todo:updated": (todo: Todo) => void;
"todo:deleted": (id: TodoID) => void;
}
export interface ClientEvents {
"todo:list": (callback: (res: Response<Todo[]>) => void) => void;
"todo:create": (
payload: Omit<Todo, "id">,
callback: (res: Response<TodoID>) => void
) => void;
"todo:read": (id: TodoID, callback: (res: Response<Todo>) => void) => void;
"todo:update": (
payload: Todo,
callback: (res?: Response<void>) => void
) => void;
"todo:delete": (id: TodoID, callback: (res?: Response<void>) => void) => void;
}

View File

@@ -1,19 +0,0 @@
import { createServer } from "http";
import { createApplication } from "./app";
import { InMemoryTodoRepository } from "./todo-management/todo.repository";
const httpServer = createServer();
createApplication(
httpServer,
{
todoRepository: new InMemoryTodoRepository(),
},
{
cors: {
origin: ["http://localhost:4200"],
},
}
);
httpServer.listen(3000);

View File

@@ -1,159 +0,0 @@
import { Errors, mapErrorDetails, sanitizeErrorMessage } from "../util";
import { v4 as uuid } from "uuid";
import { Components } from "../app";
import Joi = require("joi");
import { Todo, TodoID } from "./todo.repository";
import { ClientEvents, Response, ServerEvents } from "../events";
import { Socket } from "socket.io";
const idSchema = Joi.string().guid({
version: "uuidv4",
});
const todoSchema = Joi.object({
id: idSchema.alter({
create: (schema) => schema.forbidden(),
update: (schema) => schema.required(),
}),
title: Joi.string().max(256).required(),
completed: Joi.boolean().required(),
});
export default function (components: Components) {
const { todoRepository } = components;
return {
createTodo: async function (
payload: Omit<Todo, "id">,
callback: (res: Response<TodoID>) => void
) {
// @ts-ignore
const socket: Socket<ClientEvents, ServerEvents> = this;
// validate the payload
const { error, value } = todoSchema.tailor("create").validate(payload, {
abortEarly: false,
stripUnknown: true,
});
if (error) {
return callback({
error: Errors.INVALID_PAYLOAD,
errorDetails: mapErrorDetails(error.details),
});
}
value.id = uuid();
// persist the entity
try {
await todoRepository.save(value);
} catch (e) {
return callback({
error: sanitizeErrorMessage(e),
});
}
// acknowledge the creation
callback({
data: value.id,
});
// notify the other users
socket.broadcast.emit("todo:created", value);
},
readTodo: async function (
id: TodoID,
callback: (res: Response<Todo>) => void
) {
const { error } = idSchema.validate(id);
if (error) {
return callback({
error: Errors.ENTITY_NOT_FOUND,
});
}
try {
const todo = await todoRepository.findById(id);
callback({
data: todo,
});
} catch (e) {
callback({
error: sanitizeErrorMessage(e),
});
}
},
updateTodo: async function (
payload: Todo,
callback: (res?: Response<void>) => void
) {
// @ts-ignore
const socket: Socket<ClientEvents, ServerEvents> = this;
const { error, value } = todoSchema.tailor("update").validate(payload, {
abortEarly: false,
stripUnknown: true,
});
if (error) {
return callback({
error: Errors.INVALID_PAYLOAD,
errorDetails: mapErrorDetails(error.details),
});
}
try {
await todoRepository.save(value);
} catch (e) {
return callback({
error: sanitizeErrorMessage(e),
});
}
callback();
socket.broadcast.emit("todo:updated", value);
},
deleteTodo: async function (
id: TodoID,
callback: (res?: Response<void>) => void
) {
// @ts-ignore
const socket: Socket<ClientEvents, ServerEvents> = this;
const { error } = idSchema.validate(id);
if (error) {
return callback({
error: Errors.ENTITY_NOT_FOUND,
});
}
try {
await todoRepository.deleteById(id);
} catch (e) {
return callback({
error: sanitizeErrorMessage(e),
});
}
callback();
socket.broadcast.emit("todo:deleted", id);
},
listTodo: async function (callback: (res: Response<Todo[]>) => void) {
try {
callback({
data: await todoRepository.findAll(),
});
} catch (e) {
callback({
error: sanitizeErrorMessage(e),
});
}
},
};
}

View File

@@ -1,49 +0,0 @@
import { Errors } from "../util";
abstract class CrudRepository<T, ID> {
abstract findAll(): Promise<T[]>;
abstract findById(id: ID): Promise<T>;
abstract save(entity: T): Promise<void>;
abstract deleteById(id: ID): Promise<void>;
}
export type TodoID = string;
export interface Todo {
id: TodoID;
completed: boolean;
title: string;
}
export abstract class TodoRepository extends CrudRepository<Todo, TodoID> {}
export class InMemoryTodoRepository extends TodoRepository {
private readonly todos: Map<TodoID, Todo> = new Map();
findAll(): Promise<Todo[]> {
const entities = Array.from(this.todos.values());
return Promise.resolve(entities);
}
findById(id: TodoID): Promise<Todo> {
if (this.todos.has(id)) {
return Promise.resolve(this.todos.get(id)!);
} else {
return Promise.reject(Errors.ENTITY_NOT_FOUND);
}
}
save(entity: Todo): Promise<void> {
this.todos.set(entity.id, entity);
return Promise.resolve();
}
deleteById(id: TodoID): Promise<void> {
const deleted = this.todos.delete(id);
if (deleted) {
return Promise.resolve();
} else {
return Promise.reject(Errors.ENTITY_NOT_FOUND);
}
}
}

View File

@@ -1,24 +0,0 @@
import { ValidationErrorItem } from "joi";
export enum Errors {
ENTITY_NOT_FOUND = "entity not found",
INVALID_PAYLOAD = "invalid payload",
}
const errorValues: string[] = Object.values(Errors);
export function sanitizeErrorMessage(message: string) {
if (errorValues.includes(message)) {
return message;
} else {
return "an unknown error has occurred";
}
}
export function mapErrorDetails(details: ValidationErrorItem[]) {
return details.map((item) => ({
message: item.message,
path: item.path,
type: item.type,
}));
}

View File

@@ -1,37 +0,0 @@
{
"name": "basic-crud-server",
"version": "0.0.1",
"description": "Server for the Basic CRUD Socket.IO example",
"main": "dist/lib/index.js",
"scripts": {
"start": "ts-node lib/index.ts",
"build": "tsc",
"test": "nyc mocha --require ts-node/register test/**/*.ts"
},
"repository": {
"type": "git",
"url": "git+https://github.com/socketio/socket.io.git"
},
"author": "Damien Arrachequesne <damien.arrachequesne@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/socketio/socket.io/issues"
},
"homepage": "https://github.com/socketio/socket.io#readme",
"dependencies": {
"joi": "^17.4.0",
"socket.io": "^4.0.1",
"uuid": "^8.3.2"
},
"devDependencies": {
"@types/mocha": "^8.2.3",
"@types/chai": "^4.2.16",
"@types/uuid": "^8.3.0",
"chai": "^4.3.4",
"mocha": "^8.3.2",
"nyc": "^15.1.0",
"socket.io-client": "^4.0.1",
"ts-node": "^9.1.1",
"typescript": "^4.2.4"
}
}

View File

@@ -1,309 +0,0 @@
import { createApplication } from "../../lib/app";
import { createServer, Server } from "http";
import {
InMemoryTodoRepository,
TodoRepository,
} from "../../lib/todo-management/todo.repository";
import { AddressInfo } from "net";
import { io, Socket } from "socket.io-client";
import { ClientEvents, ServerEvents } from "../../lib/events";
import { expect } from "chai";
const createPartialDone = (count: number, done: () => void) => {
let i = 0;
return () => {
if (++i === count) {
done();
}
};
};
describe("todo management", () => {
let httpServer: Server,
socket: Socket<ServerEvents, ClientEvents>,
otherSocket: Socket<ServerEvents, ClientEvents>,
todoRepository: TodoRepository;
beforeEach((done) => {
const partialDone = createPartialDone(2, done);
httpServer = createServer();
todoRepository = new InMemoryTodoRepository();
createApplication(httpServer, {
todoRepository,
});
httpServer.listen(() => {
const port = (httpServer.address() as AddressInfo).port;
socket = io(`http://localhost:${port}`);
socket.on("connect", partialDone);
otherSocket = io(`http://localhost:${port}`);
otherSocket.on("connect", partialDone);
});
});
afterEach(() => {
httpServer.close();
socket.disconnect();
otherSocket.disconnect();
});
describe("create todo", () => {
it("should create a todo entity", (done) => {
const partialDone = createPartialDone(2, done);
socket.emit(
"todo:create",
{
title: "lorem ipsum",
completed: false,
},
async (res) => {
if ("error" in res) {
return done(new Error("should not happen"));
}
expect(res.data).to.be.a("string");
const storedEntity = await todoRepository.findById(res.data);
expect(storedEntity).to.eql({
id: res.data,
title: "lorem ipsum",
completed: false,
});
partialDone();
}
);
otherSocket.on("todo:created", (todo) => {
expect(todo.id).to.be.a("string");
expect(todo.title).to.eql("lorem ipsum");
expect(todo.completed).to.eql(false);
partialDone();
});
});
it("should fail with an invalid entity", (done) => {
const incompleteTodo = {
completed: "false",
description: true,
};
// @ts-ignore
socket.emit("todo:create", incompleteTodo, (res) => {
if (!("error" in res)) {
return done(new Error("should not happen"));
}
expect(res.error).to.eql("invalid payload");
expect(res.errorDetails).to.eql([
{
message: '"title" is required',
path: ["title"],
type: "any.required",
},
]);
done();
});
otherSocket.on("todo:created", () => {
done(new Error("should not happen"));
});
});
});
describe("read todo", () => {
it("should return a todo entity", (done) => {
todoRepository.save({
id: "254dbf85-f5b9-4675-b913-acab5d600884",
title: "lorem ipsum",
completed: true,
});
socket.emit(
"todo:read",
"254dbf85-f5b9-4675-b913-acab5d600884",
(res) => {
if ("error" in res) {
return done(new Error("should not happen"));
}
expect(res.data.id).to.eql("254dbf85-f5b9-4675-b913-acab5d600884");
expect(res.data.title).to.eql("lorem ipsum");
expect(res.data.completed).to.eql(true);
done();
}
);
});
it("should fail with an invalid ID", (done) => {
socket.emit("todo:read", "123", (res) => {
if ("error" in res) {
expect(res.error).to.eql("entity not found");
done();
} else {
done(new Error("should not happen"));
}
});
});
it("should fail with an unknown entity", (done) => {
socket.emit(
"todo:read",
"6edcf81e-7049-40e0-8497-9cdd52414f75",
(res) => {
if ("error" in res) {
expect(res.error).to.eql("entity not found");
done();
} else {
done(new Error("should not happen"));
}
}
);
});
});
describe("update todo", () => {
it("should update a todo entity", (done) => {
const partialDone = createPartialDone(2, done);
todoRepository.save({
id: "c7790b35-6bbb-45dd-8d67-a281ca407b43",
title: "lorem ipsum",
completed: true,
});
socket.emit(
"todo:update",
{
id: "c7790b35-6bbb-45dd-8d67-a281ca407b43",
title: "dolor sit amet",
completed: true,
},
async () => {
const storedEntity = await todoRepository.findById(
"c7790b35-6bbb-45dd-8d67-a281ca407b43"
);
expect(storedEntity).to.eql({
id: "c7790b35-6bbb-45dd-8d67-a281ca407b43",
title: "dolor sit amet",
completed: true,
});
partialDone();
}
);
otherSocket.on("todo:updated", (todo) => {
expect(todo.title).to.eql("dolor sit amet");
expect(todo.completed).to.eql(true);
partialDone();
});
});
it("should fail with an invalid entity", (done) => {
const incompleteTodo = {
id: "123",
completed: "false",
description: true,
};
// @ts-ignore
socket.emit("todo:update", incompleteTodo, (res) => {
if (!(res && "error" in res)) {
return done(new Error("should not happen"));
}
expect(res.error).to.eql("invalid payload");
expect(res.errorDetails).to.eql([
{
message: '"id" must be a valid GUID',
path: ["id"],
type: "string.guid",
},
{
message: '"title" is required',
path: ["title"],
type: "any.required",
},
]);
done();
});
otherSocket.on("todo:updated", () => {
done(new Error("should not happen"));
});
});
});
describe("delete todo", () => {
it("should delete a todo entity", (done) => {
const partialDone = createPartialDone(2, done);
const id = "58960ab2-4e78-4ced-8079-134f12179d46";
todoRepository.save({
id,
title: "lorem ipsum",
completed: true,
});
socket.emit("todo:delete", id, async () => {
try {
await todoRepository.findById(id);
} catch (e) {
partialDone();
}
});
otherSocket.on("todo:deleted", (id) => {
expect(id).to.eql("58960ab2-4e78-4ced-8079-134f12179d46");
partialDone();
});
});
it("should fail with an invalid ID", (done) => {
socket.emit("todo:delete", "123", (res) => {
if (!(res && "error" in res)) {
return done(new Error("should not happen"));
}
expect(res.error).to.eql("entity not found");
done();
});
otherSocket.on("todo:deleted", () => {
done(new Error("should not happen"));
});
});
});
describe("list todo", () => {
it("should return a list of entities", (done) => {
todoRepository.save({
id: "d445db6d-9d55-4ff2-88ae-bd1f81c299d2",
title: "lorem ipsum",
completed: false,
});
todoRepository.save({
id: "5f56fb59-a887-4984-93bf-eb39b4170a35",
title: "dolor sit amet",
completed: true,
});
socket.emit("todo:list", (res) => {
if ("error" in res) {
return done(new Error("should not happen"));
}
expect(res.data).to.eql([
{
id: "d445db6d-9d55-4ff2-88ae-bd1f81c299d2",
title: "lorem ipsum",
completed: false,
},
{
id: "5f56fb59-a887-4984-93bf-eb39b4170a35",
title: "dolor sit amet",
completed: true,
},
]);
done();
});
});
});
});

View File

@@ -1,11 +0,0 @@
{
"compilerOptions": {
"outDir": "./dist",
"module": "commonjs",
"target": "es2017",
"strict": true
},
"include": [
"./lib/**/*"
]
}

View File

@@ -1,12 +1,15 @@
# Socket.IO Chat
A simple chat demo for Socket.IO
A simple chat demo for socket.io
## How to use
```
$ npm ci
$ cd socket.io
$ npm install
$ cd examples/chat
$ npm install
$ npm start
```

View File

@@ -1,10 +1,10 @@
// Setup basic express server
const express = require('express');
const app = express();
const path = require('path');
const server = require('http').createServer(app);
const io = require('socket.io')(server);
const port = process.env.PORT || 3000;
var express = require('express');
var app = express();
var path = require('path');
var server = require('http').createServer(app);
var io = require('../..')(server);
var port = process.env.PORT || 3000;
server.listen(port, () => {
console.log('Server listening at port %d', port);
@@ -15,10 +15,10 @@ app.use(express.static(path.join(__dirname, 'public')));
// Chatroom
let numUsers = 0;
var numUsers = 0;
io.on('connection', (socket) => {
let addedUser = false;
var addedUser = false;
// when the client emits 'new message', this listens and executes
socket.on('new message', (data) => {

View File

@@ -7,8 +7,7 @@
"private": true,
"license": "BSD",
"dependencies": {
"express": "~4.17.1",
"socket.io": "^4.0.0"
"express": "4.13.4"
},
"scripts": {
"start": "node index.js"

View File

@@ -1,36 +1,36 @@
$(function() {
const FADE_TIME = 150; // ms
const TYPING_TIMER_LENGTH = 400; // ms
const COLORS = [
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
const $window = $(window);
const $usernameInput = $('.usernameInput'); // Input for username
const $messages = $('.messages'); // Messages area
const $inputMessage = $('.inputMessage'); // Input message input box
var $window = $(window);
var $usernameInput = $('.usernameInput'); // Input for username
var $messages = $('.messages'); // Messages area
var $inputMessage = $('.inputMessage'); // Input message input box
const $loginPage = $('.login.page'); // The login page
const $chatPage = $('.chat.page'); // The chatroom page
const socket = io();
var $loginPage = $('.login.page'); // The login page
var $chatPage = $('.chat.page'); // The chatroom page
// Prompt for setting a username
let username;
let connected = false;
let typing = false;
let lastTypingTime;
let $currentInput = $usernameInput.focus();
var username;
var connected = false;
var typing = false;
var lastTypingTime;
var $currentInput = $usernameInput.focus();
var socket = io();
const addParticipantsMessage = (data) => {
let message = '';
var 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,41 +53,45 @@ $(function() {
// Sends a chat message
const sendMessage = () => {
let message = $inputMessage.val();
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, message });
addChatMessage({
username: username,
message: message
});
// tell server to execute 'new message' and send along one parameter
socket.emit('new message', message);
}
}
// Log a message
const log = (message, options) => {
const $el = $('<li>').addClass('log').text(message);
const log = (message, options) => {
var $el = $('<li>').addClass('log').text(message);
addMessageElement($el, options);
}
// Adds the visual chat message to the message list
const addChatMessage = (data, options = {}) => {
const addChatMessage = (data, options) => {
// Don't fade the message in if there is an 'X was typing'
const $typingMessages = getTypingMessages(data);
var $typingMessages = getTypingMessages(data);
options = options || {};
if ($typingMessages.length !== 0) {
options.fade = false;
$typingMessages.remove();
}
const $usernameDiv = $('<span class="username"/>')
var $usernameDiv = $('<span class="username"/>')
.text(data.username)
.css('color', getUsernameColor(data.username));
const $messageBodyDiv = $('<span class="messageBody">')
var $messageBodyDiv = $('<span class="messageBody">')
.text(data.message);
const typingClass = data.typing ? 'typing' : '';
const $messageDiv = $('<li class="message"/>')
var typingClass = data.typing ? 'typing' : '';
var $messageDiv = $('<li class="message"/>')
.data('username', data.username)
.addClass(typingClass)
.append($usernameDiv, $messageBodyDiv);
@@ -115,7 +119,8 @@ $(function() {
// options.prepend - If the element should prepend
// all other messages (default = false)
const addMessageElement = (el, options) => {
const $el = $(el);
var $el = $(el);
// Setup default options
if (!options) {
options = {};
@@ -136,7 +141,6 @@ $(function() {
} else {
$messages.append($el);
}
$messages[0].scrollTop = $messages[0].scrollHeight;
}
@@ -155,8 +159,8 @@ $(function() {
lastTypingTime = (new Date()).getTime();
setTimeout(() => {
const typingTimer = (new Date()).getTime();
const timeDiff = typingTimer - lastTypingTime;
var typingTimer = (new Date()).getTime();
var timeDiff = typingTimer - lastTypingTime;
if (timeDiff >= TYPING_TIMER_LENGTH && typing) {
socket.emit('stop typing');
typing = false;
@@ -175,12 +179,12 @@ $(function() {
// Gets the color of a username through our hash function
const getUsernameColor = (username) => {
// Compute hash code
let hash = 7;
for (let i = 0; i < username.length; i++) {
hash = username.charCodeAt(i) + (hash << 5) - hash;
var hash = 7;
for (var i = 0; i < username.length; i++) {
hash = username.charCodeAt(i) + (hash << 5) - hash;
}
// Calculate color
const index = Math.abs(hash % COLORS.length);
var index = Math.abs(hash % COLORS.length);
return COLORS[index];
}
@@ -225,7 +229,7 @@ $(function() {
socket.on('login', (data) => {
connected = true;
// Display the welcome message
const message = 'Welcome to Socket.IO Chat ';
var message = "Welcome to Socket.IO Chat ";
log(message, {
prepend: true
});
@@ -239,13 +243,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);
});

View File

@@ -1,4 +1,4 @@
FROM node:14-alpine
FROM mhart/alpine-node:6
# 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 --prod
RUN npm install
# Bundle app source
COPY . /usr/src/app

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