@furystack/websocket-api 13.1.13 → 13.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # Changelog
2
2
 
3
+ ## [13.2.0] - 2026-03-03
4
+
5
+ ### ✨ Features
6
+
7
+ ### Lifecycle and error events via `EventHub`
8
+
9
+ `WebSocketApi` now extends `EventHub` and emits structured events:
10
+
11
+ - `onError` — emitted when action execution fails (`canExecute`, `getInstance`, or `execute`), or when a WebSocket error occurs
12
+ - `onClientConnected` — emitted when a client connects, with `{ ws, message }`
13
+ - `onClientDisconnected` — emitted when a client disconnects, with `{ ws }`
14
+
15
+ ```typescript
16
+ webSocketApi.addListener('onError', ({ error, data, socket }) => {
17
+ logger.error('WebSocket action error', { error })
18
+ })
19
+
20
+ webSocketApi.addListener('onClientConnected', ({ ws, message }) => {
21
+ logger.info('Client connected')
22
+ })
23
+ ```
24
+
25
+ ### 🐛 Bug Fixes
26
+
27
+ - Action execution now properly waits for async `execute()` to complete before calling `Symbol.dispose` on the action — the previous `using()` pattern disposed immediately while execution was still running
28
+
29
+ ### ♻️ Refactoring
30
+
31
+ - Action execution now uses explicit try/catch and `Promise.resolve().then()` instead of `using()`, ensuring errors are properly caught and emitted rather than silently dropped
32
+
33
+ ### ⬆️ Dependencies
34
+
35
+ - Updated `@furystack/rest-service` with improved error handling for malformed requests
36
+
37
+ ## [13.1.14] - 2026-02-27
38
+
39
+ ### ⬆️ Dependencies
40
+
41
+ - Updated `@furystack/rest-service` dependency
42
+
3
43
  ## [13.1.13] - 2026-02-26
4
44
 
5
45
  ### ⬆️ Dependencies
@@ -1,12 +1,35 @@
1
1
  import type { Injector } from '@furystack/inject';
2
+ import { EventHub, type ListenerErrorPayload } from '@furystack/utils';
2
3
  import { IncomingMessage } from 'http';
3
4
  import type WebSocket from 'ws';
4
5
  import type { Data } from 'ws';
5
6
  import ws from 'ws';
7
+ /**
8
+ * Events emitted by the {@link WebSocketApi}
9
+ */
10
+ export type WebSocketApiEvents = {
11
+ /** Emitted when an error occurs during action execution (canExecute, getInstance, or execute) */
12
+ onError: {
13
+ error: unknown;
14
+ data?: Data;
15
+ socket?: WebSocket;
16
+ };
17
+ /** Emitted when a client connects */
18
+ onClientConnected: {
19
+ ws: WebSocket;
20
+ message: IncomingMessage;
21
+ };
22
+ /** Emitted when a client disconnects */
23
+ onClientDisconnected: {
24
+ ws: WebSocket;
25
+ };
26
+ /** Emitted when an event listener throws or rejects */
27
+ onListenerError: ListenerErrorPayload;
28
+ };
6
29
  /**
7
30
  * A WebSocket API implementation for FuryStack
8
31
  */
