Compare commits

...

246 Commits
3.0.0 ... 4.7.5

Author SHA1 Message Date
Damien Arrachequesne
50176812a1 chore(release): 4.7.5
Diff: https://github.com/socketio/socket.io/compare/4.7.4...4.7.5
2024-03-14 17:55:23 +01:00
Damien Arrachequesne
bf64870957 fix: close the adapters when the server is closed
Related:

- https://github.com/socketio/socket.io-mongo-adapter/issues/9
- https://github.com/socketio/socket.io-postgres-adapter/issues/13
- 0e23ff0cc6
2024-02-23 12:10:29 +01:00
Damien Arrachequesne
748e18c22e ci: test with older TypeScript version
Related: https://github.com/socketio/socket.io/issues/3891
2024-02-22 10:12:18 +01:00
Wang Guan
b9ce6a25d1 refactor: create specific adapter for parent namespaces (#4950) 2024-02-19 22:01:29 +01:00
Damien Arrachequesne
54dabe5bff ci: upgrade to actions/checkout@4 and actions/setup-node@4
Reference: https://github.blog/changelog/2023-09-22-github-actions-transitioning-from-node-16-to-node-20/
2024-02-12 18:16:10 +01:00
Damien Arrachequesne
e426f3e8e1 fix: remove duplicate pipeline when serving bundle
Related: https://github.com/socketio/socket.io/issues/4946
2024-02-12 18:11:38 +01:00
Damien Arrachequesne
e36062ca2d docs: update the webtransport example
Reference: https://github.com/fails-components/webtransport#changes-for-version-1xx
2024-01-17 13:01:57 +01:00
Damien Arrachequesne
0bbe8aec77 docs: only execute the passport middleware once
Before this change, the session and user context were retrieved once
per HTTP request and not once per session.
2024-01-13 17:56:17 +01:00
Damien Arrachequesne
914a8bd2b9 docs: add example with JWT
Related: https://github.com/socketio/socket.io/issues/4910
2024-01-13 17:14:01 +01:00
Damien Arrachequesne
d943c3e0b0 docs: update the Passport.js example 2024-01-12 17:08:19 +01:00
Damien Arrachequesne
6ab2509d52 chore(release): 4.7.4
Diff: https://github.com/socketio/socket.io/compare/4.7.3...4.7.4
2024-01-12 11:09:14 +01:00
Zachary Soare
d9fb2f64b6 chore(tests): add a test for noArgs in a namespace 2024-01-08 06:38:57 +01:00
Zachary Soare
2c0a81cd87 chore(tests): fix issues due to client#id type change 2024-01-08 06:38:57 +01:00
Zachary Soare
f8d2644921 chore(tests): add type defs for expectjs and fix invalid expectations 2024-01-08 06:38:57 +01:00
Zachary Soare
04640d68cf chore(tests): indicate a future ts error with version 2024-01-08 06:38:57 +01:00
Zachary Soare
cb6d2e02aa fix(typings): calling io.emit with no arguments incorrectly errored
Refs: #4914
2024-01-08 06:38:57 +01:00
Damien Arrachequesne
80b2c34478 chore: bump socket.io-client version 2024-01-03 21:37:25 +01:00
Damien Arrachequesne
0d893196f8 chore(release): 4.7.3
Diff: https://github.com/socketio/socket.io/compare/4.7.2...4.7.3
2024-01-03 21:33:29 +01:00
BCCSTeam
df8e70f798 fix: return the first response when broadcasting to a single socket (#4878) 2024-01-02 17:43:10 +01:00
Xì Gà
8c9ebc30e5 fix(typings): allow to bind to a non-secure Http2Server (#4853) 2023-11-22 17:48:59 +01:00
Damien Arrachequesne
efb5c21e85 docs(examples): add Vue client with CRUD example 2023-11-22 10:12:17 +01:00
Damien Arrachequesne
3848280125 docs(examples): upgrade basic-crud-application to Angular v17
Related: https://github.com/socketio/socket.io/issues/4875
2023-11-21 14:15:50 +01:00
Damien Arrachequesne
9a2a83fdd4 refactor: cleanup after merge 2023-10-11 10:45:59 +02:00
Zachary Haber
f6ef267b03 refactor(typings): improve emit types (#4817)
This commit fixes several issues with emit types:

- calling `emit()` without calling `timeout()` first is now only available for events without acknowledgement
- calling `emit()` after calling `timeout()` is now only available for events with an acknowledgement
- calling `emitWithAck()` is now only available for events with an acknowledgement
- `timeout()` must be called before calling `emitWithAck()`
2023-10-11 10:37:13 +02:00
Maxime Kjaer
1cdf36bfea test: build examples in the CI (#3856) 2023-10-10 20:02:52 +02:00
Toha
bbf1fdc7a6 docs: add Elephant.IO as PHP client library (#4779) 2023-10-10 17:32:19 +02:00
Damien Arrachequesne
b4dc83eb9b docs(examples): add codesandbox configuration 2023-09-20 12:57:37 +02:00
Damien Arrachequesne
ccbb4c0773 docs: add example with connection state recovery 2023-09-20 12:45:04 +02:00
Damien Arrachequesne
d744fda772 docs: improve example with express-session
The example is now available with different syntaxes:

- CommonJS
- ES modules
- TypeScript

Related: https://github.com/socketio/socket.io/pull/4787
2023-09-13 15:56:15 +02:00
Damien Arrachequesne
8259cdac84 docs: use io.engine.use() with express-session
Related: https://github.com/socketio/socket.io/discussions/4819
2023-09-13 12:13:03 +02:00
Damien Arrachequesne
fd9dd74eee docs: use "connection" instead of "connect"
"connect" and "connection" have the same meaning, but "connection" is
the preferred version.
2023-08-12 10:10:55 +02:00
Damien Arrachequesne
c332643ad8 chore(release): 4.7.2
Diff: https://github.com/socketio/socket.io/compare/4.7.1...4.7.2
2023-08-03 01:51:04 +02:00
Damien Arrachequesne
3468a197af fix(webtransport): properly handle WebTransport-only connections
A WebTransport-only connection has no `request` attribute, so we need
to handle that case.
2023-08-03 01:45:21 +02:00
Damien Arrachequesne
09d45491c4 chore: bump engine.io to version 6.5.2
Diff: https://github.com/socketio/engine.io/compare/6.5.0...6.5.2
Release notes: https://github.com/socketio/engine.io/releases/tag/6.5.2
2023-08-03 00:39:46 +02:00
Jaro
0731c0d2f4 fix: clean up child namespace when client is rejected in middleware (#4773)
Related: https://github.com/socketio/socket.io/issues/4772
2023-07-21 08:33:46 +02:00
Damien Arrachequesne
03046a64ad docs: update the list of supported Node.js versions
The Engine.IO server uses `timeout.refresh()` (see [1]), which was
added in Node.js 10.2.0.

Reference: https://nodejs.org/api/timers.html#timeoutrefresh

Related: https://github.com/socketio/engine.io/issues/686

[1]: 37474c7e67
2023-07-09 10:14:49 +02:00
Damien Arrachequesne
443e447087 docs(examples): add example with WebTransport 2023-06-29 11:21:27 +02:00
Damien Arrachequesne
2f6cc2fa42 chore(release): 4.7.1
Diff: https://github.com/socketio/socket.io/compare/4.7.0...4.7.1
2023-06-28 09:32:32 +02:00
Damien Arrachequesne
00d8ee5b05 chore(release): 4.7.0
Diff: https://github.com/socketio/socket.io/compare/4.6.2...4.7.0
2023-06-22 11:27:45 +02:00
Damien Arrachequesne
2dd5fa9dd4 ci: add Node.js 20 in the test matrix
Reference: https://github.com/nodejs/Release
2023-06-22 07:55:32 +02:00
Damien Arrachequesne
a5dff0ac83 docs(examples): increase httpd ProxyTimeout value (2) 2023-06-21 00:07:44 +02:00
Damien Arrachequesne
3035c25982 docs(examples): increase httpd ProxyTimeout value
With a value that is too small, the HTTP long-polling request receives
an HTTP 502 response code and the connection gets closed.
2023-06-20 23:57:46 +02:00
Damien Arrachequesne
63f181cc12 feat: serve client bundles with CORS headers
The version of the `cors` package matches the one used by `engine.io`.

Related: https://github.com/socketio/socket.io/issues/3552
2023-06-20 14:32:59 +02:00
Damien Arrachequesne
a250e283da chore: bump engine.io to version 6.5.0
Diff: https://github.com/socketio/engine.io/compare/6.4.2...6.5.0
Release notes: https://github.com/socketio/engine.io/releases/tag/6.5.0
2023-06-20 09:17:10 +02:00
SyedTayyabUlMazhar
e5c62cad60 fix: remove the Partial modifier from the socket.data type (#4740)
Wrapping SocketData with Partial causes issues when reading data even
if you've made sure to pass all values. If someone want to make their
type Partial or make only a few properties optional, they can do so in
their own type instead.

Related: https://github.com/socketio/socket.io/issues/4537
2023-06-20 07:49:02 +02:00
Damien Arrachequesne
01d37624a8 docs(changelog): update the version range of the engine.io dependency 2023-05-31 11:28:00 +02:00
Damien Arrachequesne
faf914c9ab chore(release): 4.6.2
Diff: https://github.com/socketio/socket.io/compare/4.6.1...4.6.2
2023-05-31 11:15:41 +02:00
Damien Arrachequesne
15af22fc22 refactor: add a noop handler for the error event
We should reduce the scope of the "event" error in the next major
version, as it is overloaded today:

- it can be sent by the client (`socket.emit("error")`, which is a perfectly valid event name)
- it can be emitted when the connection encounters an error (an invalid packet for example)
- it can be emitted when a packet is rejected in a middleware (`socket.use()`)

Related: https://github.com/socketio/socket.io/issues/2047
2023-05-24 10:47:52 +02:00
Damien Arrachequesne
d3658944e5 chore: bump socket.io-parser to version 4.2.3
Reference: https://github.com/advisories/GHSA-cqmj-92xf-r6r9
2023-05-24 07:27:12 +02:00
Damien Arrachequesne
12b0de4f52 chore: bump engine.io to version 6.4.2
Reference: https://github.com/advisories/GHSA-q9mw-68c2-j6m5

Related: https://github.com/socketio/socket.io/issues/4711
2023-05-10 10:20:42 +02:00
Mateusz Burzyński
3d44aae381 fix(exports): move types condition to the top (#4698)
Related: https://github.com/microsoft/TypeScript/issues/50762
2023-05-04 07:27:09 +02:00
Damien Arrachequesne
cbf0362476 docs(examples): bump dependencies for the private messaging example
Related: https://github.com/socketio/socket.io/issues/4681
2023-05-02 18:07:07 +02:00
Damien Arrachequesne
59280da20b docs(examples): update examples to docker compose v2
Reference: https://docs.docker.com/compose/

Related: https://github.com/socketio/socket.io/discussions/4669
2023-04-07 15:57:20 +02:00
Damien Arrachequesne
50a4d37cb8 docs(changelog): add version of transitive dependencies 2023-03-27 17:35:42 +02:00
Damien Arrachequesne
6458b2bef1 docs(example): basic WebSocket-only client 2023-03-24 11:17:29 +01:00
Damien Arrachequesne
b56da8a99f docs(examples): upgrade to React 18
Reference: https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html
2023-03-07 08:31:21 +01:00
Damien Arrachequesne
7952312911 chore(release): 4.6.1
Diff: https://github.com/socketio/socket.io/compare/4.6.0...4.6.1
2023-02-20 17:49:41 +01:00
Damien Arrachequesne
0d0a7a22b5 fix: properly handle manually created dynamic namespaces
Namespaces that match the regex of a parent namespace will now be added
as a child of this namespace:

```js
const parentNamespace = io.of(/^\/dynamic-\d+$/);
const childNamespace = io.of("/dynamic-101");
```

Related:

- https://github.com/socketio/socket.io/issues/4615
- https://github.com/socketio/socket.io/issues/4164
- https://github.com/socketio/socket.io/issues/4015
- https://github.com/socketio/socket.io/issues/3960
2023-02-20 01:19:01 +01:00
Damien Arrachequesne
2a8565fd1e refactor: catch errors when trying to restore the connection state 2023-02-20 01:18:08 +01:00
Igor Lins e Silva
d0b22c6302 fix(types): fix nodenext module resolution compatibility (#4625)
The import added in [1] was invalid, because it used an non-exported
class.

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

[1]: d4a9b2cdcb
2023-02-20 01:15:35 +01:00
Nabaraj Subedi
e71f3d7dbe docs: minor style fix (#4619) 2023-02-16 09:25:43 +01:00
Damien Arrachequesne
a2e5d1f77f chore(release): 4.6.0
Diff: https://github.com/socketio/socket.io/compare/4.5.4...4.6.0
2023-02-07 01:07:46 +01:00
Damien Arrachequesne
d8143cc067 refactor: do not persist session if connection state recovery if disabled
This is a follow-up commit of [1]. Without it, adapter.persistSession()
would be called even if the connection state recovery feature was
disabled.

[1]: 54d5ee05a6
2023-02-06 18:03:34 +01:00
Damien Arrachequesne
b2dd7cf660 chore: bump engine.io to version 6.4.0
Diff: https://github.com/socketio/engine.io/compare/6.3.1...6.4.0
Release notes: https://github.com/socketio/engine.io/releases/tag/6.4.0
2023-02-06 17:43:02 +01:00
Damien Arrachequesne
3734b74b45 revert: feat: expose current offset to allow deduplication
This reverts commit 4e64123862.

Using the id of the socket is not possible, since it is lost upon
reconnection (unless connection recovery is successful), so we revert
the previous change.
2023-02-06 17:36:37 +01:00
Edouard Benauw
8aa94991ce feat: add description to the disconnecting and disconnect events (#4622)
See also: b862924b7f
2023-02-04 09:03:01 +01:00
Damien Arrachequesne
4e64123862 feat: expose current offset to allow deduplication
Related: 655dce9755
2023-02-04 08:56:55 +01:00
Damien Arrachequesne
115a9819fd refactor: do not include the pid by default
So that the client knows whether the connection state recovery feature
is enabled.

See also: 54d5ee05a6
2023-01-25 09:39:22 +01:00
Waldemar Schlegel
0c0eb00163 fix: add timeout method to remote socket (#4558)
The RemoteSocket interface, which is returned when the client is
connected on another Socket.IO server of the cluster, was lacking the
`timeout()` method.

Syntax:

```js
const sockets = await io.fetchSockets();

for (const socket of sockets) {
  if (someCondition) {
    socket.timeout(1000).emit("some-event", (err) => {
      if (err) {
        // the client did not acknowledge the event in the given delay
      }
    });
  }
}
```

Related: https://github.com/socketio/socket.io/issues/4595
2023-01-24 09:24:19 +01:00
Damien Arrachequesne
f8640d9451 refactor: export DisconnectReason type
Related: https://github.com/socketio/socket.io/issues/4556
2023-01-23 09:27:32 +01:00
Damien Arrachequesne
93d446a545 refactor: add charset when serving the bundle files
Reference: https://developer.mozilla.org/fr/docs/Web/HTTP/Headers/Content-Type

Related: https://github.com/socketio/socket.io/discussions/4589
2023-01-23 09:27:06 +01:00
Damien Arrachequesne
184f3cf7af feat: add promise-based acknowledgements
This commit adds some syntactic sugar around acknowledgements:

- `emitWithAck()`

```js
try {
  const responses = await io.timeout(1000).emitWithAck("some-event");
  console.log(responses); // one response per client
} catch (e) {
  // some clients did not acknowledge the event in the given delay
}

io.on("connection", async (socket) => {
    // without timeout
  const response = await socket.emitWithAck("hello", "world");

  // with a specific timeout
  try {
    const response = await socket.timeout(1000).emitWithAck("hello", "world");
  } catch (err) {
    // the client did not acknowledge the event in the given delay
  }
});
```

- `serverSideEmitWithAck()`

```js
try {
  const responses = await io.timeout(1000).serverSideEmitWithAck("some-event");
  console.log(responses); // one response per server (except itself)
} catch (e) {
  // some servers did not acknowledge the event in the given delay
}
```

Related:

- https://github.com/socketio/socket.io/issues/4175
- https://github.com/socketio/socket.io/issues/4577
- https://github.com/socketio/socket.io/issues/4583
2023-01-23 09:06:25 +01:00
Steve Baum
5d9220b69a feat: add the ability to clean up empty child namespaces (#4602)
This commit adds a new option, "cleanupEmptyChildNamespaces". With this
option enabled (disabled by default), when a socket disconnects from a
dynamic namespace and if there are no other sockets connected to it
then the namespace will be cleaned up and its adapter will be closed.

Note: the namespace can be connected to later (it will be recreated)

Related: https://github.com/socketio/socket.io-redis-adapter/issues/480
2023-01-23 07:56:14 +01:00
Damien Arrachequesne
129883958a test: add test with onAnyOutgoing() and binary attachments
Related:

- https://github.com/socketio/socket.io/issues/4374
- ae8dd88995
2023-01-19 12:05:35 +01:00
Damien Arrachequesne
6c27b8b0a6 test: add test with socket.disconnect(true)
Related: a65a047526
2023-01-19 11:53:53 +01:00
Damien Arrachequesne
f3ada7d8cc fix(typings): properly type emits with timeout
When emitting with a timeout (added in version 4.4.0), the "err"
argument was not properly typed and would require to split the client
and server typings. It will now be automatically inferred as an Error
object.

Workaround for previous versions:

```ts
type WithTimeoutAck<isEmitter extends boolean, args extends any[]> = isEmitter extends true ? [Error, ...args] : args;

interface ClientToServerEvents<isEmitter extends boolean = false> {
    withAck: (data: { argName: boolean }, callback: (...args: WithTimeoutAck<isEmitter, [string]>) => void) => void;
}

interface ServerToClientEvents<isEmitter extends boolean = false> {

}

const io = new Server<ClientToServerEvents, ServerToClientEvents<true>>(3000);

io.on("connection", (socket) => {
    socket.on("withAck", (val, cb) => {
        cb("123");
    });
});

const socket: Socket<ServerToClientEvents, ClientToServerEvents<true>> = ioc("http://localhost:3000");

socket.timeout(100).emit("withAck", { argName: true }, (err, val) => {
  // ...
});
```

Related: https://github.com/socketio/socket.io-client/issues/1555
2023-01-19 11:48:18 +01:00
Marc Jansing
a21ad88828 docs(changelog): add note about maxHttpBufferSize default value (#4596)
Reference: https://github.com/socketio/socket.io/releases/tag/2.5.0
2023-01-18 08:16:27 +01:00
Damien Arrachequesne
54d5ee05a6 feat: implement connection state recovery
Connection state recovery allows a client to reconnect after a
temporary disconnection and restore its state:

- id
- rooms
- data
- missed packets

Usage:

```js
import { Server } from "socket.io";

const io = new Server({
  connectionStateRecovery: {
    // default values
    maxDisconnectionDuration: 2 * 60 * 1000,
    skipMiddlewares: true,
  },
});

io.on("connection", (socket) => {
  console.log(socket.recovered); // whether the state was recovered or not
});
```

Here's how it works:

- the server sends a session ID during the handshake (which is
different from the current `id` attribute, which is public and can be
freely shared)

- the server also includes an offset in each packet (added at the end
of the data array, for backward compatibility)

- upon temporary disconnection, the server stores the client state for
a given delay (implemented at the adapter level)

- upon reconnection, the client sends both the session ID and the last
offset it has processed, and the server tries to restore the state

A few notes:

- the base adapter exposes two additional methods, persistSession() and
restoreSession(), that must be implemented by the other adapters in
order to allow the feature to work within a cluster

See: f5294126a8

- acknowledgements are not affected, because it won't work if the
client reconnects on another server (as the ack id is local)

- any disconnection that lasts longer than the
`maxDisconnectionDuration` value will result in a new session, so users
will still need to care for the state reconciliation between the server
and the client

Related: https://github.com/socketio/socket.io/discussions/4510
2023-01-12 12:21:56 +01:00
Damien Arrachequesne
da2b542797 perf: precompute the WebSocket frames when broadcasting
Note:

- only packets without binary attachments are affected
- the permessage-deflate extension must be disabled (which is the default)

Related:

- 5f7b47d40f
- 5e34722b0b
2023-01-12 08:50:07 +01:00
Tristan F
b7d54dbe8d docs: add Rust client implementation (#4592)
client-only implementation -- it *may* add server-side support in the future.
2023-01-12 06:26:22 +01:00
Tristan F
d4a9b2cdcb refactor(typings): add types for io.engine (#4591)
This adds typings for the socket.io engine field, which offers better
IntelliSense when retrieving the server, as well as more confidence on
the developer-side of what types of fields are entering the server.

Related: https://github.com/socketio/socket.io/issues/4590
2023-01-11 10:45:57 +01:00
Damien Arrachequesne
547c541fb9 chore: add security policy 2022-12-14 07:47:51 +01:00
Damien Arrachequesne
3b7ced7af7 chore(release): 4.5.4
Diff: https://github.com/socketio/socket.io/compare/4.5.3...4.5.4
2022-11-22 22:45:13 +01:00
Damien Arrachequesne
c00bb9564c chore: bump engine.io to version 6.2.1
In order to fix CVE-2022-41940.

See also: https://github.com/socketio/engine.io/security/advisories/GHSA-r7qp-cfhv-p84w
2022-11-22 22:35:53 +01:00
Damien Arrachequesne
57e5f25e26 chore: bump socket.io-parser to version 4.2.1
In order to fix CVE-2022-2421.

See also: https://github.com/advisories/GHSA-qm95-pgcg-qqfq
2022-11-22 22:32:09 +01:00
Damien Arrachequesne
f4b698418a docs: add missing versions in the changelog 2022-11-02 08:40:26 +01:00
Damien Arrachequesne
945c84be47 chore(release): 4.5.3
Diff: https://github.com/socketio/socket.io/compare/4.5.2...4.5.3
2022-10-15 07:14:45 +02:00
Damien Arrachequesne
d3d0a2d5be fix(typings): accept an HTTP2 server in the constructor
Related:

- https://github.com/socketio/socket.io/issues/4434
- https://github.com/socketio/socket.io/issues/4494
2022-10-14 11:24:36 +02:00
Damien Arrachequesne
19b225b0c8 docs(examples): update dependencies of the basic CRUD example
Related: https://github.com/socketio/socket.io/issues/4409
2022-10-14 10:39:40 +02:00
Damien Arrachequesne
8fae95dd18 docs: add jsdoc for each public method 2022-10-14 10:30:08 +02:00
Damien Arrachequesne
e6f6b906db docs: add deprecation notice for the allSockets() method 2022-10-13 15:02:23 +02:00
Damien Arrachequesne
596eb88af7 ci: upgrade to actions/checkout@3 and actions/setup-node@3
Reference: https://github.blog/changelog/2022-09-22-github-actions-all-actions-will-begin-running-on-node16-instead-of-node12/
2022-10-13 14:50:09 +02:00
Damien Arrachequesne
e357daf585 fix(typings): apply types to "io.timeout(...).emit()" calls
Typed events were not applied when calling "io.timeout(...).emit()".

Related: https://github.com/socketio/socket.io-client/issues/1555#issuecomment-1277289733

Reference: https://socket.io/docs/v4/typescript/
2022-10-13 14:50:09 +02:00
Damien Arrachequesne
10fa4a2690 refactor: add list of possible disconnection reasons
Note: some disconnection reasons could be merged in the next major
release, i.e. the Deno impl does not have "forced server close" and
"server shutting down"

Related: https://github.com/socketio/socket.io/issues/4387
2022-09-13 08:25:13 +02:00
Damien Arrachequesne
8be95b3bd3 chore(release): 4.5.2
Diff: https://github.com/socketio/socket.io/compare/4.5.1...4.5.2
2022-09-02 23:46:14 +01:00
Damien Arrachequesne
ba497ee3eb fix(uws): prevent the server from crashing after upgrade
This should fix a rare case where the Engine.IO connection was upgraded
to WebSocket while the Socket.IO socket was disconnected, which would
result in the following exception:

> TypeError: Cannot read properties of undefined (reading 'forEach')
>    at subscribe (/node_modules/socket.io/dist/uws.js:87:11)
>    at Socket.<anonymous> (/node_modules/socket.io/dist/uws.js:28:17)
>    at Socket.emit (node:events:402:35)
>    at WebSocket.onPacket (/node_modules/engine.io/build/socket.js:214:22)
>    at WebSocket.emit (node:events:390:28)
>    at WebSocket.onPacket (/node_modules/engine.io/build/transport.js:92:14)
>    at WebSocket.onData (/node_modules/engine.io/build/transport.js:101:14)
>    at message (/node_modules/engine.io/build/userver.js:56:30)

Related: https://github.com/socketio/socket.io/issues/4443
2022-09-02 23:42:26 +01:00
Alex
28038715cb ci: add explicit permissions to workflow (#4466)
Reference: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
2022-09-02 23:10:19 +01:00
Daniel Rosenwasser
134226e96c refactor: add missing constraints (#4431)
See also: https://github.com/microsoft/TypeScript/issues/49489
2022-09-01 21:13:55 +01:00
Damien Arrachequesne
9890b036cf chore: bump dependencies
Production:

- socket.io-parser: ~4.0.4 => ~4.2.0

Development:

- superagent: ^6.1.0 => ^8.0.0
- tsd: ^0.17.0 => ^0.21.0

Related: https://github.com/socketio/socket.io/issues/3709
2022-06-27 09:16:08 +02:00
Damien Arrachequesne
713a6b451b chore: bump mocha to version 10.0.0
Related: https://github.com/socketio/socket.io/issues/3710
2022-06-27 09:00:31 +02:00
Damien Arrachequesne
18f3fdab12 fix: prevent the socket from joining a room after disconnection
Calling `socket.join()` after disconnection would lead to a memory
leak, because the room was never removed from the memory:

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

Related:

- https://github.com/socketio/socket.io/issues/4067
- https://github.com/socketio/socket.io/issues/4380
2022-05-25 23:18:42 +02:00
Damien Arrachequesne
5ab8289c0a chore(release): 4.5.1
Diff: https://github.com/socketio/socket.io/compare/4.5.0...4.5.1
2022-05-17 23:38:07 +02:00
Damien Arrachequesne
30430f0985 fix: forward the local flag to the adapter when using fetchSockets()
Related:

- https://github.com/socketio/socket.io/issues/4359
- https://github.com/socketio/socket.io-redis-adapter/issues/454
2022-05-03 15:41:24 +02:00
h110m
9b43c9167c fix(typings): add HTTPS server to accepted types (#4351) 2022-05-03 08:15:22 +02:00
Damien Arrachequesne
8ecfcba5c1 chore(release): 4.5.0
Diff: https://github.com/socketio/socket.io/compare/4.4.1...4.5.0
2022-04-24 00:45:57 +02:00
Damien Arrachequesne
572133a58d docs(examples): update example with webpack 2022-04-22 23:10:01 +02:00
Damien Arrachequesne
6e1bb62982 chore: bump engine.io to version 6.2.0
Release notes: https://github.com/socketio/engine.io/releases/tag/6.2.0
Diff: https://github.com/socketio/engine.io/compare/6.1.3...6.2.0
2022-04-22 22:43:31 +02:00
Damien Arrachequesne
06e6838b18 docs(examples): add server bundling example with rollup
Related: https://github.com/socketio/socket.io/issues/4329
2022-04-22 22:40:15 +02:00
WD
1f03a44d1f docs(examples): update create-react-app example (#4347) 2022-04-20 22:47:34 +02:00
Damien Arrachequesne
be3d7f0f1f docs(examples): add TODO example with Postgres and Node.js cluster 2022-04-07 12:35:00 +02:00
Damien Arrachequesne
d12aab2d69 docs(examples): add example with express-session
Related: https://github.com/socketio/socket.io/issues/3933
2022-04-02 11:08:26 +02:00
Damien Arrachequesne
9f758689f6 docs(examples): pin the version of karma-jasmine-html-reporter
Related:

- https://github.com/socketio/socket.io/issues/4325
- https://github.com/dfederm/karma-jasmine-html-reporter/issues/54
2022-04-01 14:42:34 +02:00
Damien Arrachequesne
0b35dc77c0 refactor: make the protocol implementation stricter
This commit handles several edge cases that were silently ignored
before:

- receiving several CONNECT packets during a session
- receiving any packet without CONNECT packet first
2022-03-31 12:24:31 +02:00
Damien Arrachequesne
531104d332 feat: add support for catch-all listeners for outgoing packets
This is similar to `onAny()`, but for outgoing packets.

Syntax:

```js
socket.onAnyOutgoing((event, ...args) => {
  console.log(event);
});
```
2022-03-31 10:35:09 +02:00
Damien Arrachequesne
8b204570a9 feat: broadcast and expect multiple acks
Syntax:

```js
io.timeout(1000).emit("some-event", (err, responses) => {
  // ...
});
```

The adapter exposes two additional methods:

- `broadcastWithAck(packets, opts, clientCountCallback, ack)`

Similar to `broadcast(packets, opts)`, but:

* `clientCountCallback()` is called with the number of clients that
  received the packet (can be called several times in a cluster)
* `ack()` is called for each client response

- `serverCount()`

It returns the number of Socket.IO servers in the cluster (1 for the
in-memory adapter).

Those two methods will be implemented in the other adapters (Redis,
Postgres, MongoDB, ...).

Related:

- https://github.com/socketio/socket.io/issues/1811
- https://github.com/socketio/socket.io/issues/4163
- https://github.com/socketio/socket.io-redis-adapter/issues/445
2022-03-31 07:49:09 +02:00
Damien Arrachequesne
0b7d70ca42 chore: bump lockfile to v2 2022-03-30 08:15:41 +02:00
Szegedi Ádám
2f96438952 chore: bump engine.io version to fix CVE-2022-21676 (#4262)
Related: https://github.com/socketio/engine.io/security/advisories/GHSA-273r-mgr4-v34f
2022-01-25 22:18:18 +01:00
Chris Swithinbank
02c87a8561 fix(typings): ensure compatibility with TypeScript 3.x (#4259)
Labeled tuple elements were added in TypeScript 4.0.

Reference: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-0.html#labeled-tuple-elements

Related: 44e20ba5bf
2022-01-25 01:25:05 +01:00
Damien Arrachequesne
37b6d8fff0 chore: update default label for bug reports 2022-01-10 08:55:56 +01:00
Damien Arrachequesne
af54565b2d docs: remove broken badges
Related: https://github.com/socketio/socket.io/issues/4242
2022-01-10 08:03:53 +01:00
Damien Arrachequesne
aa5312a4b6 chore: revert to lockfile v1
Updating to v2 fails in the CI on Node.js 12 & 14 with the following
error:

> npm ERR! Error while executing:
> npm ERR! /usr/bin/git ls-remote -h -t ssh://git@github.com/uNetworking/uWebSockets.js.git
> npm ERR!
> npm ERR! Warning: Permanently added the RSA host key for IP address '140.82.113.3' to the list of known hosts.
> npm ERR! git@github.com: Permission denied (publickey).
> npm ERR! fatal: Could not read from remote repository.
> npm ERR!
> npm ERR! Please make sure you have the correct access rights
> npm ERR! and the repository exists.
> npm ERR!
> npm ERR! exited with error code: 128

So we will revert the change for now.
2022-01-06 08:01:00 +01:00
Damien Arrachequesne
c82a4bdf1f chore(release): 4.4.1
Diff: https://github.com/socketio/socket.io/compare/4.4.0...4.4.1
2022-01-06 07:32:03 +01:00
Orkhan Alikhanov
770ee5949f fix(types): make RemoteSocket.data type safe (#4234)
Related:

- https://github.com/socketio/socket.io/issues/4229
- fe8730ca0f
2022-01-06 07:14:55 +01:00
Damien Arrachequesne
3bf5d92735 refactor: add note about fetchSockets() for parent namespaces
Related: https://github.com/socketio/socket.io/issues/4235
2022-01-05 08:50:40 +01:00
Shayan Yousefi
fc82e44f73 refactor(typings): export Event type (#4215)
So that it can be used by the end users:

```ts
const myMiddleware = ([eventName, ...args]: Event, next: (err?: Error) => void) => {
  console.log(eventName); // inferred as string
  next();
}

io.on("connection", (socket) => {
  socket.use(myMiddleware);
});
```
2022-01-05 08:08:18 +01:00
Damien Arrachequesne
c840bad43a test: fix flaky tests 2022-01-05 08:00:55 +01:00
Orkhan Alikhanov
f2b8de7191 fix(typings): pass SocketData type to custom namespaces (#4233)
The `SocketData` type was only available on the main namespace.

Related: https://github.com/socketio/socket.io/issues/4229
See also: fe8730ca0f
2022-01-04 09:09:42 +01:00
Gray Zhang
51784d0305 chore: add types to exports field to be compatible with nodenext module resolution (#4228)
See [1] for detail, in `nodenext` module resolution it requires a
`types` field in `exports` with full filename including extension.

[1]: https://github.com/microsoft/TypeScript/issues/46770#issuecomment-966612103
2021-12-28 10:27:08 +01:00
Damien Arrachequesne
c196689545 docs: fix basic crud example
Related: https://github.com/socketio/socket.io/issues/4213
2021-12-16 23:00:20 +01:00
Mikhail Dudin
7a70f63499 docs: fix reconnection handling in the chat demo app (#4189) 2021-12-01 00:03:43 +01:00
anderslatif
e5897dd7dc docs: add usage with ES modules (#4195) 2021-12-01 00:02:13 +01:00
Damien Arrachequesne
2071a66c5a docs: simplify nginx cluster example
- remove useless Dockerfile
- clean format
- migrate to @socket.io/redis-adapter
2021-11-24 18:15:26 +01:00
Damien Arrachequesne
0f11c4745f chore(release): 4.4.0
Diff: https://github.com/socketio/socket.io/compare/4.3.2...4.4.0
2021-11-18 14:10:19 +01:00
Damien Arrachequesne
b839a3b400 fix: prevent double ack when emitting with a timeout
The ack was not properly removed upon timeout, and could be called
twice.

Related: f0ed42f18c
2021-11-18 14:03:07 +01:00
Damien Arrachequesne
f0ed42f18c feat: add timeout feature
Usage:

```js
socket.timeout(5000).emit("my-event", (err) => {
  if (err) {
    // the client did not acknowledge the event in the given delay
  }
});
```
2021-11-16 20:07:53 +01:00
Damien Arrachequesne
b7213e71e4 test: fix flaky test
`srv.close()` only closes the underlying HTTP server, but this does not
terminate the existing WebSocket connections.

Reference: https://nodejs.org/api/http.html#serverclosecallback
2021-11-16 15:58:55 +01:00
Damien Arrachequesne
2da82103d2 test: add test for volatile packet with binary
See also: 88eee5948a
2021-11-16 15:57:32 +01:00
Damien Arrachequesne
02b0f73e2c fix: only set 'connected' to true after middleware execution
The Socket instance is only considered connected when the "connection"
event is emitted, and not during the middleware(s) execution.

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

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

Related: https://github.com/socketio/socket.io/issues/4129
2021-11-12 07:31:52 +01:00
Damien Arrachequesne
c0d8c5ab23 feat: add an implementation based on uWebSockets.js
Usage:

```js
const { App } = require("uWebSockets.js");
const { Server } = require("socket.io");

const app = new App();
const server = new Server();

server.attachApp(app);

app.listen(3000);
```

The Adapter prototype is updated so we can benefit from the publish
functionality of uWebSockets.js, so this will apply to all adapters
extending the default adapter.

Reference: https://github.com/uNetworking/uWebSockets.js

Related:

- https://github.com/socketio/socket.io/issues/3601
- https://github.com/socketio/engine.io/issues/578
2021-11-12 07:01:55 +01:00
Nikita Kolmogorov
fe8730ca0f feat: add type information to socket.data (#4159)
Usage:

```js
interface SocketData {
  name: string;
  age: number;
}

const io = new Server<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>();

io.on("connection", (socket) => {
  socket.data.name = "john";
  socket.data.age = 42;
});
```
2021-11-08 15:21:48 +01:00
Damien Arrachequesne
ed8483da4d chore(release): 4.3.2
Diff: https://github.com/socketio/socket.io/compare/4.3.1...4.3.2
2021-11-08 06:39:20 +01:00
Sebastiaan Marynissen
9d86397243 fix: fix race condition in dynamic namespaces (#4137)
Using an async operation with `io.use()` could lead to the creation of
several instances of a same namespace, each of them overriding the
previous one.

Example:

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

Related: https://github.com/socketio/socket.io/pull/4136
2021-10-24 07:46:29 +02:00
Naseem
44e20ba5bf refactor: add event type for use() (#4138) 2021-10-24 07:19:43 +02:00
Damien Arrachequesne
ccc5ec39a8 chore(release): 4.3.1
Diff: https://github.com/socketio/socket.io/compare/4.3.0...4.3.1
2021-10-17 00:02:16 +02:00
Josh Field
0ef2a4d02c fix: fix server attachment (#4127)
The check excluded an HTTPS server from being properly attached.

Related: https://github.com/socketio/socket.io/issues/4124
2021-10-16 23:58:55 +02:00
Damien Arrachequesne
95810aa62d chore(release): 4.3.0
Diff: https://github.com/socketio/socket.io/compare/4.2.0...4.3.0
2021-10-14 14:59:13 +02:00
Damien Arrachequesne
60edecb3bd feat: serve ESM bundle
Related:

- 0661564dc2
- https://github.com/socketio/socket.io-client/issues/1198
2021-10-13 18:17:12 +02:00
Damien Arrachequesne
eb5fdbd03e chore: bump engine.io to version 6.0.0
Release notes: https://github.com/socketio/engine.io/releases/tag/6.0.0
Diff: https://github.com/socketio/engine.io/compare/5.2.0...6.0.0
2021-10-12 00:05:10 +02:00
roh-kan
4974e9077c docs: update .NET client library link (#4115) 2021-10-08 14:18:03 +02:00
douira
033c5d399a fix(typings): add name field to cookie option (#4099)
Reference: 18a6eb89fb/lib/server.js (L355)
2021-09-20 09:13:38 +02:00
Damien Arrachequesne
7a74b66872 test: remove hardcoded ports
Related: https://github.com/socketio/socket.io/issues/3447
2021-09-09 08:57:11 +02:00
Damien Arrachequesne
dc81fcf461 fix: send volatile packets with binary attachments
The binary attachments of volatile packets were discarded (only the
header packet was sent) due to a bug introduced by [1].

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

[1]: dc381b72c6
2021-09-09 08:55:51 +02:00
Damien Arrachequesne
c100b7b61c chore(release): 4.2.0
Diff: https://github.com/socketio/socket.io/compare/4.1.3...4.2.0
2021-08-30 09:21:00 +02:00
Damien Arrachequesne
f03eeca39a chore: bump dependencies 2021-08-30 08:27:46 +02:00
Damien Arrachequesne
d8cc8aef7e docs: update the link of the Repl.it badge
The link will now point towards a sample project, instead of the root
repository.

Related: https://github.com/socketio/socket.io/issues/3934
2021-08-30 08:03:55 +02:00
Damien Arrachequesne
ccfd8caba6 fix(typings): allow async listener in typed events
So that:

```ts
socket.on("my-event", async () => {
  // ...
});
```

is valid under the @typescript-eslint/no-misused-promises rule.

Related: https://github.com/socketio/socket.io-client/issues/1486
2021-08-30 08:01:29 +02:00
Tim Düsterhus
24fee27ba3 feat: ignore the query string when serving client JavaScript (#4024)
Related: https://github.com/socketio/socket.io/issues/4023
2021-08-30 07:59:47 +02:00
brownman
310f8557a7 docs(examples): add missing module (#4018)
Fixes the following error:

> test/todo-management/todo.tests.ts:275:3 - error TS2582: Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.

Co-authored-by: brownman <brownman@users.noreply.github.com>
2021-07-15 21:48:20 +02:00
Damien Arrachequesne
dbd2a07cda chore(release): 4.1.3
Diff: https://github.com/socketio/socket.io/compare/4.1.2...4.1.3
2021-07-10 12:13:15 +02:00
Damien Arrachequesne
94e27cd072 fix: fix io.except() method
Previously, calling `io.except("theroom").emit(...)` did not exclude
the sockets in the given room.

This method was forgotten in [1].

[1]: ac9e8ca6c7
2021-07-10 11:48:46 +02:00
Damien Arrachequesne
a4dffc6527 fix: remove x-sourcemap header
This header is useless, as the client bundle already contains a
sourceMappingURL field.

Besides, Firefox prints the following warning:

> <url> is being assigned a //# sourceMappingURL, but already has one

Related: https://github.com/socketio/socket.io/issues/3958
2021-07-04 00:51:41 +02:00
Damien Arrachequesne
7c44893d78 chore: bump dependencies 2021-07-04 00:37:35 +02:00
Daniele TDC
b833f918c8 ci: update to node 16 (#3990)
See also: https://github.com/nodejs/Release#release-schedule
2021-06-28 09:09:44 +02:00
Daniele TDC
24d8d1f67f ci: update setup-node step (#3986) 2021-06-24 14:53:46 +02:00
Damien Arrachequesne
6f2a50b932 docs(examples): update example to webpack 5 2021-06-15 22:35:06 +02:00
Damien Arrachequesne
1633150b2b chore(release): 4.1.2
Diff: https://github.com/socketio/socket.io/compare/4.1.1...4.1.2
2021-05-17 23:17:31 +02:00
Damien Arrachequesne
0cb6ac95b4 fix(typings): ensure compatibility with TypeScript 3.x
Labeled tuple elements were added in TypeScript 4.0.

Reference: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-0.html#labeled-tuple-elements

Related: https://github.com/socketio/socket.io/issues/3916
2021-05-17 23:15:22 +02:00
Damien Arrachequesne
a2cf2486c3 fix: ensure compatibility with previous versions of the adapter
Using `socket.io@4.1.0` with `socket.io-adapter@2.2.0` would lead to
the following error:

> Uncaught Error: unknown packet type NaN

Because the packet would be encoded twice, resulting in "undefined".

See also:

- 5579d40c24
- dc381b72c6

Related:

- https://github.com/socketio/socket.io/issues/3922
- https://github.com/socketio/socket.io/issues/3927
2021-05-17 23:14:36 +02:00
Damien Arrachequesne
995f38f4cc chore(release): 4.1.1
Diff: https://github.com/socketio/socket.io/compare/4.1.0...4.1.1
2021-05-12 00:04:52 +02:00
Damien Arrachequesne
891b1870e9 fix(typings): properly type the adapter attribute
Related: https://github.com/socketio/socket.io/issues/3796
2021-05-11 23:59:44 +02:00
Damien Arrachequesne
b84ed1e41c fix(typings): properly type server-side events
See also: 93cce05fb3
2021-05-11 23:59:18 +02:00
Damien Arrachequesne
fb6b0efec9 chore(release): 4.1.0
Diff: https://github.com/socketio/socket.io/compare/4.0.2...4.1.0
2021-05-11 09:27:52 +02:00
Damien Arrachequesne
95d9e4a42f test: fix randomly failing test 2021-05-11 00:06:03 +02:00
Damien Arrachequesne
499c89250d feat: notify upon namespace creation
A "new_namespace" event will be emitted when a new namespace is created:

```js
io.on("new_namespace", (namespace) => {
  // ...
});
```

This could be used for example for registering the same middleware for
each namespace.

See https://github.com/socketio/socket.io/issues/3851
2021-05-11 00:09:18 +02:00
Damien Arrachequesne
93cce05fb3 feat: add support for inter-server communication
Syntax:

```js
// server A
io.serverSideEmit("hello", "world");

// server B
io.on("hello", (arg) => {
  console.log(arg); // prints "world"
});
```

With acknowledgements:

```js
// server A
io.serverSideEmit("hello", "world", (err, responses) => {
  console.log(responses); // prints ["hi"]
});

// server B
io.on("hello", (arg, callback) => {
  callback("hi");
});
```

This feature replaces the customHook/customRequest API from the Redis
adapter: https://github.com/socketio/socket.io-redis/issues/370
2021-05-11 00:07:20 +02:00
Damien Arrachequesne
dc381b72c6 perf: add support for the "wsPreEncoded" writing option
Packets that are sent to multiple clients will now be pre-encoded for
the WebSocket transport (which means simply prepending "4" - which is
the "message" packet type in Engine.IO).

Note: buffers are not pre-encoded, since they are sent without
modification over the WebSocket connection

See also: 7706b123df

engine.io diff: https://github.com/socketio/engine.io/compare/5.0.0...5.1.0
2021-05-11 00:06:03 +02:00
Damien Arrachequesne
9fff03487c chore(release): 4.0.2
Diff: https://github.com/socketio/socket.io/compare/4.0.1...4.0.2
2021-05-06 14:38:26 +02:00
Damien Arrachequesne
b81ce4c9d0 fix(typings): make "engine" attribute public 2021-05-06 14:36:25 +02:00
Damien Arrachequesne
d65b6ee84c fix: properly export the Socket class
Before this change, `require("socket.io").Socket` would return
"undefined".

Note: having access to the Socket class allows users to modify its
prototype.

Related: https://github.com/socketio/socket.io/issues/3726
2021-05-06 14:36:12 +02:00
Damien Arrachequesne
3665aada47 docs(examples): basic CRUD application
See also: https://socket.io/get-started/basic-crud-application/
2021-04-23 00:08:18 +02:00
Damien Arrachequesne
1faa7e3aea chore(release): 4.0.1
Diff: https://github.com/socketio/socket.io/compare/4.0.0...4.0.1
2021-04-01 01:25:56 +02:00
Maxime Kjaer
a11152f42b fix(typings): add fallback to untyped event listener (#3834)
Related: https://github.com/socketio/socket.io/issues/3833
2021-03-31 11:37:37 +02:00
Maxime Kjaer
259f29720b docs(examples): remove unnecessary type annotations (#3855)
Typed events in Socket.IO 4.0 remove the need for writing type
annotations in callbacks of reserved events.
2021-03-24 23:12:22 +01:00
Damien Arrachequesne
b4ae8d2e19 docs(examples): update all examples to Socket.IO v4 2021-03-18 15:07:04 +01:00
Adán Toscano
64be1c9985 docs(examples): fix chat example (#3787) 2021-03-18 14:06:13 +01:00
Solomon English
1a72ae4fe2 fix(typings): update return type from emit (#3843)
```
(channel ? io.to(channel) : io).emit("stuff", message);
```

would no longer compile.

Related: https://github.com/socketio/socket.io/issues/3844
2021-03-18 11:36:34 +01:00
Damien Arrachequesne
5eaeffc8e2 chore(release): 4.0.0
Diff: https://github.com/socketio/socket.io/compare/3.1.2...4.0.0
2021-03-10 12:43:53 +01:00
Damien Arrachequesne
1b6d6de4ed chore: include Engine.IO v5
Release notes: https://github.com/socketio/engine.io/releases/tag/5.0.0
2021-03-10 11:14:33 +01:00
Maxime Kjaer
0107510ba8 feat: add support for typed events (#3822)
Syntax:

```ts
interface ClientToServerEvents {
  "my-event": (a: number, b: string, c: number[]) => void;
}

interface ServerToClientEvents {
  hello: (message: string) => void;
}

const io = new Server<ClientToServerEvents, ServerToClientEvents>(httpServer);

io.emit("hello", "world");

io.on("connection", (socket) => {
  socket.on("my-event", (a, b, c) => {
    // ...
  });

  socket.emit("hello", "again");
});
```

The events are not typed by default (inferred as any), so this change
is backward compatible.

Note: we could also have reused the method here ([1]) to add types to
the EventEmitter, instead of creating a StrictEventEmitter class.

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

[1]: https://github.com/binier/tiny-typed-emitter
2021-03-10 00:18:13 +01:00
Damien Arrachequesne
b25495c069 feat: add some utility methods
This commit adds the following methods:

- fetchSockets: returns the matching socket instances

Syntax:

```js
// return all Socket instances
const sockets = await io.fetchSockets();

// return all Socket instances of the "admin" namespace in the "room1" room
const sockets = await io.of("/admin").in("room1").fetchSockets();
```

- socketsJoin: makes the matching socket instances join the specified rooms

Syntax:

```js
// make all Socket instances join the "room1" room
io.socketsJoin("room1");

// make all Socket instances of the "admin" namespace in the "room1" room join the "room2" room
io.of("/admin").in("room1").socketsJoin("room2");
```

- socketsLeave: makes the matching socket instances leave the specified rooms

Syntax:

```js
// make all Socket instances leave the "room1" room
io.socketsLeave("room1");

// make all Socket instances of the "admin" namespace in the "room1" room leave the "room2" room
io.of("/admin").in("room1").socketsLeave("room2");
```

- disconnectSockets: makes the matching socket instances disconnect

Syntax:

```js
// make all Socket instances disconnect
io.disconnectSockets();

// make all Socket instances of the "admin" namespace in the "room1" room disconnect
io.of("/admin").in("room1").disconnectSockets();
```

Those methods share the same semantics as broadcasting. They will also
work with multiple Socket.IO servers when using the Redis adapter. In
that case, the fetchSockets() method will return a list of RemoteSocket
instances, which expose a subset of the methods and attributes of the
Socket class (the "request" attribute cannot be mocked, for example).

Related:

- https://github.com/socketio/socket.io/issues/3042
- https://github.com/socketio/socket.io/issues/3418
- https://github.com/socketio/socket.io/issues/3570
- https://github.com/socketio/socket.io-redis/issues/283
2021-03-02 11:17:21 +01:00
Damien Arrachequesne
085d1de9df feat: allow to pass an array to io.to(...)
In some cases it is necessary to pass an array of rooms instead of a single room.

New syntax:

```
io.to(["room1", "room2"]).except(["room3"]).emit(...);

socket.to(["room1", "room2"]).except(["room3"]).emit(...);
```

Related: https://github.com/socketio/socket.io/issues/3048
2021-03-01 23:20:46 +01:00
Damien Arrachequesne
ac9e8ca6c7 fix: make io.to(...) immutable
Previously, broadcasting to a given room (by calling `io.to()`) would
mutate the io instance, which could lead to surprising behaviors, like:

```js
io.to("room1");
io.to("room2").emit(...); // also sent to room1

// or with async/await
io.to("room3").emit("details", await fetchDetails()); // random behavior: maybe in room3, maybe to all clients
```

Calling `io.to()` (or any other broadcast modifier) will now return an
immutable instance.

Related:

- https://github.com/socketio/socket.io/issues/3431
- https://github.com/socketio/socket.io/issues/3444
2021-03-01 23:17:08 +01:00
Sebastiaan Marynissen
7de2e87e88 feat: allow to exclude specific rooms when broadcasting (#3789)
New syntax:

```
io.except("room1").emit(...);
io.to("room1").except("room2").emit(...);

socket.broadcast.except("room1").emit(...);
socket.to("room1").except("room2").emit(...);
```

Related:

- https://github.com/socketio/socket.io/issues/3629
- https://github.com/socketio/socket.io/issues/3657
2021-03-01 09:30:58 +01:00
Damien Arrachequesne
225ade062a chore(release): 3.1.2
Diff: https://github.com/socketio/socket.io/compare/3.1.1...3.1.2
2021-02-26 01:18:42 +01:00
Damien Arrachequesne
494c64e44f fix: ignore packet received after disconnection
Related: https://github.com/socketio/socket.io/issues/3095
2021-02-26 00:58:30 +01:00
Damien Arrachequesne
67a61e39e6 chore: loosen the version requirement of @types/node
Related: https://github.com/socketio/socket.io/issues/3793
2021-02-26 00:58:01 +01:00
Damien Arrachequesne
7467216e02 docs(examples): 4th and final part of the "private messaging" example
See also: https://socket.io/get-started/private-messaging-part-4/
2021-02-17 00:24:23 +01:00
Damien Arrachequesne
7247b4051f docs(examples): 3rd part of the "private messaging" example
See also: https://socket.io/get-started/private-messaging-part-3/
2021-02-17 00:22:55 +01:00
Damien Arrachequesne
992c9380c3 docs(examples): 2nd part of the "private messaging" example
See also: https://socket.io/get-started/private-messaging-part-2/
2021-02-15 01:09:12 +01:00
Damien Arrachequesne
8b404f424b docs(examples): 1st part of the "private messaging" example 2021-02-10 00:33:39 +01:00
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
Damien Arrachequesne
81c1f4e819 chore(release): 3.0.4
Diff: https://github.com/socketio/socket.io/compare/3.0.3...3.0.4
2020-12-07 12:01:24 +01:00
Damien Arrachequesne
1fba399b17 ci: migrate to GitHub Actions
Due to the recent changes to the Travis CI platform (see [1]), we will
now use GitHub Actions to run the tests.

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

[1]: https://blog.travis-ci.com/2020-11-02-travis-ci-new-billing
2020-12-07 11:37:03 +01:00
Stijn de Witt
4e6d40493d chore: make tests work on Windows (#3708) 2020-11-28 01:55:19 +01:00
Pablo Tejada
28c7cc0856 style(issue-template): fix typo (#3700)
[skip ci]
2020-11-25 11:09:17 +01:00
Damien Arrachequesne
06a2bd313a chore(release): 3.0.3
Diff: https://github.com/socketio/socket.io/compare/3.0.2...3.0.3
2020-11-19 01:31:31 +01:00
Damien Arrachequesne
85ebd356e9 chore: cleanup dist folder before compilation 2020-11-19 01:28:27 +01:00
Damien Arrachequesne
9b6f9711da chore(release): 3.0.2
Diff: https://github.com/socketio/socket.io/compare/3.0.1...3.0.2
2020-11-18 00:53:51 +01:00
Damien Arrachequesne
43705d7a91 fix: merge Engine.IO options
So that the following example:

```js
const io = require('socket.io')({
  pingTimeout: 10000
});

io.listen(3000);
```

behaves the same as:

```js
const io = require('socket.io')(3000, {
  pingTimeout: 10000
});
```

Before this change, the options in the first example were not forwarded
to the Engine.IO constructor, which is not really intuitive.

The previous syntax (which is still valid):

```js
const io = require('socket.io')();

io.listen(3000, {
  pingTimeout: 10000
});
```
2020-11-17 23:33:18 +01:00
Damien Arrachequesne
118cc686a1 chore: add 3rd party types in the list of dependencies
Those types are mandatory for TypeScript users.

Related:

- https://github.com/socketio/socket.io/issues/3690
- https://github.com/microsoft/types-publisher/issues/81#issuecomment-234051345
2020-11-17 11:55:41 +01:00
Damien Arrachequesne
c596e54343 docs(examples): update React Native example
This includes `engine.io-client@4.0.3`, which fixes two issues with
React Native.

See also:

- 177b95fe46
- ccb99e3718

[skip ci]
2020-11-17 09:44:05 +01:00
Damien Arrachequesne
f7e0009120 docs(examples): update TypeScript example
In order to test 50671d984a

[skip ci]
2020-11-09 10:47:50 +01:00
Damien Arrachequesne
e69d0ad602 chore: bump socket.io-client version 2020-11-09 10:32:10 +01:00
Damien Arrachequesne
0317a077be chore(release): 3.0.1
Diff: https://github.com/socketio/socket.io/compare/3.0.0...3.0.1
2020-11-09 10:27:58 +01:00
Damien Arrachequesne
d00c0c0d9d docs(examples): update examples to Socket.IO v3
Other examples need some additional work (Redis adapter migration, ...).
2020-11-09 10:19:40 +01:00
Avi Vahl
f62f180eda fix: export ServerOptions and Namespace types (#3684)
@types/socket.io used to export these.
2020-11-09 08:58:14 +01:00
Damien Arrachequesne
50671d984a fix(typings): update the signature of the emit method
The previous signature was not compatible with EventEmitter.emit(). The typescript compilation threw:

```
node_modules/socket.io/dist/namespace.d.ts(89,5): error TS2416: Property 'emit' in type 'Namespace' is not assignable to the same property in base type 'EventEmitter'.
  Type '(ev: string, ...args: any[]) => Namespace' is not assignable to type '(event: string | symbol, ...args: any[]) => boolean'.
    Type 'Namespace' is not assignable to type 'boolean'.
node_modules/socket.io/dist/socket.d.ts(84,5): error TS2416: Property 'emit' in type 'Socket' is not assignable to the same property in base type 'EventEmitter'.
  Type '(ev: string, ...args: any[]) => this' is not assignable to type '(event: string | symbol, ...args: any[]) => boolean'.
    Type 'this' is not assignable to type 'boolean'.
      Type 'Socket' is not assignable to type 'boolean'.
```

Note: the emit calls cannot be chained anymore:

```js
socket.emit("hello").emit("world"); // will not work anymore
```
2020-11-08 00:07:56 +01:00
Damien Arrachequesne
8a69f15437 chore: restore package-lock.json file 2020-11-05 22:25:35 +01:00
310 changed files with 43775 additions and 32431 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: 'to triage'
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Please fill the following code example:
Socket.IO server version: `x.y.z`
*Server*
```js
import { Server } from "socket.io";
const io = new Server(3000, {});
io.on("connection", (socket) => {
console.log(`connect ${socket.id}`);
socket.on("disconnect", () => {
console.log(`disconnect ${socket.id}`);
});
});
```
Socket.IO client version: `x.y.z`
*Client*
```js
import { io } from "socket.io-client";
const socket = io("ws://localhost:3000/", {});
socket.on("connect", () => {
console.log(`connect ${socket.id}`);
});
socket.on("disconnect", () => {
console.log("disconnect");
});
```
**Expected behavior**
A clear and concise description of what you expected to happen.
**Platform:**
- Device: [e.g. Samsung S8]
- OS: [e.g. Android 9.2]
**Additional context**
Add any other context about the problem here.

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

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

72
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,72 @@
name: CI
on:
push:
pull_request:
schedule:
- cron: '0 0 * * 0'
permissions:
contents: read
jobs:
test-node:
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
matrix:
node-version:
- 16
- 20
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: npm ci
- name: Install TypeScript 4.2
run: npm i typescript@4.2
if: ${{ matrix.node-version == '16' }}
- name: Run tests
run: npm test
env:
CI: true
build-examples:
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
example:
- custom-parsers
- typescript
- webpack-build
- webpack-build-server
- basic-crud-application/angular-client
- basic-crud-application/vue-client
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20
- name: Build ${{ matrix.example }}
run: |
cd examples/${{ matrix.example }}
npm install
npm run build

2
.replit Normal file
View File

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

View File

@@ -1,10 +0,0 @@
language: node_js
sudo: false
node_js:
- '10'
- '12'
- '14'
notifications:
irc: "irc.freenode.org#socket.io"
git:
depth: 1

View File

@@ -1,3 +1,819 @@
# History
## 2024
- [4.7.5](#475-2024-03-14) (Mar 2024)
- [4.7.4](#474-2024-01-12) (Jan 2024)
- [4.7.3](#473-2024-01-03) (Jan 2024)
## 2023
- [4.7.2](#472-2023-08-02) (Aug 2023)
- [4.7.1](#471-2023-06-28) (Jun 2023)
- [4.7.0](#470-2023-06-22) (Jun 2023)
- [4.6.2](#462-2023-05-31) (May 2023)
- [4.6.1](#461-2023-02-20) (Feb 2023)
- [4.6.0](#460-2023-02-07) (Feb 2023)
## 2022
- [4.5.4](#454-2022-11-22) (Nov 2022)
- [4.5.3](#453-2022-10-15) (Oct 2022)
- [4.5.2](#452-2022-09-02) (Sep 2022)
- [2.5.0](#250-2022-06-26) (Jun 2022) (from the [2.x](https://github.com/socketio/socket.io/tree/2.x) branch)
- [4.5.1](#451-2022-05-17) (May 2022)
- [4.5.0](#450-2022-04-23) (Apr 2022)
- [4.4.1](#441-2022-01-06) (Jan 2022)
## 2021
- [4.4.0](#440-2021-11-18) (Nov 2021)
- [4.3.2](#432-2021-11-08) (Nov 2021)
- [4.3.1](#431-2021-10-16) (Oct 2021)
- [4.3.0](#430-2021-10-14) (Oct 2021)
- [4.2.0](#420-2021-08-30) (Aug 2021)
- [4.1.3](#413-2021-07-10) (Jul 2021)
- [4.1.2](#412-2021-05-17) (May 2021)
- [4.1.1](#411-2021-05-11) (May 2021)
- [4.1.0](#410-2021-05-11) (May 2021)
- [4.0.2](#402-2021-05-06) (May 2021)
- [4.0.1](#401-2021-03-31) (Mar 2021)
- [**4.0.0**](#400-2021-03-10) (Mar 2021)
- [3.1.2](#312-2021-02-26) (Feb 2021)
- [3.1.1](#311-2021-02-03) (Feb 2021)
- [3.1.0](#310-2021-01-15) (Jan 2021)
- [2.4.1](#241-2021-01-07) (Jan 2021) (from the [2.x](https://github.com/socketio/socket.io/tree/2.x) branch)
- [3.0.5](#305-2021-01-05) (Jan 2021)
- [2.4.0](#240-2021-01-04) (Jan 2021) (from the [2.x](https://github.com/socketio/socket.io/tree/2.x) branch)
## 2020
- [3.0.4](#304-2020-12-07) (Dec 2020)
- [3.0.3](#303-2020-11-19) (Nov 2020)
- [3.0.2](#302-2020-11-17) (Nov 2020)
- [3.0.1](#301-2020-11-09) (Nov 2020)
- [**3.0.0**](#300-2020-11-05) (Nov 2020)
## 2019
- [2.3.0](#230-2019-09-20) (Sep 2019)
## 2018
- [2.2.0](#220-2018-11-29) (Nov 2018)
- [2.1.1](#211-2018-05-17) (May 2018)
- [2.1.0](#210-2018-03-29) (Mar 2018)
# Release notes
## [4.7.5](https://github.com/socketio/socket.io/compare/4.7.4...4.7.5) (2024-03-14)
### Bug Fixes
* close the adapters when the server is closed ([bf64870](https://github.com/socketio/socket.io/commit/bf64870957e626a73e0544716a1a41a4ba5093bb))
* remove duplicate pipeline when serving bundle ([e426f3e](https://github.com/socketio/socket.io/commit/e426f3e8e1bfea5720c32d30a3663303200ee6ad))
### Dependencies
- [`engine.io@~6.5.2`](https://github.com/socketio/engine.io/releases/tag/6.5.2) (no change)
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
## [4.7.4](https://github.com/socketio/socket.io/compare/4.7.3...4.7.4) (2024-01-12)
### Bug Fixes
* **typings:** calling io.emit with no arguments incorrectly errored ([cb6d2e0](https://github.com/socketio/socket.io/commit/cb6d2e02aa7ec03c2de1817d35cffa1128b107ef)), closes [#4914](https://github.com/socketio/socket.io/issues/4914)
### Dependencies
- [`engine.io@~6.5.2`](https://github.com/socketio/engine.io/releases/tag/6.5.2) (no change)
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
## [4.7.3](https://github.com/socketio/socket.io/compare/4.7.2...4.7.3) (2024-01-03)
### Bug Fixes
* return the first response when broadcasting to a single socket ([#4878](https://github.com/socketio/socket.io/issues/4878)) ([df8e70f](https://github.com/socketio/socket.io/commit/df8e70f79822e3887b4f21ca718af8a53bbda2c4))
* **typings:** allow to bind to a non-secure Http2Server ([#4853](https://github.com/socketio/socket.io/issues/4853)) ([8c9ebc3](https://github.com/socketio/socket.io/commit/8c9ebc30e5452ff9381af5d79f547394fa55633c))
### Dependencies
- [`engine.io@~6.5.2`](https://github.com/socketio/engine.io/releases/tag/6.5.2) (no change)
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
## [4.7.2](https://github.com/socketio/socket.io/compare/4.7.1...4.7.2) (2023-08-02)
### Bug Fixes
* clean up child namespace when client is rejected in middleware ([#4773](https://github.com/socketio/socket.io/issues/4773)) ([0731c0d](https://github.com/socketio/socket.io/commit/0731c0d2f497d5cce596ea1ec32a67c08bcccbcd))
* **webtransport:** properly handle WebTransport-only connections ([3468a19](https://github.com/socketio/socket.io/commit/3468a197afe87e65eb0d779fabd347fe683013ab))
* **webtransport:** add proper framing ([a306db0](https://github.com/socketio/engine.io/commit/a306db09e8ddb367c7d62f45fec920f979580b7c))
### Dependencies
- [`engine.io@~6.5.2`](https://github.com/socketio/engine.io/releases/tag/6.5.2) ([diff](https://github.com/socketio/engine.io/compare/6.5.0...6.5.2))
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
## [4.7.1](https://github.com/socketio/socket.io/compare/4.7.0...4.7.1) (2023-06-28)
The client bundle contains a few fixes regarding the WebTransport support.
### Dependencies
- [`engine.io@~6.5.0`](https://github.com/socketio/engine.io/releases/tag/6.5.0) (no change)
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
## [4.7.0](https://github.com/socketio/socket.io/compare/4.6.2...4.7.0) (2023-06-22)
### Bug Fixes
* remove the Partial modifier from the socket.data type ([#4740](https://github.com/socketio/socket.io/issues/4740)) ([e5c62ca](https://github.com/socketio/socket.io/commit/e5c62cad60fc7d16fbb024fd9be1d1880f4e6f5f))
### Features
#### Support for WebTransport
The Socket.IO server can now use WebTransport as the underlying transport.
WebTransport is a web API that uses the HTTP/3 protocol as a bidirectional transport. It's intended for two-way communications between a web client and an HTTP/3 server.
References:
- https://w3c.github.io/webtransport/
- https://developer.mozilla.org/en-US/docs/Web/API/WebTransport
- https://developer.chrome.com/articles/webtransport/
Until WebTransport support lands [in Node.js](https://github.com/nodejs/node/issues/38478), you can use the `@fails-components/webtransport` package:
```js
import { readFileSync } from "fs";
import { createServer } from "https";
import { Server } from "socket.io";
import { Http3Server } from "@fails-components/webtransport";
// WARNING: the total length of the validity period MUST NOT exceed two weeks (https://w3c.github.io/webtransport/#custom-certificate-requirements)
const cert = readFileSync("/path/to/my/cert.pem");
const key = readFileSync("/path/to/my/key.pem");
const httpsServer = createServer({
key,
cert
});
httpsServer.listen(3000);
const io = new Server(httpsServer, {
transports: ["polling", "websocket", "webtransport"] // WebTransport is not enabled by default
});
const h3Server = new Http3Server({
port: 3000,
host: "0.0.0.0",
secret: "changeit",
cert,
privKey: key,
});
(async () => {
const stream = await h3Server.sessionStream("/socket.io/");
const sessionReader = stream.getReader();
while (true) {
const { done, value } = await sessionReader.read();
if (done) {
break;
}
io.engine.onWebTransportSession(value);
}
})();
h3Server.startServer();
```
Added in [123b68c](https://github.com/socketio/engine.io/commit/123b68c04f9e971f59b526e0f967a488ee6b0116).
#### Client bundles with CORS headers
The bundles will now have the right `Access-Control-Allow-xxx` headers.
Added in [63f181c](https://github.com/socketio/socket.io/commit/63f181cc12cbbbf94ed40eef52d60f36a1214fbe).
### Dependencies
- [`engine.io@~6.5.0`](https://github.com/socketio/engine.io/releases/tag/6.5.0) ([diff](https://github.com/socketio/engine.io/compare/6.4.2...6.5.0))
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
## [4.6.2](https://github.com/socketio/socket.io/compare/4.6.1...4.6.2) (2023-05-31)
### Bug Fixes
* **exports:** move `types` condition to the top ([#4698](https://github.com/socketio/socket.io/issues/4698)) ([3d44aae](https://github.com/socketio/socket.io/commit/3d44aae381af38349fdb808d510d9f47a0c2507e))
### Dependencies
- [`engine.io@~6.4.2`](https://github.com/socketio/engine.io/releases/tag/6.4.0) ([diff](https://github.com/socketio/engine.io/compare/6.4.1...6.4.2))
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
## [4.6.1](https://github.com/socketio/socket.io/compare/4.6.0...4.6.1) (2023-02-20)
### Bug Fixes
* properly handle manually created dynamic namespaces ([0d0a7a2](https://github.com/socketio/socket.io/commit/0d0a7a22b5ff95f864216c529114b7dd41738d1e))
* **types:** fix nodenext module resolution compatibility ([#4625](https://github.com/socketio/socket.io/issues/4625)) ([d0b22c6](https://github.com/socketio/socket.io/commit/d0b22c630208669aceb7ae013180c99ef90279b0))
### Dependencies
- [`engine.io@~6.4.1`](https://github.com/socketio/engine.io/releases/tag/6.4.1) ([diff](https://github.com/socketio/engine.io/compare/6.4.0...6.4.1))
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
## [4.6.0](https://github.com/socketio/socket.io/compare/4.5.4...4.6.0) (2023-02-07)
### Bug Fixes
* add timeout method to remote socket ([#4558](https://github.com/socketio/socket.io/issues/4558)) ([0c0eb00](https://github.com/socketio/socket.io/commit/0c0eb0016317218c2be3641e706cfaa9bea39a2d))
* **typings:** properly type emits with timeout ([f3ada7d](https://github.com/socketio/socket.io/commit/f3ada7d8ccc02eeced2b9b9ac8e4bc921eb630d2))
### Features
#### Promise-based acknowledgements
This commit adds some syntactic sugar around acknowledgements:
- `emitWithAck()`
```js
try {
const responses = await io.timeout(1000).emitWithAck("some-event");
console.log(responses); // one response per client
} catch (e) {
// some clients did not acknowledge the event in the given delay
}
io.on("connection", async (socket) => {
// without timeout
const response = await socket.emitWithAck("hello", "world");
// with a specific timeout
try {
const response = await socket.timeout(1000).emitWithAck("hello", "world");
} catch (err) {
// the client did not acknowledge the event in the given delay
}
});
```
- `serverSideEmitWithAck()`
```js
try {
const responses = await io.timeout(1000).serverSideEmitWithAck("some-event");
console.log(responses); // one response per server (except itself)
} catch (e) {
// some servers did not acknowledge the event in the given delay
}
```
Added in [184f3cf](https://github.com/socketio/socket.io/commit/184f3cf7af57acc4b0948eee307f25f8536eb6c8).
#### Connection state recovery
This feature allows a client to reconnect after a temporary disconnection and restore its state:
- id
- rooms
- data
- missed packets
Usage:
```js
import { Server } from "socket.io";
const io = new Server({
connectionStateRecovery: {
// default values
maxDisconnectionDuration: 2 * 60 * 1000,
skipMiddlewares: true,
},
});
io.on("connection", (socket) => {
console.log(socket.recovered); // whether the state was recovered or not
});
```
Here's how it works:
- the server sends a session ID during the handshake (which is different from the current `id` attribute, which is public and can be freely shared)
- the server also includes an offset in each packet (added at the end of the data array, for backward compatibility)
- upon temporary disconnection, the server stores the client state for a given delay (implemented at the adapter level)
- upon reconnection, the client sends both the session ID and the last offset it has processed, and the server tries to restore the state
The in-memory adapter already supports this feature, and we will soon update the Postgres and MongoDB adapters. We will also create a new adapter based on [Redis Streams](https://redis.io/docs/data-types/streams/), which will support this feature.
Added in [54d5ee0](https://github.com/socketio/socket.io/commit/54d5ee05a684371191e207b8089f09fc24eb5107).
#### Compatibility (for real) with Express middlewares
This feature implements middlewares at the Engine.IO level, because Socket.IO middlewares are meant for namespace authorization and are not executed during a classic HTTP request/response cycle.
Syntax:
```js
io.engine.use((req, res, next) => {
// do something
next();
});
// with express-session
import session from "express-session";
io.engine.use(session({
secret: "keyboard cat",
resave: false,
saveUninitialized: true,
cookie: { secure: true }
}));
// with helmet
import helmet from "helmet";
io.engine.use(helmet());
```
A workaround was possible by using the allowRequest option and the "headers" event, but this feels way cleaner and works with upgrade requests too.
Added in [24786e7](https://github.com/socketio/engine.io/commit/24786e77c5403b1c4b5a2bc84e2af06f9187f74a).
#### Error details in the disconnecting and disconnect events
The `disconnect` event will now contain additional details about the disconnection reason.
```js
io.on("connection", (socket) => {
socket.on("disconnect", (reason, description) => {
console.log(description);
});
});
```
Added in [8aa9499](https://github.com/socketio/socket.io/commit/8aa94991cee5518567d6254eec04b23f81510257).
#### Automatic removal of empty child namespaces
This commit adds a new option, "cleanupEmptyChildNamespaces". With this option enabled (disabled by default), when a socket disconnects from a dynamic namespace and if there are no other sockets connected to it then the namespace will be cleaned up and its adapter will be closed.
```js
import { createServer } from "node:http";
import { Server } from "socket.io";
const httpServer = createServer();
const io = new Server(httpServer, {
cleanupEmptyChildNamespaces: true
});
```
Added in [5d9220b](https://github.com/socketio/socket.io/commit/5d9220b69adf73e086c27bbb63a4976b348f7c4c).
#### A new "addTrailingSlash" option
The trailing slash which was added by default can now be disabled:
```js
import { createServer } from "node:http";
import { Server } from "socket.io";
const httpServer = createServer();
const io = new Server(httpServer, {
addTrailingSlash: false
});
```
In the example above, the clients can omit the trailing slash and use `/socket.io` instead of `/socket.io/`.
Added in [d0fd474](https://github.com/socketio/engine.io/commit/d0fd4746afa396297f07bb62e539b0c1c4018d7c).
### Performance Improvements
* precompute the WebSocket frames when broadcasting ([da2b542](https://github.com/socketio/socket.io/commit/da2b54279749adc5279c9ac4742b01b36c01cff0))
### Dependencies
- [`engine.io@~6.4.0`](https://github.com/socketio/engine.io/releases/tag/6.4.0) (https://github.com/socketio/engine.io/compare/6.2.1...6.4.0)
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (https://github.com/websockets/ws/compare/8.2.3...8.11.0)
## [4.5.4](https://github.com/socketio/socket.io/compare/4.5.3...4.5.4) (2022-11-22)
This release contains a bump of:
- `engine.io` in order to fix [CVE-2022-41940](https://github.com/socketio/engine.io/security/advisories/GHSA-r7qp-cfhv-p84w)
- `socket.io-parser` in order to fix [CVE-2022-2421](https://github.com/advisories/GHSA-qm95-pgcg-qqfq).
### Dependencies
- [`engine.io@~6.2.1`](https://github.com/socketio/engine.io/releases/tag/6.2.1) ([diff](https://github.com/socketio/engine.io/compare/6.2.0...6.2.1))
- [`ws@~8.2.3`](https://github.com/websockets/ws/releases/tag/8.2.3) (no change)
## [4.5.3](https://github.com/socketio/socket.io/compare/4.5.2...4.5.3) (2022-10-15)
### Bug Fixes
* **typings:** accept an HTTP2 server in the constructor ([d3d0a2d](https://github.com/socketio/socket.io/commit/d3d0a2d5beaff51fd145f810bcaf6914213f8a06))
* **typings:** apply types to "io.timeout(...).emit()" calls ([e357daf](https://github.com/socketio/socket.io/commit/e357daf5858560bc84e7e50cd36f0278d6721ea1))
### Dependencies
- [`engine.io@~6.2.0`](https://github.com/socketio/engine.io/releases/tag/6.2.1) (no change)
- [`ws@~8.2.3`](https://github.com/websockets/ws/releases/tag/8.2.3) (no change)
## [4.5.2](https://github.com/socketio/socket.io/compare/4.5.1...4.5.2) (2022-09-02)
### Bug Fixes
* prevent the socket from joining a room after disconnection ([18f3fda](https://github.com/socketio/socket.io/commit/18f3fdab12947a9fee3e9c37cfc1da97027d1473))
* **uws:** prevent the server from crashing after upgrade ([ba497ee](https://github.com/socketio/socket.io/commit/ba497ee3eb52c4abf1464380d015d8c788714364))
### Dependencies
- [`engine.io@~6.2.0`](https://github.com/socketio/engine.io/releases/tag/6.2.0) (no change)
- [`ws@~8.2.3`](https://github.com/websockets/ws/releases/tag/8.2.3) (no change)
# [2.5.0](https://github.com/socketio/socket.io/compare/2.4.1...2.5.0) (2022-06-26)
⚠️ WARNING ⚠️
The default value of the `maxHttpBufferSize` option has been decreased from 100 MB to 1 MB, in order to prevent attacks by denial of service.
Security advisory: [GHSA-j4f2-536g-r55m](https://github.com/advisories/GHSA-j4f2-536g-r55m)
### Bug Fixes
* fix race condition in dynamic namespaces ([05e1278](https://github.com/socketio/socket.io/commit/05e1278cfa99f3ecf3f8f0531ffe57d850e9a05b))
* ignore packet received after disconnection ([22d4bdf](https://github.com/socketio/socket.io/commit/22d4bdf00d1a03885dc0171125faddfaef730066))
* only set 'connected' to true after middleware execution ([226cc16](https://github.com/socketio/socket.io/commit/226cc16165f9fe60f16ff4d295fb91c8971cde35))
* prevent the socket from joining a room after disconnection ([f223178](https://github.com/socketio/socket.io/commit/f223178eb655a7713303b21a78f9ef9e161d6458))
### Dependencies
- [`engine.io@~3.6.0`](https://github.com/socketio/engine.io/releases/tag/3.6.0) (https://github.com/socketio/engine.io/compare/3.5.0...3.6.0)
- [`ws@~7.4.2`](https://github.com/websockets/ws/releases/tag/7.4.2) (no change)
## [4.5.1](https://github.com/socketio/socket.io/compare/4.5.0...4.5.1) (2022-05-17)
### Bug Fixes
* forward the local flag to the adapter when using fetchSockets() ([30430f0](https://github.com/socketio/socket.io/commit/30430f0985f8e7c49394543d4c84913b6a15df60))
* **typings:** add HTTPS server to accepted types ([#4351](https://github.com/socketio/socket.io/issues/4351)) ([9b43c91](https://github.com/socketio/socket.io/commit/9b43c9167cff817c60fa29dbda2ef7cd938aff51))
### Dependencies
- [`engine.io@~6.2.0`](https://github.com/socketio/engine.io/releases/tag/6.2.0) (no change)
- [`ws@~8.2.3`](https://github.com/websockets/ws/releases/tag/8.2.3) (no change)
# [4.5.0](https://github.com/socketio/socket.io/compare/4.4.1...4.5.0) (2022-04-23)
### Bug Fixes
* **typings:** ensure compatibility with TypeScript 3.x ([#4259](https://github.com/socketio/socket.io/issues/4259)) ([02c87a8](https://github.com/socketio/socket.io/commit/02c87a85614e217b8e7b93753f315790ae9d99f6))
### Features
#### Catch-all listeners for outgoing packets
This is similar to `onAny()`, but for outgoing packets.
Syntax:
```js
socket.onAnyOutgoing((event, ...args) => {
console.log(event);
});
```
Added in [531104d](https://github.com/socketio/socket.io/commit/531104d332690138b7aab84d5583d6204132c8b4).
#### Broadcast and expect multiple acknowledgements
Syntax:
```js
io.timeout(1000).emit("some-event", (err, responses) => {
// ...
});
```
Added in [8b20457](https://github.com/socketio/socket.io/commit/8b204570a94979bbec307f23ca078f30f5cf07b0).
#### `maxHttpBufferSize` value negotiation
A "maxPayload" field is now included in the Engine.IO handshake, so that clients in HTTP long-polling can decide how many packets they have to send to stay under the `maxHttpBufferSize` value.
This is a backward compatible change which should not mandate a new major revision of the protocol (we stay in v4), as we only add a field in the JSON-encoded handshake data:
```
0{"sid":"lv_VI97HAXpY6yYWAAAC","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":5000,"maxPayload":1000000}
```
Added in [088dcb4](https://github.com/socketio/engine.io/commit/088dcb4dff60df39785df13d0a33d3ceaa1dff38).
### Dependencies
- [`engine.io@~6.2.0`](https://github.com/socketio/engine.io/releases/tag/6.2.0) (https://github.com/socketio/engine.io/compare/6.1.0...6.2.0)
- [`ws@~8.2.3`](https://github.com/websockets/ws/releases/tag/8.2.3) (no change)
## [4.4.1](https://github.com/socketio/socket.io/compare/4.4.0...4.4.1) (2022-01-06)
### Bug Fixes
* **types:** make `RemoteSocket.data` type safe ([#4234](https://github.com/socketio/socket.io/issues/4234)) ([770ee59](https://github.com/socketio/socket.io/commit/770ee5949fb47c2556876c622f06c862573657d6))
* **types:** pass `SocketData` type to custom namespaces ([#4233](https://github.com/socketio/socket.io/issues/4233)) ([f2b8de7](https://github.com/socketio/socket.io/commit/f2b8de71919e1b4d3e57f15a459972c1d1064787))
# [4.4.0](https://github.com/socketio/socket.io/compare/4.3.2...4.4.0) (2021-11-18)
### Bug Fixes
* only set 'connected' to true after middleware execution ([02b0f73](https://github.com/socketio/socket.io/commit/02b0f73e2c64b09c72c5fbf7dc5f059557bdbe50))
### Features
* add an implementation based on uWebSockets.js ([c0d8c5a](https://github.com/socketio/socket.io/commit/c0d8c5ab234d0d2bef0d0dec472973cc9662f647))
* add timeout feature ([f0ed42f](https://github.com/socketio/socket.io/commit/f0ed42f18cabef20ad976aeec37077b6bf3837a5))
* add type information to `socket.data` ([#4159](https://github.com/socketio/socket.io/issues/4159)) ([fe8730c](https://github.com/socketio/socket.io/commit/fe8730ca0f15bc92d5de81cf934c89c76d6af329))
## [4.3.2](https://github.com/socketio/socket.io/compare/4.3.1...4.3.2) (2021-11-08)
### Bug Fixes
* fix race condition in dynamic namespaces ([#4137](https://github.com/socketio/socket.io/issues/4137)) ([9d86397](https://github.com/socketio/socket.io/commit/9d86397243bcbb5775a29d96e5ef03e17148a8e7))
## [4.3.1](https://github.com/socketio/socket.io/compare/4.3.0...4.3.1) (2021-10-16)
### Bug Fixes
* fix server attachment ([#4127](https://github.com/socketio/socket.io/issues/4127)) ([0ef2a4d](https://github.com/socketio/socket.io/commit/0ef2a4d02c9350aff163df9cb61aece89c4dac0f))
# [4.3.0](https://github.com/socketio/socket.io/compare/4.2.0...4.3.0) (2021-10-14)
### Bug Fixes
* **typings:** add name field to cookie option ([#4099](https://github.com/socketio/socket.io/issues/4099)) ([033c5d3](https://github.com/socketio/socket.io/commit/033c5d399a2b985afad32c1e4b0c16d764e248cd))
* send volatile packets with binary attachments ([dc81fcf](https://github.com/socketio/socket.io/commit/dc81fcf461cfdbb5b34b1a5a96b84373754047d5))
### Features
* serve ESM bundle ([60edecb](https://github.com/socketio/socket.io/commit/60edecb3bd33801803cdcba0aefbafa381a2abb3))
# [4.2.0](https://github.com/socketio/socket.io/compare/4.1.3...4.2.0) (2021-08-30)
### Bug Fixes
* **typings:** allow async listener in typed events ([ccfd8ca](https://github.com/socketio/socket.io/commit/ccfd8caba6d38b7ba6c5114bd8179346ed07671c))
### Features
* ignore the query string when serving client JavaScript ([#4024](https://github.com/socketio/socket.io/issues/4024)) ([24fee27](https://github.com/socketio/socket.io/commit/24fee27ba36485308f8e995879c10931532c814e))
## [4.1.3](https://github.com/socketio/socket.io/compare/4.1.2...4.1.3) (2021-07-10)
### Bug Fixes
* fix io.except() method ([94e27cd](https://github.com/socketio/socket.io/commit/94e27cd072c8a4eeb9636f6ffbb7a21d382f36b0))
* remove x-sourcemap header ([a4dffc6](https://github.com/socketio/socket.io/commit/a4dffc6527f412d51a786ae5bf2e9080fe1ca63c))
## [4.1.2](https://github.com/socketio/socket.io/compare/4.1.1...4.1.2) (2021-05-17)
### Bug Fixes
* **typings:** ensure compatibility with TypeScript 3.x ([0cb6ac9](https://github.com/socketio/socket.io/commit/0cb6ac95b49a27483b6f1b6402fa54b35f82e36f))
* ensure compatibility with previous versions of the adapter ([a2cf248](https://github.com/socketio/socket.io/commit/a2cf2486c366cb62293101c10520c57f6984a3fc))
## [4.1.1](https://github.com/socketio/socket.io/compare/4.1.0...4.1.1) (2021-05-11)
### Bug Fixes
* **typings:** properly type server-side events ([b84ed1e](https://github.com/socketio/socket.io/commit/b84ed1e41c9053792caf58974c5de9395bfd509f))
* **typings:** properly type the adapter attribute ([891b187](https://github.com/socketio/socket.io/commit/891b1870e92d1ec38910f03bb839817e2d6be65a))
# [4.1.0](https://github.com/socketio/socket.io/compare/4.0.2...4.1.0) (2021-05-11)
### Features
* add support for inter-server communication ([93cce05](https://github.com/socketio/socket.io/commit/93cce05fb3faf91f21fa71212275c776aa161107))
* notify upon namespace creation ([499c892](https://github.com/socketio/socket.io/commit/499c89250d2db1ab7725ab2b74840e188c267c46))
* add a "connection_error" event ([7096e98](https://github.com/socketio/engine.io/commit/7096e98a02295a62c8ea2aa56461d4875887092d), from `engine.io`)
* add the "initial_headers" and "headers" events ([2527543](https://github.com/socketio/engine.io/commit/252754353a0e88eb036ebb3082e9d6a9a5f497db), from `engine.io`)
### Performance Improvements
* add support for the "wsPreEncoded" writing option ([dc381b7](https://github.com/socketio/socket.io/commit/dc381b72c6b2f8172001dedd84116122e4cc95b3))
## [4.0.2](https://github.com/socketio/socket.io/compare/4.0.1...4.0.2) (2021-05-06)
### Bug Fixes
* **typings:** make "engine" attribute public ([b81ce4c](https://github.com/socketio/socket.io/commit/b81ce4c9d0b00666361498e2ba5e0d007d5860b8))
* properly export the Socket class ([d65b6ee](https://github.com/socketio/socket.io/commit/d65b6ee84c8e91deb61c3c1385eb19afa196a909))
## [4.0.1](https://github.com/socketio/socket.io/compare/4.0.0...4.0.1) (2021-03-31)
### Bug Fixes
* **typings:** add fallback to untyped event listener ([#3834](https://github.com/socketio/socket.io/issues/3834)) ([a11152f](https://github.com/socketio/socket.io/commit/a11152f42b281df83409313962f60f230239c79e))
* **typings:** update return type from emit ([#3843](https://github.com/socketio/socket.io/issues/3843)) ([1a72ae4](https://github.com/socketio/socket.io/commit/1a72ae4fe27a14cf60916f991a2c94da91d9e54a))
# [4.0.0](https://github.com/socketio/socket.io/compare/3.1.2...4.0.0) (2021-03-10)
### Bug Fixes
* make io.to(...) immutable ([ac9e8ca](https://github.com/socketio/socket.io/commit/ac9e8ca6c71e00d4af45ee03f590fe56f3951186))
### Features
* add some utility methods ([b25495c](https://github.com/socketio/socket.io/commit/b25495c069031674da08e19aed68922c7c7a0e28))
* add support for typed events ([#3822](https://github.com/socketio/socket.io/issues/3822)) ([0107510](https://github.com/socketio/socket.io/commit/0107510ba8a0f148c78029d8be8919b350feb633))
* allow to exclude specific rooms when broadcasting ([#3789](https://github.com/socketio/socket.io/issues/3789)) ([7de2e87](https://github.com/socketio/socket.io/commit/7de2e87e888d849eb2dfc5e362af4c9e86044701))
* allow to pass an array to io.to(...) ([085d1de](https://github.com/socketio/socket.io/commit/085d1de9df909651de8b313cc6f9f253374b702e))
## [3.1.2](https://github.com/socketio/socket.io/compare/3.1.1...3.1.2) (2021-02-26)
### Bug Fixes
* ignore packets received after disconnection ([494c64e](https://github.com/socketio/socket.io/commit/494c64e44f645cbd24c645f1186d203789e84af0))
## [3.1.1](https://github.com/socketio/socket.io/compare/3.1.0...3.1.1) (2021-02-03)
### Bug Fixes
* properly parse the CONNECT packet in v2 compatibility mode ([6f4bd7f](https://github.com/socketio/socket.io/commit/6f4bd7f8e7c41a075a8014565330a77c38b03a8d))
* **typings:** add return types and general-case overload signatures ([#3776](https://github.com/socketio/socket.io/issues/3776)) ([9e8f288](https://github.com/socketio/socket.io/commit/9e8f288ca9f14f91064b8d3cce5946f7d23d407c))
* **typings:** update the types of "query", "auth" and "headers" ([4f2e9a7](https://github.com/socketio/socket.io/commit/4f2e9a716d9835b550c8fd9a9b429ebf069c2895))
# [3.1.0](https://github.com/socketio/socket.io/compare/3.0.5...3.1.0) (2021-01-15)
### Features
* confirm a weak but matching ETag ([#3485](https://github.com/socketio/socket.io/issues/3485)) ([161091d](https://github.com/socketio/socket.io/commit/161091dd4c9e1b1610ac3d45d964195e63d92b94))
* **esm:** export the Namespace and Socket class ([#3699](https://github.com/socketio/socket.io/issues/3699)) ([233650c](https://github.com/socketio/socket.io/commit/233650c22209708b5fccc4349c38d2fa1b465d8f))
* add support for Socket.IO v2 clients ([9925746](https://github.com/socketio/socket.io/commit/9925746c8ee3a6522bd640b5d586c83f04f2f1ba))
* add room events ([155fa63](https://github.com/socketio/socket.io-adapter/commit/155fa6333a504036e99a33667dc0397f6aede25e))
### Bug Fixes
* allow integers as event names ([1c220dd](https://github.com/socketio/socket.io-parser/commit/1c220ddbf45ea4b44bc8dbf6f9ae245f672ba1b9))
## [2.4.1](https://github.com/socketio/socket.io/compare/2.4.0...2.4.1) (2021-01-07)
### Reverts
* fix(security): do not allow all origins by default ([a169050](https://github.com/socketio/socket.io/commit/a1690509470e9dd5559cec4e60908ca6c23e9ba0))
## [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))
# [2.4.0](https://github.com/socketio/socket.io/compare/2.3.0...2.4.0) (2021-01-04)
### Bug Fixes
* **security:** do not allow all origins by default ([f78a575](https://github.com/socketio/socket.io/commit/f78a575f66ab693c3ea96ea88429ddb1a44c86c7))
* properly overwrite the query sent in the handshake ([d33a619](https://github.com/socketio/socket.io/commit/d33a619905a4905c153d4fec337c74da5b533a9e))
## [3.0.4](https://github.com/socketio/socket.io/compare/3.0.3...3.0.4) (2020-12-07)
## [3.0.3](https://github.com/socketio/socket.io/compare/3.0.2...3.0.3) (2020-11-19)
## [3.0.2](https://github.com/socketio/socket.io/compare/3.0.1...3.0.2) (2020-11-17)
### Bug Fixes
* merge Engine.IO options ([43705d7](https://github.com/socketio/socket.io/commit/43705d7a9149833afc69edc937ea7f8c9aabfeef))
## [3.0.1](https://github.com/socketio/socket.io/compare/3.0.0...3.0.1) (2020-11-09)
### Bug Fixes
* export ServerOptions and Namespace types ([#3684](https://github.com/socketio/socket.io/issues/3684)) ([f62f180](https://github.com/socketio/socket.io/commit/f62f180edafdd56d8a8a277e092bc66df0c5f07f))
* **typings:** update the signature of the emit method ([50671d9](https://github.com/socketio/socket.io/commit/50671d984a81535a6a15c704546ca7465e2ea295))
# [3.0.0](https://github.com/socketio/socket.io/compare/2.3.0...3.0.0) (2020-11-05)
### Bug Fixes
@@ -92,7 +908,7 @@ new Server(3000, {
const socket = io("/admin");
// server-side
io.on("connect", socket => {
io.on("connection", socket => {
// not triggered anymore
})
@@ -243,7 +1059,7 @@ new Server(3000, {
const socket = io("/admin");
// server-side
io.on("connect", socket => {
io.on("connection", socket => {
// not triggered anymore
})
@@ -260,3 +1076,78 @@ io.of("/admin").use((socket, next) => {
This method was kept for backward-compatibility with pre-1.0 versions.
# [2.3.0](https://github.com/socketio/socket.io/compare/2.2.0...2.3.0) (2019-09-20)
This release mainly contains a bump of the `engine.io` and `ws` packages, but no additional features.
# [2.2.0](https://github.com/socketio/socket.io/compare/2.1.1...2.2.0) (2018-11-29)
### Features
- add cache-control header when serving the client source ([#2907](https://github.com/socketio/socket.io/pull/2907)) ([b00ae50](https://github.com/socketio/socket.io/commit/b00ae50be65d1bc88fa95145f1c486a6886a6b76))
### Bug fixes
- throw an error when trying to access the clients of a dynamic namespace ([#3355](https://github.com/socketio/socket.io/pull/3355)) ([a7fbd1a](https://github.com/socketio/socket.io/commit/a7fbd1ac4a47cafd832fc62e371754df924c5903))
# [2.1.1](https://github.com/socketio/socket.io/compare/2.1.0...2.1.1) (2018-05-17)
### Features
- add local flag to the socket object ([#3129](https://github.com/socketio/socket.io/pull/3219)) ([1decae3](https://github.com/socketio/socket.io/commit/1decae341c80c0417b32d3124ca30c005240b48a))
```js
socket.local.to('room101').emit(/* */);
```
# [2.1.0](https://github.com/socketio/socket.io/compare/2.1.1...2.2.0) (2018-03-29)
### Features
- add a 'binary' flag ([#3185](https://github.com/socketio/socket.io/pull/3185)) ([f48a06c](https://github.com/socketio/socket.io/commit/f48a06c040280b44f90fd225c888910544fd63b5))
```js
// by default, the object is recursively scanned to check whether it contains some binary data
// in the following example, the check is skipped in order to improve performance
socket.binary(false).emit('plain-object', object);
// it also works at the namespace level
io.binary(false).emit('plain-object', object);
```
- add support for dynamic namespaces ([#3195](https://github.com/socketio/socket.io/pull/3195)) ([c0c79f0](https://github.com/socketio/socket.io/commit/c0c79f019e7138194e438339f8192705957c8ec3))
```js
io.of(/^\/dynamic-\d+$/).on('connect', (socket) => {
// socket.nsp.name = '/dynamic-101'
});
// client-side
const client = require('socket.io-client')('/dynamic-101');
```
### Bug fixes
- properly emit 'connect' when using a custom namespace ([#3197](https://github.com/socketio/socket.io/pull/3197)) ([f4fc517](https://github.com/socketio/socket.io/commit/f4fc517e0fe25866c95b584291487b8cbdff889d))
- include the protocol in the origins check ([#3198](https://github.com/socketio/socket.io/pull/3198)) ([1f1d64b](https://github.com/socketio/socket.io/commit/1f1d64bab61a273712a199591a3f76210d8c0959))
### Important note :warning: from Engine.IO [3.2.0 release](https://github.com/socketio/engine.io/releases/tag/3.2.0)
There are two non-breaking changes that are somehow quite important:
- `ws` was reverted as the default wsEngine (https://github.com/socketio/engine.io/pull/550), as there was several blocking issues with `uws`. You can still use `uws` by running `npm install uws --save` in your project and using the `wsEngine` option:
```js
var engine = require('engine.io');
var server = engine.listen(3000, {
wsEngine: 'uws'
});
```
- `pingTimeout` now defaults to 5 seconds (instead of 60 seconds): https://github.com/socketio/engine.io/pull/551

View File

@@ -1,10 +1,7 @@
# socket.io
[![Run on Repl.it](https://repl.it/badge/github/socketio/socket.io)](https://replit.com/@socketio/socketio-minimal-example)
[![Backers on Open Collective](https://opencollective.com/socketio/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/socketio/sponsors/badge.svg)](#sponsors)
[![Build Status](https://secure.travis-ci.org/socketio/socket.io.svg?branch=master)](https://travis-ci.org/socketio/socket.io)
[![Dependency Status](https://david-dm.org/socketio/socket.io.svg)](https://david-dm.org/socketio/socket.io)
[![devDependency Status](https://david-dm.org/socketio/socket.io/dev-status.svg)](https://david-dm.org/socketio/socket.io#info=devDependencies)
[![Build Status](https://github.com/socketio/socket.io/workflows/CI/badge.svg)](https://github.com/socketio/socket.io/actions)
[![NPM version](https://badge.fury.io/js/socket.io.svg)](https://www.npmjs.com/package/socket.io)
![Downloads](https://img.shields.io/npm/dm/socket.io.svg?style=flat)
[![](https://slackin-socketio.now.sh/badge.svg)](https://slackin-socketio.now.sh)
@@ -22,6 +19,10 @@ Some implementations in other languages are also available:
- [C++](https://github.com/socketio/socket.io-client-cpp)
- [Swift](https://github.com/socketio/socket.io-client-swift)
- [Dart](https://github.com/rikulo/socket.io-client-dart)
- [Python](https://github.com/miguelgrinberg/python-socketio)
- [.NET](https://github.com/doghappy/socket.io-client-csharp)
- [Rust](https://github.com/1c3t3a/rust-socketio)
- [PHP](https://github.com/ElephantIO/elephant.io)
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
@@ -110,11 +115,19 @@ io.on('connection', client => { ... });
io.listen(3000);
```
### Module syntax
```js
import { Server } from "socket.io";
const io = new Server(server);
io.listen(3000);
```
### In conjunction with Express
Starting with **3.0**, express applications have become request handler
functions that you pass to `http` or `http` `Server` instances. You need
to pass the `Server` to `socket.io`, and not the express application
to pass the `Server` to `socket.io`, not the express application
function. Also make sure to call `.listen` on the `server`, not the `app`.
```js

22
SECURITY.md Normal file
View File

@@ -0,0 +1,22 @@
# Security Policy
## Supported Versions
| Version | Supported |
|---------|--------------------|
| 4.x | :white_check_mark: |
| 3.x | :white_check_mark: |
| 2.4.x | :white_check_mark: |
| < 2.4.0 | :x: |
## Reporting a Vulnerability
To report a security vulnerability in this package, please send an email to [@darrachequesne](https://github.com/darrachequesne) (see address in profile) describing the vulnerability and how to reproduce it.
We will get back to you as soon as possible and publish a fix if necessary.
:warning: IMPORTANT :warning: please do not create an issue in this repository, as attackers might take advantage of it. Thank you in advance for your responsible disclosure.
## History
No security vulnerability were reported yet.

7
client-dist/socket.io.esm.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
examples/.gitignore vendored Normal file
View File

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

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
});
};

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": "^4.0.0",
"socket.io-client": "^4.0.0",
"tslib": "^2.0.0",
"zone.js": "~0.10.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.1100.4",
"@angular/cli": "~11.0.4",
"@angular/compiler-cli": "~11.0.4",
"@types/jasmine": "~3.6.0",
"@types/node": "^12.11.1",
"codelyzer": "^6.0.0",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~5.1.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.0.3",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"protractor": "~7.0.0",
"ts-node": "~8.3.0",
"tslint": "~6.1.0",
"typescript": "~4.0.2"
}
}

View File

@@ -0,0 +1,28 @@
import { Server } from "socket.io";
const io = new Server(8080, {
cors: {
origin: "http://localhost:4200",
methods: ["GET", "POST"]
}
});
interface Todo {
completed: boolean;
editing: boolean;
title: string;
}
let todos: Array<Todo> = [];
io.on("connection", (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

@@ -0,0 +1,26 @@
# Basic CRUD application with Socket.IO
Please read the related [guide](https://socket.io/get-started/basic-crud-application/).
This repository contains several implementations of the server:
| Directory | Language | Database | Cluster? |
|----------------------------|------------|------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|
| `server/` | TypeScript | in-memory | No |
| `server-postgres-cluster/` | JavaScript | Postgres, with the [Postgres adapter](https://socket.io/docs/v4/postgres-adapter/) | Yes, with the [`@socket.io/sticky`](https://github.com/socketio/socket.io-sticky) module) |
## Running the frontend
```
cd angular-client
npm install
npm start
```
### Running the server
```
cd server
npm install
npm start
```

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

View File

@@ -0,0 +1,42 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db

View File

@@ -0,0 +1,27 @@
# AngularClient
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.0.2.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
## 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 a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## 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,101 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"angular-client": {
"projectType": "application",
"schematics": {},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/angular-client",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": [
"zone.js"
],
"tsConfig": "tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.css"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.development.ts"
}
]
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "angular-client:build:production"
},
"development": {
"buildTarget": "angular-client:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "angular-client:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.css"
],
"scripts": []
}
}
}
}
}
}

View File

@@ -0,0 +1,40 @@
{
"name": "angular-client",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"@angular/animations": "^17.0.0",
"@angular/common": "^17.0.0",
"@angular/compiler": "^17.0.0",
"@angular/core": "^17.0.0",
"@angular/forms": "^17.0.0",
"@angular/platform-browser": "^17.0.0",
"@angular/platform-browser-dynamic": "^17.0.0",
"@angular/router": "^17.0.0",
"rxjs": "~7.8.0",
"socket.io-client": "^4.7.2",
"tslib": "^2.3.0",
"zone.js": "~0.14.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "^17.0.2",
"@angular/cli": "^17.0.2",
"@angular/compiler-cli": "^17.0.0",
"@types/jasmine": "~5.1.0",
"@types/node": "^20.9.2",
"jasmine-core": "~5.1.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.2.2"
}
}

View File

@@ -0,0 +1,24 @@
<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()">-->
<input class="new-todo" placeholder="What needs to be done?" autofocus="" [formControl]="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,29 @@
import { TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AppComponent],
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have the 'angular-client' title`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('angular-client');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, angular-client');
});
});

View File

@@ -0,0 +1,63 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';
import {type Todo, TodoStore} from "./store";
import { FormControl, ReactiveFormsModule } from '@angular/forms';
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, RouterOutlet, ReactiveFormsModule],
templateUrl: './app.component.html',
styleUrl: './app.component.css',
providers: [TodoStore]
})
export class AppComponent {
newTodoText = new FormControl('');
constructor(readonly 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.value?.trim().length) {
this.todoStore.add(this.newTodoText.value!);
this.newTodoText.setValue('');
}
}
}

View File

@@ -0,0 +1,8 @@
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes)]
};

View File

@@ -0,0 +1,3 @@
import { Routes } from '@angular/router';
export const routes: Routes = [];

View File

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

View File

@@ -0,0 +1,3 @@
export const environment = {
serverUrl: "http://localhost:3000"
};

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>AngularClient</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,6 @@
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));

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,14 @@
/* 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"
],
"include": [
"src/**/*.d.ts"
]
}

View File

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

View File

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

View File

@@ -0,0 +1,46 @@
export type TodoID = string;
export interface Todo {
id: TodoID;
completed: boolean;
title: string;
}
interface Error {
error: string;
errorDetails?: {
message: string;
path: Array<string | number>;
type: string;
}[];
}
interface Success<T> {
data: T;
}
export type Response<T> = Error | Success<T>;
export interface ServerEvents {
"todo:created": (todo: Todo) => void;
"todo:updated": (todo: Todo) => void;
"todo:deleted": (id: TodoID) => void;
}
export interface ClientEvents {
"todo:list": (callback: (res: Response<Todo[]>) => void) => void;
"todo:create": (
payload: Omit<Todo, "id">,
callback: (res: Response<TodoID>) => void
) => void;
"todo:read": (id: TodoID, callback: (res: Response<Todo>) => void) => void;
"todo:update": (
payload: Todo,
callback: (res?: Response<void>) => void
) => void;
"todo:delete": (id: TodoID, callback: (res?: Response<void>) => void) => void;
}

View File

@@ -0,0 +1,16 @@
A basic TODO project.
| Characteristic | |
|----------------|-------------------------------------------------------------------------------------------|
| Language | plain JavaScript |
| Database | Postgres, with the [Postgres adapter](https://socket.io/docs/v4/postgres-adapter/) |
| Cluster? | Yes, with the [`@socket.io/sticky`](https://github.com/socketio/socket.io-sticky) module) |
## Usage
```
$ docker-compose up -d
$ npm install
$ npm start
```

View File

@@ -0,0 +1,9 @@
version: "3"
services:
postgres:
image: postgres:12
ports:
- "5432:5432"
environment:
POSTGRES_PASSWORD: "changeit"

View File

@@ -0,0 +1,26 @@
import { Server } from "socket.io";
import createTodoHandlers from "./todo-management/todo.handlers.js";
import { setupWorker } from "@socket.io/sticky";
import { createAdapter } from "@socket.io/postgres-adapter";
export function createApplication(httpServer, components, serverOptions = {}) {
const io = new Server(httpServer, serverOptions);
const { createTodo, readTodo, updateTodo, deleteTodo, listTodo } =
createTodoHandlers(components);
io.on("connection", (socket) => {
socket.on("todo:create", createTodo);
socket.on("todo:read", readTodo);
socket.on("todo:update", updateTodo);
socket.on("todo:delete", deleteTodo);
socket.on("todo:list", listTodo);
});
// enable sticky session in the cluster (to remove in standalone mode)
setupWorker(io);
io.adapter(createAdapter(components.connectionPool));
return io;
}

View File

@@ -0,0 +1,28 @@
import cluster from "cluster";
import { createServer } from "http";
import { setupMaster } from "@socket.io/sticky";
import { cpus } from "os";
if (cluster.isMaster) {
console.log(`Master ${process.pid} is running`);
const httpServer = createServer();
setupMaster(httpServer, {
loadBalancingMethod: "least-connection",
});
httpServer.listen(3000);
for (let i = 0; i < cpus().length; i++) {
cluster.fork();
}
cluster.on("exit", (worker) => {
console.log(`Worker ${worker.process.pid} died`);
cluster.fork();
});
} else {
console.log(`Worker ${process.pid} started`);
import("./index.js");
}

View File

@@ -0,0 +1,51 @@
import { createServer } from "http";
import { createApplication } from "./app.js";
import { Sequelize } from "sequelize";
import pg from "pg";
import { PostgresTodoRepository } from "./todo-management/todo.repository.js";
const httpServer = createServer();
const sequelize = new Sequelize("postgres", "postgres", "changeit", {
dialect: "postgres",
});
const connectionPool = new pg.Pool({
user: "postgres",
host: "localhost",
database: "postgres",
password: "changeit",
port: 5432,
});
createApplication(
httpServer,
{
connectionPool,
todoRepository: new PostgresTodoRepository(sequelize),
},
{
cors: {
origin: ["http://localhost:4200"],
},
}
);
const main = async () => {
// create the tables if they do not exist already
await sequelize.sync();
// create the table needed by the postgres adapter
await connectionPool.query(`
CREATE TABLE IF NOT EXISTS socket_io_attachments (
id bigserial UNIQUE,
created_at timestamptz DEFAULT NOW(),
payload bytea
);
`);
// uncomment when running in standalone mode
// httpServer.listen(3000);
};
main();

View File

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

View File

@@ -0,0 +1,74 @@
import { Errors } from "../util.js";
import { Model, DataTypes } from "sequelize";
class CrudRepository {
findAll() {}
findById(id) {}
save(entity) {}
deleteById(id) {}
}
export class TodoRepository extends CrudRepository {}
class Todo extends Model {}
export class PostgresTodoRepository extends TodoRepository {
constructor(sequelize) {
super();
this.sequelize = sequelize;
Todo.init(
{
id: {
type: DataTypes.STRING,
primaryKey: true,
allowNull: false,
},
title: {
type: DataTypes.STRING,
},
completed: {
type: DataTypes.BOOLEAN,
},
},
{
sequelize,
tableName: "todos",
}
);
}
findAll() {
return this.sequelize.transaction((transaction) => {
return Todo.findAll({ transaction });
});
}
async findById(id) {
return this.sequelize.transaction(async (transaction) => {
const todo = await Todo.findByPk(id, { transaction });
if (!todo) {
throw Errors.ENTITY_NOT_FOUND;
}
return todo;
});
}
save(entity) {
return this.sequelize.transaction((transaction) => {
return Todo.upsert(entity, { transaction });
});
}
async deleteById(id) {
return this.sequelize.transaction(async (transaction) => {
const count = await Todo.destroy({ where: { id }, transaction });
if (count === 0) {
throw Errors.ENTITY_NOT_FOUND;
}
});
}
}

View File

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

View File

@@ -0,0 +1,30 @@
{
"name": "basic-crud-server",
"version": "0.0.1",
"description": "Server for the Basic CRUD Socket.IO example (with Postgres and multiple Socket.IO servers)",
"main": "lib/cluster.js",
"type": "module",
"scripts": {
"start": "node lib/cluster.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/socketio/socket.io.git"
},
"author": "Damien Arrachequesne <damien.arrachequesne@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/socketio/socket.io/issues"
},
"homepage": "https://github.com/socketio/socket.io#readme",
"dependencies": {
"@socket.io/postgres-adapter": "^0.2.0",
"@socket.io/sticky": "^1.0.1",
"joi": "^17.4.0",
"pg": "^8.7.3",
"pg-hstore": "^2.3.4",
"sequelize": "^6.18.0",
"socket.io": "^4.0.1",
"uuid": "^8.3.2"
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -0,0 +1,24 @@
# vue-client
## Project setup
```
yarn install
```
### Compiles and hot-reloads for development
```
yarn serve
```
### Compiles and minifies for production
```
yarn build
```
### Lints and fixes files
```
yarn lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

View File

@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

View File

@@ -0,0 +1,45 @@
{
"name": "vue-client",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve --port 4200",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.8.3",
"pinia": "^2.1.7",
"socket.io-client": "^4.7.2",
"vue": "^3.2.13"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "@babel/eslint-parser"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie 11"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link href="styles.css" rel="stylesheet" type="text/css" />
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

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