@hazeljs/websocket 0.2.0-beta.1
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 +530 -0
- package/dist/decorators/realtime.decorator.d.ts +113 -0
- package/dist/decorators/realtime.decorator.d.ts.map +1 -0
- package/dist/decorators/realtime.decorator.js +202 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/room/room.manager.d.ts +81 -0
- package/dist/room/room.manager.d.ts.map +1 -0
- package/dist/room/room.manager.js +209 -0
- package/dist/src/decorators/realtime.decorator.d.ts +113 -0
- package/dist/src/decorators/realtime.decorator.d.ts.map +1 -0
- package/dist/src/decorators/realtime.decorator.js +202 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +32 -0
- package/dist/src/room/room.manager.d.ts +81 -0
- package/dist/src/room/room.manager.d.ts.map +1 -0
- package/dist/src/room/room.manager.js +209 -0
- package/dist/src/sse/sse.handler.d.ts +61 -0
- package/dist/src/sse/sse.handler.d.ts.map +1 -0
- package/dist/src/sse/sse.handler.js +209 -0
- package/dist/src/websocket.gateway.d.ts +94 -0
- package/dist/src/websocket.gateway.d.ts.map +1 -0
- package/dist/src/websocket.gateway.js +309 -0
- package/dist/src/websocket.module.d.ts +57 -0
- package/dist/src/websocket.module.d.ts.map +1 -0
- package/dist/src/websocket.module.js +88 -0
- package/dist/src/websocket.types.d.ts +258 -0
- package/dist/src/websocket.types.d.ts.map +1 -0
- package/dist/src/websocket.types.js +2 -0
- package/dist/sse/sse.handler.d.ts +61 -0
- package/dist/sse/sse.handler.d.ts.map +1 -0
- package/dist/sse/sse.handler.js +209 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/websocket.gateway.d.ts +79 -0
- package/dist/websocket.gateway.d.ts.map +1 -0
- package/dist/websocket.gateway.js +214 -0
- package/dist/websocket.module.d.ts +57 -0
- package/dist/websocket.module.d.ts.map +1 -0
- package/dist/websocket.module.js +88 -0
- package/dist/websocket.types.d.ts +258 -0
- package/dist/websocket.types.d.ts.map +1 -0
- package/dist/websocket.types.js +2 -0
- package/package.json +48 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
var WebSocketModule_1;
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.WebSocketModule = void 0;
|
|
14
|
+
const core_1 = require("@hazeljs/core");
|
|
15
|
+
const sse_handler_1 = require("./sse/sse.handler");
|
|
16
|
+
const room_manager_1 = require("./room/room.manager");
|
|
17
|
+
const core_2 = __importDefault(require("@hazeljs/core"));
|
|
18
|
+
/**
|
|
19
|
+
* WebSocket module for HazelJS
|
|
20
|
+
*/
|
|
21
|
+
let WebSocketModule = WebSocketModule_1 = class WebSocketModule {
|
|
22
|
+
/**
|
|
23
|
+
* Configure WebSocket module
|
|
24
|
+
*/
|
|
25
|
+
static forRoot(options = {}) {
|
|
26
|
+
const { isGlobal = true, enableSSE = true, enableRooms = true } = options;
|
|
27
|
+
core_2.default.info('Configuring WebSocket module...');
|
|
28
|
+
const providers = [];
|
|
29
|
+
// Add SSE handler if enabled
|
|
30
|
+
if (enableSSE) {
|
|
31
|
+
providers.push({
|
|
32
|
+
provide: sse_handler_1.SSEHandler,
|
|
33
|
+
useValue: new sse_handler_1.SSEHandler(),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
// Add room manager if enabled
|
|
37
|
+
if (enableRooms) {
|
|
38
|
+
providers.push({
|
|
39
|
+
provide: room_manager_1.RoomManager,
|
|
40
|
+
useValue: new room_manager_1.RoomManager(),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
module: WebSocketModule_1,
|
|
45
|
+
providers,
|
|
46
|
+
exports: providers.map((p) => p.provide),
|
|
47
|
+
global: isGlobal,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Configure WebSocket module asynchronously
|
|
52
|
+
*/
|
|
53
|
+
static forRootAsync(options) {
|
|
54
|
+
return {
|
|
55
|
+
module: WebSocketModule_1,
|
|
56
|
+
providers: [
|
|
57
|
+
{
|
|
58
|
+
provide: 'WEBSOCKET_OPTIONS',
|
|
59
|
+
useFactory: options.useFactory,
|
|
60
|
+
inject: options.inject || [],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
provide: sse_handler_1.SSEHandler,
|
|
64
|
+
useFactory: (wsOptions) => {
|
|
65
|
+
return wsOptions.enableSSE !== false ? new sse_handler_1.SSEHandler() : null;
|
|
66
|
+
},
|
|
67
|
+
inject: ['WEBSOCKET_OPTIONS'],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
provide: room_manager_1.RoomManager,
|
|
71
|
+
useFactory: (wsOptions) => {
|
|
72
|
+
return wsOptions.enableRooms !== false ? new room_manager_1.RoomManager() : null;
|
|
73
|
+
},
|
|
74
|
+
inject: ['WEBSOCKET_OPTIONS'],
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
exports: [sse_handler_1.SSEHandler, room_manager_1.RoomManager],
|
|
78
|
+
global: true,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
exports.WebSocketModule = WebSocketModule;
|
|
83
|
+
exports.WebSocketModule = WebSocketModule = WebSocketModule_1 = __decorate([
|
|
84
|
+
(0, core_1.HazelModule)({
|
|
85
|
+
providers: [],
|
|
86
|
+
exports: [],
|
|
87
|
+
})
|
|
88
|
+
], WebSocketModule);
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { IncomingMessage } from 'http';
|
|
2
|
+
/**
|
|
3
|
+
* WebSocket client interface
|
|
4
|
+
*/
|
|
5
|
+
export interface WebSocketClient {
|
|
6
|
+
/**
|
|
7
|
+
* Unique client ID
|
|
8
|
+
*/
|
|
9
|
+
id: string;
|
|
10
|
+
/**
|
|
11
|
+
* Client socket
|
|
12
|
+
*/
|
|
13
|
+
socket: unknown;
|
|
14
|
+
/**
|
|
15
|
+
* Client metadata
|
|
16
|
+
*/
|
|
17
|
+
metadata: Map<string, unknown>;
|
|
18
|
+
/**
|
|
19
|
+
* Rooms the client is in
|
|
20
|
+
*/
|
|
21
|
+
rooms: Set<string>;
|
|
22
|
+
/**
|
|
23
|
+
* Send message to client
|
|
24
|
+
*/
|
|
25
|
+
send(event: string, data: unknown): void;
|
|
26
|
+
/**
|
|
27
|
+
* Disconnect client
|
|
28
|
+
*/
|
|
29
|
+
disconnect(): void;
|
|
30
|
+
/**
|
|
31
|
+
* Join a room
|
|
32
|
+
*/
|
|
33
|
+
join(room: string): void;
|
|
34
|
+
/**
|
|
35
|
+
* Leave a room
|
|
36
|
+
*/
|
|
37
|
+
leave(room: string): void;
|
|
38
|
+
/**
|
|
39
|
+
* Check if client is in a room
|
|
40
|
+
*/
|
|
41
|
+
inRoom(room: string): boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* WebSocket message
|
|
45
|
+
*/
|
|
46
|
+
export interface WebSocketMessage {
|
|
47
|
+
/**
|
|
48
|
+
* Event name
|
|
49
|
+
*/
|
|
50
|
+
event: string;
|
|
51
|
+
/**
|
|
52
|
+
* Message data
|
|
53
|
+
*/
|
|
54
|
+
data: unknown;
|
|
55
|
+
/**
|
|
56
|
+
* Timestamp
|
|
57
|
+
*/
|
|
58
|
+
timestamp: number;
|
|
59
|
+
/**
|
|
60
|
+
* Client ID
|
|
61
|
+
*/
|
|
62
|
+
clientId?: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* WebSocket gateway options
|
|
66
|
+
*/
|
|
67
|
+
export interface WebSocketGatewayOptions {
|
|
68
|
+
/**
|
|
69
|
+
* Gateway path
|
|
70
|
+
*/
|
|
71
|
+
path?: string;
|
|
72
|
+
/**
|
|
73
|
+
* Namespace
|
|
74
|
+
*/
|
|
75
|
+
namespace?: string;
|
|
76
|
+
/**
|
|
77
|
+
* CORS options
|
|
78
|
+
*/
|
|
79
|
+
cors?: {
|
|
80
|
+
origin?: string | string[];
|
|
81
|
+
credentials?: boolean;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Authentication required
|
|
85
|
+
*/
|
|
86
|
+
auth?: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Ping interval in milliseconds
|
|
89
|
+
*/
|
|
90
|
+
pingInterval?: number;
|
|
91
|
+
/**
|
|
92
|
+
* Ping timeout in milliseconds
|
|
93
|
+
*/
|
|
94
|
+
pingTimeout?: number;
|
|
95
|
+
/**
|
|
96
|
+
* Maximum payload size in bytes
|
|
97
|
+
*/
|
|
98
|
+
maxPayload?: number;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Room interface
|
|
102
|
+
*/
|
|
103
|
+
export interface Room {
|
|
104
|
+
/**
|
|
105
|
+
* Room name
|
|
106
|
+
*/
|
|
107
|
+
name: string;
|
|
108
|
+
/**
|
|
109
|
+
* Clients in the room
|
|
110
|
+
*/
|
|
111
|
+
clients: Set<string>;
|
|
112
|
+
/**
|
|
113
|
+
* Room metadata
|
|
114
|
+
*/
|
|
115
|
+
metadata: Map<string, unknown>;
|
|
116
|
+
/**
|
|
117
|
+
* Created timestamp
|
|
118
|
+
*/
|
|
119
|
+
createdAt: number;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* WebSocket event handler
|
|
123
|
+
*/
|
|
124
|
+
export type WebSocketEventHandler = (client: WebSocketClient, data: unknown) => void | Promise<void>;
|
|
125
|
+
/**
|
|
126
|
+
* Connection handler
|
|
127
|
+
*/
|
|
128
|
+
export type ConnectionHandler = (client: WebSocketClient, request: IncomingMessage) => void | Promise<void>;
|
|
129
|
+
/**
|
|
130
|
+
* Disconnection handler
|
|
131
|
+
*/
|
|
132
|
+
export type DisconnectionHandler = (client: WebSocketClient, reason?: string) => void | Promise<void>;
|
|
133
|
+
/**
|
|
134
|
+
* WebSocket server options
|
|
135
|
+
*/
|
|
136
|
+
export interface WebSocketServerOptions {
|
|
137
|
+
/**
|
|
138
|
+
* Port to listen on
|
|
139
|
+
*/
|
|
140
|
+
port?: number;
|
|
141
|
+
/**
|
|
142
|
+
* Host to bind to
|
|
143
|
+
*/
|
|
144
|
+
host?: string;
|
|
145
|
+
/**
|
|
146
|
+
* Path for WebSocket endpoint
|
|
147
|
+
*/
|
|
148
|
+
path?: string;
|
|
149
|
+
/**
|
|
150
|
+
* Enable per-message deflate
|
|
151
|
+
*/
|
|
152
|
+
perMessageDeflate?: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Maximum payload size
|
|
155
|
+
*/
|
|
156
|
+
maxPayload?: number;
|
|
157
|
+
/**
|
|
158
|
+
* Client tracking
|
|
159
|
+
*/
|
|
160
|
+
clientTracking?: boolean;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* SSE (Server-Sent Events) options
|
|
164
|
+
*/
|
|
165
|
+
export interface SSEOptions {
|
|
166
|
+
/**
|
|
167
|
+
* Retry interval in milliseconds
|
|
168
|
+
*/
|
|
169
|
+
retry?: number;
|
|
170
|
+
/**
|
|
171
|
+
* Keep-alive interval in milliseconds
|
|
172
|
+
*/
|
|
173
|
+
keepAlive?: number;
|
|
174
|
+
/**
|
|
175
|
+
* Event ID
|
|
176
|
+
*/
|
|
177
|
+
id?: string;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* SSE message
|
|
181
|
+
*/
|
|
182
|
+
export interface SSEMessage {
|
|
183
|
+
/**
|
|
184
|
+
* Event type
|
|
185
|
+
*/
|
|
186
|
+
event?: string;
|
|
187
|
+
/**
|
|
188
|
+
* Message data
|
|
189
|
+
*/
|
|
190
|
+
data: unknown;
|
|
191
|
+
/**
|
|
192
|
+
* Event ID
|
|
193
|
+
*/
|
|
194
|
+
id?: string;
|
|
195
|
+
/**
|
|
196
|
+
* Retry interval
|
|
197
|
+
*/
|
|
198
|
+
retry?: number;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Subscription options
|
|
202
|
+
*/
|
|
203
|
+
export interface SubscriptionOptions {
|
|
204
|
+
/**
|
|
205
|
+
* Topic pattern
|
|
206
|
+
*/
|
|
207
|
+
topic: string;
|
|
208
|
+
/**
|
|
209
|
+
* Filter function
|
|
210
|
+
*/
|
|
211
|
+
filter?: (data: unknown) => boolean;
|
|
212
|
+
/**
|
|
213
|
+
* Transform function
|
|
214
|
+
*/
|
|
215
|
+
transform?: (data: unknown) => unknown;
|
|
216
|
+
/**
|
|
217
|
+
* Batch size
|
|
218
|
+
*/
|
|
219
|
+
batchSize?: number;
|
|
220
|
+
/**
|
|
221
|
+
* Batch interval in milliseconds
|
|
222
|
+
*/
|
|
223
|
+
batchInterval?: number;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* WebSocket statistics
|
|
227
|
+
*/
|
|
228
|
+
export interface WebSocketStats {
|
|
229
|
+
/**
|
|
230
|
+
* Total connected clients
|
|
231
|
+
*/
|
|
232
|
+
connectedClients: number;
|
|
233
|
+
/**
|
|
234
|
+
* Total rooms
|
|
235
|
+
*/
|
|
236
|
+
totalRooms: number;
|
|
237
|
+
/**
|
|
238
|
+
* Messages sent
|
|
239
|
+
*/
|
|
240
|
+
messagesSent: number;
|
|
241
|
+
/**
|
|
242
|
+
* Messages received
|
|
243
|
+
*/
|
|
244
|
+
messagesReceived: number;
|
|
245
|
+
/**
|
|
246
|
+
* Bytes sent
|
|
247
|
+
*/
|
|
248
|
+
bytesSent: number;
|
|
249
|
+
/**
|
|
250
|
+
* Bytes received
|
|
251
|
+
*/
|
|
252
|
+
bytesReceived: number;
|
|
253
|
+
/**
|
|
254
|
+
* Uptime in milliseconds
|
|
255
|
+
*/
|
|
256
|
+
uptime: number;
|
|
257
|
+
}
|
|
258
|
+
//# sourceMappingURL=websocket.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket.types.d.ts","sourceRoot":"","sources":["../../src/websocket.types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from 'http';
|
|
2
|
+
import { SSEMessage, SSEOptions } from '../websocket.types';
|
|
3
|
+
/**
|
|
4
|
+
* Server-Sent Events (SSE) handler
|
|
5
|
+
*/
|
|
6
|
+
export declare class SSEHandler {
|
|
7
|
+
private connections;
|
|
8
|
+
private keepAliveIntervals;
|
|
9
|
+
/**
|
|
10
|
+
* Initialize SSE connection
|
|
11
|
+
*/
|
|
12
|
+
initConnection(req: IncomingMessage, res: ServerResponse, options?: SSEOptions): string;
|
|
13
|
+
/**
|
|
14
|
+
* Send message to a specific connection
|
|
15
|
+
*/
|
|
16
|
+
send(connectionId: string, message: SSEMessage): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Broadcast message to all connections
|
|
19
|
+
*/
|
|
20
|
+
broadcast(message: SSEMessage): void;
|
|
21
|
+
/**
|
|
22
|
+
* Close a specific connection
|
|
23
|
+
*/
|
|
24
|
+
closeConnection(connectionId: string): void;
|
|
25
|
+
/**
|
|
26
|
+
* Close all connections
|
|
27
|
+
*/
|
|
28
|
+
closeAll(): void;
|
|
29
|
+
/**
|
|
30
|
+
* Get active connection count
|
|
31
|
+
*/
|
|
32
|
+
getConnectionCount(): number;
|
|
33
|
+
/**
|
|
34
|
+
* Check if connection exists
|
|
35
|
+
*/
|
|
36
|
+
hasConnection(connectionId: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Get all connection IDs
|
|
39
|
+
*/
|
|
40
|
+
getConnectionIds(): string[];
|
|
41
|
+
/**
|
|
42
|
+
* Generate unique connection ID
|
|
43
|
+
*/
|
|
44
|
+
private generateConnectionId;
|
|
45
|
+
/**
|
|
46
|
+
* Create a stream for continuous data
|
|
47
|
+
*/
|
|
48
|
+
createStream<T>(connectionId: string, dataSource: AsyncIterable<T> | Iterable<T>, options?: {
|
|
49
|
+
event?: string;
|
|
50
|
+
transform?: (data: T) => unknown;
|
|
51
|
+
}): AsyncGenerator<boolean>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Helper function to create SSE response
|
|
55
|
+
*/
|
|
56
|
+
export declare function createSSEResponse(res: ServerResponse, options?: SSEOptions): void;
|
|
57
|
+
/**
|
|
58
|
+
* Helper function to send SSE message
|
|
59
|
+
*/
|
|
60
|
+
export declare function sendSSEMessage(res: ServerResponse, message: SSEMessage): void;
|
|
61
|
+
//# sourceMappingURL=sse.handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.handler.d.ts","sourceRoot":"","sources":["../../src/sse/sse.handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAG5D;;GAEG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,WAAW,CAA0C;IAC7D,OAAO,CAAC,kBAAkB,CAA0C;IAEpE;;OAEG;IACH,cAAc,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,EAAE,OAAO,GAAE,UAAe,GAAG,MAAM;IA0C3F;;OAEG;IACH,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO;IA0CxD;;OAEG;IACH,SAAS,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI;IAQpC;;OAEG;IACH,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAgB3C;;OAEG;IACH,QAAQ,IAAI,IAAI;IAQhB;;OAEG;IACH,kBAAkB,IAAI,MAAM;IAI5B;;OAEG;IACH,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAI5C;;OAEG;IACH,gBAAgB,IAAI,MAAM,EAAE;IAI5B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAI5B;;OAEG;IACI,YAAY,CAAC,CAAC,EACnB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,EAC1C,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAA;KAAO,GACjE,cAAc,CAAC,OAAO,CAAC;CAmB3B;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,cAAc,EAAE,OAAO,GAAE,UAAe,GAAG,IAAI,CAYrF;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,cAAc,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI,CAyB7E"}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SSEHandler = void 0;
|
|
7
|
+
exports.createSSEResponse = createSSEResponse;
|
|
8
|
+
exports.sendSSEMessage = sendSSEMessage;
|
|
9
|
+
const core_1 = __importDefault(require("@hazeljs/core"));
|
|
10
|
+
/**
|
|
11
|
+
* Server-Sent Events (SSE) handler
|
|
12
|
+
*/
|
|
13
|
+
class SSEHandler {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.connections = new Map();
|
|
16
|
+
this.keepAliveIntervals = new Map();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Initialize SSE connection
|
|
20
|
+
*/
|
|
21
|
+
initConnection(req, res, options = {}) {
|
|
22
|
+
const connectionId = this.generateConnectionId();
|
|
23
|
+
// Set SSE headers
|
|
24
|
+
res.writeHead(200, {
|
|
25
|
+
'Content-Type': 'text/event-stream',
|
|
26
|
+
'Cache-Control': 'no-cache',
|
|
27
|
+
Connection: 'keep-alive',
|
|
28
|
+
'Access-Control-Allow-Origin': '*',
|
|
29
|
+
'X-Accel-Buffering': 'no', // Disable nginx buffering
|
|
30
|
+
});
|
|
31
|
+
// Send initial retry interval
|
|
32
|
+
if (options.retry) {
|
|
33
|
+
res.write(`retry: ${options.retry}\n\n`);
|
|
34
|
+
}
|
|
35
|
+
// Store connection
|
|
36
|
+
this.connections.set(connectionId, res);
|
|
37
|
+
// Setup keep-alive
|
|
38
|
+
const keepAliveInterval = options.keepAlive || 30000;
|
|
39
|
+
const interval = setInterval(() => {
|
|
40
|
+
if (this.connections.has(connectionId)) {
|
|
41
|
+
res.write(': keep-alive\n\n');
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
clearInterval(interval);
|
|
45
|
+
}
|
|
46
|
+
}, keepAliveInterval);
|
|
47
|
+
this.keepAliveIntervals.set(connectionId, interval);
|
|
48
|
+
// Handle connection close
|
|
49
|
+
req.on('close', () => {
|
|
50
|
+
this.closeConnection(connectionId);
|
|
51
|
+
});
|
|
52
|
+
core_1.default.debug(`SSE connection initialized: ${connectionId}`);
|
|
53
|
+
return connectionId;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Send message to a specific connection
|
|
57
|
+
*/
|
|
58
|
+
send(connectionId, message) {
|
|
59
|
+
const res = this.connections.get(connectionId);
|
|
60
|
+
if (!res) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
let output = '';
|
|
65
|
+
// Add event type
|
|
66
|
+
if (message.event) {
|
|
67
|
+
output += `event: ${message.event}\n`;
|
|
68
|
+
}
|
|
69
|
+
// Add ID
|
|
70
|
+
if (message.id) {
|
|
71
|
+
output += `id: ${message.id}\n`;
|
|
72
|
+
}
|
|
73
|
+
// Add retry
|
|
74
|
+
if (message.retry) {
|
|
75
|
+
output += `retry: ${message.retry}\n`;
|
|
76
|
+
}
|
|
77
|
+
// Add data (can be multi-line)
|
|
78
|
+
const data = typeof message.data === 'string' ? message.data : JSON.stringify(message.data);
|
|
79
|
+
const lines = data.split('\n');
|
|
80
|
+
for (const line of lines) {
|
|
81
|
+
output += `data: ${line}\n`;
|
|
82
|
+
}
|
|
83
|
+
output += '\n';
|
|
84
|
+
res.write(output);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
core_1.default.error(`Failed to send SSE message to ${connectionId}:`, error);
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Broadcast message to all connections
|
|
94
|
+
*/
|
|
95
|
+
broadcast(message) {
|
|
96
|
+
for (const connectionId of this.connections.keys()) {
|
|
97
|
+
this.send(connectionId, message);
|
|
98
|
+
}
|
|
99
|
+
core_1.default.debug(`SSE broadcast: ${message.event || 'message'}`);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Close a specific connection
|
|
103
|
+
*/
|
|
104
|
+
closeConnection(connectionId) {
|
|
105
|
+
const res = this.connections.get(connectionId);
|
|
106
|
+
if (res) {
|
|
107
|
+
res.end();
|
|
108
|
+
this.connections.delete(connectionId);
|
|
109
|
+
}
|
|
110
|
+
const interval = this.keepAliveIntervals.get(connectionId);
|
|
111
|
+
if (interval) {
|
|
112
|
+
clearInterval(interval);
|
|
113
|
+
this.keepAliveIntervals.delete(connectionId);
|
|
114
|
+
}
|
|
115
|
+
core_1.default.debug(`SSE connection closed: ${connectionId}`);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Close all connections
|
|
119
|
+
*/
|
|
120
|
+
closeAll() {
|
|
121
|
+
for (const connectionId of this.connections.keys()) {
|
|
122
|
+
this.closeConnection(connectionId);
|
|
123
|
+
}
|
|
124
|
+
core_1.default.info('All SSE connections closed');
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get active connection count
|
|
128
|
+
*/
|
|
129
|
+
getConnectionCount() {
|
|
130
|
+
return this.connections.size;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Check if connection exists
|
|
134
|
+
*/
|
|
135
|
+
hasConnection(connectionId) {
|
|
136
|
+
return this.connections.has(connectionId);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Get all connection IDs
|
|
140
|
+
*/
|
|
141
|
+
getConnectionIds() {
|
|
142
|
+
return Array.from(this.connections.keys());
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Generate unique connection ID
|
|
146
|
+
*/
|
|
147
|
+
generateConnectionId() {
|
|
148
|
+
return `sse-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Create a stream for continuous data
|
|
152
|
+
*/
|
|
153
|
+
async *createStream(connectionId, dataSource, options = {}) {
|
|
154
|
+
try {
|
|
155
|
+
for await (const data of dataSource) {
|
|
156
|
+
const transformedData = options.transform ? options.transform(data) : data;
|
|
157
|
+
const sent = this.send(connectionId, {
|
|
158
|
+
event: options.event,
|
|
159
|
+
data: transformedData,
|
|
160
|
+
});
|
|
161
|
+
yield sent;
|
|
162
|
+
if (!sent) {
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
core_1.default.error(`SSE stream error for ${connectionId}:`, error);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
exports.SSEHandler = SSEHandler;
|
|
173
|
+
/**
|
|
174
|
+
* Helper function to create SSE response
|
|
175
|
+
*/
|
|
176
|
+
function createSSEResponse(res, options = {}) {
|
|
177
|
+
res.writeHead(200, {
|
|
178
|
+
'Content-Type': 'text/event-stream',
|
|
179
|
+
'Cache-Control': 'no-cache',
|
|
180
|
+
Connection: 'keep-alive',
|
|
181
|
+
'Access-Control-Allow-Origin': '*',
|
|
182
|
+
'X-Accel-Buffering': 'no',
|
|
183
|
+
});
|
|
184
|
+
if (options.retry) {
|
|
185
|
+
res.write(`retry: ${options.retry}\n\n`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Helper function to send SSE message
|
|
190
|
+
*/
|
|
191
|
+
function sendSSEMessage(res, message) {
|
|
192
|
+
let output = '';
|
|
193
|
+
if (message.event) {
|
|
194
|
+
output += `event: ${message.event}\n`;
|
|
195
|
+
}
|
|
196
|
+
if (message.id) {
|
|
197
|
+
output += `id: ${message.id}\n`;
|
|
198
|
+
}
|
|
199
|
+
if (message.retry) {
|
|
200
|
+
output += `retry: ${message.retry}\n`;
|
|
201
|
+
}
|
|
202
|
+
const data = typeof message.data === 'string' ? message.data : JSON.stringify(message.data);
|
|
203
|
+
const lines = data.split('\n');
|
|
204
|
+
for (const line of lines) {
|
|
205
|
+
output += `data: ${line}\n`;
|
|
206
|
+
}
|
|
207
|
+
output += '\n';
|
|
208
|
+
res.write(output);
|
|
209
|
+
}
|