9
- export declare class WebSocketApi implements AsyncDisposable {
32
+ export declare class WebSocketApi extends EventHub<WebSocketApiEvents> implements AsyncDisposable {
10
33
  readonly socket: import("ws").Server<typeof WebSocket, typeof IncomingMessage>;
11
34
  private clients;
12
35
  private readonly settings;
@@ -1 +1 @@
1
- {"version":3,"file":"websocket-api.d.ts","sourceRoot":"","sources":["../src/websocket-api.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAIjD,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AAEtC,OAAO,KAAK,SAAS,MAAM,IAAI,CAAA;AAC/B,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;AAC9B,OAAO,EAAuB,MAAM,IAAI,CAAA;AAIxC;;GAEG;AACH,qBACa,YAAa,YAAW,eAAe;IAClD,SAAgB,MAAM,gEAA0C;IAEhE,OAAO,CAAC,OAAO,CAA0E;IAEzF,iBACyB,QAAQ,CAAsB;IAEvD,iBACyB,aAAa,CAAe;IAErD,iBAAyB,QAAQ,CAAU;IAE3C,OAAO,CAAC,aAAa,CAAQ;IAChB,IAAI;IAmDJ,CAAC,MAAM,CAAC,YAAY,CAAC;IASrB,SAAS,CACpB,QAAQ,EAAE,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,QAAQ,CAAC;QAAC,EAAE,EAAE,EAAE,CAAC;QAAC,OAAO,EAAE,eAAe,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBhG,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS;CAQ3F"}
1
+ {"version":3,"file":"websocket-api.d.ts","sourceRoot":"","sources":["../src/websocket-api.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAGjD,OAAO,EAAE,QAAQ,EAAE,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AACtE,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AAEtC,OAAO,KAAK,SAAS,MAAM,IAAI,CAAA;AAC/B,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;AAC9B,OAAO,EAAuB,MAAM,IAAI,CAAA;AAIxC;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,iGAAiG;IACjG,OAAO,EAAE;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,IAAI,CAAC;QAAC,MAAM,CAAC,EAAE,SAAS,CAAA;KAAE,CAAA;IAC5D,qCAAqC;IACrC,iBAAiB,EAAE;QAAE,EAAE,EAAE,SAAS,CAAC;QAAC,OAAO,EAAE,eAAe,CAAA;KAAE,CAAA;IAC9D,wCAAwC;IACxC,oBAAoB,EAAE;QAAE,EAAE,EAAE,SAAS,CAAA;KAAE,CAAA;IACvC,uDAAuD;IACvD,eAAe,EAAE,oBAAoB,CAAA;CACtC,CAAA;AAED;;GAEG;AACH,qBACa,YAAa,SAAQ,QAAQ,CAAC,kBAAkB,CAAE,YAAW,eAAe;IACvF,SAAgB,MAAM,gEAA0C;IAEhE,OAAO,CAAC,OAAO,CAA0E;IAEzF,iBACyB,QAAQ,CAAsB;IAEvD,iBACyB,aAAa,CAAe;IAErD,iBAAyB,QAAQ,CAAU;IAE3C,OAAO,CAAC,aAAa,CAAQ;IAChB,IAAI;IAyDJ,CAAC,MAAM,CAAC,YAAY,CAAC;IAUrB,SAAS,CACpB,QAAQ,EAAE,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,QAAQ,CAAC;QAAC,EAAE,EAAE,EAAE,CAAC;QAAC,OAAO,EAAE,eAAe,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBhG,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS;CAkB3F"}
@@ -10,7 +10,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
10
10
  import { AggregatedError, IdentityContext } from '@furystack/core';
11
11
  import { Injectable, Injected } from '@furystack/inject';
12
12
  import { HttpUserContext, ServerManager } from '@furystack/rest-service';
13
- import { using } from '@furystack/utils';
13
+ import { EventHub } from '@furystack/utils';
14
14
  import { IncomingMessage } from 'http';
15
15
  import { URL } from 'url';
16
16
  import ws, { WebSocketServer } from 'ws';
@@ -18,7 +18,7 @@ import { WebSocketApiSettings } from './websocket-api-settings.js';
18
18
  /**
19
19
  * A WebSocket API implementation for FuryStack
20
20
  */
21
- let WebSocketApi = class WebSocketApi {
21
+ let WebSocketApi = class WebSocketApi extends EventHub {
22
22
  socket = new WebSocketServer({ noServer: true });
23
23
  clients = new Map();
24
24
  isInitialized = false;
@@ -33,13 +33,18 @@ let WebSocketApi = class WebSocketApi {
33
33
  isAuthenticated: () => httpUserContext.isAuthenticated(msg),
34
34
  }, IdentityContext);
35
35
  this.clients.set(websocket, { injector: connectionInjector, message: msg, ws: websocket });
36
+ this.emit('onClientConnected', { ws: websocket, message: msg });
36
37
  websocket.on('message', (message) => {
37
38
  this.execute(message, msg, connectionInjector, websocket);
38
39
  });
40
+ websocket.on('error', (error) => {
41
+ this.emit('onError', { error, socket: websocket });
42
+ });
39
43
  websocket.on('close', () => {
40
44
  this.clients.delete(websocket);
45
+ this.emit('onClientDisconnected', { ws: websocket });
41
46
  connectionInjector[Symbol.asyncDispose]().catch((err) => {
42
- console.error('Error disposing connection injector:', err);
47
+ this.emit('onError', { error: err, socket: websocket });
43
48
  });
44
49
  });
45
50
  });
@@ -72,6 +77,7 @@ let WebSocketApi = class WebSocketApi {
72
77
  // Dispose all child injectors
73
78
  await Promise.allSettled([...this.clients.values()].map((client) => client.injector[Symbol.asyncDispose]()));
74
79
  this.clients.clear();
80
+ super[Symbol.dispose]();
75
81
  }
76
82
  async broadcast(callback) {
77
83
  const errors = [];
@@ -90,11 +96,22 @@ let WebSocketApi = class WebSocketApi {
90
96
  }
91
97
  }
92
98
  execute(data, request, injector, socket) {
93
- const Action = this.settings.actions.find((a) => a.canExecute({ data, request, socket }));
94
- if (Action) {
95
- using(injector.getInstance(Action), (action) => {
96
- void action.execute({ data, request, socket });
97
- });
99
+ try {
100
+ const Action = this.settings.actions.find((a) => a.canExecute({ data, request, socket }));
101
+ if (Action) {
102
+ const action = injector.getInstance(Action);
103
+ Promise.resolve()
104
+ .then(() => action.execute({ data, request, socket }))
105
+ .catch((error) => {
106
+ this.emit('onError', { error, data, socket });
107
+ })
108
+ .finally(() => {
109
+ action[Symbol.dispose]();
110
+ });
111
+ }
112
+ }
113
+ catch (error) {
114
+ this.emit('onError', { error, data, socket });
98
115
  }
99
116
  }
100
117
  };
@@ -1 +1 @@
1
- {"version":3,"file":"websocket-api.js","sourceRoot":"","sources":["../src/websocket-api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"websocket-api.js","sourceRoot":"","sources":["../src/websocket-api.ts"],"names":[],"mappings":"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"}
@@ -11,7 +11,7 @@ import { getRepository } from '@furystack/repository';
11
11
  import { DefaultSession } from '@furystack/rest-service';
12
12
  import { PasswordCredential, PasswordResetToken, usePasswordPolicy } from '@furystack/security';
13
13
  import { usingAsync } from '@furystack/utils';
14
- import { describe, expect, it } from 'vitest';
14
+ import { describe, expect, it, vi } from 'vitest';
15
15
  import { WebSocket } from 'ws';
16
16
  import { useWebsockets } from './helpers.js';
17
17
  import { WebSocketApi } from './websocket-api.js';
@@ -69,6 +69,58 @@ describe('WebSocketApi', () => {
69
69
  }));
70
70
  });
71
71
  });
