use-next-sse 0.2.1 → 0.2.3
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 +67 -0
- package/dist/cjs/server/sse-server.d.ts +26 -3
- package/dist/cjs/server/sse-server.js +47 -17
- package/dist/server/sse-server.d.ts +26 -3
- package/dist/server/sse-server.js +47 -17
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -69,3 +69,70 @@ export default function Home() {
|
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
This example demonstrates a simple counter that updates every second using Server-Sent Events. The server sends updates for 10 seconds before closing the connection.
|
|
72
|
+
|
|
73
|
+
### Destructor in `createSSEHandler`
|
|
74
|
+
|
|
75
|
+
When using the `createSSEHandler` function in the `use-next-sse` library, it is important to understand the role of the destructor. The destructor is a cleanup function that is called when the SSE connection is closed. This allows you to perform any necessary cleanup tasks, such as closing database connections, stopping intervals, or clearing resources.
|
|
76
|
+
|
|
77
|
+
#### Example Usage
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { createSSEHandler } from 'use-next-sse';
|
|
81
|
+
|
|
82
|
+
const handler = createSSEHandler((send, close) => {
|
|
83
|
+
// Your SSE logic here
|
|
84
|
+
|
|
85
|
+
// Return a destructor function
|
|
86
|
+
return () => {
|
|
87
|
+
// Perform cleanup tasks here
|
|
88
|
+
console.log('SSE connection closed, performing cleanup');
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
export default handler;
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
#### Global Example
|
|
96
|
+
|
|
97
|
+
This Destructor will be called even though the handler callback is not called yet.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { createSSEHandler } from 'use-next-sse';
|
|
101
|
+
|
|
102
|
+
const handler = createSSEHandler(
|
|
103
|
+
(send, close) => {
|
|
104
|
+
// Your SSE logic here
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
onClose: () => {
|
|
108
|
+
console.log('SSE connection has been closed and cleaned up.');
|
|
109
|
+
// Perform additional cleanup tasks here
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
export default handler;
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
#### Context Example
|
|
118
|
+
|
|
119
|
+
This Destructor will be called if the SSECallback call is not done yet.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { createSSEHandler } from 'use-next-sse';
|
|
123
|
+
|
|
124
|
+
const handler = createSSEHandler(async(send, close, { onClose }) => {
|
|
125
|
+
// Your SSE logic here
|
|
126
|
+
|
|
127
|
+
onClose(() => {
|
|
128
|
+
console.log('SSE connection closed, performing cleanup.');
|
|
129
|
+
// Perform additional cleanup tasks here
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
// long running task
|
|
134
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
export default handler;
|
|
138
|
+
```
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NextRequest } from 'next/server';
|
|
1
|
+
import type { NextRequest } from 'next/server';
|
|
2
2
|
/**
|
|
3
3
|
* A function that sends data to the client.
|
|
4
4
|
* @param data - The data to send to the client.
|
|
@@ -9,12 +9,22 @@ import { NextRequest } from 'next/server';
|
|
|
9
9
|
* send({ message: 'test' }, 'testEvent');
|
|
10
10
|
*/
|
|
11
11
|
type SendFunction = (data: any, eventName?: string) => void;
|
|
12
|
+
/**
|
|
13
|
+
* A function that cleans up resources when the connection is closed.
|
|
14
|
+
* @returns void | Promise<void>
|
|
15
|
+
* @example
|
|
16
|
+
* return () => {
|
|
17
|
+
* console.log('Cleanup');
|
|
18
|
+
* };
|
|
19
|
+
*/
|
|
20
|
+
type Destructor = (() => void) | Promise<() => void> | (() => Promise<void>);
|
|
12
21
|
/**
|
|
13
22
|
* A function that handles the Server-Sent Events (SSE) connection.
|
|
14
23
|
* @param send - A function to send data to the client.
|
|
15
24
|
* @param close - A function to close the SSE connection.
|
|
16
25
|
* @returns void | Promise<void> | (() => void)
|
|
17
26
|
* The callback can return a cleanup function that will be called when the connection is closed.
|
|
27
|
+
* **HINT:** See also {@link Destructor}, the createSSEHandler {@link SSEOptions} and {@link SSECallback} context `onClose`.
|
|
18
28
|
*/
|
|
19
29
|
type SSECallback = (send: SendFunction,
|
|
20
30
|
/**
|
|
@@ -27,12 +37,23 @@ close: () => void,
|
|
|
27
37
|
/**
|
|
28
38
|
* An object containing the last event ID received by the client. Not null if the client has been reconnected.
|
|
29
39
|
* @property lastEventId - The last event ID from the client.
|
|
40
|
+
* @property onClose - A function to set a cleanup function that will be called when the connection is closed. If the cleanup function is already set, a warning will be logged. The cleanup function set by the `onClose` function will be called even if the cleanup function returned by the callback is not called.
|
|
30
41
|
* @example
|
|
31
42
|
* { lastEventId: '12345' }
|
|
32
43
|
*/
|
|
33
44
|
context: {
|
|
34
45
|
lastEventId: string | null;
|
|
35
|
-
|
|
46
|
+
onClose: (destructor: Destructor) => void;
|
|
47
|
+
}) => void | Promise<void> | Destructor;
|
|
48
|
+
/**
|
|
49
|
+
* An optional object to configure the Server-Sent Events (SSE) handler.
|
|
50
|
+
* @property onClose - A function that will be called when the connection is closed even if the SSECallback has not been called yet.
|
|
51
|
+
* @example
|
|
52
|
+
* { onClose: () => console.log('SSE connection has been closed and cleaned up.') }
|
|
53
|
+
*/
|
|
54
|
+
type SSEOptions = {
|
|
55
|
+
onClose?: Destructor;
|
|
56
|
+
};
|
|
36
57
|
/**
|
|
37
58
|
* Creates a Server-Sent Events (SSE) handler for Next.js.
|
|
38
59
|
*
|
|
@@ -56,6 +77,8 @@ context: {
|
|
|
56
77
|
});
|
|
57
78
|
```
|
|
58
79
|
*
|
|
80
|
+
* @param options - An optional object to configure the handler. {@link SSEOptions}
|
|
81
|
+
* - `onClose`: A function that will be called when the connection is closed even if the SSECallback has not been called yet.
|
|
59
82
|
* @returns A function that handles the SSE request. This function takes a `NextRequest` object as an argument.
|
|
60
83
|
*
|
|
61
84
|
* The returned function creates a `ReadableStream` to handle the SSE connection. It sets up the `send` and `close` functions,
|
|
@@ -66,5 +89,5 @@ context: {
|
|
|
66
89
|
* - `Cache-Control`: `no-cache, no-transform`
|
|
67
90
|
* - `Connection`: `keep-alive`
|
|
68
91
|
*/
|
|
69
|
-
export declare function createSSEHandler(callback: SSECallback): (request: NextRequest) => Promise<Response>;
|
|
92
|
+
export declare function createSSEHandler(callback: SSECallback, options?: SSEOptions): (request: NextRequest) => Promise<Response>;
|
|
70
93
|
export {};
|
|
@@ -24,6 +24,8 @@ exports.createSSEHandler = void 0;
|
|
|
24
24
|
});
|
|
25
25
|
```
|
|
26
26
|
*
|
|
27
|
+
* @param options - An optional object to configure the handler. {@link SSEOptions}
|
|
28
|
+
* - `onClose`: A function that will be called when the connection is closed even if the SSECallback has not been called yet.
|
|
27
29
|
* @returns A function that handles the SSE request. This function takes a `NextRequest` object as an argument.
|
|
28
30
|
*
|
|
29
31
|
* The returned function creates a `ReadableStream` to handle the SSE connection. It sets up the `send` and `close` functions,
|
|
@@ -34,14 +36,40 @@ exports.createSSEHandler = void 0;
|
|
|
34
36
|
* - `Cache-Control`: `no-cache, no-transform`
|
|
35
37
|
* - `Connection`: `keep-alive`
|
|
36
38
|
*/
|
|
37
|
-
function createSSEHandler(callback) {
|
|
39
|
+
function createSSEHandler(callback, options) {
|
|
38
40
|
return async function (request) {
|
|
39
41
|
const encoder = new TextEncoder();
|
|
40
42
|
let isClosed = false;
|
|
41
|
-
let cleanup;
|
|
43
|
+
let cleanup = options === null || options === void 0 ? void 0 : options.onClose;
|
|
44
|
+
let cleanupSetBy = (options === null || options === void 0 ? void 0 : options.onClose) ? '`onClose` through createSSEHandler options' : '';
|
|
42
45
|
let messageId = 0;
|
|
46
|
+
let controller;
|
|
47
|
+
function onClose(destructor) {
|
|
48
|
+
if (cleanup != null) {
|
|
49
|
+
if (!isClosed) {
|
|
50
|
+
logAlreadySetWarning(cleanupSetBy);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
cleanup = destructor;
|
|
55
|
+
cleanupSetBy = '`onClose` through SSECallback context';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function close() {
|
|
59
|
+
if (!isClosed) {
|
|
60
|
+
isClosed = true;
|
|
61
|
+
controller === null || controller === void 0 ? void 0 : controller.close();
|
|
62
|
+
if (typeof cleanup === 'function') {
|
|
63
|
+
cleanup();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
request.signal.addEventListener('abort', () => {
|
|
68
|
+
close();
|
|
69
|
+
});
|
|
43
70
|
const stream = new ReadableStream({
|
|
44
|
-
async start(
|
|
71
|
+
async start(cntrl) {
|
|
72
|
+
controller = cntrl;
|
|
45
73
|
const send = (data, eventName) => {
|
|
46
74
|
if (!isClosed) {
|
|
47
75
|
let message = `id: ${messageId}\n`;
|
|
@@ -49,26 +77,21 @@ function createSSEHandler(callback) {
|
|
|
49
77
|
message += `event: ${eventName}\n`;
|
|
50
78
|
}
|
|
51
79
|
message += `data: ${JSON.stringify(data)}\n\n`;
|
|
52
|
-
|
|
80
|
+
cntrl.enqueue(encoder.encode(message));
|
|
53
81
|
messageId++;
|
|
54
82
|
}
|
|
55
83
|
};
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
cleanup();
|
|
84
|
+
const result = await callback(send, close, { lastEventId: request.headers.get('Last-Event-ID'), onClose });
|
|
85
|
+
if (typeof result === 'function') {
|
|
86
|
+
if (cleanup != null) {
|
|
87
|
+
if (!isClosed) {
|
|
88
|
+
logAlreadySetWarning(cleanupSetBy);
|
|
62
89
|
}
|
|
63
90
|
}
|
|
91
|
+
else {
|
|
92
|
+
cleanup = result;
|
|
93
|
+
}
|
|
64
94
|
}
|
|
65
|
-
const result = await callback(send, close, { lastEventId: request.headers.get('Last-Event-ID') });
|
|
66
|
-
if (typeof result === 'function') {
|
|
67
|
-
cleanup = result;
|
|
68
|
-
}
|
|
69
|
-
request.signal.addEventListener('abort', () => {
|
|
70
|
-
close();
|
|
71
|
-
});
|
|
72
95
|
},
|
|
73
96
|
});
|
|
74
97
|
return new Response(stream, {
|
|
@@ -81,3 +104,10 @@ function createSSEHandler(callback) {
|
|
|
81
104
|
};
|
|
82
105
|
}
|
|
83
106
|
exports.createSSEHandler = createSSEHandler;
|
|
107
|
+
let warningLogged = false;
|
|
108
|
+
function logAlreadySetWarning(cleanupSetBy) {
|
|
109
|
+
if (!warningLogged) {
|
|
110
|
+
console.warn(`[use-next-sse:createSSEHandler]:\tCleanup function already set by ${cleanupSetBy}. Ignoring new cleanup function.`);
|
|
111
|
+
warningLogged = true;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NextRequest } from 'next/server';
|
|
1
|
+
import type { NextRequest } from 'next/server';
|
|
2
2
|
/**
|
|
3
3
|
* A function that sends data to the client.
|
|
4
4
|
* @param data - The data to send to the client.
|
|
@@ -9,12 +9,22 @@ import { NextRequest } from 'next/server';
|
|
|
9
9
|
* send({ message: 'test' }, 'testEvent');
|
|
10
10
|
*/
|
|
11
11
|
type SendFunction = (data: any, eventName?: string) => void;
|
|
12
|
+
/**
|
|
13
|
+
* A function that cleans up resources when the connection is closed.
|
|
14
|
+
* @returns void | Promise<void>
|
|
15
|
+
* @example
|
|
16
|
+
* return () => {
|
|
17
|
+
* console.log('Cleanup');
|
|
18
|
+
* };
|
|
19
|
+
*/
|
|
20
|
+
type Destructor = (() => void) | Promise<() => void> | (() => Promise<void>);
|
|
12
21
|
/**
|
|
13
22
|
* A function that handles the Server-Sent Events (SSE) connection.
|
|
14
23
|
* @param send - A function to send data to the client.
|
|
15
24
|
* @param close - A function to close the SSE connection.
|
|
16
25
|
* @returns void | Promise<void> | (() => void)
|
|
17
26
|
* The callback can return a cleanup function that will be called when the connection is closed.
|
|
27
|
+
* **HINT:** See also {@link Destructor}, the createSSEHandler {@link SSEOptions} and {@link SSECallback} context `onClose`.
|
|
18
28
|
*/
|
|
19
29
|
type SSECallback = (send: SendFunction,
|
|
20
30
|
/**
|
|
@@ -27,12 +37,23 @@ close: () => void,
|
|
|
27
37
|
/**
|
|
28
38
|
* An object containing the last event ID received by the client. Not null if the client has been reconnected.
|
|
29
39
|
* @property lastEventId - The last event ID from the client.
|
|
40
|
+
* @property onClose - A function to set a cleanup function that will be called when the connection is closed. If the cleanup function is already set, a warning will be logged. The cleanup function set by the `onClose` function will be called even if the cleanup function returned by the callback is not called.
|
|
30
41
|
* @example
|
|
31
42
|
* { lastEventId: '12345' }
|
|
32
43
|
*/
|
|
33
44
|
context: {
|
|
34
45
|
lastEventId: string | null;
|
|
35
|
-
|
|
46
|
+
onClose: (destructor: Destructor) => void;
|
|
47
|
+
}) => void | Promise<void> | Destructor;
|
|
48
|
+
/**
|
|
49
|
+
* An optional object to configure the Server-Sent Events (SSE) handler.
|
|
50
|
+
* @property onClose - A function that will be called when the connection is closed even if the SSECallback has not been called yet.
|
|
51
|
+
* @example
|
|
52
|
+
* { onClose: () => console.log('SSE connection has been closed and cleaned up.') }
|
|
53
|
+
*/
|
|
54
|
+
type SSEOptions = {
|
|
55
|
+
onClose?: Destructor;
|
|
56
|
+
};
|
|
36
57
|
/**
|
|
37
58
|
* Creates a Server-Sent Events (SSE) handler for Next.js.
|
|
38
59
|
*
|
|
@@ -56,6 +77,8 @@ context: {
|
|
|
56
77
|
});
|
|
57
78
|
```
|
|
58
79
|
*
|
|
80
|
+
* @param options - An optional object to configure the handler. {@link SSEOptions}
|
|
81
|
+
* - `onClose`: A function that will be called when the connection is closed even if the SSECallback has not been called yet.
|
|
59
82
|
* @returns A function that handles the SSE request. This function takes a `NextRequest` object as an argument.
|
|
60
83
|
*
|
|
61
84
|
* The returned function creates a `ReadableStream` to handle the SSE connection. It sets up the `send` and `close` functions,
|
|
@@ -66,5 +89,5 @@ context: {
|
|
|
66
89
|
* - `Cache-Control`: `no-cache, no-transform`
|
|
67
90
|
* - `Connection`: `keep-alive`
|
|
68
91
|
*/
|
|
69
|
-
export declare function createSSEHandler(callback: SSECallback): (request: NextRequest) => Promise<Response>;
|
|
92
|
+
export declare function createSSEHandler(callback: SSECallback, options?: SSEOptions): (request: NextRequest) => Promise<Response>;
|
|
70
93
|
export {};
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
});
|
|
22
22
|
```
|
|
23
23
|
*
|
|
24
|
+
* @param options - An optional object to configure the handler. {@link SSEOptions}
|
|
25
|
+
* - `onClose`: A function that will be called when the connection is closed even if the SSECallback has not been called yet.
|
|
24
26
|
* @returns A function that handles the SSE request. This function takes a `NextRequest` object as an argument.
|
|
25
27
|
*
|
|
26
28
|
* The returned function creates a `ReadableStream` to handle the SSE connection. It sets up the `send` and `close` functions,
|
|
@@ -31,14 +33,40 @@
|
|
|
31
33
|
* - `Cache-Control`: `no-cache, no-transform`
|
|
32
34
|
* - `Connection`: `keep-alive`
|
|
33
35
|
*/
|
|
34
|
-
export function createSSEHandler(callback) {
|
|
36
|
+
export function createSSEHandler(callback, options) {
|
|
35
37
|
return async function (request) {
|
|
36
38
|
const encoder = new TextEncoder();
|
|
37
39
|
let isClosed = false;
|
|
38
|
-
let cleanup;
|
|
40
|
+
let cleanup = options === null || options === void 0 ? void 0 : options.onClose;
|
|
41
|
+
let cleanupSetBy = (options === null || options === void 0 ? void 0 : options.onClose) ? '`onClose` through createSSEHandler options' : '';
|
|
39
42
|
let messageId = 0;
|
|
43
|
+
let controller;
|
|
44
|
+
function onClose(destructor) {
|
|
45
|
+
if (cleanup != null) {
|
|
46
|
+
if (!isClosed) {
|
|
47
|
+
logAlreadySetWarning(cleanupSetBy);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
cleanup = destructor;
|
|
52
|
+
cleanupSetBy = '`onClose` through SSECallback context';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function close() {
|
|
56
|
+
if (!isClosed) {
|
|
57
|
+
isClosed = true;
|
|
58
|
+
controller === null || controller === void 0 ? void 0 : controller.close();
|
|
59
|
+
if (typeof cleanup === 'function') {
|
|
60
|
+
cleanup();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
request.signal.addEventListener('abort', () => {
|
|
65
|
+
close();
|
|
66
|
+
});
|
|
40
67
|
const stream = new ReadableStream({
|
|
41
|
-
async start(
|
|
68
|
+
async start(cntrl) {
|
|
69
|
+
controller = cntrl;
|
|
42
70
|
const send = (data, eventName) => {
|
|
43
71
|
if (!isClosed) {
|
|
44
72
|
let message = `id: ${messageId}\n`;
|
|
@@ -46,26 +74,21 @@ export function createSSEHandler(callback) {
|
|
|
46
74
|
message += `event: ${eventName}\n`;
|
|
47
75
|
}
|
|
48
76
|
message += `data: ${JSON.stringify(data)}\n\n`;
|
|
49
|
-
|
|
77
|
+
cntrl.enqueue(encoder.encode(message));
|
|
50
78
|
messageId++;
|
|
51
79
|
}
|
|
52
80
|
};
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
cleanup();
|
|
81
|
+
const result = await callback(send, close, { lastEventId: request.headers.get('Last-Event-ID'), onClose });
|
|
82
|
+
if (typeof result === 'function') {
|
|
83
|
+
if (cleanup != null) {
|
|
84
|
+
if (!isClosed) {
|
|
85
|
+
logAlreadySetWarning(cleanupSetBy);
|
|
59
86
|
}
|
|
60
87
|
}
|
|
88
|
+
else {
|
|
89
|
+
cleanup = result;
|
|
90
|
+
}
|
|
61
91
|
}
|
|
62
|
-
const result = await callback(send, close, { lastEventId: request.headers.get('Last-Event-ID') });
|
|
63
|
-
if (typeof result === 'function') {
|
|
64
|
-
cleanup = result;
|
|
65
|
-
}
|
|
66
|
-
request.signal.addEventListener('abort', () => {
|
|
67
|
-
close();
|
|
68
|
-
});
|
|
69
92
|
},
|
|
70
93
|
});
|
|
71
94
|
return new Response(stream, {
|
|
@@ -77,3 +100,10 @@ export function createSSEHandler(callback) {
|
|
|
77
100
|
});
|
|
78
101
|
};
|
|
79
102
|
}
|
|
103
|
+
let warningLogged = false;
|
|
104
|
+
function logAlreadySetWarning(cleanupSetBy) {
|
|
105
|
+
if (!warningLogged) {
|
|
106
|
+
console.warn(`[use-next-sse:createSSEHandler]:\tCleanup function already set by ${cleanupSetBy}. Ignoring new cleanup function.`);
|
|
107
|
+
warningLogged = true;
|
|
108
|
+
}
|
|
109
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "use-next-sse",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "A lightweight Server-Sent Events (SSE) library for Next.js, enabling real-time, unidirectional data streaming from server to client",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"author": "Alexander Kasten",
|
|
34
34
|
"license": "MIT",
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"next": "
|
|
36
|
+
"next": ">=13.5.9 >=14.2.25 >=15.2.3",
|
|
37
37
|
"react": ">=18.0.0",
|
|
38
38
|
"react-dom": ">=18.0.0"
|
|
39
39
|
},
|