Compare commits

...

29 Commits
3.0.4 ... 3.1.1

Author SHA1 Message Date
Damien Arrachequesne
12221f296d chore(release): 3.1.1
Diff: https://github.com/socketio/socket.io/compare/3.1.0...3.1.1
2021-02-03 22:57:21 +01:00
Damien Arrachequesne
6f4bd7f8e7 fix: properly parse the CONNECT packet in v2 compatibility mode
In Socket.IO v2, the Socket query option was appended to the namespace
in the CONNECT packet:

{
  type: 0,
  nsp: "/my-namespace?abc=123"
}

Note: the "query" option on the client-side (v2) will be found in the
"auth" attribute on the server-side:

```
// client-side
const socket = io("/nsp1", {
  query: {
    abc: 123
  }
});
socket.query = { abc: 456 };

// server-side
const io = require("socket.io")(httpServer, {
  allowEIO3: true // enable compatibility mode
});

io.of("/nsp1").on("connection", (socket) => {
  console.log(socket.handshake.auth); // { abc: 456 } (the Socket query)
  console.log(socket.handshake.query.abc); // 123 (the Manager query)
});

More information here: 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

Related: https://github.com/socketio/socket.io/issues/3791
2021-02-03 22:54:07 +01:00
Damien Arrachequesne
4f2e9a716d fix(typings): update the types of "query", "auth" and "headers"
Related: https://github.com/socketio/socket.io/issues/3770
2021-02-03 22:53:38 +01:00
david-fong
9e8f288ca9 fix(typings): add return types and general-case overload signatures (#3776)
See also: https://stackoverflow.com/questions/52760509/typescript-returntype-of-overloaded-function/52760599#52760599
2021-02-02 11:50:08 +01:00
Damien Arrachequesne
86eb4227b2 docs(examples): add example with traefik
Reference: https://doc.traefik.io/traefik/v2.0/
2021-01-31 23:47:23 +01:00
Damien Arrachequesne
cf873fd831 docs(examples): update cluster examples to Socket.IO v3 2021-01-28 11:21:38 +01:00
Damien Arrachequesne
0d10e6131b docs(examples): update the nginx cluster example
Related: https://github.com/socketio/socket.io/discussions/3778

Reference: http://nginx.org/en/docs/http/ngx_http_upstream_module.html#hash
2021-01-28 10:52:26 +01:00
JPSO
10aafbbc16 ci: add Node.js 15 (#3765) 2021-01-20 22:34:51 +01:00
Hamdi Bayhan
f34cfca26d docs: fix broken link (#3759) 2021-01-17 22:10:37 +01:00
PrashoonB
d412e876b8 docs: add installation with yarn (#3757) 2021-01-15 22:20:04 +01:00
Damien Arrachequesne
f05a4a6f82 chore(release): 3.1.0
Diff: https://github.com/socketio/socket.io/compare/3.0.5...3.1.0
2021-01-15 02:21:54 +01:00
Damien Arrachequesne
2c883f5d4e chore: bump socket.io-adapter version
Diff: https://github.com/socketio/socket.io-adapter/compare/2.0.3...2.1.0

Includes:

- add room events ([155fa63](155fa6333a))
- make rooms and sids public ([313c5a9](313c5a9fb6))
2021-01-15 01:41:37 +01:00
Jakob Ackermann
161091dd4c feat: confirm a weak but matching ETag (#3485)
When handling compression at the proxy server level, the client receives a weak ETag.
Weak ETags are prefixed with `W/`, e.g. `W/"2.2.0"`.
Upon cache validation we should take care of these too.

Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
2021-01-15 01:04:55 +01:00
汪心禾 Wang, Xinhe
d52532b7be docs: add other client implementations (#3593)
Reference: https://socket.io/docs/#Other-client-implementations
2021-01-15 00:37:40 +01:00
Sergio Lenza
6b1d7901db docs(examples): Improve the chat example with more ES6 features (#3240) 2021-01-15 00:36:17 +01:00
EloniX-X
b55892ae80 docs: add run on repl.it badge to README (#3617) 2021-01-15 00:30:17 +01:00
Pablo Tejada
233650c222 feat(esm): export the Namespace and Socket class (#3699) 2021-01-15 00:28:20 +01:00
Damien Arrachequesne
9925746c8e feat: add support for Socket.IO v2 clients
In order to ease the migration to Socket.IO v3, the Socket.IO server
can now communicate with v2 clients.

```js
const io = require("socket.io")({
  allowEIO3: true
});
```

This feature is disabled by default.
2021-01-14 23:38:24 +01:00
Rohan Chougule
de8dffd252 refactor: strict type check in if expressions (#3744) 2021-01-08 14:58:37 +01:00
Damien Arrachequesne
f8a66fd11a chore(release): 3.0.5
Diff: https://github.com/socketio/socket.io/compare/3.0.4...3.0.5
2021-01-05 12:08:15 +01:00
Damien Arrachequesne
752dfe3b1e chore: bump debug version 2021-01-05 11:53:27 +01:00
Damien Arrachequesne
bf54327421 revert: restore the socket middleware functionality
This functionality was removed in [1] (included in 3.0.0), but
catch-all listeners and socket middleware features are complementary
rather than mutually exclusive.

The only difference with the previous implementation is that passing an
error to the `next` handler will create an error on the server-side,
and not on the client-side anymore.

```js
io.on("connection", (socket) => {

  socket.use(([ event, ...args ], next) => {
    next(new Error("stop"));
  });

  socket.on("error", (err) => {
    // to restore the previous behavior
    socket.emit("error", err);

    // or close the connection, depending on your use case
    socket.disconnect(true);
  });
});
```

This creates additional possibilities about custom error handlers, which
may be implemented in the future.

```js
// user-defined error handler
socket.use((err, [ event ], next) => {
  // either handle it
  socket.disconnect();

  // or forward the error to the default error handler
  next(err);
});