72
+ it('Should emit onClientConnected and onClientDisconnected events', async () => {
73
+ const port = getPort();
74
+ await usingAsync(new Injector(), async (i) => {
75
+ setupStoresAndDataSets(i);
76
+ await useWebsockets(i, { path: '/ws-events', port });
77
+ const api = i.getInstance(WebSocketApi);
78
+ const connectedHandler = vi.fn();
79
+ const disconnectedHandler = vi.fn();
80
+ api.addListener('onClientConnected', connectedHandler);
81
+ api.addListener('onClientDisconnected', disconnectedHandler);
82
+ const client = new WebSocket(`ws://localhost:${port}/ws-events`);
83
+ await new Promise((resolve) => client.once('open', () => resolve()));
84
+ expect(connectedHandler).toHaveBeenCalled();
85
+ expect(connectedHandler).toHaveBeenCalledWith(expect.objectContaining({ ws: expect.any(Object), message: expect.any(Object) }));
86
+ client.close();
87
+ await new Promise((resolve) => client.once('close', () => resolve()));
88
+ await new Promise((resolve) => setTimeout(resolve, 50));
89
+ expect(disconnectedHandler).toHaveBeenCalled();
90
+ });
91
+ });
92
+ it('Should emit onError when action execution throws', async () => {
93
+ const port = getPort();
94
+ await usingAsync(new Injector(), async (i) => {
95
+ setupStoresAndDataSets(i);
96
+ let FailingAction = class FailingAction {
97
+ [Symbol.dispose]() {
98
+ /** */
99
+ }
100
+ static canExecute() {
101
+ return true;
102
+ }
103
+ async execute() {
104
+ throw new Error('action failed');
105
+ }
106
+ };
107
+ FailingAction = __decorate([
108
+ Injectable()
109
+ ], FailingAction);
110
+ await useWebsockets(i, { path: '/ws-error-test', port, actions: [FailingAction] });
111
+ const api = i.getInstance(WebSocketApi);
112
+ const errorHandler = vi.fn();
113
+ api.addListener('onError', errorHandler);
114
+ const client = new WebSocket(`ws://localhost:${port}/ws-error-test`);
115
+ await new Promise((resolve) => client.once('open', () => resolve()));
116
+ await new Promise((resolve, reject) => client.send('trigger', (err) => (err ? reject(err) : resolve())));
117
+ await new Promise((resolve) => setTimeout(resolve, 100));
118
+ expect(errorHandler).toHaveBeenCalled();
119
+ expect(errorHandler).toHaveBeenCalledWith(expect.objectContaining({ error: expect.any(Error) }));
120
+ client.close();
121
+ await new Promise((resolve) => client.once('close', () => resolve()));
122
+ });
123
+ });
72
124
  it('Should receive client messages', async () => {
73
125
  const port = getPort();
74
126
  await usingAsync(new Injector(), async (i) => {
@@ -1 +1 @@
1
- {"version":3,"file":"websocket-api.spec.js","sourceRoot":"","sources":["../src/websocket-api.spec.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAA;AACxD,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAC/F,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,EAAE,SAAS,EAAa,MAAM,IAAI,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAE5C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD,MAAM,sBAAsB,GAAG,CAAC,QAAkB,EAAE,EAAE;IACpD,QAAQ,CAAC,QAAQ,EAAE,IAAI,aAAa,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;SAC3E,QAAQ,CAAC,IAAI,aAAa,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC,CAAC;SAC/E,QAAQ,CAAC,IAAI,aAAa,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;SAClF,QAAQ,CAAC,IAAI,aAAa,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC,CAAA;IAElF,MAAM,IAAI,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAA;IACpC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,UAAU,CAAC,CAAA;IACpC,IAAI,CAAC,aAAa,CAAC,cAAc,EAAE,WAAW,CAAC,CAAA;IAC/C,IAAI,CAAC,aAAa,CAAC,kBAAkB,EAAE,UAAU,CAAC,CAAA;IAClD,IAAI,CAAC,aAAa,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAA;IAE/C,iBAAiB,CAAC,QAAQ,CAAC,CAAA;AAC7B,CAAC,CAAA;AAED,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,iBAAiB,EAAE,KAAK,IAAI,EAAE;QAC/B,MAAM,UAAU,CAAC,IAAI,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;YAC3C,sBAAsB,CAAC,CAAC,CAAC,CAAA;YACzB,MAAM,aAAa,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,CAAA;YAC3C,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC,CAAC,cAAc,CAAC,YAAY,CAAC,CAAA;QAClE,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IACF,EAAE,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;QAC7C,MAAM,UAAU,CAAC,IAAI,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;YAC3C,sBAAsB,CAAC,CAAC,CAAC,CAAA;YACzB,MAAM,aAAa,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,CAAA;YAChE,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC,CAAC,cAAc,CAAC,YAAY,CAAC,CAAA;QAClE,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2BAA2B,EAAE,KAAK,IAAI,EAAE;QACzC,MAAM,IAAI,GAAG,OAAO,EAAE,CAAA;QACtB,MAAM,UAAU,CAAC,IAAI,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;YAC3C,sBAAsB,CAAC,CAAC,CAAC,CAAA;YAEzB,MAAM,aAAa,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAA;YACrD,MAAM,GAAG,GAAG,CAAC,CAAC,WAAW,CAAC,YAAY,CAAC,CAAA;YAEvC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE;gBAC7B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,kBAAkB,IAAI,aAAa,CAAC,CAAA;gBACjE,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;gBAC1E,OAAO,MAAM,CAAA;YACf,CAAC,CAAC,CACH,CAAA;YAED,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CACjC,CAAC,MAAM,EAAE,EAAE,CACT,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,EAAE;gBAC9B,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAE,IAAe,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAA;YACxE,CAAC,CAAC,CACL,CAAA;YAED,MAAM,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE;gBAC7B,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YACjB,CAAC,CAAC,CAAA;YAEF,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAA;YACnD,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;gBAC3B,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YAC1B,CAAC;YAED,MAAM,OAAO,CAAC,GAAG,CACf,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;gBAC3B,MAAM,CAAC,KAAK,EAAE,CAAA;gBACd,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;YAC7E,CAAC,CAAC,CACH,CAAA;QACH,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAC9C,MAAM,IAAI,GAAG,OAAO,EAAE,CAAA;QACtB,MAAM,UAAU,CAAC,IAAI,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;YAC3C,sBAAsB,CAAC,CAAC,CAAC,CAAA;YAEzB,MAAM,IAAI,GAAG,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAA;YAE7C,IAAM,eAAe,GAArB,MAAM,eAAe;gBACZ,CAAC,MAAM,CAAC,OAAO,CAAC;oBACrB,MAAM;gBACR,CAAC;gBACM,MAAM,CAAC,UAAU,CAAC,YAA4B;oBACnD,IAAI,CAAC;wBACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAE,YAAY,CAAC,IAAe,CAAC,QAAQ,EAAE,CAAY,CAAA;wBAC9E,OAAO,CACL,OAAO,MAAM,KAAK,QAAQ;4BAC1B,MAAM,KAAK,IAAI;4BACf,OAAO,IAAI,MAAM;4BACjB,MAAM,CAAC,KAAK,KAAK,qBAAqB,CACvC,CAAA;oBACH,CAAC;oBAAC,MAAM,CAAC;wBACP,OAAO,KAAK,CAAA;oBACd,CAAC;gBACH,CAAC;gBAEM,KAAK,CAAC,OAAO,CAAC,OAA0C;oBAC7D,MAAM,CAAC,IAAI,CAAC,KAAK,CAAE,OAAO,CAAC,IAAe,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;oBACrE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;gBAC7B,CAAC;aACF,CAAA;YAtBK,eAAe;gBADpB,UAAU,EAAE;eACP,eAAe,CAsBpB;YAED,MAAM,aAAa,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,eAAe,CAAC,EAAE,CAAC,CAAA;YACtF,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,kBAAkB,IAAI,kBAAkB,CAAC,CAAA;YACtE,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;YAE1E,MAAM,eAAe,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;gBACpD,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE;oBAC1B,OAAO,EAAE,CAAA;gBACX,CAAC,CAAC,CAAA;YACJ,CAAC,CAAC,CAAA;YAEF,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAC1C,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAC5E,CAAA;YAED,MAAM,eAAe,CAAA;YACrB,MAAM,CAAC,KAAK,EAAE,CAAA;YACd,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;QAC7E,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"websocket-api.spec.js","sourceRoot":"","sources":["../src/websocket-api.spec.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAA;AACxD,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAC/F,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AACjD,OAAO,EAAE,SAAS,EAAa,MAAM,IAAI,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAE5C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD,MAAM,sBAAsB,GAAG,CAAC,QAAkB,EAAE,EAAE;IACpD,QAAQ,CAAC,QAAQ,EAAE,IAAI,aAAa,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;SAC3E,QAAQ,CAAC,IAAI,aAAa,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC,CAAC;SAC/E,QAAQ,CAAC,IAAI,aAAa,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;SAClF,QAAQ,CAAC,IAAI,aAAa,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC,CAAA;IAElF,MAAM,IAAI,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAA;IACpC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,UAAU,CAAC,CAAA;IACpC,IAAI,CAAC,aAAa,CAAC,cAAc,EAAE,WAAW,CAAC,CAAA;IAC/C,IAAI,CAAC,aAAa,CAAC,kBAAkB,EAAE,UAAU,CAAC,CAAA;IAClD,IAAI,CAAC,aAAa,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAA;IAE/C,iBAAiB,CAAC,QAAQ,CAAC,CAAA;AAC7B,CAAC,CAAA;AAED,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,iBAAiB,EAAE,KAAK,IAAI,EAAE;QAC/B,MAAM,UAAU,CAAC,IAAI,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;YAC3C,sBAAsB,CAAC,CAAC,CAAC,CAAA;YACzB,MAAM,aAAa,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,CAAA;YAC3C,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC,CAAC,cAAc,CAAC,YAAY,CAAC,CAAA;QAClE,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IACF,EAAE,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;QAC7C,MAAM,UAAU,CAAC,IAAI,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;YAC3C,sBAAsB,CAAC,CAAC,CAAC,CAAA;YACzB,MAAM,aAAa,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,CAAA;YAChE,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC,CAAC,cAAc,CAAC,YAAY,CAAC,CAAA;QAClE,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2BAA2B,EAAE,KAAK,IAAI,EAAE;QACzC,MAAM,IAAI,GAAG,OAAO,EAAE,CAAA;QACtB,MAAM,UAAU,CAAC,IAAI,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;YAC3C,sBAAsB,CAAC,CAAC,CAAC,CAAA;YAEzB,MAAM,aAAa,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAA;YACrD,MAAM,GAAG,GAAG,CAAC,CAAC,WAAW,CAAC,YAAY,CAAC,CAAA;YAEvC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE;gBAC7B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,kBAAkB,IAAI,aAAa,CAAC,CAAA;gBACjE,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;gBAC1E,OAAO,MAAM,CAAA;YACf,CAAC,CAAC,CACH,CAAA;YAED,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CACjC,CAAC,MAAM,EAAE,EAAE,CACT,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,EAAE;gBAC9B,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAE,IAAe,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAA;YACxE,CAAC,CAAC,CACL,CAAA;YAED,MAAM,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE;gBAC7B,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YACjB,CAAC,CAAC,CAAA;YAEF,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAA;YACnD,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;gBAC3B,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YAC1B,CAAC;YAED,MAAM,OAAO,CAAC,GAAG,CACf,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;gBAC3B,MAAM,CAAC,KAAK,EAAE,CAAA;gBACd,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;YAC7E,CAAC,CAAC,CACH,CAAA;QACH,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+DAA+D,EAAE,KAAK,IAAI,EAAE;QAC7E,MAAM,IAAI,GAAG,OAAO,EAAE,CAAA;QACtB,MAAM,UAAU,CAAC,IAAI,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;YAC3C,sBAAsB,CAAC,CAAC,CAAC,CAAA;YACzB,MAAM,aAAa,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAA;YACpD,MAAM,GAAG,GAAG,CAAC,CAAC,WAAW,CAAC,YAAY,CAAC,CAAA;YAEvC,MAAM,gBAAgB,GAAG,EAAE,CAAC,EAAE,EAAE,CAAA;YAChC,MAAM,mBAAmB,GAAG,EAAE,CAAC,EAAE,EAAE,CAAA;YACnC,GAAG,CAAC,WAAW,CAAC,mBAAmB,EAAE,gBAAgB,CAAC,CAAA;YACtD,GAAG,CAAC,WAAW,CAAC,sBAAsB,EAAE,mBAAmB,CAAC,CAAA;YAE5D,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,kBAAkB,IAAI,YAAY,CAAC,CAAA;YAChE,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;YAE1E,MAAM,CAAC,gBAAgB,CAAC,CAAC,gBAAgB,EAAE,CAAA;YAC3C,MAAM,CAAC,gBAAgB,CAAC,CAAC,oBAAoB,CAC3C,MAAM,CAAC,gBAAgB,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAW,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAW,EAAE,CAAC,CACrG,CAAA;YAED,MAAM,CAAC,KAAK,EAAE,CAAA;YACd,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;YAC3E,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;YAEvD,MAAM,CAAC,mBAAmB,CAAC,CAAC,gBAAgB,EAAE,CAAA;QAChD,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;QAChE,MAAM,IAAI,GAAG,OAAO,EAAE,CAAA;QACtB,MAAM,UAAU,CAAC,IAAI,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;YAC3C,sBAAsB,CAAC,CAAC,CAAC,CAAA;YAGzB,IAAM,aAAa,GAAnB,MAAM,aAAa;gBACV,CAAC,MAAM,CAAC,OAAO,CAAC;oBACrB,MAAM;gBACR,CAAC;gBACM,MAAM,CAAC,UAAU;oBACtB,OAAO,IAAI,CAAA;gBACb,CAAC;gBACM,KAAK,CAAC,OAAO;oBAClB,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAA;gBAClC,CAAC;aACF,CAAA;YAVK,aAAa;gBADlB,UAAU,EAAE;eACP,aAAa,CAUlB;YAED,MAAM,aAAa,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,CAAA;YAClF,MAAM,GAAG,GAAG,CAAC,CAAC,WAAW,CAAC,YAAY,CAAC,CAAA;YAEvC,MAAM,YAAY,GAAG,EAAE,CAAC,EAAE,EAAE,CAAA;YAC5B,GAAG,CAAC,WAAW,CAAC,SAAS,EAAE,YAAY,CAAC,CAAA;YAExC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,kBAAkB,IAAI,gBAAgB,CAAC,CAAA;YACpE,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;YAE1E,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAA;YAE9G,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAA;YAExD,MAAM,CAAC,YAAY,CAAC,CAAC,gBAAgB,EAAE,CAAA;YACvC,MAAM,CAAC,YAAY,CAAC,CAAC,oBAAoB,CAAC,MAAM,CAAC,gBAAgB,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAU,EAAE,CAAC,CAAC,CAAA;YAEzG,MAAM,CAAC,KAAK,EAAE,CAAA;YACd,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;QAC7E,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAC9C,MAAM,IAAI,GAAG,OAAO,EAAE,CAAA;QACtB,MAAM,UAAU,CAAC,IAAI,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;YAC3C,sBAAsB,CAAC,CAAC,CAAC,CAAA;YAEzB,MAAM,IAAI,GAAG,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAA;YAE7C,IAAM,eAAe,GAArB,MAAM,eAAe;gBACZ,CAAC,MAAM,CAAC,OAAO,CAAC;oBACrB,MAAM;gBACR,CAAC;gBACM,MAAM,CAAC,UAAU,CAAC,YAA4B;oBACnD,IAAI,CAAC;wBACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAE,YAAY,CAAC,IAAe,CAAC,QAAQ,EAAE,CAAY,CAAA;wBAC9E,OAAO,CACL,OAAO,MAAM,KAAK,QAAQ;4BAC1B,MAAM,KAAK,IAAI;4BACf,OAAO,IAAI,MAAM;4BACjB,MAAM,CAAC,KAAK,KAAK,qBAAqB,CACvC,CAAA;oBACH,CAAC;oBAAC,MAAM,CAAC;wBACP,OAAO,KAAK,CAAA;oBACd,CAAC;gBACH,CAAC;gBAEM,KAAK,CAAC,OAAO,CAAC,OAA0C;oBAC7D,MAAM,CAAC,IAAI,CAAC,KAAK,CAAE,OAAO,CAAC,IAAe,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;oBACrE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;gBAC7B,CAAC;aACF,CAAA;YAtBK,eAAe;gBADpB,UAAU,EAAE;eACP,eAAe,CAsBpB;YAED,MAAM,aAAa,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,eAAe,CAAC,EAAE,CAAC,CAAA;YACtF,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,kBAAkB,IAAI,kBAAkB,CAAC,CAAA;YACtE,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;YAE1E,MAAM,eAAe,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;gBACpD,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE;oBAC1B,OAAO,EAAE,CAAA;gBACX,CAAC,CAAC,CAAA;YACJ,CAAC,CAAC,CAAA;YAEF,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAC1C,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAC5E,CAAA;YAED,MAAM,eAAe,CAAA;YACrB,MAAM,CAAC,KAAK,EAAE,CAAA;YACd,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;QAC7E,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@furystack/websocket-api",
3
- "version": "13.1.13",
3
+ "version": "13.2.0",
4
4
  "description": "WebSocket API implementation for FuryStack",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -37,15 +37,15 @@
37
37
  },
38
38
  "homepage": "https://github.com/furystack/furystack",
39
39
  "dependencies": {
40
- "@furystack/core": "^15.2.2",
41
- "@furystack/inject": "^12.0.30",
42
- "@furystack/rest-service": "^12.1.0",
43
- "@furystack/utils": "^8.1.10",
40
+ "@furystack/core": "^15.2.3",
41
+ "@furystack/inject": "^12.0.31",
42
+ "@furystack/rest-service": "^12.2.0",
43
+ "@furystack/utils": "^8.2.0",
44
44
  "ws": "^8.19.0"
45
45
  },
46
46
  "devDependencies": {
47
- "@furystack/repository": "^10.1.3",
48
- "@furystack/security": "^7.0.1",
47
+ "@furystack/repository": "^10.1.4",
48
+ "@furystack/security": "^7.0.2",
49
49
  "@types/ws": "^8.18.1",
50
50
  "typescript": "^5.9.3",
51
51
  "vitest": "^4.0.18"
@@ -5,7 +5,7 @@ import { getRepository } from '@furystack/repository'
5
5
  import { DefaultSession } from '@furystack/rest-service'
6
6
  import { PasswordCredential, PasswordResetToken, usePasswordPolicy } from '@furystack/security'
7
7
  import { usingAsync } from '@furystack/utils'
8
- import { describe, expect, it } from 'vitest'
8
+ import { describe, expect, it, vi } from 'vitest'
9
9
  import { WebSocket, type Data } from 'ws'
10
10
  import { useWebsockets } from './helpers.js'
11
11
  import type { WebSocketAction } from './models/websocket-action.js'
@@ -83,6 +83,73 @@ describe('WebSocketApi', () => {
83
83
  })
84
84
  })
85
85
 
86
+ it('Should emit onClientConnected and onClientDisconnected events', async () => {
87
+ const port = getPort()
88
+ await usingAsync(new Injector(), async (i) => {
89
+ setupStoresAndDataSets(i)
90
+ await useWebsockets(i, { path: '/ws-events', port })
91
+ const api = i.getInstance(WebSocketApi)
92
+
93
+ const connectedHandler = vi.fn()
94
+ const disconnectedHandler = vi.fn()
95
+ api.addListener('onClientConnected', connectedHandler)
96
+ api.addListener('onClientDisconnected', disconnectedHandler)
97
+
98
+ const client = new WebSocket(`ws://localhost:${port}/ws-events`)
99
+ await new Promise<void>((resolve) => client.once('open', () => resolve()))
100
+
101
+ expect(connectedHandler).toHaveBeenCalled()
102
+ expect(connectedHandler).toHaveBeenCalledWith(
103
+ expect.objectContaining({ ws: expect.any(Object) as object, message: expect.any(Object) as object }),
104
+ )
105
+
106
+ client.close()
107
+ await new Promise<void>((resolve) => client.once('close', () => resolve()))
108
+ await new Promise((resolve) => setTimeout(resolve, 50))
109
+
110
+ expect(disconnectedHandler).toHaveBeenCalled()
111
+ })
112
+ })
113
+
114
+ it('Should emit onError when action execution throws', async () => {
115
+ const port = getPort()
116
+ await usingAsync(new Injector(), async (i) => {
117
+ setupStoresAndDataSets(i)
118
+
119
+ @Injectable()
120
+ class FailingAction implements WebSocketAction {
121
+ public [Symbol.dispose]() {
122
+ /** */
123
+ }
124
+ public static canExecute() {
125
+ return true
126
+ }
127
+ public async execute(): Promise<void> {
128
+ throw new Error('action failed')
129
+ }
130
+ }
131
+
132
+ await useWebsockets(i, { path: '/ws-error-test', port, actions: [FailingAction] })
133
+ const api = i.getInstance(WebSocketApi)
134
+
135
+ const errorHandler = vi.fn()
136
+ api.addListener('onError', errorHandler)
137
+
138
+ const client = new WebSocket(`ws://localhost:${port}/ws-error-test`)
139
+ await new Promise<void>((resolve) => client.once('open', () => resolve()))
140
+
141
+ await new Promise<void>((resolve, reject) => client.send('trigger', (err) => (err ? reject(err) : resolve())))
142
+
143
+ await new Promise((resolve) => setTimeout(resolve, 100))
144
+
145
+ expect(errorHandler).toHaveBeenCalled()
146
+ expect(errorHandler).toHaveBeenCalledWith(expect.objectContaining({ error: expect.any(Error) as Error }))
147
+
148
+ client.close()
149
+ await new Promise<void>((resolve) => client.once('close', () => resolve()))
150
+ })
151
+ })
152
+
86
153
  it('Should receive client messages', async () => {
87
154
  const port = getPort()
88
155
  await usingAsync(new Injector(), async (i) => {
@@ -2,7 +2,7 @@ import { AggregatedError, IdentityContext, type User } from '@furystack/core'
2
2
  import type { Injector } from '@furystack/inject'
3
3
  import { Injectable, Injected } from '@furystack/inject'
4
4
  import { HttpUserContext, ServerManager } from '@furystack/rest-service'
5
- import { using } from '@furystack/utils'
5
+ import { EventHub, type ListenerErrorPayload } from '@furystack/utils'
6
6
  import { IncomingMessage } from 'http'
7
7
  import { URL } from 'url'
8
8
  import type WebSocket from 'ws'
@@ -11,11 +11,25 @@ import ws, { WebSocketServer } from 'ws'
11
11
  import type { WebSocketAction } from './models/websocket-action.js'
12
12
  import { WebSocketApiSettings } from './websocket-api-settings.js'
13
13
 
14
+ /**
15
+ * Events emitted by the {@link WebSocketApi}
16
+ */
17
+ export type WebSocketApiEvents = {
18
+ /** Emitted when an error occurs during action execution (canExecute, getInstance, or execute) */
19
+ onError: { error: unknown; data?: Data; socket?: WebSocket }
20
+ /** Emitted when a client connects */
21
+ onClientConnected: { ws: WebSocket; message: IncomingMessage }
22
+ /** Emitted when a client disconnects */
23
+ onClientDisconnected: { ws: WebSocket }
24
+ /** Emitted when an event listener throws or rejects */
25
+ onListenerError: ListenerErrorPayload
26
+ }
27
+
14
28
  /**
15
29
  * A WebSocket API implementation for FuryStack
16
30
  */
17
31
  @Injectable({ lifetime: 'scoped' })
18
- export class WebSocketApi implements AsyncDisposable {
32
+ export class WebSocketApi extends EventHub<WebSocketApiEvents> implements AsyncDisposable {
19
33
  public readonly socket = new WebSocketServer({ noServer: true })
20
34
 
21
35
  private clients = new Map<ws, { injector: Injector; ws: ws; message: IncomingMessage }>()
@@ -45,14 +59,20 @@ export class WebSocketApi implements AsyncDisposable {
45
59
  )
46
60
 
47
61
  this.clients.set(websocket, { injector: connectionInjector, message: msg, ws: websocket })
62
+ this.emit('onClientConnected', { ws: websocket, message: msg })
48
63
  websocket.on('message', (message) => {
49
64
  this.execute(message, msg, connectionInjector, websocket)
50
65
  })
51
66
 
67
+ websocket.on('error', (error) => {
68
+ this.emit('onError', { error, socket: websocket })
69
+ })
70
+
52
71
  websocket.on('close', () => {
53
72
  this.clients.delete(websocket)
73
+ this.emit('onClientDisconnected', { ws: websocket })
54
74
  connectionInjector[Symbol.asyncDispose]().catch((err) => {
55
- console.error('Error disposing connection injector:', err)
75
+ this.emit('onError', { error: err, socket: websocket })
56
76
  })
57
77
  })
58
78
  })
@@ -87,6 +107,7 @@ export class WebSocketApi implements AsyncDisposable {
87
107
  // Dispose all child injectors
88
108
  await Promise.allSettled([...this.clients.values()].map((client) => client.injector[Symbol.asyncDispose]()))
89
109
  this.clients.clear()
110
+ super[Symbol.dispose]()
90
111
  }
91
112
 
92
113
  public async broadcast(
@@ -110,11 +131,21 @@ export class WebSocketApi implements AsyncDisposable {
110
131
  }
111
132
 
112
133
  public execute(data: Data, request: IncomingMessage, injector: Injector, socket: WebSocket) {
113
- const Action = this.settings.actions.find((a) => a.canExecute({ data, request, socket }))
114
- if (Action) {
115
- using(injector.getInstance<WebSocketAction>(Action), (action) => {
116
- void action.execute({ data, request, socket })
117
- })
134
+ try {
135
+ const Action = this.settings.actions.find((a) => a.canExecute({ data, request, socket }))
136
+ if (Action) {
137
+ const action = injector.getInstance<WebSocketAction>(Action)
138
+ Promise.resolve()
139
+ .then(() => action.execute({ data, request, socket }))
140
+ .catch((error: unknown) => {
141
+ this.emit('onError', { error, data, socket })
142
+ })
143
+ .finally(() => {
144
+ action[Symbol.dispose]()
145
+ })
146
+ }
147
+ } catch (error) {
148
+ this.emit('onError', { error, data, socket })
118
149
  }
119
150
  }
120
151
  }