@valtimo/sse 0.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 ADDED
@@ -0,0 +1,31 @@
1
+ # Sse
2
+
3
+ This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version
4
+ 14.2.0.
5
+
6
+ ## Code scaffolding
7
+
8
+ Run `ng generate component component-name --project sse` to generate a new component. You can also
9
+ use `ng generate directive|pipe|service|class|guard|interface|enum|module --project sse`.
10
+
11
+ > Note: Don't forget to add `--project sse` or else it will be added to the default project in your
12
+ > `angular.json` file.
13
+
14
+ ## Build
15
+
16
+ Run `ng build sse` to build the project. The build artifacts will be stored in the `dist/`
17
+ directory.
18
+
19
+ ## Publishing
20
+
21
+ After building your library with `ng build sse`, go to the dist folder `cd dist/sse` and run
22
+ `npm publish`.
23
+
24
+ ## Running unit tests
25
+
26
+ Run `ng test sse` to execute the unit tests via [Karma](https://karma-runner.github.io).
27
+
28
+ ## Further help
29
+
30
+ To get more help on the Angular CLI use `ng help` or go check out the
31
+ [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
@@ -0,0 +1,391 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Injectable, NgModule } from '@angular/core';
3
+ import { filter, Subject, map, Observable } from 'rxjs';
4
+ import * as i1 from '@valtimo/shared';
5
+ import * as i2 from 'ngx-logger';
6
+
7
+ /*
8
+ * Copyright 2015-2025 Ritense BV, the Netherlands.
9
+ *
10
+ * Licensed under EUPL, Version 1.2 (the "License");
11
+ * you may not use this file except in compliance with the License.
12
+ * You may obtain a copy of the License at
13
+ *
14
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
15
+ *
16
+ * Unless required by applicable law or agreed to in writing, software
17
+ * distributed under the License is distributed on an "AS IS" basis,
18
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
+ * See the License for the specific language governing permissions and
20
+ * limitations under the License.
21
+ */
22
+ class SseSubscriptionBucket {
23
+ constructor() {
24
+ this.listeners = [];
25
+ }
26
+ on(listener) {
27
+ this.listeners.push(listener);
28
+ }
29
+ off(listener) {
30
+ const index = this.listeners.indexOf(listener);
31
+ if (index > -1) {
32
+ this.listeners.splice(index, 1);
33
+ }
34
+ }
35
+ offAll() {
36
+ this.listeners.length = 0;
37
+ }
38
+ sendEvent(event) {
39
+ this.listeners.forEach(listener => {
40
+ listener(event);
41
+ });
42
+ }
43
+ }
44
+ // error bucket for error listeners
45
+ class SseErrorBucket extends SseSubscriptionBucket {
46
+ }
47
+ // implicit type bucket with event for filtering
48
+ class SseEventSubscriptionBucket extends SseSubscriptionBucket {
49
+ constructor(event) {
50
+ super();
51
+ this.event = event;
52
+ }
53
+ }
54
+
55
+ /*
56
+ * Copyright 2015-2025 Ritense BV, the Netherlands.
57
+ *
58
+ * Licensed under EUPL, Version 1.2 (the "License");
59
+ * you may not use this file except in compliance with the License.
60
+ * You may obtain a copy of the License at
61
+ *
62
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
63
+ *
64
+ * Unless required by applicable law or agreed to in writing, software
65
+ * distributed under the License is distributed on an "AS IS" basis,
66
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
67
+ * See the License for the specific language governing permissions and
68
+ * limitations under the License.
69
+ */
70
+
71
+ /*
72
+ * Copyright 2015-2025 Ritense BV, the Netherlands.
73
+ *
74
+ * Licensed under EUPL, Version 1.2 (the "License");
75
+ * you may not use this file except in compliance with the License.
76
+ * You may obtain a copy of the License at
77
+ *
78
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
79
+ *
80
+ * Unless required by applicable law or agreed to in writing, software
81
+ * distributed under the License is distributed on an "AS IS" basis,
82
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
83
+ * See the License for the specific language governing permissions and
84
+ * limitations under the License.
85
+ */
86
+
87
+ /*
88
+ * Copyright 2015-2025 Ritense BV, the Netherlands.
89
+ *
90
+ * Licensed under EUPL, Version 1.2 (the "License");
91
+ * you may not use this file except in compliance with the License.
92
+ * You may obtain a copy of the License at
93
+ *
94
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
95
+ *
96
+ * Unless required by applicable law or agreed to in writing, software
97
+ * distributed under the License is distributed on an "AS IS" basis,
98
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
99
+ * See the License for the specific language governing permissions and
100
+ * limitations under the License.
101
+ */
102
+ /**
103
+ * Server-side events service, for connecting and reconnecting to SSE,
104
+ * and a translation layer between SSE json string data and TS typed events.
105
+ *
106
+ * The service is always present, and so events can be registered with {@link onMessage} and {@link onEvent},
107
+ * and will not be lost on disconnect or reconnect of the SSE connection itself.
108
+ * It is expected to also unregister events using the off-method variants of the on-method listeners.
109
+ *
110
+ * To ensure messages are received the connection must be explicitly opened with {@link connect},
111
+ * likewise the connection should also be closed with {@link disconnect} to stop receiving messages.
112
+ *
113
+ * This service can reconnect itself with the backend, and might use the {@link subscriptionId} to ask for its
114
+ * previous connection state. This state currently exists in the backend as the "Subscriber" class, and might
115
+ * have a state containing queues of items that need to be sent, so you can reconnect and receive the rest.
116
+ *
117
+ * At this time no state data is kept, so reconnection without a subscriptionId does not give a different result
118
+ */
119
+ class SseService {
120
+ static { this.CONNECTION_RETRIES_EXCEEDED = -1; }
121
+ static { this.NOT_CONNECTED = 0; }
122
+ static { this.CONNECTING = 1; }
123
+ static { this.RECONNECTING = 2; }
124
+ static { this.CONNECTED = 10; }
125
+ get sseMessages$() {
126
+ return this._sseMessages$.asObservable().pipe(filter(message => !!message));
127
+ }
128
+ constructor(configService, logger) {
129
+ this.configService = configService;
130
+ this.logger = logger;
131
+ this.connectionCount = 0; // amount of times we have connected sequentially, no concurrent connections
132
+ this.establishedDataHandler = null;
133
+ this.establishedConnection = null;
134
+ this.establishedConnectionObservable = null;
135
+ this.subscriptionId = null;
136
+ this.sequentialConnectionAttemptFailCount = 0;
137
+ this.errorBucket = new SseErrorBucket();
138
+ this.anySubscribersBucket = new SseSubscriptionBucket();
139
+ this.eventSubscribersBuckets = [];
140
+ this.state = SseService.NOT_CONNECTED;
141
+ this._sseMessages$ = new Subject();
142
+ this.VALTIMO_ENDPOINT_URL = configService.config.valtimoApi.endpointUri;
143
+ this.connect();
144
+ }
145
+ getSseMessagesObservableByEventType(eventTypes) {
146
+ return this._sseMessages$.asObservable().pipe(filter(message => !!message), filter(message => eventTypes.includes(message.data?.eventType)));
147
+ }
148
+ getSseEventObservable(eventType) {
149
+ return this._sseMessages$.asObservable().pipe(filter(message => eventType === message?.data?.eventType), map(message => message.data));
150
+ }
151
+ /**
152
+ * Start receiving SSE events
153
+ */
154
+ connect() {
155
+ this.ensureConnection();
156
+ return this;
157
+ }
158
+ onMessage(listener) {
159
+ this.ensureConnection(); // ensure connection
160
+ this.anySubscribersBucket.on(listener);
161
+ return this;
162
+ }
163
+ onEvent(event, listener) {
164
+ this.ensureConnection(); // ensure connection
165
+ let found = false;
166
+ this.eventSubscribersBuckets.forEach(bucket => {
167
+ if (bucket.event === event) {
168
+ bucket.on(listener);
169
+ found = true;
170
+ }
171
+ });
172
+ if (!found) {
173
+ const bucket = new SseEventSubscriptionBucket(event);
174
+ bucket.on(listener);
175
+ this.eventSubscribersBuckets.push(bucket);
176
+ }
177
+ return this;
178
+ }
179
+ offMessage(listener) {
180
+ this.anySubscribersBucket.off(listener);
181
+ }
182
+ offEvent(event, listener) {
183
+ this.eventSubscribersBuckets.forEach(bucket => {
184
+ if (bucket.event === event) {
185
+ bucket.off(listener);
186
+ }
187
+ });
188
+ }
189
+ offEvents(type) {
190
+ this.eventSubscribersBuckets.forEach(bucket => {
191
+ if (type === null || type === bucket.event) {
192
+ bucket.offAll();
193
+ }
194
+ });
195
+ }
196
+ offMessages() {
197
+ this.anySubscribersBucket.offAll();
198
+ }
199
+ offAll() {
200
+ this.offEvents();
201
+ this.offMessages();
202
+ }
203
+ disconnect(keepSubscriptionId = false) {
204
+ this.disconnectWith(SseService.NOT_CONNECTED, keepSubscriptionId);
205
+ }
206
+ disconnectWith(state, keepSubscriptionId = false) {
207
+ this.state = state;
208
+ if (this.establishedConnection?.readyState !== EventSource.CLOSED) {
209
+ this.establishedConnection?.close();
210
+ }
211
+ this.establishedDataHandler.unsubscribe();
212
+ this.establishedConnection = null;
213
+ this.establishedConnectionObservable = null;
214
+ this.establishedDataHandler = null;
215
+ if (!keepSubscriptionId) {
216
+ this.subscriptionId = null;
217
+ }
218
+ }
219
+ ensureConnection(retry = false) {
220
+ if (this.establishedConnection !== null && this.establishedConnectionObservable !== null) {
221
+ if (this.establishedConnection.readyState !== EventSource.CLOSED) {
222
+ return; // found
223
+ }
224
+ }
225
+ if (this.state === SseService.CONNECTING || this.state === SseService.RECONNECTING) {
226
+ return; // already connecting
227
+ }
228
+ this.establishedConnection = null;
229
+ this.establishedConnectionObservable = null;
230
+ this.constructNewSse(retry); // create new
231
+ }
232
+ constructNewSse(retry) {
233
+ this.logger.debug('subscribing to sse events');
234
+ if (this.connectionCount > 0) {
235
+ this.state = SseService.RECONNECTING;
236
+ }
237
+ else {
238
+ this.state = SseService.CONNECTING;
239
+ }
240
+ const observable = new Observable(observer => {
241
+ const eventSource = this.getEventSource();
242
+ eventSource.onopen = () => {
243
+ this.establishedConnection = eventSource;
244
+ this.logger.debug('connected to sse');
245
+ this.connectionCount++;
246
+ this.sequentialConnectionAttemptFailCount = 0; // reset retry count
247
+ this.state = SseService.CONNECTED;
248
+ };
249
+ eventSource.onmessage = event => {
250
+ observer.next({
251
+ ...event, // forward event object but replace data field
252
+ data: JSON.parse(event.data), // parse JSON string to JSON object
253
+ });
254
+ };
255
+ eventSource.onerror = () => {
256
+ eventSource.close();
257
+ observer.complete();
258
+ if (retry) {
259
+ this.sequentialConnectionAttemptFailCount++;
260
+ this.logger.debug(`retry failed: ${this.sequentialConnectionAttemptFailCount}`);
261
+ if (this.sequentialConnectionAttemptFailCount > 3) {
262
+ this.disconnectWith(SseService.CONNECTION_RETRIES_EXCEEDED, false);
263
+ this.err(`Failed to connect to SSE after ${this.sequentialConnectionAttemptFailCount} retries`);
264
+ return;
265
+ }
266
+ }
267
+ this.disconnect(true);
268
+ this.ensureConnection(true);
269
+ };
270
+ return () => eventSource.close();
271
+ });
272
+ this.establishedConnectionObservable = observable;
273
+ this.registerSseEventHandling(observable);
274
+ return observable;
275
+ }
276
+ getEventSource() {
277
+ let suffix = '';
278
+ if (this.subscriptionId != null) {
279
+ suffix = '/' + this.subscriptionId;
280
+ }
281
+ // subscribe to /sse or /sse/<subscriptionId>
282
+ return new EventSource(this.VALTIMO_ENDPOINT_URL + 'v1/sse' + suffix);
283
+ }
284
+ registerSseEventHandling(observable) {
285
+ this.establishedDataHandler = observable.subscribe(event => {
286
+ this._sseMessages$.next(event);
287
+ this.internalListenerEstablishConnection(event);
288
+ // notify all generic listeners
289
+ this.anySubscribersBucket.sendEvent(event.data);
290
+ // notify the specific event listener bucket
291
+ this.eventSubscribersBuckets.forEach(bucket => {
292
+ if (bucket.event === event?.data?.eventType) {
293
+ bucket.sendEvent(event.data);
294
+ }
295
+ });
296
+ });
297
+ }
298
+ internalListenerEstablishConnection(event) {
299
+ if (event?.data?.eventType !== 'ESTABLISHED_CONNECTION') {
300
+ return;
301
+ }
302
+ this.logger.debug(`established connection: ${event}`);
303
+ this.subscriptionId = event.data.subscriptionId;
304
+ }
305
+ err(message, ...data) {
306
+ this.errorBucket.sendEvent({
307
+ state: this.state,
308
+ message,
309
+ data,
310
+ });
311
+ }
312
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SseService, deps: [{ token: i1.ConfigService }, { token: i2.NGXLogger }], target: i0.ɵɵFactoryTarget.Injectable }); }
313
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SseService, providedIn: 'root' }); }
314
+ }
315
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SseService, decorators: [{
316
+ type: Injectable,
317
+ args: [{
318
+ providedIn: 'root',
319
+ }]
320
+ }], ctorParameters: () => [{ type: i1.ConfigService }, { type: i2.NGXLogger }] });
321
+
322
+ /*
323
+ * Copyright 2015-2025 Ritense BV, the Netherlands.
324
+ *
325
+ * Licensed under EUPL, Version 1.2 (the "License");
326
+ * you may not use this file except in compliance with the License.
327
+ * You may obtain a copy of the License at
328
+ *
329
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
330
+ *
331
+ * Unless required by applicable law or agreed to in writing, software
332
+ * distributed under the License is distributed on an "AS IS" basis,
333
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
334
+ * See the License for the specific language governing permissions and
335
+ * limitations under the License.
336
+ */
337
+
338
+ /*
339
+ * Copyright 2015-2025 Ritense BV, the Netherlands.
340
+ *
341
+ * Licensed under EUPL, Version 1.2 (the "License");
342
+ * you may not use this file except in compliance with the License.
343
+ * You may obtain a copy of the License at
344
+ *
345
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
346
+ *
347
+ * Unless required by applicable law or agreed to in writing, software
348
+ * distributed under the License is distributed on an "AS IS" basis,
349
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
350
+ * See the License for the specific language governing permissions and
351
+ * limitations under the License.
352
+ */
353
+ class SseModule {
354
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SseModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
355
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.17", ngImport: i0, type: SseModule }); }
356
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SseModule }); }
357
+ }
358
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SseModule, decorators: [{
359
+ type: NgModule,
360
+ args: [{
361
+ declarations: [],
362
+ imports: [],
363
+ exports: [],
364
+ }]
365
+ }] });
366
+
367
+ /*
368
+ * Copyright 2015-2025 Ritense BV, the Netherlands.
369
+ *
370
+ * Licensed under EUPL, Version 1.2 (the "License");
371
+ * you may not use this file except in compliance with the License.
372
+ * You may obtain a copy of the License at
373
+ *
374
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
375
+ *
376
+ * Unless required by applicable law or agreed to in writing, software
377
+ * distributed under the License is distributed on an "AS IS" basis,
378
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
379
+ * See the License for the specific language governing permissions and
380
+ * limitations under the License.
381
+ */
382
+ /*
383
+ * Public API Surface of sse
384
+ */
385
+
386
+ /**
387
+ * Generated bundle index. Do not edit.
388
+ */
389
+
390
+ export { SseErrorBucket, SseEventSubscriptionBucket, SseModule, SseService, SseSubscriptionBucket };
391
+ //# sourceMappingURL=valtimo-sse.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"valtimo-sse.mjs","sources":["../../../../projects/valtimo/sse/src/lib/models/sse-bucket.model.ts","../../../../projects/valtimo/sse/src/lib/models/sse-events.model.ts","../../../../projects/valtimo/sse/src/lib/models/index.ts","../../../../projects/valtimo/sse/src/lib/services/sse.service.ts","../../../../projects/valtimo/sse/src/lib/services/index.ts","../../../../projects/valtimo/sse/src/lib/sse.module.ts","../../../../projects/valtimo/sse/src/public-api.ts","../../../../projects/valtimo/sse/src/valtimo-sse.ts"],"sourcesContent":["/*\n * Copyright 2015-2025 Ritense BV, the Netherlands.\n *\n * Licensed under EUPL, Version 1.2 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" basis,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// generic bucket for listeners of any type\nimport {BaseSseEvent, SseError, SseEventListener} from './sse-events.model';\n\nexport class SseSubscriptionBucket<T> {\n readonly listeners: SseEventListener<T>[] = [];\n\n on(listener: SseEventListener<T>) {\n this.listeners.push(listener);\n }\n\n off(listener: SseEventListener<T>) {\n const index = this.listeners.indexOf(listener);\n if (index > -1) {\n this.listeners.splice(index, 1);\n }\n }\n\n offAll() {\n this.listeners.length = 0;\n }\n\n sendEvent(event: T) {\n this.listeners.forEach(listener => {\n listener(event);\n });\n }\n}\n\n// error bucket for error listeners\nexport class SseErrorBucket extends SseSubscriptionBucket<SseError> {}\n\n// implicit type bucket with event for filtering\nexport class SseEventSubscriptionBucket<T extends BaseSseEvent> extends SseSubscriptionBucket<T> {\n readonly event: string;\n\n constructor(event: string) {\n super();\n this.event = event;\n }\n}\n","/*\n * Copyright 2015-2025 Ritense BV, the Netherlands.\n *\n * Licensed under EUPL, Version 1.2 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" basis,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\ntype SseEventType =\n | 'CASE_CREATED'\n | 'TASK_UPDATE'\n | 'DOCUMENT_UPDATED'\n | 'PROCESS_END'\n | 'CASE_ASSIGNED'\n | 'CASE_UNASSIGNED'\n | 'ESTABLISHED_CONNECTION';\n\ntype SseEventListener<T> = (event: T) => void;\n\n// base event containing the event type\ninterface BaseSseEvent {\n eventType?: SseEventType;\n processInstanceId?: string;\n}\n\ninterface EstablishedConnectionSseEvent extends BaseSseEvent {\n subscriptionId: string;\n}\n\ninterface SseError {\n state: number;\n message: string;\n data?: any;\n}\n\nexport {SseError, EstablishedConnectionSseEvent, BaseSseEvent, SseEventListener, SseEventType};\n","/*\n * Copyright 2015-2025 Ritense BV, the Netherlands.\n *\n * Licensed under EUPL, Version 1.2 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" basis,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport * from './sse-bucket.model';\nexport * from './sse-events.model';\n","/*\n * Copyright 2015-2025 Ritense BV, the Netherlands.\n *\n * Licensed under EUPL, Version 1.2 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" basis,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {Injectable} from '@angular/core';\nimport {filter, map, Observable, Subject, Subscription} from 'rxjs';\nimport {\n BaseSseEvent,\n EstablishedConnectionSseEvent,\n SseEventListener,\n SseEventType,\n} from '../models/sse-events.model';\nimport {ConfigService} from '@valtimo/shared';\nimport {\n SseErrorBucket,\n SseEventSubscriptionBucket,\n SseSubscriptionBucket,\n} from '../models/sse-bucket.model';\nimport {NGXLogger} from 'ngx-logger';\n\n/**\n * Server-side events service, for connecting and reconnecting to SSE,\n * and a translation layer between SSE json string data and TS typed events.\n *\n * The service is always present, and so events can be registered with {@link onMessage} and {@link onEvent},\n * and will not be lost on disconnect or reconnect of the SSE connection itself.\n * It is expected to also unregister events using the off-method variants of the on-method listeners.\n *\n * To ensure messages are received the connection must be explicitly opened with {@link connect},\n * likewise the connection should also be closed with {@link disconnect} to stop receiving messages.\n *\n * This service can reconnect itself with the backend, and might use the {@link subscriptionId} to ask for its\n * previous connection state. This state currently exists in the backend as the \"Subscriber\" class, and might\n * have a state containing queues of items that need to be sent, so you can reconnect and receive the rest.\n *\n * At this time no state data is kept, so reconnection without a subscriptionId does not give a different result\n */\n@Injectable({\n providedIn: 'root',\n})\nexport class SseService {\n private static readonly CONNECTION_RETRIES_EXCEEDED = -1;\n private static readonly NOT_CONNECTED = 0;\n private static readonly CONNECTING = 1;\n private static readonly RECONNECTING = 2;\n private static readonly CONNECTED = 10;\n\n private readonly VALTIMO_ENDPOINT_URL: string;\n private connectionCount = 0; // amount of times we have connected sequentially, no concurrent connections\n private establishedDataHandler?: Subscription = null;\n private establishedConnection?: EventSource = null;\n private establishedConnectionObservable: Observable<MessageEvent<BaseSseEvent>> = null;\n private subscriptionId?: string = null;\n private sequentialConnectionAttemptFailCount = 0;\n\n private errorBucket = new SseErrorBucket();\n private anySubscribersBucket = new SseSubscriptionBucket<BaseSseEvent>();\n private eventSubscribersBuckets: SseEventSubscriptionBucket<BaseSseEvent>[] = [];\n\n private state: number = SseService.NOT_CONNECTED;\n\n private _sseMessages$ = new Subject<MessageEvent<BaseSseEvent>>();\n\n get sseMessages$(): Observable<MessageEvent<BaseSseEvent>> {\n return this._sseMessages$.asObservable().pipe(filter(message => !!message));\n }\n\n constructor(\n private readonly configService: ConfigService,\n private readonly logger: NGXLogger\n ) {\n this.VALTIMO_ENDPOINT_URL = configService.config.valtimoApi.endpointUri;\n this.connect();\n }\n\n public getSseMessagesObservableByEventType(\n eventTypes: Array<SseEventType>\n ): Observable<MessageEvent<BaseSseEvent>> {\n return this._sseMessages$.asObservable().pipe(\n filter(message => !!message),\n filter(message => eventTypes.includes(message.data?.eventType))\n );\n }\n\n public getSseEventObservable<Event>(eventType: SseEventType): Observable<Event> {\n return this._sseMessages$.asObservable().pipe(\n filter(message => eventType === message?.data?.eventType),\n map(message => message.data as Event)\n );\n }\n\n /**\n * Start receiving SSE events\n */\n private connect() {\n this.ensureConnection();\n return this;\n }\n\n private onMessage(listener: SseEventListener<BaseSseEvent>) {\n this.ensureConnection(); // ensure connection\n this.anySubscribersBucket.on(listener);\n return this;\n }\n\n private onEvent<T extends BaseSseEvent>(event: string, listener: SseEventListener<T>) {\n this.ensureConnection(); // ensure connection\n let found = false;\n this.eventSubscribersBuckets.forEach(bucket => {\n if (bucket.event === event) {\n bucket.on(listener);\n found = true;\n }\n });\n if (!found) {\n const bucket = new SseEventSubscriptionBucket(event);\n bucket.on(listener);\n this.eventSubscribersBuckets.push(bucket);\n }\n return this;\n }\n\n private offMessage(listener: SseEventListener<BaseSseEvent>) {\n this.anySubscribersBucket.off(listener);\n }\n\n private offEvent(event: string, listener: SseEventListener<any>) {\n this.eventSubscribersBuckets.forEach(bucket => {\n if (bucket.event === event) {\n bucket.off(listener);\n }\n });\n }\n\n private offEvents(type?: string) {\n this.eventSubscribersBuckets.forEach(bucket => {\n if (type === null || type === bucket.event) {\n bucket.offAll();\n }\n });\n }\n\n private offMessages() {\n this.anySubscribersBucket.offAll();\n }\n\n private offAll() {\n this.offEvents();\n this.offMessages();\n }\n\n private disconnect(keepSubscriptionId: boolean = false) {\n this.disconnectWith(SseService.NOT_CONNECTED, keepSubscriptionId);\n }\n\n private disconnectWith(state: number, keepSubscriptionId: boolean = false) {\n this.state = state;\n if (this.establishedConnection?.readyState !== EventSource.CLOSED) {\n this.establishedConnection?.close();\n }\n this.establishedDataHandler.unsubscribe();\n this.establishedConnection = null;\n this.establishedConnectionObservable = null;\n this.establishedDataHandler = null;\n if (!keepSubscriptionId) {\n this.subscriptionId = null;\n }\n }\n\n private ensureConnection(retry: boolean = false) {\n if (this.establishedConnection !== null && this.establishedConnectionObservable !== null) {\n if (this.establishedConnection.readyState !== EventSource.CLOSED) {\n return; // found\n }\n }\n if (this.state === SseService.CONNECTING || this.state === SseService.RECONNECTING) {\n return; // already connecting\n }\n this.establishedConnection = null;\n this.establishedConnectionObservable = null;\n\n this.constructNewSse(retry); // create new\n }\n\n private constructNewSse(retry: boolean) {\n this.logger.debug('subscribing to sse events');\n if (this.connectionCount > 0) {\n this.state = SseService.RECONNECTING;\n } else {\n this.state = SseService.CONNECTING;\n }\n const observable = new Observable<MessageEvent<BaseSseEvent>>(observer => {\n const eventSource = this.getEventSource();\n eventSource.onopen = () => {\n this.establishedConnection = eventSource;\n this.logger.debug('connected to sse');\n this.connectionCount++;\n this.sequentialConnectionAttemptFailCount = 0; // reset retry count\n this.state = SseService.CONNECTED;\n };\n eventSource.onmessage = event => {\n observer.next({\n ...event, // forward event object but replace data field\n data: JSON.parse(event.data), // parse JSON string to JSON object\n });\n };\n eventSource.onerror = () => {\n eventSource.close();\n observer.complete();\n if (retry) {\n this.sequentialConnectionAttemptFailCount++;\n this.logger.debug(`retry failed: ${this.sequentialConnectionAttemptFailCount}`);\n if (this.sequentialConnectionAttemptFailCount > 3) {\n this.disconnectWith(SseService.CONNECTION_RETRIES_EXCEEDED, false);\n this.err(\n `Failed to connect to SSE after ${this.sequentialConnectionAttemptFailCount} retries`\n );\n return;\n }\n }\n this.disconnect(true);\n this.ensureConnection(true);\n };\n return () => eventSource.close();\n });\n this.establishedConnectionObservable = observable;\n this.registerSseEventHandling(observable);\n return observable;\n }\n\n private getEventSource() {\n let suffix = '';\n if (this.subscriptionId != null) {\n suffix = '/' + this.subscriptionId;\n }\n // subscribe to /sse or /sse/<subscriptionId>\n return new EventSource(this.VALTIMO_ENDPOINT_URL + 'v1/sse' + suffix);\n }\n\n private registerSseEventHandling(observable: Observable<MessageEvent<BaseSseEvent>>) {\n this.establishedDataHandler = observable.subscribe(event => {\n this._sseMessages$.next(event);\n this.internalListenerEstablishConnection(event);\n // notify all generic listeners\n this.anySubscribersBucket.sendEvent(event.data);\n // notify the specific event listener bucket\n this.eventSubscribersBuckets.forEach(bucket => {\n if (bucket.event === event?.data?.eventType) {\n bucket.sendEvent(event.data);\n }\n });\n });\n }\n\n private internalListenerEstablishConnection(event: MessageEvent<BaseSseEvent>) {\n if (event?.data?.eventType !== 'ESTABLISHED_CONNECTION') {\n return;\n }\n this.logger.debug(`established connection: ${event}`);\n this.subscriptionId = (event.data as EstablishedConnectionSseEvent).subscriptionId;\n }\n\n private err(message: string, ...data: any) {\n this.errorBucket.sendEvent({\n state: this.state,\n message,\n data,\n });\n }\n}\n","/*\n * Copyright 2015-2025 Ritense BV, the Netherlands.\n *\n * Licensed under EUPL, Version 1.2 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" basis,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport * from './sse.service';\n","/*\n * Copyright 2015-2025 Ritense BV, the Netherlands.\n *\n * Licensed under EUPL, Version 1.2 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" basis,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {NgModule} from '@angular/core';\n\n@NgModule({\n declarations: [],\n imports: [],\n exports: [],\n})\nexport class SseModule {}\n","/*\n * Copyright 2015-2025 Ritense BV, the Netherlands.\n *\n * Licensed under EUPL, Version 1.2 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" basis,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*\n * Public API Surface of sse\n */\n\nexport * from './lib/models';\nexport * from './lib/services';\nexport * from './lib/sse.module';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
package/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Generated bundle index. Do not edit.
3
+ */
4
+ /// <amd-module name="@valtimo/sse" />
5
+ export * from './public-api';
@@ -0,0 +1,2 @@
1
+ export * from './sse-bucket.model';
2
+ export * from './sse-events.model';
@@ -0,0 +1,14 @@
1
+ import { BaseSseEvent, SseError, SseEventListener } from './sse-events.model';
2
+ export declare class SseSubscriptionBucket<T> {
3
+ readonly listeners: SseEventListener<T>[];
4
+ on(listener: SseEventListener<T>): void;
5
+ off(listener: SseEventListener<T>): void;
6
+ offAll(): void;
7
+ sendEvent(event: T): void;
8
+ }
9
+ export declare class SseErrorBucket extends SseSubscriptionBucket<SseError> {
10
+ }
11
+ export declare class SseEventSubscriptionBucket<T extends BaseSseEvent> extends SseSubscriptionBucket<T> {
12
+ readonly event: string;
13
+ constructor(event: string);
14
+ }
@@ -0,0 +1,15 @@
1
+ type SseEventType = 'CASE_CREATED' | 'TASK_UPDATE' | 'DOCUMENT_UPDATED' | 'PROCESS_END' | 'CASE_ASSIGNED' | 'CASE_UNASSIGNED' | 'ESTABLISHED_CONNECTION';
2
+ type SseEventListener<T> = (event: T) => void;
3
+ interface BaseSseEvent {
4
+ eventType?: SseEventType;
5
+ processInstanceId?: string;
6
+ }
7
+ interface EstablishedConnectionSseEvent extends BaseSseEvent {
8
+ subscriptionId: string;
9
+ }
10
+ interface SseError {
11
+ state: number;
12
+ message: string;
13
+ data?: any;
14
+ }
15
+ export { SseError, EstablishedConnectionSseEvent, BaseSseEvent, SseEventListener, SseEventType };
@@ -0,0 +1 @@
1
+ export * from './sse.service';
@@ -0,0 +1,68 @@
1
+ import { Observable } from 'rxjs';
2
+ import { BaseSseEvent, SseEventType } from '../models/sse-events.model';
3
+ import { ConfigService } from '@valtimo/shared';
4
+ import { NGXLogger } from 'ngx-logger';
5
+ import * as i0 from "@angular/core";
6
+ /**
7
+ * Server-side events service, for connecting and reconnecting to SSE,
8
+ * and a translation layer between SSE json string data and TS typed events.
9
+ *
10
+ * The service is always present, and so events can be registered with {@link onMessage} and {@link onEvent},
11
+ * and will not be lost on disconnect or reconnect of the SSE connection itself.
12
+ * It is expected to also unregister events using the off-method variants of the on-method listeners.
13
+ *
14
+ * To ensure messages are received the connection must be explicitly opened with {@link connect},
15
+ * likewise the connection should also be closed with {@link disconnect} to stop receiving messages.
16
+ *
17
+ * This service can reconnect itself with the backend, and might use the {@link subscriptionId} to ask for its
18
+ * previous connection state. This state currently exists in the backend as the "Subscriber" class, and might
19
+ * have a state containing queues of items that need to be sent, so you can reconnect and receive the rest.
20
+ *
21
+ * At this time no state data is kept, so reconnection without a subscriptionId does not give a different result
22
+ */
23
+ export declare class SseService {
24
+ private readonly configService;
25
+ private readonly logger;
26
+ private static readonly CONNECTION_RETRIES_EXCEEDED;
27
+ private static readonly NOT_CONNECTED;
28
+ private static readonly CONNECTING;
29
+ private static readonly RECONNECTING;
30
+ private static readonly CONNECTED;
31
+ private readonly VALTIMO_ENDPOINT_URL;
32
+ private connectionCount;
33
+ private establishedDataHandler?;
34
+ private establishedConnection?;
35
+ private establishedConnectionObservable;
36
+ private subscriptionId?;
37
+ private sequentialConnectionAttemptFailCount;
38
+ private errorBucket;
39
+ private anySubscribersBucket;
40
+ private eventSubscribersBuckets;
41
+ private state;
42
+ private _sseMessages$;
43
+ get sseMessages$(): Observable<MessageEvent<BaseSseEvent>>;
44
+ constructor(configService: ConfigService, logger: NGXLogger);
45
+ getSseMessagesObservableByEventType(eventTypes: Array<SseEventType>): Observable<MessageEvent<BaseSseEvent>>;
46
+ getSseEventObservable<Event>(eventType: SseEventType): Observable<Event>;
47
+ /**
48
+ * Start receiving SSE events
49
+ */
50
+ private connect;
51
+ private onMessage;
52
+ private onEvent;
53
+ private offMessage;
54
+ private offEvent;
55
+ private offEvents;
56
+ private offMessages;
57
+ private offAll;
58
+ private disconnect;
59
+ private disconnectWith;
60
+ private ensureConnection;
61
+ private constructNewSse;
62
+ private getEventSource;
63
+ private registerSseEventHandling;
64
+ private internalListenerEstablishConnection;
65
+ private err;
66
+ static ɵfac: i0.ɵɵFactoryDeclaration<SseService, never>;
67
+ static ɵprov: i0.ɵɵInjectableDeclaration<SseService>;
68
+ }
@@ -0,0 +1,6 @@
1
+ import * as i0 from "@angular/core";
2
+ export declare class SseModule {
3
+ static ɵfac: i0.ɵɵFactoryDeclaration<SseModule, never>;
4
+ static ɵmod: i0.ɵɵNgModuleDeclaration<SseModule, never, never, never>;
5
+ static ɵinj: i0.ɵɵInjectorDeclaration<SseModule>;
6
+ }
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@valtimo/sse",
3
+ "license": "EUPL-1.2",
4
+ "version": "0.0.0",
5
+ "peerDependencies": {
6
+ "@angular/common": "^19.2.8",
7
+ "@angular/core": "^19.2.8"
8
+ },
9
+ "dependencies": {
10
+ "tslib": "2.8.1"
11
+ },
12
+ "module": "fesm2022/valtimo-sse.mjs",
13
+ "typings": "index.d.ts",
14
+ "exports": {
15
+ "./package.json": {
16
+ "default": "./package.json"
17
+ },
18
+ ".": {
19
+ "types": "./index.d.ts",
20
+ "default": "./fesm2022/valtimo-sse.mjs"
21
+ }
22
+ },
23
+ "sideEffects": false
24
+ }
@@ -0,0 +1,3 @@
1
+ export * from './lib/models';
2
+ export * from './lib/services';
3
+ export * from './lib/sse.module';