// default error handler
socket.use((err, _, next) => {
  socket.emit("error", err);
});
```

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

[1]: 5c73733985
2021-01-05 11:51:50 +01:00
Damien Arrachequesne
170b739f14 fix: properly clear timeout on connection failure
Related: https://github.com/socketio/socket.io/issues/3720
2021-01-05 11:51:08 +01:00
Stijn de Witt
230cd19164 chore: bump dependencies 2021-01-04 10:28:17 +01:00
Damien Arrachequesne
a0a3481c64 test: fix random test failure 2021-01-04 10:27:57 +01:00
Damien Arrachequesne
f773b4889c chore: update GitHub issue templates 2020-12-30 11:34:30 +01:00
Damien Arrachequesne
292d62ea69 docs(examples): update TypeScript example
Includes b3de861a92
2020-12-30 09:45:22 +01:00
Damien Arrachequesne
178e899f48 docs(examples): add Angular TodoMVC + Socket.IO example 2020-12-17 12:42:23 +01:00
David Fong
d1bfe40dbb refactor: add more typing info and upgrade prettier (#3725)
This upgrades prettier to 2.2.0 to gain support for TypeScript's new
type-only-imports feature.
2020-12-11 12:19:20 +01:00
84 changed files with 17329 additions and 1257 deletions

View File

@@ -1,32 +0,0 @@
**Note**: for support questions, please use one of these channels: [stackoverflow](http://stackoverflow.com/questions/tagged/socket.io) or [slack](https://socketio.slack.com)
For bug reports and feature requests for the **Swift client**, please open an issue [there](https://github.com/socketio/socket.io-client-swift).
For bug reports and feature requests for the **Java client**, please open an issue [there](https://github.com/socketio/socket.io-client-java).
### You want to:
* [x] report a *bug*
* [ ] request a *feature*
### Current behaviour
*What is actually happening?*
### Steps to reproduce (if the current behaviour is a bug)
**Note**: the best way (and by that we mean **the only way**) to get a quick answer is to provide a failing test case by forking the following [fiddle](https://github.com/socketio/socket.io-fiddle).
### Expected behaviour
*What is expected?*
### Setup
- OS:
- browser:
- socket.io version:
### Other information (e.g. stacktraces, related issues, suggestions how to fix)

61
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

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

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

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

View File

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

View File

@@ -12,7 +12,7 @@ jobs:
strategy:
matrix:
node-version: [10.x, 12.x, 14.x]
node-version: [10.x, 12.x, 14.x, 15.x]
steps:
- uses: actions/checkout@v2

2
.replit Normal file
View File

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

View File

@@ -1,3 +1,42 @@
## [3.1.1](https://github.com/socketio/socket.io/compare/3.1.0...3.1.1) (2021-02-03)
### Bug Fixes
* properly parse the CONNECT packet in v2 compatibility mode ([6f4bd7f](https://github.com/socketio/socket.io/commit/6f4bd7f8e7c41a075a8014565330a77c38b03a8d))
* **typings:** add return types and general-case overload signatures ([#3776](https://github.com/socketio/socket.io/issues/3776)) ([9e8f288](https://github.com/socketio/socket.io/commit/9e8f288ca9f14f91064b8d3cce5946f7d23d407c))
* **typings:** update the types of "query", "auth" and "headers" ([4f2e9a7](https://github.com/socketio/socket.io/commit/4f2e9a716d9835b550c8fd9a9b429ebf069c2895))
# [3.1.0](https://github.com/socketio/socket.io/compare/3.0.5...3.1.0) (2021-01-15)
### Features
* confirm a weak but matching ETag ([#3485](https://github.com/socketio/socket.io/issues/3485)) ([161091d](https://github.com/socketio/socket.io/commit/161091dd4c9e1b1610ac3d45d964195e63d92b94))
* **esm:** export the Namespace and Socket class ([#3699](https://github.com/socketio/socket.io/issues/3699)) ([233650c](https://github.com/socketio/socket.io/commit/233650c22209708b5fccc4349c38d2fa1b465d8f))
* add support for Socket.IO v2 clients ([9925746](https://github.com/socketio/socket.io/commit/9925746c8ee3a6522bd640b5d586c83f04f2f1ba))
* add room events ([155fa63](https://github.com/socketio/socket.io-adapter/commit/155fa6333a504036e99a33667dc0397f6aede25e))
### Bug Fixes
* allow integers as event names ([1c220dd](https://github.com/socketio/socket.io-parser/commit/1c220ddbf45ea4b44bc8dbf6f9ae245f672ba1b9))
## [3.0.5](https://github.com/socketio/socket.io/compare/3.0.4...3.0.5) (2021-01-05)
### Bug Fixes
* properly clear timeout on connection failure ([170b739](https://github.com/socketio/socket.io/commit/170b739f147cb6c92b423729b877e242e376927d))
### Reverts
* restore the socket middleware functionality ([bf54327](https://github.com/socketio/socket.io/commit/bf5432742158e4d5ba2722cff4a614967dffa5b9))
## [3.0.4](https://github.com/socketio/socket.io/compare/3.0.3...3.0.4) (2020-12-07)

View File

@@ -1,6 +1,5 @@
# socket.io
[![Run on Repl.it](https://repl.it/badge/github/socketio/socket.io)](https://repl.it/github/socketio/socket.io)
[![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)
@@ -22,6 +21,8 @@ Some implementations in other languages are also available:
- [C++](https://github.com/socketio/socket.io-client-cpp)
- [Swift](https://github.com/socketio/socket.io-client-swift)
- [Dart](https://github.com/rikulo/socket.io-client-dart)
- [Python](https://github.com/miguelgrinberg/python-socketio)
- [.Net](https://github.com/Quobject/SocketIoClientDotNet)
Its main features are:
@@ -35,7 +36,7 @@ For this purpose, it relies on [Engine.IO](https://github.com/socketio/engine.io
#### Auto-reconnection support
Unless instructed otherwise a disconnected client will try to reconnect forever, until the server is available again. Please see the available reconnection options [here](https://github.com/socketio/socket.io-client/blob/master/docs/API.md#new-managerurl-options).
Unless instructed otherwise a disconnected client will try to reconnect forever, until the server is available again. Please see the available reconnection options [here](https://socket.io/docs/v3/client-api/#new-Manager-url-options).
#### Disconnection detection
@@ -84,7 +85,11 @@ This is a useful feature to send notifications to a group of users, or to a give
## Installation
```bash
// with npm
npm install socket.io
// with yarn
yarn add socket.io
```
## How to use

View File

@@ -1,6 +1,6 @@
/*!
* Socket.IO v3.0.4
* (c) 2014-2020 Guillermo Rauch
* Socket.IO v3.1.1
* (c) 2014-2021 Guillermo Rauch
* Released under the MIT License.
*/
(function webpackUniversalModuleDefinition(root, factory) {
@@ -12,17 +12,7 @@
exports["io"] = factory();
else
root["io"] = factory();
})((() => {
if (typeof self !== 'undefined') {
return self;
} else if (typeof window !== 'undefined') {
return window;
} else if (typeof global !== 'undefined') {
return global;
} else {
return Function('return this')();
}
})(), function() {
})(window, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
@@ -161,7 +151,7 @@ function lookup(uri, opts) {
}
opts = opts || {};
var parsed = url_1.url(uri);
var parsed = url_1.url(uri, opts.path);
var source = parsed.source;
var id = parsed.id;
var path = parsed.path;
@@ -182,7 +172,7 @@ function lookup(uri, opts) {
}
if (parsed.query && !opts.query) {
opts.query = parsed.query;
opts.query = parsed.queryKey;
}
return io.socket(parsed.path, opts);
@@ -279,8 +269,6 @@ var parser = __webpack_require__(/*! socket.io-parser */ "./node_modules/socket.
var on_1 = __webpack_require__(/*! ./on */ "./build/on.js");
var bind = __webpack_require__(/*! component-bind */ "./node_modules/component-bind/index.js");
var Backoff = __webpack_require__(/*! backo2 */ "./node_modules/backo2/index.js");
var debug = __webpack_require__(/*! debug */ "./node_modules/debug/src/browser.js")("socket.io-client:manager");
@@ -427,7 +415,7 @@ var Manager = /*#__PURE__*/function (_Emitter) {
this._readyState = "opening";
this.skipReconnect = false; // emit `open`
var openSub = on_1.on(socket, "open", function () {
var openSubDestroy = on_1.on(socket, "open", function () {
self.onopen();
fn && fn();
}); // emit `error`
@@ -452,31 +440,29 @@ var Manager = /*#__PURE__*/function (_Emitter) {
debug("connect attempt will timeout after %d", timeout);
if (timeout === 0) {
openSub.destroy(); // prevents a race condition with the 'open' event
openSubDestroy(); // prevents a race condition with the 'open' event
} // set timer
var timer = setTimeout(function () {
debug("connect attempt timed out after %d", timeout);
openSub.destroy();
openSubDestroy();
socket.close();
socket.emit("error", new Error("timeout"));
}, timeout);
this.subs.push({
destroy: function destroy() {
clearTimeout(timer);
}
this.subs.push(function subDestroy() {
clearTimeout(timer);
});
}
this.subs.push(openSub);
this.subs.push(openSubDestroy);
this.subs.push(errorSub);
return this;
}
/**
* Alias for open()
*
* @return {Manager} self
* @return self
* @public
*/
@@ -504,7 +490,7 @@ var Manager = /*#__PURE__*/function (_Emitter) {
var socket = this.engine;
this.subs.push(on_1.on(socket, "data", bind(this, "ondata")), on_1.on(socket, "ping", bind(this, "onping")), on_1.on(socket, "error", bind(this, "onerror")), on_1.on(socket, "close", bind(this, "onclose")), on_1.on(this.decoder, "decoded", bind(this, "ondecoded")));
this.subs.push(on_1.on(socket, "ping", this.onping.bind(this)), on_1.on(socket, "data", this.ondata.bind(this)), on_1.on(socket, "error", this.onerror.bind(this)), on_1.on(socket, "close", this.onclose.bind(this)), on_1.on(this.decoder, "decoded", this.ondecoded.bind(this)));
}
/**
* Called upon a ping.
@@ -606,7 +592,6 @@ var Manager = /*#__PURE__*/function (_Emitter) {
key: "_packet",
value: function _packet(packet) {
debug("writing packet %j", packet);
if (packet.query && packet.type === 0) packet.nsp += "?" + packet.query;
var encodedPackets = this.encoder.encode(packet);
for (var i = 0; i < encodedPackets.length; i++) {
@@ -623,13 +608,10 @@ var Manager = /*#__PURE__*/function (_Emitter) {
key: "cleanup",
value: function cleanup() {
debug("cleanup");
var subsLength = this.subs.length;
for (var i = 0; i < subsLength; i++) {
var sub = this.subs.shift();
sub.destroy();
}
this.subs.forEach(function (subDestroy) {
return subDestroy();
});
this.subs.length = 0;
this.decoder.destroy();
}
/**
@@ -732,10 +714,8 @@ var Manager = /*#__PURE__*/function (_Emitter) {
}
});
}, delay);
this.subs.push({
destroy: function destroy() {
clearTimeout(timer);
}
this.subs.push(function subDestroy() {
clearTimeout(timer);
});
}
}
@@ -780,10 +760,8 @@ exports.on = void 0;
function on(obj, ev, fn) {
obj.on(ev, fn);
return {
destroy: function destroy() {
obj.off(ev, fn);
}
return function subDestroy() {
obj.off(ev, fn);
};
}
@@ -844,8 +822,6 @@ var Emitter = __webpack_require__(/*! component-emitter */ "./node_modules/compo
var on_1 = __webpack_require__(/*! ./on */ "./build/on.js");
var bind = __webpack_require__(/*! component-bind */ "./node_modules/component-bind/index.js");
var debug = __webpack_require__(/*! debug */ "./node_modules/debug/src/browser.js")("socket.io-client:socket");
/**
* Internal events.
@@ -879,10 +855,10 @@ var Socket = /*#__PURE__*/function (_Emitter) {
_classCallCheck(this, Socket);
_this = _super.call(this);
_this.ids = 0;
_this.acks = {};
_this.receiveBuffer = [];
_this.sendBuffer = [];
_this.ids = 0;
_this.acks = {};
_this.flags = {};
_this.io = io;
_this.nsp = nsp;
@@ -913,7 +889,7 @@ var Socket = /*#__PURE__*/function (_Emitter) {
value: function subEvents() {
if (this.subs) return;
var io = this.io;
this.subs = [on_1.on(io, "open", bind(this, "onopen")), on_1.on(io, "packet", bind(this, "onpacket")), on_1.on(io, "close", bind(this, "onclose"))];
this.subs = [on_1.on(io, "open", this.onopen.bind(this)), on_1.on(io, "packet", this.onpacket.bind(this)), on_1.on(io, "error", this.onerror.bind(this)), on_1.on(io, "close", this.onclose.bind(this))];
}
/**
* Whether the Socket will try to reconnect when its Manager connects or reconnects
@@ -1051,6 +1027,20 @@ var Socket = /*#__PURE__*/function (_Emitter) {
});
}
}
/**
* Called upon engine or manager `error`.
*
* @param err
* @private
*/
}, {
key: "onerror",
value: function onerror(err) {
if (!this.connected) {
_get(_getPrototypeOf(Socket.prototype), "emit", this).call(this, "connect_error", err);
}
}
/**
* Called upon engine `close`.
*
@@ -1281,11 +1271,10 @@ var Socket = /*#__PURE__*/function (_Emitter) {
value: function destroy() {
if (this.subs) {
// clean subscriptions to avoid reconnections
for (var i = 0; i < this.subs.length; i++) {
this.subs[i].destroy();
}
this.subs = null;
this.subs.forEach(function (subDestroy) {
return subDestroy();
});
this.subs = undefined;
}
this.io["_destroy"](this);
@@ -1468,13 +1457,16 @@ var debug = __webpack_require__(/*! debug */ "./node_modules/debug/src/browser.j
* URL parser.
*
* @param uri - url
* @param path - the request path of the connection
* @param loc - An object meant to mimic window.location.
* Defaults to window.location.
* @public
*/
function url(uri, loc) {
function url(uri) {
var path = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "";
var loc = arguments.length > 2 ? arguments[2] : undefined;
var obj = uri; // default to window.location
loc = loc || typeof location !== "undefined" && location;
@@ -1517,7 +1509,7 @@ function url(uri, loc) {
var ipv6 = obj.host.indexOf(":") !== -1;
var host = ipv6 ? "[" + obj.host + "]" : obj.host; // define unique id
obj.id = obj.protocol + "://" + host + ":" + obj.port; // define href
obj.id = obj.protocol + "://" + host + ":" + obj.port + path; // define href
obj.href = obj.protocol + "://" + host + (loc && loc.port === obj.port ? "" : ":" + obj.port);
return obj;
@@ -1620,121 +1612,6 @@ Backoff.prototype.setJitter = function (jitter) {
/***/ }),
/***/ "./node_modules/base64-arraybuffer/lib/base64-arraybuffer.js":
/*!*******************************************************************!*\
!*** ./node_modules/base64-arraybuffer/lib/base64-arraybuffer.js ***!
\*******************************************************************/
/*! no static exports found */
/***/ (function(module, exports) {
/*
* base64-arraybuffer
* https://github.com/niklasvh/base64-arraybuffer
*
* Copyright (c) 2012 Niklas von Hertzen
* Licensed under the MIT license.
*/
(function () {
"use strict";
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; // Use a lookup table to find the index.
var lookup = new Uint8Array(256);
for (var i = 0; i < chars.length; i++) {
lookup[chars.charCodeAt(i)] = i;
}
exports.encode = function (arraybuffer) {
var bytes = new Uint8Array(arraybuffer),
i,
len = bytes.length,
base64 = "";
for (i = 0; i < len; i += 3) {
base64 += chars[bytes[i] >> 2];
base64 += chars[(bytes[i] & 3) << 4 | bytes[i + 1] >> 4];
base64 += chars[(bytes[i + 1] & 15) << 2 | bytes[i + 2] >> 6];
base64 += chars[bytes[i + 2] & 63];
}
if (len % 3 === 2) {
base64 = base64.substring(0, base64.length - 1) + "=";
} else if (len % 3 === 1) {
base64 = base64.substring(0, base64.length - 2) + "==";
}
return base64;
};
exports.decode = function (base64) {
var bufferLength = base64.length * 0.75,
len = base64.length,
i,
p = 0,
encoded1,
encoded2,
encoded3,
encoded4;
if (base64[base64.length - 1] === "=") {
bufferLength--;
if (base64[base64.length - 2] === "=") {
bufferLength--;
}
}
var arraybuffer = new ArrayBuffer(bufferLength),
bytes = new Uint8Array(arraybuffer);
for (i = 0; i < len; i += 4) {
encoded1 = lookup[base64.charCodeAt(i)];
encoded2 = lookup[base64.charCodeAt(i + 1)];
encoded3 = lookup[base64.charCodeAt(i + 2)];
encoded4 = lookup[base64.charCodeAt(i + 3)];
bytes[p++] = encoded1 << 2 | encoded2 >> 4;
bytes[p++] = (encoded2 & 15) << 4 | encoded3 >> 2;
bytes[p++] = (encoded3 & 3) << 6 | encoded4 & 63;
}
return arraybuffer;
};
})();
/***/ }),
/***/ "./node_modules/component-bind/index.js":
/*!**********************************************!*\
!*** ./node_modules/component-bind/index.js ***!
\**********************************************/
/*! no static exports found */
/***/ (function(module, exports) {
/**
* Slice reference.
*/
var slice = [].slice;
/**
* Bind `obj` to `fn`.
*
* @param {Object} obj
* @param {Function|String} fn or string
* @return {Function}
* @api public
*/
module.exports = function (obj, fn) {
if ('string' == typeof fn) fn = obj[fn];
if ('function' != typeof fn) throw new Error('bind() requires a function');
var args = slice.call(arguments, 2);
return function () {
return fn.apply(obj, args.concat(slice.call(arguments)));
};
};
/***/ }),
/***/ "./node_modules/component-emitter/index.js":
/*!*************************************************!*\
!*** ./node_modules/component-emitter/index.js ***!
@@ -1922,23 +1799,31 @@ Emitter.prototype.hasListeners = function (event) {
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
/* WEBPACK VAR INJECTION */(function(process) {function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
/* eslint-env browser */
/**
* This is the web browser implementation of `debug()`.
*/
exports.log = log;
exports.formatArgs = formatArgs;
exports.save = save;
exports.load = load;
exports.useColors = useColors;
exports.storage = localstorage();
exports.destroy = function () {
var warned = false;
return function () {
if (!warned) {
warned = true;
console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.');
}
};
}();
/**
* Colors.
*/
exports.colors = ['#0000CC', '#0000FF', '#0033CC', '#0033FF', '#0066CC', '#0066FF', '#0099CC', '#0099FF', '#00CC00', '#00CC33', '#00CC66', '#00CC99', '#00CCCC', '#00CCFF', '#3300CC', '#3300FF', '#3333CC', '#3333FF', '#3366CC', '#3366FF', '#3399CC', '#3399FF', '#33CC00', '#33CC33', '#33CC66', '#33CC99', '#33CCCC', '#33CCFF', '#6600CC', '#6600FF', '#6633CC', '#6633FF', '#66CC00', '#66CC33', '#9900CC', '#9900FF', '#9933CC', '#9933FF', '#99CC00', '#99CC33', '#CC0000', '#CC0033', '#CC0066', '#CC0099', '#CC00CC', '#CC00FF', '#CC3300', '#CC3333', '#CC3366', '#CC3399', '#CC33CC', '#CC33FF', '#CC6600', '#CC6633', '#CC9900', '#CC9933', '#CCCC00', '#CCCC33', '#FF0000', '#FF0033', '#FF0066', '#FF0099', '#FF00CC', '#FF00FF', '#FF3300', '#FF3333', '#FF3366', '#FF3399', '#FF33CC', '#FF33FF', '#FF6600', '#FF6633', '#FF9900', '#FF9933', '#FFCC00', '#FFCC33'];
/**
* Currently only WebKit-based Web Inspectors, Firefox >= v31,
@@ -2007,20 +1892,16 @@ function formatArgs(args) {
args.splice(lastC, 0, c);
}
/**
* Invokes `console.log()` when available.
* No-op when `console.log` is not a "function".
* Invokes `console.debug()` when available.
* No-op when `console.debug` is not a "function".
* If `console.debug` is not available, falls back
* to `console.log`.
*
* @api public
*/
function log() {
var _console;
// This hackery is required for IE8/9, where
// the `console.log` function doesn't have 'apply'
return (typeof console === "undefined" ? "undefined" : _typeof(console)) === 'object' && console.log && (_console = console).log.apply(_console, arguments);
}
exports.log = console.debug || console.log || function () {};
/**
* Save `namespaces`.
*
@@ -2099,7 +1980,6 @@ formatters.j = function (v) {
return '[UnexpectedJSONParseError]: ' + error.message;
}
};
/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./../../process/browser.js */ "./node_modules/process/browser.js")))
/***/ }),
@@ -2134,15 +2014,11 @@ function setup(env) {
createDebug.enable = enable;
createDebug.enabled = enabled;
createDebug.humanize = __webpack_require__(/*! ms */ "./node_modules/ms/index.js");
createDebug.destroy = destroy;
Object.keys(env).forEach(function (key) {
createDebug[key] = env[key];
});
/**
* Active `debug` instances.
*/
createDebug.instances = [];
/**
* The currently active debug mode names, and names to skip.
*/
@@ -2184,6 +2060,7 @@ function setup(env) {
function createDebug(namespace) {
var prevTime;
var enableOverride = null;
function debug() {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
@@ -2215,7 +2092,7 @@ function setup(env) {
args[0] = args[0].replace(/%([a-zA-Z%])/g, function (match, format) {
// If we encounter an escaped % then don't increase the array index
if (match === '%%') {
return match;
return '%';
}
index++;
@@ -2238,33 +2115,29 @@ function setup(env) {
}
debug.namespace = namespace;
debug.enabled = createDebug.enabled(namespace);
debug.useColors = createDebug.useColors();
debug.color = selectColor(namespace);
debug.destroy = destroy;
debug.extend = extend; // Debug.formatArgs = formatArgs;
// debug.rawLog = rawLog;
// env-specific initialization logic for debug instances
debug.color = createDebug.selectColor(namespace);
debug.extend = extend;
debug.destroy = createDebug.destroy; // XXX Temporary. Will be removed in the next major release.
Object.defineProperty(debug, 'enabled', {
enumerable: true,
configurable: false,
get: function get() {
return enableOverride === null ? createDebug.enabled(namespace) : enableOverride;
},
set: function set(v) {
enableOverride = v;
}
}); // Env-specific initialization logic for debug instances
if (typeof createDebug.init === 'function') {
createDebug.init(debug);
}
createDebug.instances.push(debug);
return debug;
}
function destroy() {
var index = createDebug.instances.indexOf(this);
if (index !== -1) {
createDebug.instances.splice(index, 1);
return true;
}
return false;
}
function extend(namespace, delimiter) {
var newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace);
newDebug.log = this.log;
@@ -2301,11 +2174,6 @@ function setup(env) {
createDebug.names.push(new RegExp('^' + namespaces + '$'));
}
}
for (i = 0; i < createDebug.instances.length; i++) {
var instance = createDebug.instances[i];
instance.enabled = createDebug.enabled(instance.namespace);
}
}
/**
* Disable debug output.
@@ -2381,6 +2249,15 @@ function setup(env) {
return val;
}
/**
* XXX DO NOT USE. This is a temporary stub function.
* XXX It WILL be removed in the next major release.
*/
function destroy() {
console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.');
}
createDebug.enable(createDebug.load());
return createDebug;
@@ -4418,7 +4295,7 @@ var WS = /*#__PURE__*/function (_Transport) {
var uri = this.uri();
var protocols = this.opts.protocols; // React Native only supports the 'headers' option, and will print a warning if anything else is passed
var opts = isReactNative ? {} : pick(this.opts, "agent", "perMessageDeflate", "pfx", "key", "passphrase", "cert", "ca", "ciphers", "rejectUnauthorized", "localAddress");
var opts = isReactNative ? {} : pick(this.opts, "agent", "perMessageDeflate", "pfx", "key", "passphrase", "cert", "ca", "ciphers", "rejectUnauthorized", "localAddress", "protocolVersion", "origin", "maxPayload", "family", "checkServerIdentity");
if (this.opts.extraHeaders) {
opts.headers = this.opts.extraHeaders;
@@ -4626,7 +4503,10 @@ module.exports.pick = function (obj) {
}
return attr.reduce(function (acc, k) {
acc[k] = obj[k];
if (obj.hasOwnProperty(k)) {
acc[k] = obj[k];
}
return acc;
}, {});
};
@@ -4725,7 +4605,7 @@ var withNativeArrayBuffer = typeof ArrayBuffer === "function";
var base64decoder;
if (withNativeArrayBuffer) {
base64decoder = __webpack_require__(/*! base64-arraybuffer */ "./node_modules/base64-arraybuffer/lib/base64-arraybuffer.js");
base64decoder = __webpack_require__(/*! base64-arraybuffer */ "./node_modules/engine.io-parser/node_modules/base64-arraybuffer/lib/base64-arraybuffer.js");
}
var decodePacket = function decodePacket(encodedPacket, binaryType) {
@@ -4897,6 +4777,82 @@ module.exports = {
/***/ }),
/***/ "./node_modules/engine.io-parser/node_modules/base64-arraybuffer/lib/base64-arraybuffer.js":
/*!*************************************************************************************************!*\
!*** ./node_modules/engine.io-parser/node_modules/base64-arraybuffer/lib/base64-arraybuffer.js ***!
\*************************************************************************************************/
/*! no static exports found */
/***/ (function(module, exports) {
/*
* base64-arraybuffer
* https://github.com/niklasvh/base64-arraybuffer
*
* Copyright (c) 2012 Niklas von Hertzen
* Licensed under the MIT license.
*/
(function (chars) {
"use strict";
exports.encode = function (arraybuffer) {
var bytes = new Uint8Array(arraybuffer),
i,
len = bytes.length,
base64 = "";
for (i = 0; i < len; i += 3) {
base64 += chars[bytes[i] >> 2];
base64 += chars[(bytes[i] & 3) << 4 | bytes[i + 1] >> 4];
base64 += chars[(bytes[i + 1] & 15) << 2 | bytes[i + 2] >> 6];
base64 += chars[bytes[i + 2] & 63];
}
if (len % 3 === 2) {
base64 = base64.substring(0, base64.length - 1) + "=";
} else if (len % 3 === 1) {
base64 = base64.substring(0, base64.length - 2) + "==";
}
return base64;
};
exports.decode = function (base64) {
var bufferLength = base64.length * 0.75,
len = base64.length,
i,
p = 0,
encoded1,
encoded2,
encoded3,
encoded4;
if (base64[base64.length - 1] === "=") {
bufferLength--;
if (base64[base64.length - 2] === "=") {
bufferLength--;
}
}
var arraybuffer = new ArrayBuffer(bufferLength),
bytes = new Uint8Array(arraybuffer);
for (i = 0; i < len; i += 4) {
encoded1 = chars.indexOf(base64[i]);
encoded2 = chars.indexOf(base64[i + 1]);
encoded3 = chars.indexOf(base64[i + 2]);
encoded4 = chars.indexOf(base64[i + 3]);
bytes[p++] = encoded1 << 2 | encoded2 >> 4;
bytes[p++] = (encoded2 & 15) << 4 | encoded3 >> 2;
bytes[p++] = (encoded3 & 3) << 6 | encoded4 & 63;
}
return arraybuffer;
};
})("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
/***/ }),
/***/ "./node_modules/has-cors/index.js":
/*!****************************************!*\
!*** ./node_modules/has-cors/index.js ***!
@@ -5233,224 +5189,6 @@ function queryKey(uri, query) {
/***/ }),
/***/ "./node_modules/process/browser.js":
/*!*****************************************!*\
!*** ./node_modules/process/browser.js ***!
\*****************************************/
/*! no static exports found */
/***/ (function(module, exports) {
// shim for using process in browser
var process = module.exports = {}; // cached from whatever global is present so that test runners that stub it
// don't break things. But we need to wrap it in a try catch in case it is
// wrapped in strict mode code which doesn't define any globals. It's inside a
// function because try/catches deoptimize in certain engines.
var cachedSetTimeout;
var cachedClearTimeout;
function defaultSetTimout() {
throw new Error('setTimeout has not been defined');
}
function defaultClearTimeout() {
throw new Error('clearTimeout has not been defined');
}
(function () {
try {
if (typeof setTimeout === 'function') {
cachedSetTimeout = setTimeout;
} else {
cachedSetTimeout = defaultSetTimout;
}
} catch (e) {
cachedSetTimeout = defaultSetTimout;
}
try {
if (typeof clearTimeout === 'function') {
cachedClearTimeout = clearTimeout;
} else {
cachedClearTimeout = defaultClearTimeout;
}
} catch (e) {
cachedClearTimeout = defaultClearTimeout;
}
})();
function runTimeout(fun) {
if (cachedSetTimeout === setTimeout) {
//normal enviroments in sane situations
return setTimeout(fun, 0);
} // if setTimeout wasn't available but was latter defined
if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
cachedSetTimeout = setTimeout;
return setTimeout(fun, 0);
}
try {
// when when somebody has screwed with setTimeout but no I.E. maddness
return cachedSetTimeout(fun, 0);
} catch (e) {
try {
// When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
return cachedSetTimeout.call(null, fun, 0);
} catch (e) {
// same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
return cachedSetTimeout.call(this, fun, 0);
}
}
}
function runClearTimeout(marker) {
if (cachedClearTimeout === clearTimeout) {
//normal enviroments in sane situations
return clearTimeout(marker);
} // if clearTimeout wasn't available but was latter defined
if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
cachedClearTimeout = clearTimeout;
return clearTimeout(marker);
}
try {
// when when somebody has screwed with setTimeout but no I.E. maddness
return cachedClearTimeout(marker);
} catch (e) {
try {
// When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
return cachedClearTimeout.call(null, marker);
} catch (e) {
// same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
// Some versions of I.E. have different rules for clearTimeout vs setTimeout
return cachedClearTimeout.call(this, marker);
}
}
}
var queue = [];
var draining = false;
var currentQueue;
var queueIndex = -1;
function cleanUpNextTick() {
if (!draining || !currentQueue) {
return;
}
draining = false;
if (currentQueue.length) {
queue = currentQueue.concat(queue);
} else {
queueIndex = -1;
}
if (queue.length) {
drainQueue();
}
}
function drainQueue() {
if (draining) {
return;
}
var timeout = runTimeout(cleanUpNextTick);
draining = true;
var len = queue.length;
while (len) {
currentQueue = queue;
queue = [];
while (++queueIndex < len) {
if (currentQueue) {
currentQueue[queueIndex].run();
}
}
queueIndex = -1;
len = queue.length;
}
currentQueue = null;
draining = false;
runClearTimeout(timeout);
}
process.nextTick = function (fun) {
var args = new Array(arguments.length - 1);
if (arguments.length > 1) {
for (var i = 1; i < arguments.length; i++) {
args[i - 1] = arguments[i];
}
}
queue.push(new Item(fun, args));
if (queue.length === 1 && !draining) {
runTimeout(drainQueue);
}
}; // v8 likes predictible objects
function Item(fun, array) {
this.fun = fun;
this.array = array;
}
Item.prototype.run = function () {
this.fun.apply(null, this.array);
};
process.title = 'browser';
process.browser = true;
process.env = {};
process.argv = [];
process.version = ''; // empty string to avoid regexp issues
process.versions = {};
function noop() {}
process.on = noop;
process.addListener = noop;
process.once = noop;
process.off = noop;
process.removeListener = noop;
process.removeAllListeners = noop;
process.emit = noop;
process.prependListener = noop;
process.prependOnceListener = noop;
process.listeners = function (name) {
return [];
};
process.binding = function (name) {
throw new Error('process.binding is not supported');
};
process.cwd = function () {
return '/';
};
process.chdir = function (dir) {
throw new Error('process.chdir is not supported');
};
process.umask = function () {
return 0;
};
/***/ }),
/***/ "./node_modules/socket.io-parser/dist/binary.js":
/*!******************************************************!*\
!*** ./node_modules/socket.io-parser/dist/binary.js ***!
@@ -5889,7 +5627,7 @@ var Decoder = /*#__PURE__*/function (_Emitter) {
case PacketType.EVENT:
case PacketType.BINARY_EVENT:
return Array.isArray(payload) && typeof payload[0] === "string";
return Array.isArray(payload) && payload.length > 0;
case PacketType.ACK:
case PacketType.BINARY_ACK:

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

View File

@@ -0,0 +1,17 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support
# You can see what browsers were selected by your queries by running:
# npx browserslist
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.

View File

@@ -0,0 +1,16 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false

46
examples/angular-todomvc/.gitignore vendored Normal file
View File

@@ -0,0 +1,46 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out
# dependencies
/node_modules
# profiling files
chrome-profiler-events*.json
speed-measure-plugin*.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db

View File

@@ -0,0 +1,35 @@
# Angular TodoMVC + Socket.IO
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 11.0.4.
Inspired from the [TodoMVC](http://todomvc.com/) [angular example](https://github.com/tastejs/todomvc/tree/master/examples/angular2).
![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

@@ -0,0 +1,128 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"angular-todomvc": {
"projectType": "application",
"schematics": {
"@schematics/angular:application": {
"strict": true
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/angular-todomvc",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"aot": true,
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.css"
],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "angular-todomvc:build"
},
"configurations": {
"production": {
"browserTarget": "angular-todomvc:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "angular-todomvc:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.css"
],
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json"
],
"exclude": [
"**/node_modules/**"
]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "angular-todomvc:serve"
},
"configurations": {
"production": {
"devServerTarget": "angular-todomvc:serve:production"
}
}
}
}
}
},
"defaultProject": "angular-todomvc"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

View File

@@ -0,0 +1,37 @@
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
*/
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
browserName: 'chrome'
},
directConnect: true,
SELENIUM_PROMISE_MANAGER: false,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
});
jasmine.getEnv().addReporter(new SpecReporter({
spec: {
displayStacktrace: StacktraceOption.PRETTY
}
}));
}
};

View File

@@ -0,0 +1,23 @@
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', async () => {
await page.navigateTo();
expect(await page.getTitleText()).toEqual('angular-todomvc app is running!');
});
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry));
});
});

View File

@@ -0,0 +1,11 @@
import { browser, by, element } from 'protractor';
export class AppPage {
async navigateTo(): Promise<unknown> {
return browser.get(browser.baseUrl);
}
async getTitleText(): Promise<string> {
return element(by.css('app-root .content span')).getText();
}
}

View File

@@ -0,0 +1,13 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es2018",
"types": [
"jasmine",
"node"
]
}
}

View File

@@ -0,0 +1,44 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true // removes the duplicated traces
},
coverageReporter: {
dir: require('path').join(__dirname, './coverage/angular-todomvc'),
subdir: '.',
reporters: [
{ type: 'html' },
{ type: 'text-summary' }
]
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

13694
examples/angular-todomvc/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,48 @@
{
"name": "angular-todomvc",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e",
"start:server": "ts-node -O '{\"module\":\"commonjs\"}' server.ts"
},
"private": true,
"dependencies": {
"@angular/animations": "~11.0.4",
"@angular/common": "~11.0.4",
"@angular/compiler": "~11.0.4",
"@angular/core": "~11.0.4",
"@angular/forms": "~11.0.4",
"@angular/platform-browser": "~11.0.4",
"@angular/platform-browser-dynamic": "~11.0.4",
"@angular/router": "~11.0.4",
"rxjs": "~6.6.0",
"socket.io": "^3.0.4",
"socket.io-client": "^3.0.4",
"tslib": "^2.0.0",
"zone.js": "~0.10.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.1100.4",
"@angular/cli": "~11.0.4",
"@angular/compiler-cli": "~11.0.4",
"@types/jasmine": "~3.6.0",
"@types/node": "^12.11.1",
"codelyzer": "^6.0.0",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~5.1.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.0.3",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"protractor": "~7.0.0",
"ts-node": "~8.3.0",
"tslint": "~6.1.0",
"typescript": "~4.0.2"
}
}

View File

@@ -0,0 +1,28 @@
import { Server, Socket } from "socket.io";
const io = new Server(8080, {
cors: {
origin: "http://localhost:4200",
methods: ["GET", "POST"]
}
});
interface Todo {
completed: boolean;
editing: boolean;
title: string;
}
let todos: Array<Todo> = [];
io.on("connect", (socket: Socket) => {
socket.emit("todos", todos);
// note: we could also create a CRUD (create/read/update/delete) service for the todo list
socket.on("update-store", (updatedTodos) => {
// store it locally
todos = updatedTodos;
// broadcast to everyone but the sender
socket.broadcast.emit("todos", todos);
});
});

View File

@@ -0,0 +1,23 @@
<section class="todoapp">
<header class="header">
<h1>todos</h1>
<input class="new-todo" placeholder="What needs to be done?" autofocus="" [(ngModel)]="newTodoText" (keyup.enter)="addTodo()">
</header>
<section class="main" *ngIf="todoStore.todos.length > 0">
<input id="toggle-all" class="toggle-all" type="checkbox" *ngIf="todoStore.todos.length" #toggleall [checked]="todoStore.allCompleted()" (click)="todoStore.setAllTo(toggleall.checked)">
<ul class="todo-list">
<li *ngFor="let todo of todoStore.todos" [class.completed]="todo.completed" [class.editing]="todo.editing">
<div class="view">
<input class="toggle" type="checkbox" (click)="toggleCompletion(todo)" [checked]="todo.completed">
<label (dblclick)="editTodo(todo)">{{todo.title}}</label>
<button class="destroy" (click)="remove(todo)"></button>
</div>
<input class="edit" *ngIf="todo.editing" [value]="todo.title" #editedtodo (blur)="stopEditing(todo, editedtodo.value)" (keyup.enter)="updateEditingTodo(todo, editedtodo.value)" (keyup.escape)="cancelEditingTodo(todo)">
</li>
</ul>
</section>
<footer class="footer" *ngIf="todoStore.todos.length > 0">
<span class="todo-count"><strong>{{todoStore.getRemaining().length}}</strong> {{todoStore.getRemaining().length == 1 ? 'item' : 'items'}} left</span>
<button class="clear-completed" *ngIf="todoStore.getCompleted().length > 0" (click)="removeCompleted()">Clear completed</button>
</footer>
</section>

View File

@@ -0,0 +1,31 @@
import { TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'angular-todomvc'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('angular-todomvc');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement;
expect(compiled.querySelector('.content span').textContent).toContain('angular-todomvc app is running!');
});
});

View File

@@ -0,0 +1,59 @@
import { Component } from '@angular/core';
import { RemoteTodoStore, Todo } from './store';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
todoStore: RemoteTodoStore;
newTodoText = '';
constructor(todoStore: RemoteTodoStore) {
this.todoStore = todoStore;
}
stopEditing(todo: Todo, editedTitle: string) {
todo.title = editedTitle;
todo.editing = false;
}
cancelEditingTodo(todo: Todo) {
todo.editing = false;
}
updateEditingTodo(todo: Todo, editedTitle: string) {
editedTitle = editedTitle.trim();
todo.editing = false;
if (editedTitle.length === 0) {
return this.todoStore.remove(todo);
}
todo.title = editedTitle;
}
editTodo(todo: Todo) {
todo.editing = true;
}
removeCompleted() {
this.todoStore.removeCompleted();
}
toggleCompletion(todo: Todo) {
this.todoStore.toggleCompletion(todo);
}
remove(todo: Todo){
this.todoStore.remove(todo);
}
addTodo() {
if (this.newTodoText.trim().length) {
this.todoStore.add(this.newTodoText);
this.newTodoText = '';
}
}
}

View File

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

View File

@@ -0,0 +1,95 @@
import { io, Socket } from "socket.io-client";
export class Todo {
completed: boolean;
editing: boolean;
private _title: String = "";
get title() {
return this._title;
}
set title(value: String) {
this._title = value.trim();
}
constructor(title: String) {
this.completed = false;
this.editing = false;
this.title = title.trim();
}
}
export class TodoStore {
todos: Array<Todo>;
constructor() {
let persistedTodos = JSON.parse(localStorage.getItem('angular2-todos') || '[]');
// Normalize back into classes
this.todos = persistedTodos.map( (todo: {_title: String, completed: boolean}) => {
let ret = new Todo(todo._title);
ret.completed = todo.completed;
return ret;
});
}
protected updateStore() {
localStorage.setItem('angular2-todos', JSON.stringify(this.todos));
}
private getWithCompleted(completed: boolean) {
return this.todos.filter((todo: Todo) => todo.completed === completed);
}
allCompleted() {
return this.todos.length === this.getCompleted().length;
}
setAllTo(completed: boolean) {
this.todos.forEach((t: Todo) => t.completed = completed);
this.updateStore();
}
removeCompleted() {
this.todos = this.getWithCompleted(false);
this.updateStore();
}
getRemaining() {
return this.getWithCompleted(false);
}
getCompleted() {
return this.getWithCompleted(true);
}
toggleCompletion(todo: Todo) {
todo.completed = !todo.completed;
this.updateStore();
}
remove(todo: Todo) {
this.todos.splice(this.todos.indexOf(todo), 1);
this.updateStore();
}
add(title: String) {
this.todos.push(new Todo(title));
this.updateStore();
}
}
export class RemoteTodoStore extends TodoStore {
private socket: Socket;
constructor() {
super();
this.socket = io("http://localhost:8080");
this.socket.on("todos", (updatedTodos: Array<Todo>) => {
this.todos = updatedTodos;
});
}
protected updateStore() {
this.socket.emit("update-store", this.todos.map(({ title, editing, completed }) => ({ title, editing, completed })));
}
}

View File

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

View File

@@ -0,0 +1,16 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false
};
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.

Binary file not shown.

After

Width:  |  Height:  |  Size: 948 B

View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Angular Todo MVC</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@@ -0,0 +1,12 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));

View File

@@ -0,0 +1,63 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/** IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/**
* Web Animations `@angular/platform-browser/animations`
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
*/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/

View File

@@ -0,0 +1,381 @@
/* imported from node_modules/todomvc-app-css/index.css */
html,
body {
margin: 0;
padding: 0;
}
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
font-weight: inherit;
color: inherit;
-webkit-appearance: none;
appearance: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #f5f5f5;
color: #111111;
min-width: 230px;
max-width: 550px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-weight: 300;
}
:focus {
outline: 0;
}
.hidden {
display: none;
}
.todoapp {
background: #fff;
margin: 130px 0 40px 0;
position: relative;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.todoapp input::-webkit-input-placeholder {
font-style: italic;
font-weight: 300;
color: rgba(0, 0, 0, 0.4);
}
.todoapp input::-moz-placeholder {
font-style: italic;
font-weight: 300;
color: rgba(0, 0, 0, 0.4);
}
.todoapp input::input-placeholder {
font-style: italic;
font-weight: 300;
color: rgba(0, 0, 0, 0.4);
}
.todoapp h1 {
position: absolute;
top: -140px;
width: 100%;
font-size: 80px;
font-weight: 200;
text-align: center;
color: #b83f45;
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
.new-todo,
.edit {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
font-weight: inherit;
line-height: 1.4em;
color: inherit;
padding: 6px;
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.new-todo {
padding: 16px 16px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.003);
box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
}
.main {
position: relative;
z-index: 2;
border-top: 1px solid #e6e6e6;
}
.toggle-all {
width: 1px;
height: 1px;
border: none; /* Mobile Safari */
opacity: 0;
position: absolute;
right: 100%;
bottom: 100%;
}
.toggle-all + label {
width: 60px;
height: 34px;
font-size: 0;
position: absolute;
top: -52px;
left: -13px;
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
.toggle-all + label:before {
content: '';
font-size: 22px;
color: #e6e6e6;
padding: 10px 27px 10px 27px;
}
.toggle-all:checked + label:before {
color: #737373;
}
.todo-list {
margin: 0;
padding: 0;
list-style: none;
}
.todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px solid #ededed;
}
.todo-list li:last-child {
border-bottom: none;
}
.todo-list li.editing {
border-bottom: none;
padding: 0;
}
.todo-list li.editing .edit {
display: block;
width: calc(100% - 43px);
padding: 12px 16px;
margin: 0 0 0 43px;
}
.todo-list li.editing .view {
display: none;
}
.todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
border: none; /* Mobile Safari */
-webkit-appearance: none;
appearance: none;
}
.todo-list li .toggle {
opacity: 0;
}
.todo-list li .toggle + label {
/*
Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/
*/
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
background-repeat: no-repeat;
background-position: center left;
}
.todo-list li .toggle:checked + label {
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');
}
.todo-list li label {
word-break: break-all;
padding: 15px 15px 15px 60px;
display: block;
line-height: 1.2;
transition: color 0.4s;
font-weight: 400;
color: #4d4d4d;
}
.todo-list li.completed label {
color: #cdcdcd;
text-decoration: line-through;
}
.todo-list li .destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 30px;
color: #cc9a9a;
margin-bottom: 11px;
transition: color 0.2s ease-out;
}
.todo-list li .destroy:hover {
color: #af5b5e;
}
.todo-list li .destroy:after {
content: '×';
}
.todo-list li:hover .destroy {
display: block;
}
.todo-list li .edit {
display: none;
}
.todo-list li.editing:last-child {
margin-bottom: -1px;
}
.footer {
padding: 10px 15px;
height: 20px;
text-align: center;
font-size: 15px;
border-top: 1px solid #e6e6e6;
}
.footer:before {
content: '';
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 50px;
overflow: hidden;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
0 8px 0 -3px #f6f6f6,
0 9px 1px -3px rgba(0, 0, 0, 0.2),
0 16px 0 -6px #f6f6f6,
0 17px 2px -6px rgba(0, 0, 0, 0.2);
}
.todo-count {
float: left;
text-align: left;
}
.todo-count strong {
font-weight: 300;
}
.filters {
margin: 0;
padding: 0;
list-style: none;
position: absolute;
right: 0;
left: 0;
}
.filters li {
display: inline;
}
.filters li a {
color: inherit;
margin: 3px;
padding: 3px 7px;
text-decoration: none;
border: 1px solid transparent;
border-radius: 3px;
}
.filters li a:hover {
border-color: rgba(175, 47, 47, 0.1);
}
.filters li a.selected {
border-color: rgba(175, 47, 47, 0.2);
}
.clear-completed,
html .clear-completed:active {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
cursor: pointer;
}
.clear-completed:hover {
text-decoration: underline;
}
.info {
margin: 65px auto 0;
color: #4d4d4d;
font-size: 11px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
text-align: center;
}
.info p {
line-height: 1;
}
.info a {
color: inherit;
text-decoration: none;
font-weight: 400;
}
.info a:hover {
text-decoration: underline;
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
.toggle-all,
.todo-list li .toggle {
background: none;
}
.todo-list li .toggle {
height: 40px;
}
}
@media (max-width: 430px) {
.footer {
height: 50px;
}
.filters {
bottom: 10px;
}
}

View File

@@ -0,0 +1,25 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: {
context(path: string, deep?: boolean, filter?: RegExp): {
keys(): string[];
<T>(id: string): T;
};
};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

View File

@@ -0,0 +1,15 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.d.ts"
]
}

View File

@@ -0,0 +1,29 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "es2015",
"module": "es2020",
"lib": [
"es2018",
"dom"
]
},
"angularCompilerOptions": {
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

View File

@@ -0,0 +1,18 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine"
]
},
"files": [
"src/test.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

View File

@@ -0,0 +1,152 @@
{
"extends": "tslint:recommended",
"rulesDirectory": [
"codelyzer"
],
"rules": {
"align": {
"options": [
"parameters",
"statements"
]
},
"array-type": false,
"arrow-return-shorthand": true,
"curly": true,
"deprecation": {
"severity": "warning"
},
"eofline": true,
"import-blacklist": [
true,
"rxjs/Rx"
],
"import-spacing": true,
"indent": {
"options": [
"spaces"
]
},
"max-classes-per-file": false,
"max-line-length": [
true,
140
],
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-empty": false,
"no-inferrable-types": [
true,
"ignore-params"
],
"no-non-null-assertion": true,
"no-redundant-jsdoc": true,
"no-switch-case-fall-through": true,
"no-var-requires": false,
"object-literal-key-quotes": [
true,
"as-needed"
],
"quotemark": [
true,
"single"
],
"semicolon": {
"options": [
"always"
]
},
"space-before-function-paren": {
"options": {
"anonymous": "never",
"asyncArrow": "always",
"constructor": "never",
"method": "never",
"named": "never"
}
},
"typedef": [
true,
"call-signature"
],
"typedef-whitespace": {
"options": [
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
},
{
"call-signature": "onespace",
"index-signature": "onespace",
"parameter": "onespace",
"property-declaration": "onespace",
"variable-declaration": "onespace"
}
]
},
"variable-name": {
"options": [
"ban-keywords",
"check-format",
"allow-pascal-case"
]
},
"whitespace": {
"options": [
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type",
"check-typecast"
]
},
"component-class-suffix": true,
"contextual-lifecycle": true,
"directive-class-suffix": true,
"no-conflicting-lifecycle": true,
"no-host-metadata-property": true,
"no-input-rename": true,
"no-inputs-metadata-property": true,
"no-output-native": true,
"no-output-on-prefix": true,
"no-output-rename": true,
"no-outputs-metadata-property": true,
"template-banana-in-box": true,
"template-no-negated-async": true,
"use-lifecycle-interface": true,
"use-pipe-transform-interface": true,
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
]
}
}

View File

@@ -1,36 +1,36 @@
$(function() {
var FADE_TIME = 150; // ms
var TYPING_TIMER_LENGTH = 400; // ms
var COLORS = [
const FADE_TIME = 150; // ms
const TYPING_TIMER_LENGTH = 400; // ms
const COLORS = [
'#e21400', '#91580f', '#f8a700', '#f78b00',
'#58dc00', '#287b00', '#a8f07a', '#4ae8c4',
'#3b88eb', '#3824aa', '#a700ff', '#d300e7'
];
// Initialize variables
var $window = $(window);
var $usernameInput = $('.usernameInput'); // Input for username
var $messages = $('.messages'); // Messages area
var $inputMessage = $('.inputMessage'); // Input message input box
const $window = $(window);
const $usernameInput = $('.usernameInput'); // Input for username
const $messages = $('.messages'); // Messages area
const $inputMessage = $('.inputMessage'); // Input message input box
var $loginPage = $('.login.page'); // The login page
var $chatPage = $('.chat.page'); // The chatroom page
const $loginPage = $('.login.page'); // The login page
const $chatPage = $('.chat.page'); // The chatroom page
const socket = io();
// Prompt for setting a username
var username;
var connected = false;
var typing = false;
var lastTypingTime;
var $currentInput = $usernameInput.focus();
var socket = io();
let username;
let connected = false;
let typing = false;
let lastTypingTime;
let $currentInput = $usernameInput.focus();
const addParticipantsMessage = (data) => {
var message = '';
let message = '';
if (data.numUsers === 1) {
message += "there's 1 participant";
message += `there's 1 participant`;
} else {
message += "there are " + data.numUsers + " participants";
message += `there are ${data.numUsers} participants`;
}
log(message);
}
@@ -53,45 +53,41 @@ $(function() {
// Sends a chat message
const sendMessage = () => {
var message = $inputMessage.val();
let message = $inputMessage.val();
// Prevent markup from being injected into the message
message = cleanInput(message);
// if there is a non-empty message and a socket connection
if (message && connected) {
$inputMessage.val('');
addChatMessage({
username: username,
message: message
});
addChatMessage({ username, message });
// tell server to execute 'new message' and send along one parameter
socket.emit('new message', message);
}
}
// Log a message
const log = (message, options) => {
var $el = $('<li>').addClass('log').text(message);
const log = (message, options) => {
const $el = $('<li>').addClass('log').text(message);
addMessageElement($el, options);
}
// Adds the visual chat message to the message list
const addChatMessage = (data, options) => {
// Don't fade the message in if there is an 'X was typing'
var $typingMessages = getTypingMessages(data);
options = options || {};
const $typingMessages = getTypingMessages(data);
if ($typingMessages.length !== 0) {
options.fade = false;
$typingMessages.remove();
}
var $usernameDiv = $('<span class="username"/>')
const $usernameDiv = $('<span class="username"/>')
.text(data.username)
.css('color', getUsernameColor(data.username));
var $messageBodyDiv = $('<span class="messageBody">')
const $messageBodyDiv = $('<span class="messageBody">')
.text(data.message);
var typingClass = data.typing ? 'typing' : '';
var $messageDiv = $('<li class="message"/>')
const typingClass = data.typing ? 'typing' : '';
const $messageDiv = $('<li class="message"/>')
.data('username', data.username)
.addClass(typingClass)
.append($usernameDiv, $messageBodyDiv);
@@ -119,8 +115,7 @@ $(function() {
// options.prepend - If the element should prepend
// all other messages (default = false)
const addMessageElement = (el, options) => {
var $el = $(el);
const $el = $(el);
// Setup default options
if (!options) {
options = {};
@@ -141,6 +136,7 @@ $(function() {
} else {
$messages.append($el);
}
$messages[0].scrollTop = $messages[0].scrollHeight;
}
@@ -159,8 +155,8 @@ $(function() {
lastTypingTime = (new Date()).getTime();
setTimeout(() => {
var typingTimer = (new Date()).getTime();
var timeDiff = typingTimer - lastTypingTime;
const typingTimer = (new Date()).getTime();
const timeDiff = typingTimer - lastTypingTime;
if (timeDiff >= TYPING_TIMER_LENGTH && typing) {
socket.emit('stop typing');
typing = false;
@@ -179,12 +175,12 @@ $(function() {
// Gets the color of a username through our hash function
const getUsernameColor = (username) => {
// Compute hash code
var hash = 7;
for (var i = 0; i < username.length; i++) {
hash = username.charCodeAt(i) + (hash << 5) - hash;
let hash = 7;
for (let i = 0; i < username.length; i++) {
hash = username.charCodeAt(i) + (hash << 5) - hash;
}
// Calculate color
var index = Math.abs(hash % COLORS.length);
const index = Math.abs(hash % COLORS.length);
return COLORS[index];
}
@@ -229,7 +225,7 @@ $(function() {
socket.on('login', (data) => {
connected = true;
// Display the welcome message
var message = "Welcome to Socket.IO Chat ";
const message = 'Welcome to Socket.IO Chat ';
log(message, {
prepend: true
});
@@ -243,13 +239,13 @@ $(function() {
// Whenever the server emits 'user joined', log it in the chat body
socket.on('user joined', (data) => {
log(data.username + ' joined');
log(`${data.username} joined`);
addParticipantsMessage(data);
});
// Whenever the server emits 'user left', log it in the chat body
socket.on('user left', (data) => {
log(data.username + ' left');
log(`${data.username} left`);
addParticipantsMessage(data);
removeChatTyping(data);
});

View File

@@ -1,4 +1,4 @@
FROM mhart/alpine-node:6
FROM node:14-alpine
# Create app directory
RUN mkdir -p /usr/src/app
@@ -6,7 +6,7 @@ WORKDIR /usr/src/app
# Install app dependencies
COPY package.json /usr/src/app/
RUN npm install
RUN npm install --prod
# Bundle app source
COPY . /usr/src/app

View File

@@ -8,8 +8,8 @@
"license": "BSD",
"dependencies": {
"express": "4.13.4",
"socket.io": "^1.7.2",
"socket.io-redis": "^3.0.0"
"socket.io": "^3.1.0",
"socket.io-redis": "^6.0.1"
},
"scripts": {
"start": "node index.js"

View File

@@ -3,6 +3,8 @@ Listen 80
ServerName localhost
LoadModule mpm_event_module modules/mod_mpm_event.so
LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authz_host_module modules/mod_authz_host.so

View File

@@ -6,7 +6,7 @@ WORKDIR /usr/src/app
# Install app dependencies
COPY package.json /usr/src/app/
RUN npm install
RUN npm install --prod
# Bundle app source
COPY . /usr/src/app

View File

@@ -8,8 +8,8 @@
"license": "BSD",
"dependencies": {
"express": "4.13.4",
"socket.io": "^1.7.2",
"socket.io-redis": "^3.0.0"
"socket.io": "^3.1.0",
"socket.io-redis": "^6.0.1"
},
"scripts": {
"start": "node index.js"

View File

@@ -22,6 +22,16 @@ Each node connects to the redis backend, which will enable to broadcast to every
$ docker-compose stop server-george
```
A `client` container is included in the `docker-compose.yml` file, in order to test the routing.
You can create additional `client` containers with:
```
$ docker-compose up -d --scale=client=10 client
# and then
$ docker-compose logs client
```
## Features
- Multiple users can join a chat room by each entering a unique username

View File

@@ -0,0 +1,15 @@
FROM node:14-alpine
# Create app directory
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# Install app dependencies
COPY package.json /usr/src/app/
RUN npm install --prod
# Bundle app source
COPY . /usr/src/app
EXPOSE 3000
CMD [ "npm", "start" ]

View File

@@ -0,0 +1,13 @@
const socket = require('socket.io-client')('ws://nginx');
socket.on('connect', () => {
console.log('connected');
});
socket.on('my-name-is', (serverName) => {
console.log(`connected to ${serverName}`);
});
socket.on('disconnect', (reason) => {
console.log(`disconnected due to ${reason}`);
});

View File

@@ -0,0 +1,15 @@
{
"name": "socket.io-chat",
"version": "0.0.0",
"description": "A simple chat client using socket.io",
"main": "index.js",
"author": "Grant Timmerman",
"private": true,
"license": "MIT",
"dependencies": {
"socket.io-client": "^3.1.0"
},
"scripts": {
"start": "node index.js"
}
}

View File

@@ -45,6 +45,11 @@ server-ringo:
environment:
- NAME=Ringo
client:
build: ./client
links:
- nginx
redis:
image: redis:alpine
expose:

View File

@@ -24,8 +24,12 @@ http {
}
upstream nodes {
# enable sticky session
ip_hash;
# enable sticky session with either "hash" (uses the complete IP address)
hash $remote_addr consistent;
# or "ip_hash" (uses the first three octets of the client IPv4 address, or the entire IPv6 address)
# ip_hash;
# or "sticky" (needs commercial subscription)
# sticky cookie srv_id expires=1h domain=.example.com path=/;
server server-john:3000;
server server-paul:3000;

View File

@@ -1,4 +1,4 @@
FROM mhart/alpine-node:6
FROM node:14-alpine
# Create app directory
RUN mkdir -p /usr/src/app
@@ -6,7 +6,7 @@ WORKDIR /usr/src/app
# Install app dependencies
COPY package.json /usr/src/app/
RUN npm install
RUN npm install --prod
# Bundle app source
COPY . /usr/src/app

View File

@@ -8,8 +8,8 @@
"license": "MIT",
"dependencies": {
"express": "4.13.4",
"socket.io": "^1.7.2",
"socket.io-redis": "^3.0.0"
"socket.io": "^3.1.0",
"socket.io-redis": "^6.0.1"
},
"scripts": {
"start": "node index.js"

View File

@@ -0,0 +1,22 @@
# Socket.IO Chat with traefik & [redis](https://redis.io/)
A simple chat demo for Socket.IO
## How to use
Install [Docker Compose](https://docs.docker.com/compose/install/), then:
```
$ docker-compose up -d
```
And then point your browser to `http://localhost:3000`.
You can then scale the server to multiple instances:
```
$ docker-compose up -d --scale=server=7
```
The session stickiness, which is [required](https://socket.io/docs/v3/using-multiple-nodes/) when using multiple Socket.IO server instances, is achieved with a cookie. More information [here](https://doc.traefik.io/traefik/v2.0/routing/services/#sticky-sessions).

View File

@@ -0,0 +1,27 @@
version: "3"
services:
traefik:
image: traefik:2.4
volumes:
- ./traefik.yml:/etc/traefik/traefik.yml
- /var/run/docker.sock:/var/run/docker.sock
links:
- server
ports:
- "3000:80"
- "8080:8080"
server:
build: ./server
links:
- redis
labels:
- "traefik.http.routers.chat.rule=PathPrefix(`/`)"
- traefik.http.services.chat.loadBalancer.sticky.cookie.name=server_id
- traefik.http.services.chat.loadBalancer.sticky.cookie.httpOnly=true
redis:
image: redis:6-alpine
labels:
- traefik.enable=false

View File

@@ -0,0 +1,15 @@
FROM node:14-alpine
# Create app directory
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# Install app dependencies
COPY package.json /usr/src/app/
RUN npm install --prod
# Bundle app source
COPY . /usr/src/app
EXPOSE 3000
CMD [ "npm", "start" ]

View File

@@ -0,0 +1,83 @@
// Setup basic express server
var express = require('express');
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io')(server);
var redis = require('socket.io-redis');
var port = process.env.PORT || 3000;
var crypto = require('crypto');
var serverName = crypto.randomBytes(3).toString('hex');
io.adapter(redis({ host: 'redis', port: 6379 }));
server.listen(port, function () {
console.log('Server listening at port %d', port);
console.log('Hello, I\'m %s, how can I help?', serverName);
});
// Routing
app.use(express.static(__dirname + '/public'));
// Chatroom
var numUsers = 0;
io.on('connection', function (socket) {
socket.emit('my-name-is', serverName);
var addedUser = false;
// when the client emits 'new message', this listens and executes
socket.on('new message', function (data) {
// we tell the client to execute 'new message'
socket.broadcast.emit('new message', {
username: socket.username,
message: data
});
});
// when the client emits 'add user', this listens and executes
socket.on('add user', function (username) {
if (addedUser) return;
// we store the username in the socket session for this client
socket.username = username;
++numUsers;
addedUser = true;
socket.emit('login', {
numUsers: numUsers
});
// echo globally (all clients) that a person has connected
socket.broadcast.emit('user joined', {
username: socket.username,
numUsers: numUsers
});
});
// when the client emits 'typing', we broadcast it to others
socket.on('typing', function () {
socket.broadcast.emit('typing', {
username: socket.username
});
});
// when the client emits 'stop typing', we broadcast it to others
socket.on('stop typing', function () {
socket.broadcast.emit('stop typing', {
username: socket.username
});
});
// when the user disconnects.. perform this
socket.on('disconnect', function () {
if (addedUser) {
--numUsers;
// echo globally that this client has left
socket.broadcast.emit('user left', {
username: socket.username,
numUsers: numUsers
});
}
});
});

View File

@@ -0,0 +1,17 @@
{
"name": "socket.io-chat",
"version": "0.0.0",
"description": "A simple chat client using socket.io",
"main": "index.js",
"author": "Grant Timmerman",
"private": true,
"license": "MIT",
"dependencies": {
"express": "4.13.4",
"socket.io": "^3.1.0",
"socket.io-redis": "^6.0.1"
},
"scripts": {
"start": "node index.js"
}
}

View File

@@ -0,0 +1,28 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Socket.IO Chat Example</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<ul class="pages">
<li class="chat page">
<div class="chatArea">
<ul class="messages"></ul>
</div>
<input class="inputMessage" placeholder="Type here..."/>
</li>
<li class="login page">
<div class="form">
<h3 class="title">What's your nickname?</h3>
<input class="usernameInput" type="text" maxlength="14" />
</div>
</li>
</ul>
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script src="/main.js"></script>
</body>
</html>

View File

@@ -0,0 +1,286 @@
$(function() {
var FADE_TIME = 150; // ms
var TYPING_TIMER_LENGTH = 400; // ms
var COLORS = [
'#e21400', '#91580f', '#f8a700', '#f78b00',
'#58dc00', '#287b00', '#a8f07a', '#4ae8c4',
'#3b88eb', '#3824aa', '#a700ff', '#d300e7'
];
// Initialize variables
var $window = $(window);
var $usernameInput = $('.usernameInput'); // Input for username
var $messages = $('.messages'); // Messages area
var $inputMessage = $('.inputMessage'); // Input message input box
var $loginPage = $('.login.page'); // The login page
var $chatPage = $('.chat.page'); // The chatroom page
// Prompt for setting a username
var username;
var connected = false;
var typing = false;
var lastTypingTime;
var $currentInput = $usernameInput.focus();
var socket = io();
function addParticipantsMessage (data) {
var message = '';
if (data.numUsers === 1) {
message += "there's 1 participant";
} else {
message += "there are " + data.numUsers + " participants";
}
log(message);
}
// Sets the client's username
function setUsername () {
username = cleanInput($usernameInput.val().trim());
// If the username is valid
if (username) {
$loginPage.fadeOut();
$chatPage.show();
$loginPage.off('click');
$currentInput = $inputMessage.focus();
// Tell the server your username
socket.emit('add user', username);
}
}
// Sends a chat message
function sendMessage () {
var message = $inputMessage.val();
// Prevent markup from being injected into the message
message = cleanInput(message);
// if there is a non-empty message and a socket connection
if (message && connected) {
$inputMessage.val('');
addChatMessage({
username: username,
message: message
});
// tell server to execute 'new message' and send along one parameter
socket.emit('new message', message);
}
}
// Log a message
function log (message, options) {
var $el = $('<li>').addClass('log').text(message);
addMessageElement($el, options);
}
// Adds the visual chat message to the message list
function addChatMessage (data, options) {
// Don't fade the message in if there is an 'X was typing'
var $typingMessages = getTypingMessages(data);
options = options || {};
if ($typingMessages.length !== 0) {
options.fade = false;
$typingMessages.remove();
}
var $usernameDiv = $('<span class="username"/>')
.text(data.username)
.css('color', getUsernameColor(data.username));
var $messageBodyDiv = $('<span class="messageBody">')
.text(data.message);
var typingClass = data.typing ? 'typing' : '';
var $messageDiv = $('<li class="message"/>')
.data('username', data.username)
.addClass(typingClass)
.append($usernameDiv, $messageBodyDiv);
addMessageElement($messageDiv, options);
}
// Adds the visual chat typing message
function addChatTyping (data) {
data.typing = true;
data.message = 'is typing';
addChatMessage(data);
}
// Removes the visual chat typing message
function removeChatTyping (data) {
getTypingMessages(data).fadeOut(function () {
$(this).remove();
});
}
// Adds a message element to the messages and scrolls to the bottom
// el - The element to add as a message
// options.fade - If the element should fade-in (default = true)
// options.prepend - If the element should prepend
// all other messages (default = false)
function addMessageElement (el, options) {
var $el = $(el);
// Setup default options
if (!options) {
options = {};
}
if (typeof options.fade === 'undefined') {
options.fade = true;
}
if (typeof options.prepend === 'undefined') {
options.prepend = false;
}
// Apply options
if (options.fade) {
$el.hide().fadeIn(FADE_TIME);
}
if (options.prepend) {
$messages.prepend($el);
} else {
$messages.append($el);
}
$messages[0].scrollTop = $messages[0].scrollHeight;
}
// Prevents input from having injected markup
function cleanInput (input) {
return $('<div/>').text(input).text();
}
// Updates the typing event
function updateTyping () {
if (connected) {
if (!typing) {
typing = true;
socket.emit('typing');
}
lastTypingTime = (new Date()).getTime();
setTimeout(function () {
var typingTimer = (new Date()).getTime();
var timeDiff = typingTimer - lastTypingTime;
if (timeDiff >= TYPING_TIMER_LENGTH && typing) {
socket.emit('stop typing');
typing = false;
}
}, TYPING_TIMER_LENGTH);
}
}
// Gets the 'X is typing' messages of a user
function getTypingMessages (data) {
return $('.typing.message').filter(function (i) {
return $(this).data('username') === data.username;
});
}
// Gets the color of a username through our hash function
function getUsernameColor (username) {
// Compute hash code
var hash = 7;
for (var i = 0; i < username.length; i++) {
hash = username.charCodeAt(i) + (hash << 5) - hash;
}
// Calculate color
var index = Math.abs(hash % COLORS.length);
return COLORS[index];
}
// Keyboard events
$window.keydown(function (event) {
// Auto-focus the current input when a key is typed
if (!(event.ctrlKey || event.metaKey || event.altKey)) {
$currentInput.focus();
}
// When the client hits ENTER on their keyboard
if (event.which === 13) {
if (username) {
sendMessage();
socket.emit('stop typing');
typing = false;
} else {
setUsername();
}
}
});
$inputMessage.on('input', function() {
updateTyping();
});
// Click events
// Focus input when clicking anywhere on login page
$loginPage.click(function () {
$currentInput.focus();
});
// Focus input when clicking on the message input's border
$inputMessage.click(function () {
$inputMessage.focus();
});
// Socket events
// Whenever the server emits 'login', log the login message
socket.on('login', function (data) {
connected = true;
// Display the welcome message
var message = "Welcome to Socket.IO Chat ";
log(message, {
prepend: true
});
addParticipantsMessage(data);
});
// Whenever the server emits 'new message', update the chat body
socket.on('new message', function (data) {
addChatMessage(data);
});
// Whenever the server emits 'user joined', log it in the chat body
socket.on('user joined', function (data) {
log(data.username + ' joined');
addParticipantsMessage(data);
});
// Whenever the server emits 'user left', log it in the chat body
socket.on('user left', function (data) {
log(data.username + ' left');
addParticipantsMessage(data);
removeChatTyping(data);
});
// Whenever the server emits 'typing', show the typing message
socket.on('typing', function (data) {
addChatTyping(data);
});
// Whenever the server emits 'stop typing', kill the typing message
socket.on('stop typing', function (data) {
removeChatTyping(data);
});
socket.on('disconnect', function () {
log('you have been disconnected');
});
socket.on('connect', function () {
if (username) {
log('you have been reconnected');
socket.emit('add user', username);
}
});
socket.io.on('reconnect_error', function () {
log('attempt to reconnect has failed');
});
socket.on('my-name-is', function (serverName) {
log('host is now ' + serverName);
})
});

View File

@@ -0,0 +1,149 @@
/* Fix user-agent */
* {
box-sizing: border-box;
}
html {
font-weight: 300;
-webkit-font-smoothing: antialiased;
}
html, input {
font-family:
"HelveticaNeue-Light",
"Helvetica Neue Light",
"Helvetica Neue",
Helvetica,
Arial,
"Lucida Grande",
sans-serif;
}
html, body {
height: 100%;
margin: 0;
padding: 0;
}
ul {
list-style: none;
word-wrap: break-word;
}
/* Pages */
.pages {
height: 100%;
margin: 0;
padding: 0;
width: 100%;
}
.page {
height: 100%;
position: absolute;
width: 100%;
}
/* Login Page */
.login.page {
background-color: #000;
}
.login.page .form {
height: 100px;
margin-top: -100px;
position: absolute;
text-align: center;
top: 50%;
width: 100%;
}
.login.page .form .usernameInput {
background-color: transparent;
border: none;
border-bottom: 2px solid #fff;
outline: none;
padding-bottom: 15px;
text-align: center;
width: 400px;
}
.login.page .title {
font-size: 200%;
}
.login.page .usernameInput {
font-size: 200%;
letter-spacing: 3px;
}
.login.page .title, .login.page .usernameInput {
color: #fff;
font-weight: 100;
}
/* Chat page */
.chat.page {
display: none;
}
/* Font */
.messages {
font-size: 150%;
}
.inputMessage {
font-size: 100%;
}
.log {
color: gray;
font-size: 70%;
margin: 5px;
text-align: center;
}
/* Messages */
.chatArea {
height: 100%;
padding-bottom: 60px;
}
.messages {
height: 100%;
margin: 0;
overflow-y: scroll;
padding: 10px 20px 10px 20px;
}
.message.typing .messageBody {
color: gray;
}
.username {
font-weight: 700;
overflow: hidden;
padding-right: 15px;
text-align: right;
}
/* Input */
.inputMessage {
border: 10px solid #000;
bottom: 0;
height: 60px;
left: 0;
outline: none;
padding-left: 10px;
position: absolute;
right: 0;
width: 100%;
}

View File

@@ -0,0 +1,10 @@
api:
insecure: true
entryPoints:
web:
address: ":80"
providers:
docker: {}

View File

@@ -1,7 +1,6 @@
import { Manager } from "socket.io-client";
import { io } from "socket.io-client";
const manager = new Manager("ws://localhost:8080");
const socket = manager.socket("/");
const socket = io("ws://localhost:8080/", {});
socket.on("connect", () => {
console.log(`connect ${socket.id}`);

View File

@@ -9,6 +9,21 @@
"resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz",
"integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg=="
},
"@types/cookie": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.0.tgz",
"integrity": "sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg=="
},
"@types/cors": {
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.9.tgz",
"integrity": "sha512-zurD1ibz21BRlAOIKP8yhrxlqKx6L9VCwkB5kMiP6nZAhoF5MvC7qS1qPA7nRcr1GJolfkQC7/EAL4hdYejLtg=="
},
"@types/node": {
"version": "14.14.16",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.16.tgz",
"integrity": "sha512-naXYePhweTi+BMv11TgioE2/FXU4fSl29HAH1ffxVciNsH3rYXjNP2yM8wqmSm7jS20gM8TIklKiTen+1iVncw=="
},
"accepts": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
@@ -81,9 +96,9 @@
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A=="
},
"engine.io": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.0.2.tgz",
"integrity": "sha512-sumdttqWLNjbuSMOSgDdL2xiEld9s5QZDk9VLyr4e28o+lzNNADhU3qpQDAY7cm2VZH0Otw/U0fL8mEjZ6kBMg==",
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.0.5.tgz",
"integrity": "sha512-Ri+whTNr2PKklxQkfbGjwEo+kCBUM4Qxk4wtLqLrhH+b1up2NFL9g9pjYWiCV/oazwB0rArnvF/ZmZN2ab5Hpg==",
"requires": {
"accepts": "~1.3.4",
"base64id": "2.0.0",
@@ -95,9 +110,9 @@
}
},
"engine.io-client": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-4.0.2.tgz",
"integrity": "sha512-cfzFu0u7rr/Gmz/CefwZ6mBj9kxtsOtOavV/YLbn+2sPGE1ZTSWh3tj8427a0od+BK27zsWDpnDx98fnpnmksA==",
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-4.0.5.tgz",
"integrity": "sha512-1lkn0QdekHQPMTcxUh8LqIuxQHNtKV5GvqkQzmZ1rYKAvB6puMm13U7K1ps3OQZ4joE46asQiAKrcdL9weNEVw==",
"requires": {
"base64-arraybuffer": "0.1.4",
"component-emitter": "~1.3.0",
@@ -119,9 +134,12 @@
}
},
"engine.io-parser": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.1.tgz",
"integrity": "sha512-v5aZK1hlckcJDGmHz3W8xvI3NUHYc9t8QtTbqdR5OaH3S9iJZilPubauOm+vLWOMMWzpE3hiq92l9lTAHamRCg=="
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.2.tgz",
"integrity": "sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg==",
"requires": {
"base64-arraybuffer": "0.1.4"
}
},
"has-cors": {
"version": "1.1.0",
@@ -147,9 +165,9 @@
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"negotiator": {
"version": "0.6.2",
@@ -172,10 +190,13 @@
"integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow=="
},
"socket.io": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-3.0.1.tgz",
"integrity": "sha512-oVYbCQ4sCwm4wVi+f1bsE3YFXcvd6b4JjVP8D7IZnQqBeJOKX9XrdgJWSbXqBEqUXPY3jdTqb1M3s4KFTa/IHg==",
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-3.0.4.tgz",
"integrity": "sha512-Vj1jUoO75WGc9txWd311ZJJqS9Dr8QtNJJ7gk2r7dcM/yGe9sit7qOijQl3GAwhpBOz/W8CwkD7R6yob07nLbA==",
"requires": {
"@types/cookie": "^0.4.0",
"@types/cors": "^2.8.8",
"@types/node": "^14.14.7",
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"debug": "~4.1.0",
@@ -190,9 +211,9 @@
"integrity": "sha512-2wo4EXgxOGSFueqvHAdnmi5JLZzWqMArjuP4nqC26AtLh5PoCPsaRbRdah2xhcwTAMooZfjYiNVNkkmmSMaxOQ=="
},
"socket.io-client": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-3.0.1.tgz",
"integrity": "sha512-iIzWRDrF/h3KPtHjvLt5LL/1n7Euvv35zVa1r10ScRjVw40yc8DxFj7GnKrj1RNYkbtveWOwEsy2lWp3oFJO7w==",
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-3.0.4.tgz",
"integrity": "sha512-qMvBuS+W9JIN2mkfAWDCxuIt+jpIKDf8C0604zEqx1JrPaPSS6cN0F3B2GYWC83TqBeVJXW66GFxWV3KD88n0Q==",
"requires": {
"@types/component-emitter": "^1.2.10",
"backo2": "1.0.2",
@@ -205,10 +226,11 @@
}
},
"socket.io-parser": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.1.tgz",
"integrity": "sha512-5JfNykYptCwU2lkOI0ieoePWm+6stEhkZ2UnLDjqnE1YEjUlXXLd1lpxPZ+g+h3rtaytwWkWrLQCaJULlGqjOg==",
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.2.tgz",
"integrity": "sha512-Bs3IYHDivwf+bAAuW/8xwJgIiBNtlvnjYRc4PbXgniLmcP1BrakBoq/QhO24rgtgW7VZ7uAaswRGxutUnlAK7g==",
"requires": {
"@types/component-emitter": "^1.2.10",
"component-emitter": "~1.3.0",
"debug": "~4.1.0"
}
@@ -250,9 +272,9 @@
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
},
"ws": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.0.tgz",
"integrity": "sha512-kyFwXuV/5ymf+IXhS6f0+eAFvydbaBW3zjpT6hUdAh/hbVjTIB5EHBGi0bPoCLSK2wcuz3BrEkB9LrYv1Nm4NQ=="
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz",
"integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA=="
},
"xmlhttprequest-ssl": {
"version": "1.5.5",

View File

@@ -11,8 +11,8 @@
"author": "Damien Arrachequesne",
"license": "MIT",
"dependencies": {
"socket.io": "^3.0.1",
"socket.io-client": "^3.0.1",
"socket.io": "^3.0.4",
"socket.io-client": "^3.0.4",
"ts-node": "^9.0.0",
"typescript": "^4.0.5"
}

View File

@@ -1,10 +1,10 @@
import { Decoder, Encoder, Packet, PacketType } from "socket.io-parser";
import debugModule = require("debug");
import { IncomingMessage } from "http";
import { Server } from "./index";
import { Socket } from "./socket";
import { SocketId } from "socket.io-adapter";
import { ParentNamespace } from "./parent-namespace";
import url = require("url");
import type { IncomingMessage } from "http";
import type { Namespace, Server } from "./index";
import type { Socket } from "./socket";
import type { SocketId } from "socket.io-adapter";
const debug = debugModule("socket.io:client");
@@ -17,16 +17,16 @@ export class Client {
private readonly decoder: Decoder;
private sockets: Map<SocketId, Socket> = new Map();
private nsps: Map<string, Socket> = new Map();
private connectTimeout: NodeJS.Timeout;
private connectTimeout?: NodeJS.Timeout;
/**
* Client constructor.
*
* @param {Server} server instance
* @param {Socket} conn
* @param server instance
* @param conn
* @package
*/
constructor(server: Server, conn) {
constructor(server: Server, conn: Socket) {
this.server = server;
this.conn = conn;
this.encoder = server.encoder;
@@ -78,47 +78,52 @@ export class Client {
* @param {Object} auth - the auth parameters
* @private
*/
private connect(name: string, auth: object = {}) {
private connect(name: string, auth: object = {}): void {
if (this.server._nsps.has(name)) {
debug("connecting to namespace %s", name);
return this.doConnect(name, auth);
}
this.server._checkNamespace(name, auth, (dynamicNsp: ParentNamespace) => {
if (dynamicNsp) {
debug("dynamic namespace %s was created", dynamicNsp.name);
this.doConnect(name, auth);
} else {
debug("creation of namespace %s was denied", name);
this._packet({
type: PacketType.CONNECT_ERROR,
nsp: name,
data: {
message: "Invalid namespace"
}
});
this.server._checkNamespace(
name,
auth,
(dynamicNspName: Namespace | false) => {
if (dynamicNspName) {
debug("dynamic namespace %s was created", dynamicNspName);
this.doConnect(name, auth);
} else {
debug("creation of namespace %s was denied", name);
this._packet({
type: PacketType.CONNECT_ERROR,
nsp: name,
data: {
message: "Invalid namespace",
},
});
}
}
});
);
}
/**
* Connects a client to a namespace.
*
* @param {String} name - the namespace
* @param name - the namespace
* @param {Object} auth - the auth parameters
*
* @private
*/
private doConnect(name: string, auth: object) {
if (this.connectTimeout) {
clearTimeout(this.connectTimeout);
this.connectTimeout = null;
}
private doConnect(name: string, auth: object): void {
const nsp = this.server.of(name);
const socket = nsp._add(this, auth, () => {
this.sockets.set(socket.id, socket);
this.nsps.set(nsp.name, socket);
if (this.connectTimeout) {
clearTimeout(this.connectTimeout);
this.connectTimeout = undefined;
}
});
}
@@ -127,7 +132,7 @@ export class Client {
*
* @private
*/
_disconnect() {
_disconnect(): void {
for (const socket of this.sockets.values()) {
socket.disconnect();
}
@@ -140,9 +145,9 @@ export class Client {
*
* @private
*/
_remove(socket: Socket) {
_remove(socket: Socket): void {
if (this.sockets.has(socket.id)) {
const nsp = this.sockets.get(socket.id).nsp.name;
const nsp = this.sockets.get(socket.id)!.nsp.name;
this.sockets.delete(socket.id);
this.nsps.delete(nsp);
} else {
@@ -155,8 +160,8 @@ export class Client {
*
* @private
*/
private close() {
if ("open" == this.conn.readyState) {
private close(): void {
if ("open" === this.conn.readyState) {
debug("forcing transport close");
this.conn.close();
this.onclose("forced server close");
@@ -170,19 +175,20 @@ export class Client {
* @param {Object} opts
* @private
*/
_packet(packet, opts?) {
_packet(packet: Packet, opts?: any): void {
opts = opts || {};
const self = this;
// this writes to the actual connection
function writeToEngine(encodedPackets) {
function writeToEngine(encodedPackets: any) {
// TODO clarify this.
if (opts.volatile && !self.conn.transport.writable) return;
for (let i = 0; i < encodedPackets.length; i++) {
self.conn.write(encodedPackets[i], { compress: opts.compress });
}
}
if ("open" == this.conn.readyState) {
if ("open" === this.conn.readyState) {
debug("writing packet %j", packet);
if (!opts.preEncoded) {
// not broadcasting, need to encode
@@ -201,7 +207,7 @@ export class Client {
*
* @private
*/
private ondata(data) {
private ondata(data): void {
// try/catch is needed for protocol violations (GH-1880)
try {
this.decoder.add(data);
@@ -215,13 +221,18 @@ export class Client {
*
* @private
*/
private ondecoded(packet: Packet) {
if (PacketType.CONNECT == packet.type) {
this.connect(packet.nsp, packet.data);
private ondecoded(packet: Packet): void {
if (PacketType.CONNECT === packet.type) {
if (this.conn.protocol === 3) {
const parsed = url.parse(packet.nsp, true);
this.connect(parsed.pathname!, parsed.query);
} else {
this.connect(packet.nsp, packet.data);
}
} else {
const socket = this.nsps.get(packet.nsp);
if (socket) {
process.nextTick(function() {
process.nextTick(function () {
socket._onpacket(packet);
});
} else {
@@ -236,7 +247,7 @@ export class Client {
* @param {Object} err object
* @private
*/
private onerror(err) {
private onerror(err): void {
for (const socket of this.sockets.values()) {
socket._onerror(err);
}
@@ -249,7 +260,7 @@ export class Client {
* @param reason
* @private
*/
private onclose(reason: string) {
private onclose(reason: string): void {
debug("client close with reason %s", reason);
// ignore a potential subsequent `close` event
@@ -268,11 +279,16 @@ export class Client {
* Cleans up event listeners.
* @private
*/
private destroy() {
private destroy(): void {
this.conn.removeListener("data", this.ondata);
this.conn.removeListener("error", this.onerror);
this.conn.removeListener("close", this.onclose);
// @ts-ignore
this.decoder.removeListener("decoded", this.ondecoded);
if (this.connectTimeout) {
clearTimeout(this.connectTimeout);
this.connectTimeout = undefined;
}
}
}

View File

@@ -11,11 +11,11 @@ import { ExtendedError, Namespace } from "./namespace";
import { ParentNamespace } from "./parent-namespace";
import { Adapter, Room, SocketId } from "socket.io-adapter";
import * as parser from "socket.io-parser";
import { Encoder } from "socket.io-parser";
import type { Encoder } from "socket.io-parser";
import debugModule from "debug";
import { Socket } from "./socket";
import { CookieSerializeOptions } from "cookie";
import { CorsOptions } from "cors";
import type { CookieSerializeOptions } from "cookie";
import type { CorsOptions } from "cors";
const debug = debugModule("socket.io:server");
@@ -23,6 +23,11 @@ const clientVersion = require("../package.json").version;
const dotMapRegex = /\.map/;
type Transport = "polling" | "websocket";
type ParentNspNameMatchFn = (
name: string,
auth: { [key: string]: any },
fn: (err: Error | null, success: boolean) => void
) => void;
interface EngineOptions {
/**
@@ -95,6 +100,11 @@ interface EngineOptions {
* the options that will be forwarded to the cors module
*/
cors: CorsOptions;
/**
* whether to enable compatibility with Socket.IO v2 clients
* @default false
*/
allowEIO3: boolean;
}
interface AttachOptions {
@@ -149,7 +159,7 @@ export class Server extends EventEmitter {
public readonly sockets: Namespace;
/** @private */
readonly _parser;
readonly _parser: typeof parser;
/** @private */
readonly encoder: Encoder;
@@ -157,17 +167,8 @@ export class Server extends EventEmitter {
* @private
*/
_nsps: Map<string, Namespace> = new Map();
private parentNsps: Map<
| string
| RegExp
| ((
name: string,
query: object,
fn: (err: Error, success: boolean) => void
) => void),
ParentNamespace
> = new Map();
private _adapter: any;
private parentNsps: Map<ParentNspNameMatchFn, ParentNamespace> = new Map();
private _adapter?: typeof Adapter;
private _serveClient: boolean;
private opts: Partial<EngineOptions>;
private eio;
@@ -184,18 +185,28 @@ export class Server extends EventEmitter {
/**
* Server constructor.
*
* @param {http.Server|Number|Object} srv http server, port or options
* @param {Object} [opts]
* @param srv http server, port, or options
* @param [opts]
* @public
*/
constructor(opts?: Partial<ServerOptions>);
constructor(srv: http.Server, opts?: Partial<ServerOptions>);
constructor(srv: number, opts?: Partial<ServerOptions>);
constructor(srv?: any, opts: Partial<ServerOptions> = {}) {
constructor(srv?: http.Server | number, opts?: Partial<ServerOptions>);
constructor(
srv: undefined | Partial<ServerOptions> | http.Server | number,
opts?: Partial<ServerOptions>
);
constructor(
srv: undefined | Partial<ServerOptions> | http.Server | number,
opts: Partial<ServerOptions> = {}
) {
super();
if ("object" == typeof srv && srv instanceof Object && !srv.listen) {
opts = srv;
srv = null;
if (
"object" === typeof srv &&
srv instanceof Object &&
!(srv as Partial<http.Server>).listen
) {
opts = srv as Partial<ServerOptions>;
srv = undefined;
}
this.path(opts.path || "/socket.io");
this.connectTimeout(opts.connectTimeout || 45000);
@@ -205,44 +216,45 @@ export class Server extends EventEmitter {
this.adapter(opts.adapter || Adapter);
this.sockets = this.of("/");
this.opts = opts;
if (srv) this.attach(srv);
if (srv) this.attach(srv as http.Server);
}
/**
* Sets/gets whether client code is being served.
*
* @param {Boolean} v - whether to serve client code
* @return {Server|Boolean} self when setting or value when getting
* @param v - whether to serve client code
* @return self when setting or value when getting
* @public
*/
public serveClient(v: boolean): Server;
public serveClient(v: boolean): this;
public serveClient(): boolean;
public serveClient(v?: boolean): Server | boolean {
public serveClient(v?: boolean): this | boolean;
public serveClient(v?: boolean): this | boolean {
if (!arguments.length) return this._serveClient;
this._serveClient = v;
this._serveClient = v!;
return this;
}
/**
* Executes the middleware for an incoming namespace not already created on the server.
*
* @param {String} name - name of incoming namespace
* @param {Object} auth - the auth parameters
* @param {Function} fn - callback
* @param name - name of incoming namespace
* @param auth - the auth parameters
* @param fn - callback
*
* @private
*/
_checkNamespace(
name: string,
auth: object,
fn: (nsp: Namespace | boolean) => void
) {
auth: { [key: string]: any },
fn: (nsp: Namespace | false) => void
): void {
if (this.parentNsps.size === 0) return fn(false);
const keysIterator = this.parentNsps.keys();
const run = () => {
let nextFn = keysIterator.next();
const nextFn = keysIterator.next();
if (nextFn.done) {
return fn(false);
}
@@ -250,7 +262,7 @@ export class Server extends EventEmitter {
if (err || !allow) {
run();
} else {
fn(this.parentNsps.get(nextFn.value).createChild(name));
fn(this.parentNsps.get(nextFn.value)!.createChild(name));
}
});
};
@@ -265,12 +277,13 @@ export class Server extends EventEmitter {
* @return {Server|String} self when setting or value when getting
* @public
*/
public path(v: string): Server;
public path(v: string): this;
public path(): string;
public path(v?: string): Server | string {
public path(v?: string): this | string;
public path(v?: string): this | string {
if (!arguments.length) return this._path;
this._path = v.replace(/\/$/, "");
this._path = v!.replace(/\/$/, "");
const escapedPath = this._path.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
this.clientPathRegex = new RegExp(
@@ -286,9 +299,10 @@ export class Server extends EventEmitter {
* @param v
* @public
*/
public connectTimeout(v: number): Server;
public connectTimeout(v: number): this;
public connectTimeout(): number;
public connectTimeout(v?: number): Server | number {
public connectTimeout(v?: number): this | number;
public connectTimeout(v?: number): this | number {
if (v === undefined) return this._connectTimeout;
this._connectTimeout = v;
return this;
@@ -297,13 +311,14 @@ export class Server extends EventEmitter {
/**
* Sets the adapter for rooms.
*
* @param {Adapter} v pathname
* @return {Server|Adapter} self when setting or value when getting
* @param v pathname
* @return self when setting or value when getting
* @public
*/
public adapter(): any;
public adapter(v: any);
public adapter(v?): Server | any {
public adapter(): typeof Adapter | undefined;
public adapter(v: typeof Adapter): this;
public adapter(v?: typeof Adapter): typeof Adapter | undefined | this;
public adapter(v?: typeof Adapter): typeof Adapter | undefined | this {
if (!arguments.length) return this._adapter;
this._adapter = v;
for (const nsp of this._nsps.values()) {
@@ -315,28 +330,30 @@ export class Server extends EventEmitter {
/**
* Attaches socket.io to a server or port.
*
* @param {http.Server|Number} srv - server or port
* @param {Object} opts - options passed to engine.io
* @return {Server} self
* @param srv - server or port
* @param opts - options passed to engine.io
* @return self
* @public
*/
public listen(srv: http.Server, opts?: Partial<ServerOptions>): Server;
public listen(srv: number, opts?: Partial<ServerOptions>): Server;
public listen(srv: any, opts: Partial<ServerOptions> = {}): Server {
public listen(
srv: http.Server | number,
opts: Partial<ServerOptions> = {}
): this {
return this.attach(srv, opts);
}
/**
* Attaches socket.io to a server or port.
*
* @param {http.Server|Number} srv - server or port
* @param {Object} opts - options passed to engine.io
* @return {Server} self
* @param srv - server or port
* @param opts - options passed to engine.io
* @return self
* @public
*/
public attach(srv: http.Server, opts?: Partial<ServerOptions>): Server;
public attach(port: number, opts?: Partial<ServerOptions>): Server;
public attach(srv: any, opts: Partial<ServerOptions> = {}): Server {
public attach(
srv: http.Server | number,
opts: Partial<ServerOptions> = {}
): this {
if ("function" == typeof srv) {
const msg =
"You are trying to attach socket.io to an express " +
@@ -376,7 +393,10 @@ export class Server extends EventEmitter {
* @param opts - options passed to engine.io
* @private
*/
private initEngine(srv: http.Server, opts: Partial<EngineAttachOptions>) {
private initEngine(
srv: http.Server,
opts: Partial<EngineAttachOptions>
): void {
// initialize engine
debug("creating engine.io instance with opts %j", opts);
this.eio = engine.attach(srv, opts);
@@ -394,10 +414,10 @@ export class Server extends EventEmitter {
/**
* Attaches the static file serving.
*
* @param {Function|http.Server} srv http server
* @param srv http server
* @private
*/
private attachServe(srv) {
private attachServe(srv: http.Server): void {
debug("attaching client serving req handler");
const evs = srv.listeners("request").slice(0);
@@ -416,22 +436,23 @@ export class Server extends EventEmitter {
/**
* Handles a request serving of client source and map
*
* @param {http.IncomingMessage} req
* @param {http.ServerResponse} res
* @param req
* @param res
* @private
*/
private serve(req: http.IncomingMessage, res: http.ServerResponse) {
const filename = req.url.replace(this._path, "");
private serve(req: http.IncomingMessage, res: http.ServerResponse): void {
const filename = req.url!.replace(this._path, "");
const isMap = dotMapRegex.test(filename);
const type = isMap ? "map" : "source";
// Per the standard, ETags must be quoted:
// https://tools.ietf.org/html/rfc7232#section-2.3
const expectedEtag = '"' + clientVersion + '"';
const weakEtag = "W/" + expectedEtag;
const etag = req.headers["if-none-match"];
if (etag) {
if (expectedEtag == etag) {
if (expectedEtag === etag || weakEtag === etag) {
debug("serve client %s 304", type);
res.writeHead(304);
res.end();
@@ -464,13 +485,13 @@ export class Server extends EventEmitter {
filename: string,
req: http.IncomingMessage,
res: http.ServerResponse
) {
): void {
const readStream = createReadStream(
path.join(__dirname, "../client-dist/", filename)
);
const encoding = accepts(req).encodings(["br", "gzip", "deflate"]);
const onError = err => {
const onError = (err: NodeJS.ErrnoException | null) => {
if (err) {
res.end();
}
@@ -500,10 +521,10 @@ export class Server extends EventEmitter {
* Binds socket.io to an engine.io instance.
*
* @param {engine.Server} engine engine.io (or compatible) server
* @return {Server} self
* @return self
* @public
*/
public bind(engine): Server {
public bind(engine): this {
this.engine = engine;
this.engine.on("connection", this.onconnection.bind(this));
return this;
@@ -513,12 +534,16 @@ export class Server extends EventEmitter {
* Called with each incoming transport connection.
*
* @param {engine.Socket} conn
* @return {Server} self
* @return self
* @private
*/
private onconnection(conn): Server {
private onconnection(conn): this {
debug("incoming connection with id %s", conn.id);
new Client(this, conn);
const client = new Client(this, conn);
if (conn.protocol === 3) {
// @ts-ignore
client.connect("/");
}
return this;
}
@@ -526,20 +551,13 @@ export class Server extends EventEmitter {
* Looks up a namespace.
*
* @param {String|RegExp|Function} name nsp name
* @param {Function} [fn] optional, nsp `connection` ev handler
* @param fn optional, nsp `connection` ev handler
* @public
*/
public of(
name:
| string
| RegExp
| ((
name: string,
query: object,
fn: (err: Error, success: boolean) => void
) => void),
name: string | RegExp | ParentNspNameMatchFn,
fn?: (socket: Socket) => void
) {
): Namespace {
if (typeof name === "function" || name instanceof RegExp) {
const parentNsp = new ParentNamespace(this);
debug("initializing parent namespace %s", parentNsp.name);
@@ -573,7 +591,7 @@ export class Server extends EventEmitter {
/**
* Closes server connection
*
* @param {Function} [fn] optional, called as `fn([err])` on error OR all conns closed
* @param [fn] optional, called as `fn([err])` on error OR all conns closed
* @public
*/
public close(fn?: (err?: Error) => void): void {
@@ -593,12 +611,12 @@ export class Server extends EventEmitter {
/**
* Sets up namespace middleware.
*
* @return {Server} self
* @return self
* @public
*/
public use(
fn: (socket: Socket, next: (err?: ExtendedError) => void) => void
): Server {
): this {
this.sockets.use(fn);
return this;
}
@@ -606,11 +624,11 @@ export class Server extends EventEmitter {
/**
* Targets a room when emitting.
*
* @param {String} name
* @return {Server} self
* @param name
* @return self
* @public
*/
public to(name: Room): Server {
public to(name: Room): this {
this.sockets.to(name);
return this;
}
@@ -618,11 +636,11 @@ export class Server extends EventEmitter {
/**
* Targets a room when emitting.
*
* @param {String} name
* @return {Server} self
* @param name
* @return self
* @public
*/
public in(name: Room): Server {
public in(name: Room): this {
this.sockets.in(name);
return this;
}
@@ -630,24 +648,22 @@ export class Server extends EventEmitter {
/**
* Sends a `message` event to all clients.
*
* @return {Server} self
* @return self
* @public
*/
public send(...args): Server {
args.unshift("message");
this.sockets.emit.apply(this.sockets, args);
public send(...args: readonly any[]): this {
this.sockets.emit("message", ...args);
return this;
}
/**
* Sends a `message` event to all clients.
*
* @return {Server} self
* @return self
* @public
*/
public write(...args): Server {
args.unshift("message");
this.sockets.emit.apply(this.sockets, args);
public write(...args: readonly any[]): this {
this.sockets.emit("message", ...args);
return this;
}
@@ -663,11 +679,11 @@ export class Server extends EventEmitter {
/**
* Sets the compress flag.
*
* @param {Boolean} compress - if `true`, compresses the sending data
* @return {Server} self
* @param compress - if `true`, compresses the sending data
* @return self
* @public
*/
public compress(compress: boolean): Server {
public compress(compress: boolean): this {
this.sockets.compress(compress);
return this;
}
@@ -677,10 +693,10 @@ export class Server extends EventEmitter {
* 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).
*
* @return {Server} self
* @return self
* @public
*/
public get volatile(): Server {
public get volatile(): this {
this.sockets.volatile;
return this;
}
@@ -688,10 +704,10 @@ export class Server extends EventEmitter {
/**
* Sets a modifier for a subsequent event emission that the event data will only be broadcast to the current node.
*
* @return {Server} self
* @return self
* @public
*/
public get local(): Server {
public get local(): this {
this.sockets.local;
return this;
}
@@ -701,14 +717,14 @@ export class Server extends EventEmitter {
* Expose main namespace (/).
*/
const emitterMethods = Object.keys(EventEmitter.prototype).filter(function(
const emitterMethods = Object.keys(EventEmitter.prototype).filter(function (
key
) {
return typeof EventEmitter.prototype[key] === "function";
});
emitterMethods.forEach(function(fn) {
Server.prototype[fn] = function() {
emitterMethods.forEach(function (fn) {
Server.prototype[fn] = function () {
return this.sockets[fn].apply(this.sockets, arguments);
};
});

View File

@@ -1,10 +1,10 @@
import { Socket, RESERVED_EVENTS } from "./socket";
import { Server } from "./index";
import { Client } from "./client";
import type { Server } from "./index";
import type { Client } from "./client";
import { EventEmitter } from "events";
import { PacketType } from "socket.io-parser";
import debugModule from "debug";
import { Adapter, Room, SocketId } from "socket.io-adapter";
import type { Adapter, Room, SocketId } from "socket.io-adapter";
const debug = debugModule("socket.io:namespace");
@@ -23,7 +23,7 @@ export class Namespace extends EventEmitter {
/** @private */
_fns: Array<
(socket: Socket, next: (err: ExtendedError) => void) => void
(socket: Socket, next: (err?: ExtendedError) => void) => void
> = [];
/** @private */
@@ -38,8 +38,8 @@ export class Namespace extends EventEmitter {
/**
* Namespace constructor.
*
* @param {Server} server instance
* @param {string} name
* @param server instance
* @param name
*/
constructor(server: Server, name: string) {
super();
@@ -56,18 +56,18 @@ export class Namespace extends EventEmitter {
* @private
*/
_initAdapter(): void {
this.adapter = new (this.server.adapter())(this);
this.adapter = new (this.server.adapter()!)(this);
}
/**
* Sets up namespace middleware.
*
* @return {Namespace} self
* @return self
* @public
*/
public use(
fn: (socket: Socket, next: (err?: ExtendedError) => void) => void
): Namespace {
): this {
this._fns.push(fn);
return this;
}
@@ -75,16 +75,16 @@ export class Namespace extends EventEmitter {
/**
* Executes the middleware for an incoming client.
*
* @param {Socket} socket - the socket that will get added
* @param {Function} fn - last fn call in the middleware
* @param socket - the socket that will get added
* @param fn - last fn call in the middleware
* @private
*/
private run(socket: Socket, fn: (err: ExtendedError) => void) {
private run(socket: Socket, fn: (err: ExtendedError | null) => void) {
const fns = this._fns.slice(0);
if (!fns.length) return fn(null);
function run(i) {
fns[i](socket, function(err) {
function run(i: number) {
fns[i](socket, function (err) {
// upon error, short-circuit
if (err) return fn(err);
@@ -102,11 +102,11 @@ export class Namespace extends EventEmitter {
/**
* Targets a room when emitting.
*
* @param {String} name
* @return {Namespace} self
* @param name
* @return self
* @public
*/
public to(name: Room): Namespace {
public to(name: Room): this {
this._rooms.add(name);
return this;
}
@@ -114,11 +114,11 @@ export class Namespace extends EventEmitter {
/**
* Targets a room when emitting.
*
* @param {String} name
* @return {Namespace} self
* @param name
* @return self
* @public
*/
public in(name: Room): Namespace {
public in(name: Room): this {
this._rooms.add(name);
return this;
}
@@ -132,14 +132,19 @@ export class Namespace extends EventEmitter {
_add(client: Client, query, fn?: () => void): Socket {
debug("adding socket to nsp %s", this.name);
const socket = new Socket(this, client, query);
this.run(socket, err => {
this.run(socket, (err) => {
process.nextTick(() => {
if ("open" == client.conn.readyState) {
if (err)
return socket._error({
message: err.message,
data: err.data
});
if (err) {
if (client.conn.protocol === 3) {
return socket._error(err.data || err.message);
} else {
return socket._error({
message: err.message,
data: err.data,
});
}
}
// track socket
this.sockets.set(socket.id, socket);
@@ -178,10 +183,10 @@ export class Namespace extends EventEmitter {
/**
* Emits to all clients.
*
* @return {Boolean} Always true
* @return Always true
* @public
*/
public emit(ev: string, ...args: any[]): boolean {
public emit(ev: string | Symbol, ...args: any[]): true {
if (RESERVED_EVENTS.has(ev)) {
throw new Error(`"${ev}" is a reserved event name`);
}
@@ -189,7 +194,7 @@ export class Namespace extends EventEmitter {
args.unshift(ev);
const packet = {
type: PacketType.EVENT,
data: args
data: args,
};
if ("function" == typeof args[args.length - 1]) {
@@ -205,7 +210,7 @@ export class Namespace extends EventEmitter {
this.adapter.broadcast(packet, {
rooms: rooms,
flags: flags
flags: flags,
});
return true;
@@ -214,31 +219,29 @@ export class Namespace extends EventEmitter {
/**
* Sends a `message` event to all clients.
*
* @return {Namespace} self
* @return self
* @public
*/
public send(...args): Namespace {
args.unshift("message");
this.emit.apply(this, args);
public send(...args: readonly any[]): this {
this.emit("message", ...args);
return this;
}
/**
* Sends a `message` event to all clients.
*
* @return {Namespace} self
* @return self
* @public
*/
public write(...args): Namespace {
args.unshift("message");
this.emit.apply(this, args);
public write(...args: readonly any[]): this {
this.emit("message", ...args);
return this;
}
/**
* Gets a list of clients.
*
* @return {Namespace} self
* @return self
* @public
*/
public allSockets(): Promise<Set<SocketId>> {
@@ -255,11 +258,11 @@ export class Namespace extends EventEmitter {
/**
* Sets the compress flag.
*
* @param {Boolean} compress - if `true`, compresses the sending data
* @return {Namespace} self
* @param compress - if `true`, compresses the sending data
* @return self
* @public
*/
public compress(compress: boolean): Namespace {
public compress(compress: boolean): this {
this._flags.compress = compress;
return this;
}
@@ -269,10 +272,10 @@ export class Namespace extends EventEmitter {
* 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).
*
* @return {Namespace} self
* @return self
* @public
*/
public get volatile(): Namespace {
public get volatile(): this {
this._flags.volatile = true;
return this;
}
@@ -280,10 +283,10 @@ export class Namespace extends EventEmitter {
/**
* Sets a modifier for a subsequent event emission that the event data will only be broadcast to the current node.
*
* @return {Namespace} self
* @return self
* @public
*/
public get local(): Namespace {
public get local(): this {
this._flags.local = true;
return this;
}

View File

@@ -1,20 +1,26 @@
import { Namespace } from "./namespace";
import type { Server } from "./index";
export class ParentNamespace extends Namespace {
private static count: number = 0;
private children: Set<Namespace> = new Set();
constructor(server) {
constructor(server: Server) {
super(server, "/_" + ParentNamespace.count++);
}
_initAdapter() {}
/**
* @private
*/
_initAdapter(): void {
/* no-op */
}
public emit(...args: any[]): boolean {
this.children.forEach(nsp => {
public emit(ev: string | Symbol, ...args: [...any]): true {
this.children.forEach((nsp) => {
nsp._rooms = this._rooms;
nsp._flags = this._flags;
nsp.emit.apply(nsp, args);
nsp.emit(ev, ...args);
});
this._rooms.clear();
this._flags = {};
@@ -22,16 +28,14 @@ export class ParentNamespace extends Namespace {
return true;
}
createChild(name) {
createChild(name: string): Namespace {
const namespace = new Namespace(this.server, name);
namespace._fns = this._fns.slice(0);
this.listeners("connect").forEach(listener =>
// @ts-ignore
namespace.on("connect", listener)
this.listeners("connect").forEach((listener) =>
namespace.on("connect", listener as (...args: any[]) => void)
);
this.listeners("connection").forEach(listener =>
// @ts-ignore
namespace.on("connection", listener)
this.listeners("connection").forEach((listener) =>
namespace.on("connection", listener as (...args: any[]) => void)
);
this.children.add(namespace);
this.server._nsps.set(name, namespace);

View File

@@ -1,25 +1,31 @@
import { EventEmitter } from "events";
import { PacketType } from "socket.io-parser";
import { Packet, PacketType } from "socket.io-parser";
import url = require("url");
import debugModule from "debug";
import { Server } from "./index";
import { Client } from "./client";
import { Namespace } from "./namespace";
import { IncomingMessage } from "http";
import { Adapter, BroadcastFlags, Room, SocketId } from "socket.io-adapter";
import type { Server } from "./index";
import type { Client } from "./client";
import type { Namespace } from "./namespace";
import type { IncomingMessage, IncomingHttpHeaders } from "http";
import type {
Adapter,
BroadcastFlags,
Room,
SocketId,
} from "socket.io-adapter";
import base64id from "base64id";
import type { ParsedUrlQuery } from "querystring";
const debug = debugModule("socket.io:socket");
export const RESERVED_EVENTS = new Set([
export const RESERVED_EVENTS = new Set(<const>[
"connect",
"connect_error",
"disconnect",
"disconnecting",
// EventEmitter reserved events: https://nodejs.org/api/events.html#events_event_newlistener
"newListener",
"removeListener"
]);
"removeListener",
]) as ReadonlySet<string | Symbol>;
/**
* The handshake details
@@ -28,7 +34,7 @@ export interface Handshake {
/**
* The headers sent as part of the handshake
*/
headers: object;
headers: IncomingHttpHeaders;
/**
* The date of creation (as string)
@@ -63,12 +69,12 @@ export interface Handshake {
/**
* The query object
*/
query: object;
query: ParsedUrlQuery;
/**
* The auth object
*/
auth: object;
auth: { [key: string]: any };
}
export class Socket extends EventEmitter {
@@ -86,7 +92,7 @@ export class Socket extends EventEmitter {
> = [];
private flags: BroadcastFlags = {};
private _rooms: Set<Room> = new Set();
private _anyListeners: Array<(...args: any[]) => void>;
private _anyListeners?: Array<(...args: any[]) => void>;
/**
* Interface to a `Client` for a given `Namespace`.
@@ -100,7 +106,12 @@ export class Socket extends EventEmitter {
super();
this.server = nsp.server;
this.adapter = this.nsp.adapter;
this.id = base64id.generateId(); // don't reuse the Engine.IO id because it's sensitive information
if (client.conn.protocol === 3) {
// @ts-ignore
this.id = nsp.name !== "/" ? nsp.name + "#" + client.id : client.id;
} else {
this.id = base64id.generateId(); // don't reuse the Engine.IO id because it's sensitive information
}
this.connected = true;
this.disconnected = false;
this.handshake = this.buildHandshake(auth);
@@ -120,16 +131,16 @@ export class Socket extends EventEmitter {
// @ts-ignore
secure: !!this.request.connection.encrypted,
issued: +new Date(),
url: this.request.url,
query: url.parse(this.request.url, true).query,
auth
url: this.request.url!,
query: url.parse(this.request.url!, true).query,
auth,
};
}
/**
* Emits to this client.
*
* @return {Boolean} Always true
* @return Always returns `true`.
* @public
*/
public emit(ev: string, ...args: any[]): boolean {
@@ -139,7 +150,7 @@ export class Socket extends EventEmitter {
args.unshift(ev);
const packet: any = {
type: PacketType.EVENT,
data: args
data: args,
};
// access last argument to see if it's an ACK callback
@@ -164,7 +175,7 @@ export class Socket extends EventEmitter {
this.adapter.broadcast(packet, {
except: new Set([this.id]),
rooms: rooms,
flags: flags
flags: flags,
});
} else {
// dispatch packet
@@ -176,11 +187,11 @@ export class Socket extends EventEmitter {
/**
* Targets a room when broadcasting.
*
* @param {String} name
* @return {Socket} self
* @param name
* @return self
* @public
*/
public to(name: Room) {
public to(name: Room): this {
this._rooms.add(name);
return this;
}
@@ -188,11 +199,11 @@ export class Socket extends EventEmitter {
/**
* Targets a room when broadcasting.
*
* @param {String} name
* @return {Socket} self
* @param name
* @return self
* @public
*/
public in(name: Room): Socket {
public in(name: Room): this {
this._rooms.add(name);
return this;
}
@@ -200,24 +211,22 @@ export class Socket extends EventEmitter {
/**
* Sends a `message` event.
*
* @return {Socket} self
* @return self
* @public
*/
public send(...args): Socket {
args.unshift("message");
this.emit.apply(this, args);
public send(...args: readonly any[]): this {
this.emit("message", ...args);
return this;
}
/**
* Sends a `message` event.
*
* @return {Socket} self
* @return self
* @public
*/
public write(...args): Socket {
args.unshift("message");
this.emit.apply(this, args);
public write(...args: readonly any[]): this {
this.emit("message", ...args);
return this;
}
@@ -228,10 +237,13 @@ export class Socket extends EventEmitter {
* @param {Object} opts - options
* @private
*/
private packet(packet, opts: any = {}) {
private packet(
packet: Omit<Packet, "nsp"> & Partial<Pick<Packet, "nsp">>,
opts: any = {}
): void {
packet.nsp = this.nsp.name;
opts.compress = false !== opts.compress;
this.client._packet(packet, opts);
this.client._packet(packet as Packet, opts);
}
/**
@@ -283,7 +295,11 @@ export class Socket extends EventEmitter {
_onconnect(): void {
debug("socket connected - writing packet");
this.join(this.id);
this.packet({ type: PacketType.CONNECT, data: { sid: this.id } });
if (this.conn.protocol === 3) {
this.packet({ type: PacketType.CONNECT });
} else {
this.packet({ type: PacketType.CONNECT, data: { sid: this.id } });
}
}
/**
@@ -292,7 +308,7 @@ export class Socket extends EventEmitter {
* @param {Object} packet
* @private
*/
_onpacket(packet) {
_onpacket(packet: Packet): void {
debug("got packet %j", packet);
switch (packet.type) {
case PacketType.EVENT:
@@ -323,10 +339,10 @@ export class Socket extends EventEmitter {
/**
* Called upon event packet.
*
* @param {Object} packet - packet object
* @param {Packet} packet - packet object
* @private
*/
private onevent(packet): void {
private onevent(packet: Packet): void {
const args = packet.data || [];
debug("emitting event %j", args);
@@ -341,7 +357,7 @@ export class Socket extends EventEmitter {
listener.apply(this, args);
}
}
super.emit.apply(this, args);
this.dispatch(args);
}
/**
@@ -350,10 +366,10 @@ export class Socket extends EventEmitter {
* @param {Number} id - packet id
* @private
*/
private ack(id: number) {
private ack(id: number): () => void {
const self = this;
let sent = false;
return function() {
return function () {
// prevent double callbacks
if (sent) return;
const args = Array.prototype.slice.call(arguments);
@@ -362,7 +378,7 @@ export class Socket extends EventEmitter {
self.packet({
id: id,
type: PacketType.ACK,
data: args
data: args,
});
sent = true;
@@ -374,12 +390,12 @@ export class Socket extends EventEmitter {
*
* @private
*/
private onack(packet): void {
const ack = this.acks.get(packet.id);
private onack(packet: Packet): void {
const ack = this.acks.get(packet.id!);
if ("function" == typeof ack) {
debug("calling ack %s with %j", packet.id, packet.data);
ack.apply(this, packet.data);
this.acks.delete(packet.id);
this.acks.delete(packet.id!);
} else {
debug("bad ack %s", packet.id);
}
@@ -417,7 +433,7 @@ export class Socket extends EventEmitter {
*
* @private
*/
_onclose(reason: string) {
_onclose(reason: string): this | undefined {
if (!this.connected) return this;
debug("closing socket - reason %s", reason);
super.emit("disconnecting", reason);
@@ -427,6 +443,7 @@ export class Socket extends EventEmitter {
this.connected = false;
this.disconnected = true;
super.emit("disconnect", reason);
return;
}
/**
@@ -436,7 +453,7 @@ export class Socket extends EventEmitter {
*
* @private
*/
_error(err) {
_error(err): void {
this.packet({ type: PacketType.CONNECT_ERROR, data: err });
}
@@ -448,7 +465,7 @@ export class Socket extends EventEmitter {
*
* @public
*/
public disconnect(close = false): Socket {
public disconnect(close = false): this {
if (!this.connected) return this;
if (close) {
this.client._disconnect();
@@ -466,7 +483,7 @@ export class Socket extends EventEmitter {
* @return {Socket} self
* @public
*/
public compress(compress: boolean): Socket {
public compress(compress: boolean): this {
this.flags.compress = compress;
return this;
}
@@ -479,7 +496,7 @@ export class Socket extends EventEmitter {
* @return {Socket} self
* @public
*/
public get volatile(): Socket {
public get volatile(): this {
this.flags.volatile = true;
return this;
}
@@ -491,7 +508,7 @@ export class Socket extends EventEmitter {
* @return {Socket} self
* @public
*/
public get broadcast(): Socket {
public get broadcast(): this {
this.flags.broadcast = true;
return this;
}
@@ -502,11 +519,73 @@ export class Socket extends EventEmitter {
* @return {Socket} self
* @public
*/
public get local(): Socket {
public get local(): this {
this.flags.local = true;
return this;
}
/**
* Dispatch incoming event to socket listeners.
*
* @param {Array} event - event that will get emitted
* @private
*/
private dispatch(event: [eventName: string, ...args: any[]]): void {
debug("dispatching an event %j", event);
this.run(event, (err) => {
process.nextTick(() => {
if (err) {
return this._onerror(err);
}
super.emit.apply(this, event);
});
});
}
/**
* Sets up socket middleware.
*
* @param {Function} fn - middleware function (event, next)
* @return {Socket} self
* @public
*/
public use(
fn: (event: Array<any>, next: (err: Error) => void) => void
): this {
this.fns.push(fn);
return this;
}
/**
* Executes the middleware for an incoming event.
*
* @param {Array} event - event that will get emitted
* @param {Function} fn - last fn call in the middleware
* @private
*/
private run(
event: [eventName: string, ...args: any[]],
fn: (err: Error | null) => void
): void {
const fns = this.fns.slice(0);
if (!fns.length) return fn(null);
function run(i: number) {
fns[i](event, function (err) {
// upon error, short-circuit
if (err) return fn(err);
// if no middleware left, summon callback
if (!fns[i + 1]) return fn(null);
// go on to next
run(i + 1);
});
}
run(0);
}
/**
* A reference to the request that originated the underlying Engine.IO Socket.
*
@@ -539,7 +618,7 @@ export class Socket extends EventEmitter {
* @param listener
* @public
*/
public onAny(listener: (...args: any[]) => void): Socket {
public onAny(listener: (...args: any[]) => void): this {
this._anyListeners = this._anyListeners || [];
this._anyListeners.push(listener);
return this;
@@ -552,7 +631,7 @@ export class Socket extends EventEmitter {
* @param listener
* @public
*/
public prependAny(listener: (...args: any[]) => void): Socket {
public prependAny(listener: (...args: any[]) => void): this {
this._anyListeners = this._anyListeners || [];
this._anyListeners.unshift(listener);
return this;
@@ -564,7 +643,7 @@ export class Socket extends EventEmitter {
* @param listener
* @public
*/
public offAny(listener?: (...args: any[]) => void): Socket {
public offAny(listener?: (...args: any[]) => void): this {
if (!this._anyListeners) {
return this;
}

364
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "socket.io",
"version": "3.0.4",
"version": "3.1.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -362,8 +362,7 @@
"@types/component-emitter": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz",
"integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg==",
"dev": true
"integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg=="
},
"@types/connect": {
"version": "3.4.33",
@@ -413,15 +412,15 @@
"integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q=="
},
"@types/mocha": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.0.3.tgz",
"integrity": "sha512-vyxR57nv8NfcU0GZu8EUXZLTbCMupIUwy95LJ6lllN+JRPG25CwMHoB1q5xKh8YKhQnHYRAn4yW2yuHbf/5xgg==",
"version": "8.0.4",
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.0.4.tgz",
"integrity": "sha512-M4BwiTJjHmLq6kjON7ZoI2JMlBvpY3BYSdiP6s/qCT3jb1s9/DeJF0JELpAxiVSIxXDzfNKe+r7yedMIoLbknQ==",
"dev": true
},
"@types/node": {
"version": "14.14.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.7.tgz",
"integrity": "sha512-Zw1vhUSQZYw+7u5dAwNbIA9TuTotpzY/OF7sJM9FqPOF3SPjKnxrjoTktXDZgUjybf4cWVBP7O8wvKdSaGHweg=="
"version": "14.14.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.10.tgz",
"integrity": "sha512-J32dgx2hw8vXrSbu4ZlVhn1Nm3GbeCFNw2FWL8S5QKucHGY0cyNwjdQdO+KMBZ4wpmC7KhLCiNsdk1RFRIYUQQ=="
},
"@types/qs": {
"version": "6.9.5",
@@ -463,6 +462,12 @@
"integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==",
"dev": true
},
"after": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
"integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=",
"dev": true
},
"aggregate-error": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
@@ -536,6 +541,12 @@
"sprintf-js": "~1.0.2"
}
},
"arraybuffer.slice": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
"integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==",
"dev": true
},
"astral-regex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
@@ -585,6 +596,12 @@
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="
},
"blob": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz",
"integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==",
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -734,6 +751,12 @@
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
},
"component-inherit": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
"integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=",
"dev": true
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -760,12 +783,6 @@
"integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==",
"dev": true
},
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true
},
"cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
@@ -787,11 +804,11 @@
}
},
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"requires": {
"ms": "^2.1.1"
"ms": "2.1.2"
}
},
"decamelize": {
@@ -843,43 +860,35 @@
"dev": true
},
"engine.io": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.0.1.tgz",
"integrity": "sha512-6EaSBxasBUwxRdf6B68SEYpD3tcrG80J4YTzHl/D+9Q+vM0AMHZabfYcc2WdnvEaQxZjX/UZsa+UdGoM0qQQkQ==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.1.0.tgz",
"integrity": "sha512-vW7EAtn0HDQ4MtT5QbmCHF17TaYLONv2/JwdYsq9USPRZVM4zG7WB3k0Nc321z8EuSOlhGokrYlYx4176QhD0A==",
"requires": {
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.4.1",
"cors": "~2.8.5",
"debug": "~4.1.0",
"debug": "~4.3.1",
"engine.io-parser": "~4.0.0",
"ws": "^7.1.2"
"ws": "~7.4.2"
}
},
"engine.io-client": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-4.0.5.tgz",
"integrity": "sha512-1lkn0QdekHQPMTcxUh8LqIuxQHNtKV5GvqkQzmZ1rYKAvB6puMm13U7K1ps3OQZ4joE46asQiAKrcdL9weNEVw==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-4.1.1.tgz",
"integrity": "sha512-iYasV/EttP/2pLrdowe9G3zwlNIFhwny8VSIh+vPlMnYZqSzLsTzSLa9hFy015OrH1s4fzoYxeHjVkO8hSFKwg==",
"dev": true,
"requires": {
"base64-arraybuffer": "0.1.4",
"component-emitter": "~1.3.0",
"debug": "~4.1.0",
"debug": "~4.3.1",
"engine.io-parser": "~4.0.1",
"has-cors": "1.1.0",
"parseqs": "0.0.6",
"parseuri": "0.0.6",
"ws": "~7.2.1",
"ws": "~7.4.2",
"xmlhttprequest-ssl": "~1.5.4",
"yeast": "0.1.2"
},
"dependencies": {
"ws": {
"version": "7.2.5",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.2.5.tgz",
"integrity": "sha512-C34cIU4+DB2vMyAbmEKossWq2ZQDr6QEyuuCzWrM9zfw1sGc0mYiJ0UnG9zzNykt49C2Fi34hvr2vssFQRS6EA==",
"dev": true
}
}
},
"engine.io-parser": {
@@ -909,9 +918,9 @@
"dev": true
},
"eslint": {
"version": "7.12.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.12.1.tgz",
"integrity": "sha512-HlMTEdr/LicJfN08LB3nM1rRYliDXOmfoO4vj39xN6BLpFzF00hbwBoqHk8UcJ2M/3nlARZWy/mslvGEuZFvsg==",
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.14.0.tgz",
"integrity": "sha512-5YubdnPXrlrYAFCKybPuHIAH++PINe1pmKNc5wQRB9HSbqIK1ywAnntE3Wwua4giKu0bjligf1gLF6qxMGOYRA==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
@@ -1122,12 +1131,6 @@
"integrity": "sha1-sKWaDS7/VDdUTr8M6qYBWEHQm1s=",
"dev": true
},
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"dev": true
},
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -1146,6 +1149,12 @@
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
"dev": true
},
"fast-safe-stringify": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz",
"integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==",
"dev": true
},
"file-entry-cache": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
@@ -1215,13 +1224,13 @@
}
},
"form-data": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz",
"integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz",
"integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==",
"dev": true,
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.6",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
}
},
@@ -1335,6 +1344,15 @@
"function-bind": "^1.1.1"
}
},
"has-binary2": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz",
"integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==",
"dev": true,
"requires": {
"isarray": "2.0.1"
}
},
"has-cors": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
@@ -1397,6 +1415,12 @@
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
"dev": true
},
"indexof": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
"integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=",
"dev": true
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@@ -1462,9 +1486,9 @@
"dev": true
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
"integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=",
"dev": true
},
"isexe": {
@@ -1758,9 +1782,9 @@
"dev": true
},
"mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz",
"integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==",
"dev": true
},
"mime-db": {
@@ -2083,15 +2107,9 @@
"dev": true
},
"prettier": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz",
"integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==",
"dev": true
},
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz",
"integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==",
"dev": true
},
"process-on-spawn": {
@@ -2122,18 +2140,14 @@
"dev": true
},
"readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dev": true,
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
},
"regexpp": {
@@ -2239,33 +2253,125 @@
}
},
"socket.io-adapter": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.0.3.tgz",
"integrity": "sha512-2wo4EXgxOGSFueqvHAdnmi5JLZzWqMArjuP4nqC26AtLh5PoCPsaRbRdah2xhcwTAMooZfjYiNVNkkmmSMaxOQ=="
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.1.0.tgz",
"integrity": "sha512-+vDov/aTsLjViYTwS9fPy5pEtTkrbEKsw2M+oVSoFGw6OD1IpvlV1VPhUzNbofCQ8oyMbdYJqDtGdmHQK6TdPg=="
},
"socket.io-client": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-3.0.4.tgz",
"integrity": "sha512-qMvBuS+W9JIN2mkfAWDCxuIt+jpIKDf8C0604zEqx1JrPaPSS6cN0F3B2GYWC83TqBeVJXW66GFxWV3KD88n0Q==",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-3.1.1.tgz",
"integrity": "sha512-BLgIuCjI7Sf3mDHunKddX9zKR/pbkP7IACM3sJS3jha+zJ6/pGKRV6Fz5XSBHCfUs9YzT8kYIqNwOOuFNLtnYA==",
"dev": true,
"requires": {
"@types/component-emitter": "^1.2.10",
"backo2": "~1.0.2",
"component-emitter": "~1.3.0",
"debug": "~4.3.1",
"engine.io-client": "~4.1.0",
"parseuri": "0.0.6",
"socket.io-parser": "~4.0.4"
},
"dependencies": {
"socket.io-parser": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz",
"integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==",
"dev": true,
"requires": {
"@types/component-emitter": "^1.2.10",
"component-emitter": "~1.3.0",
"debug": "~4.3.1"
}
}
}
},
"socket.io-client-v2": {
"version": "npm:socket.io-client@2.4.0",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.4.0.tgz",
"integrity": "sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ==",
"dev": true,
"requires": {
"backo2": "1.0.2",
"component-bind": "1.0.0",
"component-emitter": "~1.3.0",
"debug": "~4.1.0",
"engine.io-client": "~4.0.0",
"debug": "~3.1.0",
"engine.io-client": "~3.5.0",
"has-binary2": "~1.0.2",
"indexof": "0.0.1",
"parseqs": "0.0.6",
"parseuri": "0.0.6",
"socket.io-parser": "~4.0.1"
"socket.io-parser": "~3.3.0",
"to-array": "0.1.4"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
},
"engine.io-client": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.0.tgz",
"integrity": "sha512-12wPRfMrugVw/DNyJk34GQ5vIVArEcVMXWugQGGuw2XxUSztFNmJggZmv8IZlLyEdnpO1QB9LkcjeWewO2vxtA==",
"dev": true,
"requires": {
"component-emitter": "~1.3.0",
"component-inherit": "0.0.3",
"debug": "~3.1.0",
"engine.io-parser": "~2.2.0",
"has-cors": "1.1.0",
"indexof": "0.0.1",
"parseqs": "0.0.6",
"parseuri": "0.0.6",
"ws": "~7.4.2",
"xmlhttprequest-ssl": "~1.5.4",
"yeast": "0.1.2"
}
},
"engine.io-parser": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.1.tgz",
"integrity": "sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==",
"dev": true,
"requires": {
"after": "0.8.2",
"arraybuffer.slice": "~0.0.7",
"base64-arraybuffer": "0.1.4",
"blob": "0.0.5",
"has-binary2": "~1.0.2"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
},
"socket.io-parser": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.2.tgz",
"integrity": "sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg==",
"dev": true,
"requires": {
"component-emitter": "~1.3.0",
"debug": "~3.1.0",
"isarray": "2.0.1"
}
}
}
},
"socket.io-parser": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.1.tgz",
"integrity": "sha512-5JfNykYptCwU2lkOI0ieoePWm+6stEhkZ2UnLDjqnE1YEjUlXXLd1lpxPZ+g+h3rtaytwWkWrLQCaJULlGqjOg==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.3.tgz",
"integrity": "sha512-m4ybFiP4UYVORRt7jcdqf8UWx+ywVdAqqsJyruXxAdD3Sv6MDemijWij34mOWdMJ55bEdIb9jACBhxUgNK6sxw==",
"requires": {
"@types/component-emitter": "^1.2.10",
"component-emitter": "~1.3.0",
"debug": "~4.1.0"
"debug": "~4.3.1"
}
},
"source-map": {
@@ -2341,12 +2447,20 @@
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"dev": true,
"requires": {
"safe-buffer": "~5.1.0"
"safe-buffer": "~5.2.0"
},
"dependencies": {
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true
}
}
},
"strip-ansi": {
@@ -2371,42 +2485,32 @@
"dev": true
},
"superagent": {
"version": "3.8.3",
"resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz",
"integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==",
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz",
"integrity": "sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==",
"dev": true,
"requires": {
"component-emitter": "^1.2.0",
"cookiejar": "^2.1.0",
"debug": "^3.1.0",
"extend": "^3.0.0",
"form-data": "^2.3.1",
"formidable": "^1.2.0",
"methods": "^1.1.1",
"mime": "^1.4.1",
"qs": "^6.5.1",
"readable-stream": "^2.3.5"
},
"dependencies": {
"debug": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"dev": true,
"requires": {
"ms": "^2.1.1"
}
}
"component-emitter": "^1.3.0",
"cookiejar": "^2.1.2",
"debug": "^4.1.1",
"fast-safe-stringify": "^2.0.7",
"form-data": "^3.0.0",
"formidable": "^1.2.2",
"methods": "^1.1.2",
"mime": "^2.4.6",
"qs": "^6.9.4",
"readable-stream": "^3.6.0",
"semver": "^7.3.2"
}
},
"supertest": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/supertest/-/supertest-3.4.2.tgz",
"integrity": "sha512-WZWbwceHUo2P36RoEIdXvmqfs47idNNZjCuJOqDz6rvtkk8ym56aU5oglORCpPeXGxT7l9rkJ41+O1lffQXYSA==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/supertest/-/supertest-6.0.1.tgz",
"integrity": "sha512-8yDNdm+bbAN/jeDdXsRipbq9qMpVF7wRsbwLgsANHqdjPsCoecmlTuqEcLQMGpmojFBhxayZ0ckXmLXYq7e+0g==",
"dev": true,
"requires": {
"methods": "^1.1.2",
"superagent": "^3.8.3"
"methods": "1.1.2",
"superagent": "6.1.0"
}
},
"supports-color": {
@@ -2447,6 +2551,12 @@
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
"dev": true
},
"to-array": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
"integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=",
"dev": true
},
"to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@@ -2499,9 +2609,9 @@
}
},
"typescript": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz",
"integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==",
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.2.tgz",
"integrity": "sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ==",
"dev": true
},
"uri-js": {
@@ -2645,9 +2755,9 @@
}
},
"ws": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz",
"integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA=="
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz",
"integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA=="
},
"xmlhttprequest-ssl": {
"version": "1.5.5",

View File

@@ -1,6 +1,6 @@
{
"name": "socket.io",
"version": "3.0.4",
"version": "3.1.1",
"description": "node.js realtime framework server",
"keywords": [
"realtime",
@@ -14,8 +14,15 @@
"files": [
"dist/",
"client-dist/",
"wrapper.mjs"
"wrapper.mjs",
"!**/*.tsbuildinfo"
],
"directories": {
"doc": "docs/",
"example": "example/",
"lib": "lib/",
"test": "test/"
},
"type": "commonjs",
"main": "./dist/index.js",
"exports": {
@@ -38,29 +45,30 @@
"dependencies": {
"@types/cookie": "^0.4.0",
"@types/cors": "^2.8.8",
"@types/node": "^14.14.7",
"@types/node": "^14.14.10",
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"debug": "~4.1.0",
"engine.io": "~4.0.0",
"socket.io-adapter": "~2.0.3",
"socket.io-parser": "~4.0.1"
"debug": "~4.3.1",
"engine.io": "~4.1.0",
"socket.io-adapter": "~2.1.0",
"socket.io-parser": "~4.0.3"
},
"devDependencies": {
"@types/mocha": "^8.0.3",
"@types/mocha": "^8.0.4",
"babel-eslint": "^10.1.0",
"eslint": "^7.9.0",
"eslint": "^7.14.0",
"eslint-config-prettier": "^6.11.0",
"expect.js": "0.3.1",
"mocha": "^3.5.3",
"nyc": "^15.1.0",
"prettier": "^1.19.1",
"prettier": "^2.2.0",
"rimraf": "^3.0.2",
"socket.io-client": "3.0.4",
"superagent": "^3.8.2",
"supertest": "^3.0.0",
"socket.io-client": "3.1.1",
"socket.io-client-v2": "npm:socket.io-client@^2.4.0",
"superagent": "^6.1.0",
"supertest": "^6.0.1",
"ts-node": "^9.0.0",
"typescript": "^4.0.3"
"typescript": "^4.1.2"
},
"contributors": [
{

File diff suppressed because it is too large Load Diff

View File

@@ -3,15 +3,15 @@ const i = expect.stringify;
// add support for Set/Map
const contain = expect.Assertion.prototype.contain;
expect.Assertion.prototype.contain = function(...args) {
expect.Assertion.prototype.contain = function (...args) {
if (typeof this.obj === "object") {
args.forEach(obj => {
args.forEach((obj) => {
this.assert(
this.obj.has(obj),
function() {
function () {
return "expected " + i(this.obj) + " to contain " + i(obj);
},
function() {
function () {
return "expected " + i(this.obj) + " to not contain " + i(obj);
}
);

View File

@@ -4,7 +4,17 @@
"target": "es2017",
"module": "commonjs",
"declaration": true,
"esModuleInterop": true
"esModuleInterop": true,
"incremental": true,
"skipLibCheck": true,
"strict": true,
"noImplicitAny": false,
"noImplicitThis": false,
"strictPropertyInitialization": false,
"noImplicitReturns": true,
"importsNotUsedAsValues": "error",
},
"include": [
"./lib/**/*"

View File

@@ -1,3 +1,3 @@
import io from "./dist/index.js";
export const Server = io.Server;
export const {Server, Namespace, Socket} = io;