light-sse 1.0.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/README.md +108 -0
- package/dist/eventsource2.js +77 -0
- package/package.json +13 -0
package/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# EventSource2
|
|
2
|
+
|
|
3
|
+
A lightweight Server-Sent Events (SSE) client library built with TypeScript. Unlike the native `EventSource` API, this library supports custom headers, making it ideal for authenticated SSE connections.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install
|
|
9
|
+
npm run build
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
### Basic Example
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { EventSource2 } from "./eventsource2";
|
|
18
|
+
|
|
19
|
+
const eventSource = await EventSource2.new("https://api.example.com/stream", {
|
|
20
|
+
onmessage: (payload) => {
|
|
21
|
+
console.log("Received:", payload);
|
|
22
|
+
},
|
|
23
|
+
onerror: (error) => {
|
|
24
|
+
console.error("Error:", error);
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// When done, close the connection
|
|
29
|
+
eventSource.close();
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### With Custom Headers
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { EventSource2 } from "./eventsource2";
|
|
36
|
+
|
|
37
|
+
const eventSource = await EventSource2.new(
|
|
38
|
+
"https://api.example.com/stream",
|
|
39
|
+
{
|
|
40
|
+
onmessage: (payload) => {
|
|
41
|
+
console.log("Received:", payload);
|
|
42
|
+
},
|
|
43
|
+
onerror: (error) => {
|
|
44
|
+
console.error("Error:", error);
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
Authorization: "Bearer your-token-here",
|
|
49
|
+
"X-Custom-Header": "custom-value",
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
eventSource.close();
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## API
|
|
57
|
+
|
|
58
|
+
### `EventSource2.new(url, callbacks, headers?)`
|
|
59
|
+
|
|
60
|
+
Creates a new SSE connection.
|
|
61
|
+
|
|
62
|
+
**Parameters:**
|
|
63
|
+
|
|
64
|
+
| Parameter | Type | Required | Description |
|
|
65
|
+
|-------------|-----------------------------------|----------|------------------------------------------|
|
|
66
|
+
| `url` | `string` | Yes | The SSE endpoint URL |
|
|
67
|
+
| `callbacks` | `{ onmessage, onerror? }` | Yes | Event handlers |
|
|
68
|
+
| `headers` | `HeadersInit` | No | Custom headers for the request |
|
|
69
|
+
|
|
70
|
+
**Callbacks:**
|
|
71
|
+
|
|
72
|
+
- `onmessage(payload: string)`: Called when a message is received. Returns the parsed data content.
|
|
73
|
+
- `onerror(error: any)`: Called when an error occurs (optional).
|
|
74
|
+
|
|
75
|
+
### `eventSource.close()`
|
|
76
|
+
|
|
77
|
+
Closes the SSE connection and cancels the underlying reader.
|
|
78
|
+
|
|
79
|
+
## How It Works
|
|
80
|
+
|
|
81
|
+
1. **Connection**: Uses the Fetch API with `Accept: text/event-stream` header
|
|
82
|
+
2. **Reading**: Utilizes `ReadableStream` to read chunks from the response body
|
|
83
|
+
3. **Parsing**: Detects SSE message boundaries (`\n\n` or `\r\n\r\n`) and extracts `data:` fields
|
|
84
|
+
4. **Decoding**: Uses `TextDecoder` to convert binary chunks to strings
|
|
85
|
+
|
|
86
|
+
## SSE Format
|
|
87
|
+
|
|
88
|
+
The library expects the standard SSE format:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
data: Message 1
|
|
92
|
+
|
|
93
|
+
data: Message 2
|
|
94
|
+
|
|
95
|
+
data: Message 3
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Multiple `data:` lines within the same block are joined with newlines.
|
|
99
|
+
|
|
100
|
+
## Browser Support
|
|
101
|
+
|
|
102
|
+
Requires browsers that support:
|
|
103
|
+
- [Fetch API](https://caniuse.com/fetch)
|
|
104
|
+
- [ReadableStream](https://caniuse.com/streams)
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EventSource2 = void 0;
|
|
4
|
+
class EventSource2 {
|
|
5
|
+
reader;
|
|
6
|
+
decoder = new TextDecoder();
|
|
7
|
+
buffer = "";
|
|
8
|
+
onmessage;
|
|
9
|
+
onerror;
|
|
10
|
+
static async new(url, callbacks, headers) {
|
|
11
|
+
const event = new EventSource2();
|
|
12
|
+
await event._init(url, headers);
|
|
13
|
+
event.onmessage = callbacks.onmessage;
|
|
14
|
+
if (callbacks.onerror)
|
|
15
|
+
event.onerror = callbacks.onerror;
|
|
16
|
+
event.startReading();
|
|
17
|
+
return event;
|
|
18
|
+
}
|
|
19
|
+
async _init(url, headers = {}) {
|
|
20
|
+
const response = await fetch(url, {
|
|
21
|
+
method: 'GET',
|
|
22
|
+
headers: {
|
|
23
|
+
...headers,
|
|
24
|
+
"Accept": "text/event-stream",
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
throw new Error(`Failed to connect to EventSource: ${response.status} ${response.statusText}`);
|
|
29
|
+
}
|
|
30
|
+
this.reader = response.body?.getReader();
|
|
31
|
+
}
|
|
32
|
+
async startReading() {
|
|
33
|
+
if (!this.reader)
|
|
34
|
+
return;
|
|
35
|
+
while (true) {
|
|
36
|
+
try {
|
|
37
|
+
const { value, done } = await this.reader.read();
|
|
38
|
+
if (done)
|
|
39
|
+
break;
|
|
40
|
+
this.buffer += this.decoder.decode(value, { stream: true });
|
|
41
|
+
let boundary = this.buffer.indexOf('\n\n');
|
|
42
|
+
while (boundary !== -1) {
|
|
43
|
+
const messageBlock = this.buffer.substring(0, boundary).trim();
|
|
44
|
+
this.buffer = this.buffer.substring(boundary + 2);
|
|
45
|
+
if (this.onmessage && messageBlock) {
|
|
46
|
+
const cleanMessage = this.parseSSE(messageBlock);
|
|
47
|
+
if (cleanMessage) {
|
|
48
|
+
this.onmessage(cleanMessage);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
boundary = this.buffer.indexOf('\n\n');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
if (error?.name === 'AbortError')
|
|
56
|
+
break;
|
|
57
|
+
if (this.onerror)
|
|
58
|
+
this.onerror(error);
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
parseSSE(block) {
|
|
64
|
+
return block
|
|
65
|
+
.split('\n')
|
|
66
|
+
.filter(line => line.startsWith('data:'))
|
|
67
|
+
.map(line => line.replace(/^data:\s?/, ''))
|
|
68
|
+
.join('\n');
|
|
69
|
+
}
|
|
70
|
+
close() {
|
|
71
|
+
if (this.reader) {
|
|
72
|
+
this.reader.cancel().catch(() => { });
|
|
73
|
+
this.reader = undefined;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.EventSource2 = EventSource2;
|
package/package.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "light-sse",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "dist/main.js",
|
|
5
|
+
"description": "Tu descripción",
|
|
6
|
+
"author": "Tu nombre",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"keywords": ["eventsource", "sse"],
|
|
9
|
+
"files": ["dist/"],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc"
|
|
12
|
+
}
|
|
13
|
+
}
|