better-sse 0.7.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +177 -177
- package/build/Channel.d.ts +27 -17
- package/build/Session.d.ts +76 -34
- package/build/createChannel.d.ts +1 -1
- package/build/createSession.d.ts +2 -2
- package/build/index.js +1 -1
- package/build/lib/generateId.d.ts +2 -0
- package/build/lib/testUtils.d.ts +7 -4
- package/package.json +20 -23
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2020 Matthew
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Matthew
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,177 +1,177 @@
|
|
|
1
|
-
# Better SSE
|
|
2
|
-
|
|
3
|
-
<p>
|
|
4
|
-
<img src="https://img.shields.io/npm/v/better-sse?color=blue&style=flat-square" />
|
|
5
|
-
<img src="https://img.shields.io/npm/l/better-sse?color=green&style=flat-square" />
|
|
6
|
-
<img src="https://img.shields.io/npm/dt/better-sse?color=grey&style=flat-square" />
|
|
7
|
-
<a href="https://github.com/MatthewWid/better-sse"><img src="https://img.shields.io/github/stars/MatthewWid/better-sse?style=social" /></a>
|
|
8
|
-
</p>
|
|
9
|
-
|
|
10
|
-
A dead simple, dependency-less, spec-compliant server-side events implementation for Node, written in TypeScript.
|
|
11
|
-
|
|
12
|
-
This package aims to be the easiest to use, most compliant and most streamlined solution to server-side events with Node that is framework
|
|
13
|
-
|
|
14
|
-
Please consider starring the project [on GitHub ⭐](https://github.com/MatthewWid/better-sse).
|
|
15
|
-
|
|
16
|
-
## Why use Server-sent Events?
|
|
17
|
-
|
|
18
|
-
Server-sent events (SSE) is a standardised protocol that allows web-servers to push data to clients without the need for alternative mechanisms such as pinging
|
|
19
|
-
|
|
20
|
-
Using SSE can allow for significant savings in bandwidth and battery life on portable devices
|
|
21
|
-
|
|
22
|
-
Compared to WebSockets it has comparable performance and bandwidth usage, especially over HTTP/2, and natively includes event ID generation and automatic reconnection when clients are disconnected.
|
|
23
|
-
|
|
24
|
-
* [Comparison: Server-sent Events vs WebSockets vs Polling](https://medium.com/dailyjs/a-comparison-between-websockets-server-sent-events-and-polling-7a27c98cb1e3)
|
|
25
|
-
* [WHATWG standards section for server-sent events](https://html.spec.whatwg.org/multipage/server-sent-events.html)
|
|
26
|
-
* [MDN guide to server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)
|
|
27
|
-
|
|
28
|
-
## Highlights
|
|
29
|
-
|
|
30
|
-
* Compatible with all popular Node HTTP frameworks ([http](https://nodejs.org/api/http.html), [Express](https://nodejs.org/api/http.html), [Koa](https://www.npmjs.com/package/koa), [Fastify](https://www.npmjs.com/package/fastify), etc.)
|
|
31
|
-
* Fully written in TypeScript (+ ships with types directly).
|
|
32
|
-
* [Thoroughly tested](./src/Session.test.ts) (+ 100% code coverage!).
|
|
33
|
-
* [Comprehensively documented](./docs) with guides and API documentation.
|
|
34
|
-
* [Channels](./docs/channels.md) allow you to broadcast events to many clients at once.
|
|
35
|
-
* Configurable reconnection time, message serialization and data sanitization (
|
|
36
|
-
* Trust or ignore the client-given last event ID.
|
|
37
|
-
* Automatically send keep-alive pings to keep connections open.
|
|
38
|
-
* Add or override the response status code and headers.
|
|
39
|
-
* Fine-grained control by either sending [individual fields](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#fields) of events or sending full events with simple helpers.
|
|
40
|
-
* Pipe [streams](https://nodejs.org/api/stream.html#stream_readable_streams) and [iterables](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators) directly from the server to the client as a
|
|
41
|
-
* Support for popular EventStream polyfills [`event-source-polyfill`](https://www.npmjs.com/package/event-source-polyfill) and [`eventsource-polyfill`](https://www.npmjs.com/package/eventsource-polyfill).
|
|
42
|
-
|
|
43
|
-
[See a comparison with other Node SSE libraries in the documentation.](./docs/comparison.md)
|
|
44
|
-
|
|
45
|
-
# Installation
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
# npm
|
|
49
|
-
npm install better-sse
|
|
50
|
-
|
|
51
|
-
# Yarn
|
|
52
|
-
yarn add better-sse
|
|
53
|
-
|
|
54
|
-
# pnpm
|
|
55
|
-
pnpm add better-sse
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
_Better SSE ships with types built in. No need to install from
|
|
59
|
-
|
|
60
|
-
# Usage
|
|
61
|
-
|
|
62
|
-
The following example shows usage with [Express](http://expressjs.com/), but Better SSE works with any web-server framework
|
|
63
|
-
|
|
64
|
-
See the [Recipes](./docs/recipes.md) section of the documentation for use with other frameworks and libraries.
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
Use [sessions](./docs/api.md#session) to push events to clients:
|
|
69
|
-
|
|
70
|
-
```typescript
|
|
71
|
-
// Server
|
|
72
|
-
import {createSession} from "better-sse";
|
|
73
|
-
|
|
74
|
-
app.get("/sse", async (req, res) => {
|
|
75
|
-
const session = await createSession(req, res);
|
|
76
|
-
|
|
77
|
-
session.push("Hello world!");
|
|
78
|
-
});
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
// Client
|
|
83
|
-
const sse = new EventSource("/sse");
|
|
84
|
-
|
|
85
|
-
sse.addEventListener("message", ({data}) => {
|
|
86
|
-
console.log(data);
|
|
87
|
-
});
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
Use [channels](./docs/channels.md) to send events to many clients at once:
|
|
93
|
-
|
|
94
|
-
```typescript
|
|
95
|
-
import {createSession, createChannel} from "better-sse";
|
|
96
|
-
|
|
97
|
-
const channel = createChannel();
|
|
98
|
-
|
|
99
|
-
app.get("/sse", async (req, res) => {
|
|
100
|
-
const session = await createSession(req, res);
|
|
101
|
-
|
|
102
|
-
channel.register(session);
|
|
103
|
-
|
|
104
|
-
channel.broadcast("A user has joined.", "join-notification");
|
|
105
|
-
});
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
---
|
|
109
|
-
|
|
110
|
-
Loop over sync and async [iterables](./docs/api.md#sessioniterate-iterable-iterable--asynciterable-options-object--promisevoid) and send each value as an event:
|
|
111
|
-
|
|
112
|
-
```typescript
|
|
113
|
-
const session = await createSession(req, res);
|
|
114
|
-
|
|
115
|
-
const list = [1, 2, 3];
|
|
116
|
-
|
|
117
|
-
await session.iterate(list);
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
---
|
|
121
|
-
|
|
122
|
-
Pipe [readable stream](#sessionstream-stream-readable-options-object--promiseboolean) data to the client as a stream of events:
|
|
123
|
-
|
|
124
|
-
```typescript
|
|
125
|
-
const session = await createSession(req, res);
|
|
126
|
-
|
|
127
|
-
const stream = Readable.from([1, 2, 3]);
|
|
128
|
-
|
|
129
|
-
await session.stream(stream);
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
---
|
|
133
|
-
|
|
134
|
-
Check the [API documentation](./docs/api.md) and [live examples](
|
|
135
|
-
|
|
136
|
-
# Documentation
|
|
137
|
-
|
|
138
|
-
API documentation, getting started guides and usage with other frameworks is [available on GitHub](
|
|
139
|
-
|
|
140
|
-
# Contributing
|
|
141
|
-
|
|
142
|
-
This library is always open to contributions, whether it be code, bug reports, documentation or anything else.
|
|
143
|
-
|
|
144
|
-
Please submit suggestions, bugs and issues to the [GitHub issues page](https://github.com/MatthewWid/better-sse/issues).
|
|
145
|
-
|
|
146
|
-
For code or documentation changes, [submit a pull request on GitHub](https://github.com/MatthewWid/better-sse/pulls).
|
|
147
|
-
|
|
148
|
-
## Local Development
|
|
149
|
-
|
|
150
|
-
Install Node:
|
|
151
|
-
|
|
152
|
-
```bash
|
|
153
|
-
curl -L https://git.io/n-install | bash
|
|
154
|
-
n auto
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
Install pnpm:
|
|
158
|
-
|
|
159
|
-
```bash
|
|
160
|
-
npm i -g pnpm
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
Install dependencies:
|
|
164
|
-
|
|
165
|
-
```bash
|
|
166
|
-
pnpm i
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
Run tests:
|
|
170
|
-
|
|
171
|
-
```bash
|
|
172
|
-
pnpm t
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
# License
|
|
176
|
-
|
|
177
|
-
This project is licensed under the MIT license.
|
|
1
|
+
# Better SSE
|
|
2
|
+
|
|
3
|
+
<p>
|
|
4
|
+
<img src="https://img.shields.io/npm/v/better-sse?color=blue&style=flat-square" />
|
|
5
|
+
<img src="https://img.shields.io/npm/l/better-sse?color=green&style=flat-square" />
|
|
6
|
+
<img src="https://img.shields.io/npm/dt/better-sse?color=grey&style=flat-square" />
|
|
7
|
+
<a href="https://github.com/MatthewWid/better-sse"><img src="https://img.shields.io/github/stars/MatthewWid/better-sse?style=social" /></a>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
A dead simple, dependency-less, spec-compliant server-side events implementation for Node, written in TypeScript.
|
|
11
|
+
|
|
12
|
+
This package aims to be the easiest to use, most compliant and most streamlined solution to server-side events with Node that is framework-agnostic and feature-rich.
|
|
13
|
+
|
|
14
|
+
Please consider starring the project [on GitHub ⭐](https://github.com/MatthewWid/better-sse).
|
|
15
|
+
|
|
16
|
+
## Why use Server-sent Events?
|
|
17
|
+
|
|
18
|
+
Server-sent events (SSE) is a standardised protocol that allows web-servers to push data to clients without the need for alternative mechanisms such as pinging, long-polling or [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API).
|
|
19
|
+
|
|
20
|
+
Using SSE can allow for significant savings in bandwidth and battery life on portable devices and will work with your existing infrastructure as it operates directly over the HTTP protocol without the need for the connection upgrade that WebSockets require.
|
|
21
|
+
|
|
22
|
+
Compared to WebSockets it has comparable performance and bandwidth usage, especially over HTTP/2, and natively includes event ID generation and automatic reconnection when clients are disconnected.
|
|
23
|
+
|
|
24
|
+
* [Comparison: Server-sent Events vs WebSockets vs Polling](https://medium.com/dailyjs/a-comparison-between-websockets-server-sent-events-and-polling-7a27c98cb1e3)
|
|
25
|
+
* [WHATWG standards section for server-sent events](https://html.spec.whatwg.org/multipage/server-sent-events.html)
|
|
26
|
+
* [MDN guide to server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)
|
|
27
|
+
|
|
28
|
+
## Highlights
|
|
29
|
+
|
|
30
|
+
* Compatible with all popular Node HTTP frameworks ([http](https://nodejs.org/api/http.html), [Express](https://nodejs.org/api/http.html), [Koa](https://www.npmjs.com/package/koa), [Fastify](https://www.npmjs.com/package/fastify), etc.)
|
|
31
|
+
* Fully written in TypeScript (+ ships with types directly).
|
|
32
|
+
* [Thoroughly tested](./src/Session.test.ts) (+ 100% code coverage!).
|
|
33
|
+
* [Comprehensively documented](./docs) with guides and API documentation.
|
|
34
|
+
* [Channels](./docs/channels.md) allow you to broadcast events to many clients at once.
|
|
35
|
+
* Configurable reconnection time, message serialization and data sanitization (with good defaults).
|
|
36
|
+
* Trust or ignore the client-given last event ID.
|
|
37
|
+
* Automatically send keep-alive pings to keep connections open.
|
|
38
|
+
* Add or override the response status code and headers.
|
|
39
|
+
* Fine-grained control by either sending [individual fields](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#fields) of events or by sending full events with simple helpers.
|
|
40
|
+
* Pipe [streams](https://nodejs.org/api/stream.html#stream_readable_streams) and [iterables](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators) directly from the server to the client as a series of events.
|
|
41
|
+
* Support for popular EventStream polyfills [`event-source-polyfill`](https://www.npmjs.com/package/event-source-polyfill) and [`eventsource-polyfill`](https://www.npmjs.com/package/eventsource-polyfill).
|
|
42
|
+
|
|
43
|
+
[See a comparison with other Node SSE libraries in the documentation.](./docs/comparison.md)
|
|
44
|
+
|
|
45
|
+
# Installation
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# npm
|
|
49
|
+
npm install better-sse
|
|
50
|
+
|
|
51
|
+
# Yarn
|
|
52
|
+
yarn add better-sse
|
|
53
|
+
|
|
54
|
+
# pnpm
|
|
55
|
+
pnpm add better-sse
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
_Better SSE ships with types built in. No need to install from DefinitelyTyped for TypeScript users!_
|
|
59
|
+
|
|
60
|
+
# Usage
|
|
61
|
+
|
|
62
|
+
The following example shows usage with [Express](http://expressjs.com/), but Better SSE works with any web-server framework that uses the underlying Node [HTTP module](https://nodejs.org/api/http.html).
|
|
63
|
+
|
|
64
|
+
See the [Recipes](./docs/recipes.md) section of the documentation for use with other frameworks and libraries.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
Use [sessions](./docs/api.md#session) to push events to clients:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// Server
|
|
72
|
+
import { createSession } from "better-sse";
|
|
73
|
+
|
|
74
|
+
app.get("/sse", async (req, res) => {
|
|
75
|
+
const session = await createSession(req, res);
|
|
76
|
+
|
|
77
|
+
session.push("Hello world!");
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// Client
|
|
83
|
+
const sse = new EventSource("/sse");
|
|
84
|
+
|
|
85
|
+
sse.addEventListener("message", ({ data }) => {
|
|
86
|
+
console.log(data);
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
Use [channels](./docs/channels.md) to send events to many clients at once:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { createSession, createChannel } from "better-sse";
|
|
96
|
+
|
|
97
|
+
const channel = createChannel();
|
|
98
|
+
|
|
99
|
+
app.get("/sse", async (req, res) => {
|
|
100
|
+
const session = await createSession(req, res);
|
|
101
|
+
|
|
102
|
+
channel.register(session);
|
|
103
|
+
|
|
104
|
+
channel.broadcast("A user has joined.", "join-notification");
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
Loop over sync and async [iterables](./docs/api.md#sessioniterate-iterable-iterable--asynciterable-options-object--promisevoid) and send each value as an event:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
const session = await createSession(req, res);
|
|
114
|
+
|
|
115
|
+
const list = [1, 2, 3];
|
|
116
|
+
|
|
117
|
+
await session.iterate(list);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
Pipe [readable stream](#sessionstream-stream-readable-options-object--promiseboolean) data to the client as a stream of events:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const session = await createSession(req, res);
|
|
126
|
+
|
|
127
|
+
const stream = Readable.from([1, 2, 3]);
|
|
128
|
+
|
|
129
|
+
await session.stream(stream);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
Check the [API documentation](./docs/api.md) and [live examples](./examples) for information on getting more fine-tuned control over your data such as managing event IDs, data serialization, event filtering, dispatch controls and more!
|
|
135
|
+
|
|
136
|
+
# Documentation
|
|
137
|
+
|
|
138
|
+
API documentation, getting started guides and usage with other frameworks is [available on GitHub](./docs).
|
|
139
|
+
|
|
140
|
+
# Contributing
|
|
141
|
+
|
|
142
|
+
This library is always open to contributions, whether it be code, bug reports, documentation or anything else.
|
|
143
|
+
|
|
144
|
+
Please submit suggestions, bugs and issues to the [GitHub issues page](https://github.com/MatthewWid/better-sse/issues).
|
|
145
|
+
|
|
146
|
+
For code or documentation changes, [submit a pull request on GitHub](https://github.com/MatthewWid/better-sse/pulls).
|
|
147
|
+
|
|
148
|
+
## Local Development
|
|
149
|
+
|
|
150
|
+
Install Node:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
curl -L https://git.io/n-install | bash
|
|
154
|
+
n auto
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Install pnpm:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
npm i -g pnpm
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Install dependencies:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
pnpm i
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Run tests:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
pnpm t
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
# License
|
|
176
|
+
|
|
177
|
+
This project is licensed under the [MIT license](https://opensource.org/license/mit/).
|
package/build/Channel.d.ts
CHANGED
|
@@ -1,27 +1,33 @@
|
|
|
1
1
|
import { TypedEmitter, EventMap } from "./lib/TypedEmitter";
|
|
2
|
-
import { Session } from "./Session";
|
|
3
|
-
interface BroadcastOptions {
|
|
2
|
+
import { Session, DefaultSessionState } from "./Session";
|
|
3
|
+
interface BroadcastOptions<SessionState extends Record<string, unknown> = DefaultSessionState> {
|
|
4
4
|
/**
|
|
5
5
|
* Filter sessions that should receive the event.
|
|
6
6
|
*
|
|
7
7
|
* Called with each session and should return `true` to allow the event to be sent and otherwise return `false` to prevent the session from receiving the event.
|
|
8
8
|
*/
|
|
9
|
-
filter?: (session: Session) => boolean;
|
|
9
|
+
filter?: (session: Session<SessionState>) => boolean;
|
|
10
10
|
}
|
|
11
|
-
interface ChannelEvents extends EventMap {
|
|
12
|
-
"session-registered": (session: Session) => void;
|
|
13
|
-
"session-deregistered": (session: Session) => void;
|
|
14
|
-
"session-disconnected": (session: Session) => void;
|
|
15
|
-
broadcast: (data: unknown, eventName: string) => void;
|
|
11
|
+
interface ChannelEvents<SessionState extends Record<string, unknown> = DefaultSessionState> extends EventMap {
|
|
12
|
+
"session-registered": (session: Session<SessionState>) => void;
|
|
13
|
+
"session-deregistered": (session: Session<SessionState>) => void;
|
|
14
|
+
"session-disconnected": (session: Session<SessionState>) => void;
|
|
15
|
+
broadcast: (data: unknown, eventName: string, eventId: string) => void;
|
|
16
|
+
}
|
|
17
|
+
interface DefaultChannelState {
|
|
18
|
+
[key: string]: unknown;
|
|
16
19
|
}
|
|
17
20
|
/**
|
|
18
|
-
* A Channel is used to broadcast events to many sessions at once.
|
|
21
|
+
* A `Channel` is used to broadcast events to many sessions at once.
|
|
19
22
|
*
|
|
20
23
|
* It extends from the {@link https://nodejs.org/api/events.html#events_class_eventemitter | EventEmitter} class.
|
|
24
|
+
*
|
|
25
|
+
* You may use the second generic argument `SessionState` to enforce that only sessions with the same state type may be registered with this channel.
|
|
21
26
|
*/
|
|
22
|
-
declare class Channel<State extends Record<string, unknown> = Record<string, unknown
|
|
27
|
+
declare class Channel<State extends Record<string, unknown> = DefaultChannelState, SessionState extends Record<string, unknown> = DefaultSessionState> extends TypedEmitter<ChannelEvents<SessionState>> {
|
|
23
28
|
/**
|
|
24
29
|
* Custom state for this channel.
|
|
30
|
+
*
|
|
25
31
|
* Use this object to safely store information related to the channel.
|
|
26
32
|
*/
|
|
27
33
|
state: State;
|
|
@@ -30,7 +36,7 @@ declare class Channel<State extends Record<string, unknown> = Record<string, unk
|
|
|
30
36
|
/**
|
|
31
37
|
* List of the currently active sessions subscribed to this channel.
|
|
32
38
|
*/
|
|
33
|
-
get activeSessions(): ReadonlyArray<Session
|
|
39
|
+
get activeSessions(): ReadonlyArray<Session<SessionState>>;
|
|
34
40
|
/**
|
|
35
41
|
* Number of sessions subscribed to this channel.
|
|
36
42
|
*/
|
|
@@ -38,21 +44,25 @@ declare class Channel<State extends Record<string, unknown> = Record<string, unk
|
|
|
38
44
|
/**
|
|
39
45
|
* Register a session so that it can start receiving events from this channel.
|
|
40
46
|
*
|
|
47
|
+
* If the session was already registered to begin with this method does nothing.
|
|
48
|
+
*
|
|
41
49
|
* @param session - Session to register.
|
|
42
50
|
*/
|
|
43
|
-
register(session: Session): this;
|
|
51
|
+
register(session: Session<SessionState>): this;
|
|
44
52
|
/**
|
|
45
53
|
* Deregister a session so that it no longer receives events from this channel.
|
|
46
54
|
*
|
|
55
|
+
* If the session was not registered to begin with this method does nothing.
|
|
56
|
+
*
|
|
47
57
|
* @param session - Session to deregister.
|
|
48
58
|
*/
|
|
49
|
-
deregister(session: Session): this;
|
|
59
|
+
deregister(session: Session<SessionState>): this;
|
|
50
60
|
/**
|
|
51
|
-
*
|
|
61
|
+
* Broadcast an event with the given data and name to every active session registered with this channel.
|
|
52
62
|
*
|
|
53
|
-
*
|
|
63
|
+
* Note that the broadcasted event will have the same ID across all receiving sessions instead of generating a unique ID for each.
|
|
54
64
|
*/
|
|
55
|
-
broadcast: (data: unknown, eventName?: string
|
|
65
|
+
broadcast: (data: unknown, eventName?: string, options?: BroadcastOptions<SessionState>) => this;
|
|
56
66
|
}
|
|
57
|
-
export type { BroadcastOptions, ChannelEvents };
|
|
67
|
+
export type { BroadcastOptions, ChannelEvents, DefaultChannelState };
|
|
58
68
|
export { Channel };
|
package/build/Session.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
2
3
|
import { Readable } from "stream";
|
|
3
|
-
import { IncomingMessage, ServerResponse, OutgoingHttpHeaders } from "http";
|
|
4
|
+
import { IncomingMessage as Http1ServerRequest, ServerResponse as Http1ServerResponse, OutgoingHttpHeaders } from "http";
|
|
5
|
+
import { Http2ServerRequest, Http2ServerResponse } from "http2";
|
|
4
6
|
import { TypedEmitter, EventMap } from "./lib/TypedEmitter";
|
|
5
7
|
import { SerializerFunction } from "./lib/serialize";
|
|
6
8
|
import { SanitizerFunction } from "./lib/sanitize";
|
|
@@ -20,7 +22,7 @@ interface SessionOptions {
|
|
|
20
22
|
*/
|
|
21
23
|
sanitizer?: SanitizerFunction;
|
|
22
24
|
/**
|
|
23
|
-
* Whether to trust the last event ID given by the client in the `Last-Event-ID` request header.
|
|
25
|
+
* Whether to trust or ignore the last event ID given by the client in the `Last-Event-ID` request header.
|
|
24
26
|
*
|
|
25
27
|
* When set to `false`, the `lastId` property will always be initialized to an empty string.
|
|
26
28
|
*
|
|
@@ -28,9 +30,11 @@ interface SessionOptions {
|
|
|
28
30
|
*/
|
|
29
31
|
trustClientEventId?: boolean;
|
|
30
32
|
/**
|
|
31
|
-
* Time in milliseconds for the client to wait before attempting to reconnect if the connection is closed.
|
|
33
|
+
* Time in milliseconds for the client to wait before attempting to reconnect if the connection is closed.
|
|
34
|
+
*
|
|
35
|
+
* This is a request to the client browser, and does not guarantee that the client will actually respect the given time.
|
|
32
36
|
*
|
|
33
|
-
*
|
|
37
|
+
* Equivalent to immediately calling `.retry().dispatch().flush()` after a connection is made.
|
|
34
38
|
*
|
|
35
39
|
* Give as `null` to avoid sending an explicit reconnection time and allow the client browser to decide itself.
|
|
36
40
|
*
|
|
@@ -51,7 +55,6 @@ interface SessionOptions {
|
|
|
51
55
|
* Status code to be sent to the client.
|
|
52
56
|
*
|
|
53
57
|
* Event stream requests can be redirected using HTTP 301 and 307 status codes.
|
|
54
|
-
*
|
|
55
58
|
* Make sure to set `Location` header when using these status codes using the `headers` property.
|
|
56
59
|
*
|
|
57
60
|
* A client can be asked to stop reconnecting by using 204 status code.
|
|
@@ -80,7 +83,7 @@ interface IterateOptions {
|
|
|
80
83
|
*/
|
|
81
84
|
eventName?: string;
|
|
82
85
|
}
|
|
83
|
-
interface
|
|
86
|
+
interface DefaultSessionState {
|
|
84
87
|
[key: string]: unknown;
|
|
85
88
|
}
|
|
86
89
|
interface SessionEvents extends EventMap {
|
|
@@ -89,37 +92,64 @@ interface SessionEvents extends EventMap {
|
|
|
89
92
|
push: (data: unknown, eventName: string, eventId: string) => void;
|
|
90
93
|
}
|
|
91
94
|
/**
|
|
92
|
-
* A Session represents an open connection between the server and a client.
|
|
95
|
+
* A `Session` represents an open connection between the server and a client.
|
|
93
96
|
*
|
|
94
97
|
* It extends from the {@link https://nodejs.org/api/events.html#events_class_eventemitter | EventEmitter} class.
|
|
95
98
|
*
|
|
96
|
-
* It emits the `connected` event after it has connected and
|
|
97
|
-
* `disconnected` event after
|
|
99
|
+
* It emits the `connected` event after it has connected and sent all headers to the client, and the
|
|
100
|
+
* `disconnected` event after the connection has been closed.
|
|
101
|
+
*
|
|
102
|
+
* Note that creating a new session will immediately send the initial status code and headers to the client.
|
|
103
|
+
* Attempting to write additional headers after you have created a new session will result in an error.
|
|
104
|
+
*
|
|
105
|
+
* As a performance optimisation, all events and data are first written to an internal buffer
|
|
106
|
+
* where it is stored until it is flushed to the client by calling the `flush` method. This is
|
|
107
|
+
* done for you when using the `push` helper method.
|
|
98
108
|
*
|
|
99
109
|
* @param req - The Node HTTP {@link https://nodejs.org/api/http.html#http_class_http_incomingmessage | ServerResponse} object.
|
|
100
110
|
* @param res - The Node HTTP {@link https://nodejs.org/api/http.html#http_class_http_serverresponse | IncomingMessage} object.
|
|
101
111
|
* @param options - Options given to the session instance.
|
|
102
112
|
*/
|
|
103
|
-
declare class Session<State extends Record<string, unknown> =
|
|
113
|
+
declare class Session<State extends Record<string, unknown> = DefaultSessionState> extends TypedEmitter<SessionEvents> {
|
|
104
114
|
/**
|
|
105
|
-
* The last ID sent to the client.
|
|
115
|
+
* The last event ID sent to the client.
|
|
116
|
+
*
|
|
106
117
|
* This is initialized to the last event ID given by the user, and otherwise is equal to the last number given to the `.id` method.
|
|
107
118
|
*
|
|
119
|
+
* For security reasons, keep in mind that the client can provide *any* initial ID here. Use the `trustClientEventId` to ignore the client-given initial ID.
|
|
120
|
+
*
|
|
108
121
|
* @readonly
|
|
109
122
|
*/
|
|
110
123
|
lastId: string;
|
|
111
124
|
/**
|
|
112
|
-
* Indicates whether the session and connection is open or not.
|
|
125
|
+
* Indicates whether the session and underlying connection is open or not.
|
|
113
126
|
*
|
|
114
127
|
* @readonly
|
|
115
128
|
*/
|
|
116
129
|
isConnected: boolean;
|
|
117
130
|
/**
|
|
118
131
|
* Custom state for this session.
|
|
132
|
+
*
|
|
119
133
|
* Use this object to safely store information related to the session and user.
|
|
134
|
+
*
|
|
135
|
+
* Use [module augmentation and declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation)
|
|
136
|
+
* to safely add new properties to the `DefaultSessionState` interface.
|
|
120
137
|
*/
|
|
121
138
|
state: State;
|
|
139
|
+
/**
|
|
140
|
+
* Internal buffer used to store raw data from written fields.
|
|
141
|
+
*
|
|
142
|
+
* When Session#dispatch is called its buffer data will be flushed.
|
|
143
|
+
*/
|
|
144
|
+
private buffer;
|
|
145
|
+
/**
|
|
146
|
+
* Raw HTTP request.
|
|
147
|
+
*/
|
|
122
148
|
private req;
|
|
149
|
+
/**
|
|
150
|
+
* Raw HTTP response that is the minimal interface needed and forms the
|
|
151
|
+
* intersection between the HTTP/1.1 and HTTP/2 server response interfaces.
|
|
152
|
+
*/
|
|
123
153
|
private res;
|
|
124
154
|
private serialize;
|
|
125
155
|
private sanitize;
|
|
@@ -129,8 +159,8 @@ declare class Session<State extends Record<string, unknown> = SessionState> exte
|
|
|
129
159
|
private keepAliveTimer?;
|
|
130
160
|
private statusCode;
|
|
131
161
|
private headers;
|
|
132
|
-
constructor(req:
|
|
133
|
-
private
|
|
162
|
+
constructor(req: Http1ServerRequest | Http2ServerRequest, res: Http1ServerResponse | Http2ServerResponse, options?: SessionOptions);
|
|
163
|
+
private initialize;
|
|
134
164
|
private onDisconnected;
|
|
135
165
|
/**
|
|
136
166
|
* Write a line with a field key and value appended with a newline character.
|
|
@@ -138,17 +168,13 @@ declare class Session<State extends Record<string, unknown> = SessionState> exte
|
|
|
138
168
|
private writeField;
|
|
139
169
|
private keepAlive;
|
|
140
170
|
/**
|
|
141
|
-
*
|
|
142
|
-
*/
|
|
143
|
-
dispatch: () => this;
|
|
144
|
-
/**
|
|
145
|
-
* Set the event to the given name (also referred to as "type" in the specification).
|
|
171
|
+
* Set the event to the given name (also referred to as the event "type" in the specification).
|
|
146
172
|
*
|
|
147
173
|
* @param type - Event name/type.
|
|
148
174
|
*/
|
|
149
175
|
event(type: string): this;
|
|
150
176
|
/**
|
|
151
|
-
* Write arbitrary data
|
|
177
|
+
* Write an arbitrary data field that is automatically serialized to a string using the given `serializer` function option or JSON stringification by default.
|
|
152
178
|
*
|
|
153
179
|
* @param data - Data to serialize and write.
|
|
154
180
|
*/
|
|
@@ -156,11 +182,11 @@ declare class Session<State extends Record<string, unknown> = SessionState> exte
|
|
|
156
182
|
/**
|
|
157
183
|
* Set the event ID to the given string.
|
|
158
184
|
*
|
|
159
|
-
*
|
|
185
|
+
* Defaults to an empty string if no argument is given.
|
|
160
186
|
*
|
|
161
187
|
* @param id - Identification string to write.
|
|
162
188
|
*/
|
|
163
|
-
id: (id
|
|
189
|
+
id: (id?: string) => this;
|
|
164
190
|
/**
|
|
165
191
|
* Set the suggested reconnection time to the given milliseconds.
|
|
166
192
|
*
|
|
@@ -174,26 +200,41 @@ declare class Session<State extends Record<string, unknown> = SessionState> exte
|
|
|
174
200
|
*
|
|
175
201
|
* @param text - Text of the comment. Otherwise writes an empty field value.
|
|
176
202
|
*/
|
|
177
|
-
comment: (text?: string
|
|
203
|
+
comment: (text?: string) => this;
|
|
204
|
+
/**
|
|
205
|
+
* Indicate that the event has finished being created by writing an additional newline character.
|
|
206
|
+
*
|
|
207
|
+
* Note that this does **not** send the written data to the client. Use `flush` to flush the internal buffer.
|
|
208
|
+
*/
|
|
209
|
+
dispatch: () => this;
|
|
210
|
+
/**
|
|
211
|
+
* Flush the buffered data to the client and clear the buffer.
|
|
212
|
+
*/
|
|
213
|
+
flush: () => this;
|
|
178
214
|
/**
|
|
179
|
-
* Create
|
|
180
|
-
*
|
|
215
|
+
* Create, write, dispatch and flush an event with the given data to the client all at once.
|
|
216
|
+
*
|
|
217
|
+
* This is equivalent to calling the methods `event`, `id`, `data`, `dispatch` and `flush` in that order.
|
|
181
218
|
*
|
|
182
|
-
* If no event name is given, the event name
|
|
219
|
+
* If no event name is given, the event name is set to `"message"`.
|
|
183
220
|
*
|
|
184
|
-
*
|
|
221
|
+
* If no event ID is given, the event ID (and thus the `lastid` property) is set to a unique string generated using a cryptographic pseudorandom number generator.
|
|
222
|
+
*
|
|
223
|
+
* Emits the `push` event with the given data, event name and event ID in that order.
|
|
185
224
|
*
|
|
186
225
|
* @param data - Data to write.
|
|
187
226
|
* @param eventName - Event name to write.
|
|
227
|
+
* @param eventId - Event ID to write.
|
|
188
228
|
*/
|
|
189
|
-
push: (data: unknown, eventName?: string
|
|
229
|
+
push: (data: unknown, eventName?: string, eventId?: string) => this;
|
|
190
230
|
/**
|
|
191
231
|
* Pipe readable stream data to the client.
|
|
192
232
|
*
|
|
193
|
-
* Each data emission by the stream
|
|
233
|
+
* Each data emission by the stream pushes a new event to the client.
|
|
234
|
+
*
|
|
194
235
|
* This uses the `push` method under the hood.
|
|
195
236
|
*
|
|
196
|
-
* If no event name is given in the options object, the event name
|
|
237
|
+
* If no event name is given in the options object, the event name is set to `"stream"`.
|
|
197
238
|
*
|
|
198
239
|
* @param stream - Readable stream to consume from.
|
|
199
240
|
* @param options - Options to alter how the stream is flushed to the client.
|
|
@@ -202,12 +243,13 @@ declare class Session<State extends Record<string, unknown> = SessionState> exte
|
|
|
202
243
|
*/
|
|
203
244
|
stream: (stream: Readable, options?: StreamOptions) => Promise<boolean>;
|
|
204
245
|
/**
|
|
205
|
-
* Iterate over an iterable and send yielded values
|
|
246
|
+
* Iterate over an iterable and send yielded values to the client.
|
|
247
|
+
*
|
|
248
|
+
* Each yield pushes a new event to the client.
|
|
206
249
|
*
|
|
207
|
-
* Each yield emits a new event that is dispatched to the client.
|
|
208
250
|
* This uses the `push` method under the hood.
|
|
209
251
|
*
|
|
210
|
-
* If no event name is given in the options object, the event name
|
|
252
|
+
* If no event name is given in the options object, the event name is set to `"iteration"`.
|
|
211
253
|
*
|
|
212
254
|
* @param iterable - Iterable to consume data from.
|
|
213
255
|
*
|
|
@@ -215,5 +257,5 @@ declare class Session<State extends Record<string, unknown> = SessionState> exte
|
|
|
215
257
|
*/
|
|
216
258
|
iterate: <DataType = unknown>(iterable: Iterable<DataType> | AsyncIterable<DataType>, options?: IterateOptions) => Promise<void>;
|
|
217
259
|
}
|
|
218
|
-
export type { SessionOptions, StreamOptions, IterateOptions,
|
|
260
|
+
export type { SessionOptions, StreamOptions, IterateOptions, SessionEvents, DefaultSessionState, };
|
|
219
261
|
export { Session };
|
package/build/createChannel.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { Channel } from "./Channel";
|
|
2
|
-
declare const createChannel: <State extends Record<string, unknown>>() => Channel<State>;
|
|
2
|
+
declare const createChannel: <State extends Record<string, unknown>, SessionState extends Record<string, unknown>>() => Channel<State, SessionState>;
|
|
3
3
|
export { createChannel };
|
package/build/createSession.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Session
|
|
1
|
+
import { Session } from "./Session";
|
|
2
2
|
/**
|
|
3
3
|
* Create a new session and return the session instance once it has connected.
|
|
4
4
|
*/
|
|
5
|
-
declare const createSession: <State extends Record<string, unknown
|
|
5
|
+
declare const createSession: <State extends Record<string, unknown>>(req: import("http").IncomingMessage | import("http2").Http2ServerRequest, res: import("http").ServerResponse<import("http").IncomingMessage> | import("http2").Http2ServerResponse, options?: import("./Session").SessionOptions | undefined) => Promise<Session<State>>;
|
|
6
6
|
export { createSession };
|
package/build/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
!function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var s=t();for(var i in s)("object"==typeof exports?exports:e)[i]=s[i]}}(global,(
|
|
1
|
+
!function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var s=t();for(var i in s)("object"==typeof exports?exports:e)[i]=s[i]}}(global,(()=>(()=>{"use strict";var e={n:t=>{var s=t&&t.__esModule?()=>t.default:()=>t;return e.d(s,{a:s}),s},d:(t,s)=>{for(var i in s)e.o(s,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:s[i]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},t={};e.r(t),e.d(t,{Channel:()=>f,Session:()=>u,createChannel:()=>v,createSession:()=>p});const s=require("http"),i=require("events");var r=e.n(i);class n extends(r()){addListener(e,t){return super.addListener(e,t)}prependListener(e,t){return super.prependListener(e,t)}prependOnceListener(e,t){return super.prependOnceListener(e,t)}on(e,t){return super.on(e,t)}once(e,t){return super.once(e,t)}emit(e,...t){return super.emit(e,...t)}off(e,t){return super.off(e,t)}removeListener(e,t){return super.removeListener(e,t)}}const o=e=>JSON.stringify(e),a=/(\r\n|\r|\n)/g,h=/\n+$/g,l=e=>{let t=e;return t=t.replace(a,"\n"),t=t.replace(h,""),t},d=require("crypto");let c;c=d.randomUUID?()=>(0,d.randomUUID)():()=>(0,d.randomBytes)(4).toString("hex");class u extends n{constructor(e,t,i={}){var r,n,a,h,d,u,p;super(),this.lastId="",this.isConnected=!1,this.state={},this.buffer="",this.initialize=()=>{var e,t,i;const r=`http://${this.req.headers.host}${this.req.url}`,n=new URL(r).searchParams;if(this.trustClientEventId){const s=null!==(i=null!==(t=null!==(e=this.req.headers["last-event-id"])&&void 0!==e?e:n.get("lastEventId"))&&void 0!==t?t:n.get("evs_last_event_id"))&&void 0!==i?i:"";this.lastId=s}const o={};this.res instanceof s.ServerResponse?(o["Content-Type"]="text/event-stream",o["Cache-Control"]="private, no-cache, no-store, no-transform, must-revalidate, max-age=0",o.Connection="keep-alive",o.Pragma="no-cache",o["X-Accel-Buffering"]="no"):(o["content-type"]="text/event-stream",o["cache-control"]="private, no-cache, no-store, no-transform, must-revalidate, max-age=0",o.pragma="no-cache",o["x-accel-buffering"]="no");for(const[e,t]of Object.entries(this.headers))o[e]=null!=t?t:"";this.res.writeHead(this.statusCode,o),n.has("padding")&&this.comment(" ".repeat(2049)).dispatch(),n.has("evs_preamble")&&this.comment(" ".repeat(2056)).dispatch(),null!==this.initialRetry&&this.retry(this.initialRetry).dispatch(),this.flush(),null!==this.keepAliveInterval&&(this.keepAliveTimer=setInterval(this.keepAlive,this.keepAliveInterval)),this.isConnected=!0,this.emit("connected")},this.onDisconnected=()=>{this.keepAliveTimer&&clearInterval(this.keepAliveTimer),this.isConnected=!1,this.emit("disconnected")},this.writeField=(e,t)=>{const s=this.sanitize(t);return this.buffer+=e+":"+s+"\n",this},this.keepAlive=()=>{this.comment().dispatch().flush()},this.data=e=>{const t=this.serialize(e);return this.writeField("data",t),this},this.id=(e="")=>(this.writeField("id",e),this.lastId=e,this),this.retry=e=>{const t=e.toString();return this.writeField("retry",t),this},this.comment=(e="")=>(this.writeField("",e),this),this.dispatch=()=>(this.buffer+="\n",this),this.flush=()=>(this.res.write(this.buffer),this.buffer="",this),this.push=(e,t="message",s=c())=>(this.event(t).id(s).data(e).dispatch().flush(),this.emit("push",e,t,s),this),this.stream=async(e,t={})=>{const{eventName:s="stream"}=t;return new Promise(((t,i)=>{e.on("data",(e=>{let t;t=Buffer.isBuffer(e)?e.toString():e,this.push(t,s)})),e.once("end",(()=>t(!0))),e.once("close",(()=>t(!0))),e.once("error",(e=>i(e)))}))},this.iterate=async(e,t={})=>{const{eventName:s="iteration"}=t;for await(const t of e)this.push(t,s)},this.req=e,this.res=t,this.serialize=null!==(r=i.serializer)&&void 0!==r?r:o,this.sanitize=null!==(n=i.sanitizer)&&void 0!==n?n:l,this.trustClientEventId=null===(a=i.trustClientEventId)||void 0===a||a,this.initialRetry=null===i.retry?null:null!==(h=i.retry)&&void 0!==h?h:2e3,this.keepAliveInterval=null===i.keepAlive?null:null!==(d=i.keepAlive)&&void 0!==d?d:1e4,this.statusCode=null!==(u=i.statusCode)&&void 0!==u?u:200,this.headers=null!==(p=i.headers)&&void 0!==p?p:{},this.req.once("close",this.onDisconnected),setImmediate(this.initialize)}event(e){return this.writeField("event",e),this}}const p=(...e)=>new Promise((t=>{const s=new u(...e);s.once("connected",(()=>{t(s)}))}));class f extends n{constructor(){super(),this.state={},this.sessions=new Set,this.broadcast=(e,t="message",s={})=>{const i=c();let r;r=s.filter?Array.from(this.sessions).filter(s.filter):this.sessions;for(const s of r)s.push(e,t,i);return this.emit("broadcast",e,t,i),this}}get activeSessions(){return Array.from(this.sessions)}get sessionCount(){return this.sessions.size}register(e){if(this.sessions.has(e))return this;if(!e.isConnected)throw new Error("Cannot register a non-active session.");return e.once("disconnected",(()=>{this.emit("session-disconnected",e),this.deregister(e)})),this.sessions.add(e),this.emit("session-registered",e),this}deregister(e){return this.sessions.has(e)?(this.sessions.delete(e),this.emit("session-deregistered",e),this):this}}const v=(...e)=>new f(...e);return t})()));
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/build/lib/testUtils.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import http from "http";
|
|
2
|
+
import http2 from "http2";
|
|
3
|
+
import net from "net";
|
|
2
4
|
import { Session } from "../Session";
|
|
3
|
-
declare const
|
|
4
|
-
declare const
|
|
5
|
-
declare const
|
|
5
|
+
declare const createHttpServer: () => Promise<http.Server>;
|
|
6
|
+
declare const createHttp2Server: () => Promise<http2.Http2Server>;
|
|
7
|
+
declare const closeServer: (server: net.Server) => Promise<void>;
|
|
8
|
+
declare const getUrl: (server: net.Server) => string;
|
|
6
9
|
declare const waitForConnect: (session: Session) => Promise<void>;
|
|
7
|
-
export {
|
|
10
|
+
export { createHttpServer, createHttp2Server, closeServer, getUrl, waitForConnect, };
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "better-sse",
|
|
3
3
|
"description": "Dead simple, dependency-less, spec-compliant server-side events implementation for Node, written in TypeScript.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.9.0",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"types": "./build/index.d.ts",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"author": "Matthew W. <matthew.widdi@gmail.com>",
|
|
9
9
|
"repository": "github:MatthewWid/better-sse",
|
|
10
|
-
"homepage": "https://github.com/MatthewWid/better-sse
|
|
10
|
+
"homepage": "https://github.com/MatthewWid/better-sse",
|
|
11
11
|
"bugs": "https://github.com/MatthewWid/better-sse/issues",
|
|
12
12
|
"keywords": [
|
|
13
13
|
"server-sent-events",
|
|
@@ -26,32 +26,29 @@
|
|
|
26
26
|
"pnpm": ">=6"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@types/eventsource": "^1.1.
|
|
30
|
-
"@types/express": "^4.17.
|
|
31
|
-
"@types/
|
|
32
|
-
"@
|
|
33
|
-
"@typescript-eslint/
|
|
34
|
-
"
|
|
35
|
-
"eslint": "^
|
|
36
|
-
"
|
|
37
|
-
"eventsource": "^1.1.0",
|
|
38
|
-
"jest": "^26.6.3",
|
|
29
|
+
"@types/eventsource": "^1.1.11",
|
|
30
|
+
"@types/express": "^4.17.16",
|
|
31
|
+
"@types/node": "^18.11.18",
|
|
32
|
+
"@typescript-eslint/eslint-plugin": "^5.49.0",
|
|
33
|
+
"@typescript-eslint/parser": "^5.49.0",
|
|
34
|
+
"eslint": "^8.32.0",
|
|
35
|
+
"eslint-plugin-tsdoc": "^0.2.17",
|
|
36
|
+
"eventsource": "^2.0.2",
|
|
39
37
|
"npm-run-all": "^4.1.5",
|
|
40
|
-
"prettier": "^2.
|
|
41
|
-
"rimraf": "^
|
|
42
|
-
"ts-
|
|
43
|
-
"ts-
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"webpack": "^5.
|
|
47
|
-
"webpack-cli": "^
|
|
38
|
+
"prettier": "^2.8.3",
|
|
39
|
+
"rimraf": "^4.1.2",
|
|
40
|
+
"ts-loader": "^9.4.2",
|
|
41
|
+
"ts-node": "^10.9.1",
|
|
42
|
+
"typescript": "^4.9.4",
|
|
43
|
+
"vitest": "^0.28.2",
|
|
44
|
+
"webpack": "^5.75.0",
|
|
45
|
+
"webpack-cli": "^5.0.1"
|
|
48
46
|
},
|
|
49
47
|
"scripts": {
|
|
50
48
|
"build": "webpack --env production",
|
|
51
|
-
"test": "
|
|
49
|
+
"test": "vitest",
|
|
52
50
|
"clean": "rimraf ./build",
|
|
53
51
|
"format": "prettier --write ./src/**/*.ts",
|
|
54
52
|
"lint": "eslint \"./src/**/*.ts\""
|
|
55
|
-
}
|
|
56
|
-
"readme": "# Better SSE\n\n<p>\n\t<img src=\"https://img.shields.io/npm/v/better-sse?color=blue&style=flat-square\" />\n\t<img src=\"https://img.shields.io/npm/l/better-sse?color=green&style=flat-square\" />\n\t<img src=\"https://img.shields.io/npm/dt/better-sse?color=grey&style=flat-square\" />\n\t<a href=\"https://github.com/MatthewWid/better-sse\"><img src=\"https://img.shields.io/github/stars/MatthewWid/better-sse?style=social\" /></a>\n</p>\n\nA dead simple, dependency-less, spec-compliant server-side events implementation for Node, written in TypeScript.\n\nThis package aims to be the easiest to use, most compliant and most streamlined solution to server-side events with Node that is framework agnostic and feature rich.\n\nPlease consider starring the project [on GitHub ⭐](https://github.com/MatthewWid/better-sse).\n\n## Why use Server-sent Events?\n\nServer-sent events (SSE) is a standardised protocol that allows web-servers to push data to clients without the need for alternative mechanisms such as pinging or long-polling.\n\nUsing SSE can allow for significant savings in bandwidth and battery life on portable devices, and will work with your existing infrastructure as it operates directly over the HTTP protocol without the need for the connection upgrade that [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) require.\n\nCompared to WebSockets it has comparable performance and bandwidth usage, especially over HTTP/2, and natively includes event ID generation and automatic reconnection when clients are disconnected.\n\n* [Comparison: Server-sent Events vs WebSockets vs Polling](https://medium.com/dailyjs/a-comparison-between-websockets-server-sent-events-and-polling-7a27c98cb1e3)\n* [WHATWG standards section for server-sent events](https://html.spec.whatwg.org/multipage/server-sent-events.html)\n* [MDN guide to server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)\n\n## Highlights\n\n* Compatible with all popular Node HTTP frameworks ([http](https://nodejs.org/api/http.html), [Express](https://nodejs.org/api/http.html), [Koa](https://www.npmjs.com/package/koa), [Fastify](https://www.npmjs.com/package/fastify), etc.)\n* Fully written in TypeScript (+ ships with types directly).\n* [Thoroughly tested](./src/Session.test.ts) (+ 100% code coverage!).\n* [Comprehensively documented](./docs) with guides and API documentation.\n* [Channels](./docs/channels.md) allow you to broadcast events to many clients at once.\n* Configurable reconnection time, message serialization and data sanitization (but with good defaults).\n* Trust or ignore the client-given last event ID.\n* Automatically send keep-alive pings to keep connections open.\n* Add or override the response status code and headers.\n* Fine-grained control by either sending [individual fields](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#fields) of events or sending full events with simple helpers.\n* Pipe [streams](https://nodejs.org/api/stream.html#stream_readable_streams) and [iterables](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators) directly from the server to the client as a stream of events.\n* Support for popular EventStream polyfills [`event-source-polyfill`](https://www.npmjs.com/package/event-source-polyfill) and [`eventsource-polyfill`](https://www.npmjs.com/package/eventsource-polyfill).\n\n[See a comparison with other Node SSE libraries in the documentation.](./docs/comparison.md)\n\n# Installation\n\n```bash\n# npm\nnpm install better-sse\n\n# Yarn\nyarn add better-sse\n\n# pnpm\npnpm add better-sse\n```\n\n_Better SSE ships with types built in. No need to install from `@types` for TypeScript users!_\n\n# Usage\n\nThe following example shows usage with [Express](http://expressjs.com/), but Better SSE works with any web-server framework (that uses the underlying Node [HTTP module](https://nodejs.org/api/http.html)).\n\nSee the [Recipes](./docs/recipes.md) section of the documentation for use with other frameworks and libraries.\n\n---\n\nUse [sessions](./docs/api.md#session) to push events to clients:\n\n```typescript\n// Server\nimport {createSession} from \"better-sse\";\n\napp.get(\"/sse\", async (req, res) => {\n\tconst session = await createSession(req, res);\n\n\tsession.push(\"Hello world!\");\n});\n```\n\n```typescript\n// Client\nconst sse = new EventSource(\"/sse\");\n\nsse.addEventListener(\"message\", ({data}) => {\n\tconsole.log(data);\n});\n```\n\n---\n\nUse [channels](./docs/channels.md) to send events to many clients at once:\n\n```typescript\nimport {createSession, createChannel} from \"better-sse\";\n\nconst channel = createChannel();\n\napp.get(\"/sse\", async (req, res) => {\n\tconst session = await createSession(req, res);\n\n\tchannel.register(session);\n\n\tchannel.broadcast(\"A user has joined.\", \"join-notification\");\n});\n```\n\n---\n\nLoop over sync and async [iterables](./docs/api.md#sessioniterate-iterable-iterable--asynciterable-options-object--promisevoid) and send each value as an event:\n\n```typescript\nconst session = await createSession(req, res);\n\nconst list = [1, 2, 3];\n\nawait session.iterate(list);\n```\n\n---\n\nPipe [readable stream](#sessionstream-stream-readable-options-object--promiseboolean) data to the client as a stream of events:\n\n```typescript\nconst session = await createSession(req, res);\n\nconst stream = Readable.from([1, 2, 3]);\n\nawait session.stream(stream);\n```\n\n---\n\nCheck the [API documentation](./docs/api.md) and [live examples](https://github.com/MatthewWid/better-sse/tree/master/examples) for information on getting more fine-tuned control over your data such as managing event IDs, data serialization, event filtering, dispatch controls and more!\n\n# Documentation\n\nAPI documentation, getting started guides and usage with other frameworks is [available on GitHub](https://github.com/MatthewWid/better-sse/tree/master/docs).\n\n# Contributing\n\nThis library is always open to contributions, whether it be code, bug reports, documentation or anything else.\n\nPlease submit suggestions, bugs and issues to the [GitHub issues page](https://github.com/MatthewWid/better-sse/issues).\n\nFor code or documentation changes, [submit a pull request on GitHub](https://github.com/MatthewWid/better-sse/pulls).\n\n## Local Development\n\nInstall Node:\n\n```bash\ncurl -L https://git.io/n-install | bash\nn auto\n```\n\nInstall pnpm:\n\n```bash\nnpm i -g pnpm\n```\n\nInstall dependencies:\n\n```bash\npnpm i\n```\n\nRun tests:\n\n```bash\npnpm t\n```\n\n# License\n\nThis project is licensed under the MIT license.\n"
|
|
53
|
+
}
|
|
57
54
|
}
|