@equinor/fusion-framework-module-signalr 10.0.1 → 11.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +28 -2
  2. package/README.md +133 -0
  3. package/dist/esm/SignalRModule.js +16 -0
  4. package/dist/esm/SignalRModule.js.map +1 -1
  5. package/dist/esm/SignalRModuleConfigurator.js +35 -7
  6. package/dist/esm/SignalRModuleConfigurator.js.map +1 -1
  7. package/dist/esm/SignalRModuleProvider.js +33 -1
  8. package/dist/esm/SignalRModuleProvider.js.map +1 -1
  9. package/dist/esm/index.js +13 -0
  10. package/dist/esm/index.js.map +1 -1
  11. package/dist/esm/lib/Topic.js +40 -0
  12. package/dist/esm/lib/Topic.js.map +1 -1
  13. package/dist/esm/lib/utils/configure-from-framework.js +12 -0
  14. package/dist/esm/lib/utils/configure-from-framework.js.map +1 -1
  15. package/dist/esm/lib/utils/enable-signalr.js +29 -24
  16. package/dist/esm/lib/utils/enable-signalr.js.map +1 -1
  17. package/dist/esm/version.js +1 -1
  18. package/dist/tsconfig.tsbuildinfo +1 -1
  19. package/dist/types/SignalRModule.d.ts +25 -0
  20. package/dist/types/SignalRModuleConfigurator.d.ts +91 -15
  21. package/dist/types/SignalRModuleProvider.d.ts +56 -3
  22. package/dist/types/index.d.ts +13 -0
  23. package/dist/types/lib/Topic.d.ts +40 -0
  24. package/dist/types/lib/utils/configure-from-framework.d.ts +1 -8
  25. package/dist/types/lib/utils/enable-signalr.d.ts +46 -24
  26. package/dist/types/version.d.ts +1 -1
  27. package/package.json +8 -7
  28. package/src/SignalRModule.ts +25 -0
  29. package/src/SignalRModuleConfigurator.ts +92 -15
  30. package/src/SignalRModuleProvider.ts +58 -4
  31. package/src/index.ts +14 -0
  32. package/src/lib/Topic.ts +40 -0
  33. package/src/lib/utils/configure-from-framework.ts +12 -0
  34. package/src/lib/utils/enable-signalr.ts +47 -24
  35. package/src/version.ts +1 -1
@@ -5,26 +5,79 @@ import type { SignalRConfig } from './SignalRModuleConfigurator';
5
5
 
6
6
  import { Topic } from './lib/Topic';
7
7
 
8
+ /**
9
+ * Public interface for the SignalR module provider.
10
+ *
11
+ * Use {@link ISignalRProvider.connect} to subscribe to a named hub method
12
+ * through an RxJS-based {@link Topic}. Connections are reference-counted
13
+ * and automatically torn down when all subscribers unsubscribe.
14
+ */
8
15
  export interface ISignalRProvider {
9
16
  /**
10
- * connect to SignalR hub
11
- * re-use existing connections and close connection when nobody is listening
17
+ * Connect to a SignalR hub method and return an observable {@link Topic}.
18
+ *
19
+ * Existing hub connections are reused (shared via `shareReplay` with
20
+ * `refCount`). When the last subscriber unsubscribes, the underlying
21
+ * `HubConnection` is stopped automatically.
22
+ *
23
+ * @template T - Type of messages received from the hub method
24
+ * @param hubId - Name of the hub as registered in the configurator
25
+ * @param methodName - Server-side method name to listen on
26
+ * @returns A {@link Topic} observable that emits messages from the hub method
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * const provider = modules.signalR;
31
+ * const topic = provider.connect<MyMessage>('notifications', 'OnNewMessage');
32
+ * topic.subscribe((msg) => console.log('Received:', msg));
33
+ * ```
12
34
  */
13
- connect<T>(ubId: string, methodName: string): Topic<T>;
35
+ connect<T>(hubId: string, methodName: string): Topic<T>;
14
36
  }
15
37
 
38
+ /**
39
+ * Default {@link ISignalRProvider} implementation.
40
+ *
41
+ * Creates and manages `@microsoft/signalr` `HubConnection` instances based on
42
+ * the resolved {@link SignalRConfig}. Hub connections are lazily created on
43
+ * first subscription and shared across all callers via `shareReplay`.
44
+ */
16
45
  export class SignalRModuleProvider implements ISignalRProvider {
17
46
  #config: SignalRConfig;
18
47
  #hubConnections: Record<string, Observable<HubConnection>> = {};
19
48
 
49
+ /**
50
+ * @param config - Resolved SignalR configuration containing all registered hubs
51
+ */
20
52
  constructor(config: SignalRConfig) {
21
53
  this.#config = config;
22
54
  }
23
55
 
56
+ /**
57
+ * Connect to a named hub method and return an observable {@link Topic}.
58
+ *
59
+ * @template T - Type of messages received from the hub method
60
+ * @param hubId - Name of the hub as registered in the configurator
61
+ * @param methodName - Server-side method name to listen on
62
+ * @returns A {@link Topic} observable emitting hub messages
63
+ * @throws {Error} When no hub configuration exists for `hubId`
64
+ */
24
65
  public connect<T>(hubId: string, methodName: string): Topic<T> {
25
66
  return new Topic<T>(methodName, this._createHubConnection(hubId));
26
67
  }
27
68
 
69
+ /**
70
+ * Create or retrieve a shared `HubConnection` observable for the given hub.
71
+ *
72
+ * The connection is lazily built using `HubConnectionBuilder` and shared
73
+ * with `shareReplay({ bufferSize: 1, refCount: true })` so that:
74
+ * - New subscribers immediately receive the current connection.
75
+ * - The connection is stopped when the last subscriber unsubscribes.
76
+ *
77
+ * @param hubId - Name of the hub as registered in the configurator
78
+ * @returns Observable that emits the active `HubConnection`
79
+ * @throws {Error} When no hub configuration exists for `hubId`
80
+ */
28
81
  protected _createHubConnection(hubId: string): Observable<HubConnection> {
29
82
  const LOG_LEVEL_CRITICAL = 5;
30
83
 
@@ -54,12 +107,13 @@ export class SignalRModuleProvider implements ISignalRProvider {
54
107
  })
55
108
  .catch((error: unknown) => {
56
109
  if (error instanceof AbortError) {
57
- // Omit AbortError
110
+ // AbortError is expected during teardown — safe to ignore
58
111
  } else {
59
112
  throw error;
60
113
  }
61
114
  });
62
115
 
116
+ // Stop the connection and clean up the cache entry on unsubscribe
63
117
  const teardown = () => {
64
118
  connection.stop();
65
119
  observer.complete();
package/src/index.ts CHANGED
@@ -1,3 +1,17 @@
1
+ /**
2
+ * @packageDocumentation
3
+ *
4
+ * Fusion Framework module for real-time communication via
5
+ * [SignalR](https://learn.microsoft.com/aspnet/core/signalr/introduction).
6
+ *
7
+ * Provides an RxJS-based API for connecting to SignalR hubs, subscribing to
8
+ * server-side methods, and sending messages. Hub connections are reference-counted
9
+ * and automatically stopped when no subscribers remain.
10
+ *
11
+ * @see {@link enableSignalR} for the quickest way to register the module.
12
+ * @see {@link ISignalRProvider.connect} for subscribing to hub methods at runtime.
13
+ */
14
+
1
15
  export {
2
16
  ISignalRConfigurator,
3
17
  SignalRConfigurator,
package/src/lib/Topic.ts CHANGED
@@ -1,9 +1,35 @@
1
1
  import type { HubConnection } from '@microsoft/signalr';
2
2
  import { Observable } from 'rxjs';
3
3
 
4
+ /**
5
+ * RxJS Observable wrapper around a SignalR hub method.
6
+ *
7
+ * A `Topic` subscribes to a named method on a `HubConnection` and emits
8
+ * incoming messages as observable values. It also exposes {@link Topic.send}
9
+ * and {@link Topic.invoke} for sending messages back to the server.
10
+ *
11
+ * Created by {@link SignalRModuleProvider.connect} — consumers typically
12
+ * do not instantiate `Topic` directly.
13
+ *
14
+ * @template T - Type of messages received from the hub method
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * const topic = provider.connect<ChatMessage>('chat', 'ReceiveMessage');
19
+ * topic.subscribe((msg) => console.log(msg.text));
20
+ *
21
+ * // Send a message to the server on the same method
22
+ * topic.send('Hello, world!');
23
+ * ```
24
+ */
4
25
  export class Topic<T> extends Observable<T> {
26
+ /** The active hub connection, set once the connection observable emits. */
5
27
  connection: HubConnection | undefined;
6
28
 
29
+ /**
30
+ * @param topic - Server-side method name to listen on and send to
31
+ * @param hubConnection - Observable that emits the active `HubConnection`
32
+ */
7
33
  constructor(
8
34
  public topic: string,
9
35
  public hubConnection: Observable<HubConnection>,
@@ -21,6 +47,12 @@ export class Topic<T> extends Observable<T> {
21
47
  });
22
48
  }
23
49
 
50
+ /**
51
+ * Send a fire-and-forget message to the server on this topic.
52
+ *
53
+ * @param args - Arguments forwarded to `HubConnection.send()`
54
+ * @throws {Error} When the hub connection has not been established yet
55
+ */
24
56
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
57
  public send(...args: any[]): void {
26
58
  if (!this.connection) {
@@ -29,6 +61,14 @@ export class Topic<T> extends Observable<T> {
29
61
  this.connection.send(this.topic, args);
30
62
  }
31
63
 
64
+ /**
65
+ * Invoke a server method on this topic and wait for a response.
66
+ *
67
+ * @template T - Expected return type from the server method
68
+ * @param args - Arguments forwarded to `HubConnection.invoke()`
69
+ * @returns Promise resolving with the server's response
70
+ * @throws {Error} When the hub connection has not been established yet
71
+ */
32
72
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
73
  public invoke<T>(...args: any[]): Promise<T> {
34
74
  if (!this.connection) {
@@ -3,6 +3,18 @@ import type { ServiceDiscoveryModule } from '@equinor/fusion-framework-module-se
3
3
 
4
4
  import type { SignalRModuleConfigBuilder } from '../../SignalRModuleConfigurator';
5
5
 
6
+ /**
7
+ * Configure a SignalR hub connection using Fusion Framework service-discovery
8
+ * and MSAL authentication.
9
+ *
10
+ * Resolves the hub endpoint URL from the service registry and creates an
11
+ * `accessTokenFactory` that acquires tokens via the MSAL auth provider.
12
+ *
13
+ * @param args - Hub name, service identifier, and path to append to the resolved service URI
14
+ * @param builder - Module config builder with access to MSAL and service-discovery instances
15
+ *
16
+ * @internal
17
+ */
6
18
  export const configureFromFramework = async (
7
19
  args: { name: string; service: string; path: string },
8
20
  builder: SignalRModuleConfigBuilder<[MsalModule, ServiceDiscoveryModule]>,
@@ -3,13 +3,31 @@ import type { SignalRModuleConfigBuilderCallback } from '../../SignalRModuleConf
3
3
  import { module } from '../../SignalRModule';
4
4
  import { configureFromFramework } from './configure-from-framework';
5
5
 
6
+ /**
7
+ * Call-signature overloads for {@link enableSignalR}.
8
+ */
6
9
  export interface enableSignalR {
10
+ /**
11
+ * Enable SignalR with a custom configuration builder callback.
12
+ *
13
+ * @param configurator - The module configurator instance
14
+ * @param name - Hub name identifier
15
+ * @param cb - Builder callback for manual hub configuration
16
+ */
7
17
  (
8
18
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
19
  configurator: IModulesConfigurator<any, any>,
10
20
  name: string,
11
21
  cb: SignalRModuleConfigBuilderCallback,
12
22
  ): void;
23
+
24
+ /**
25
+ * Enable SignalR using service-discovery to resolve the hub URL automatically.
26
+ *
27
+ * @param configurator - The module configurator instance
28
+ * @param name - Hub name identifier
29
+ * @param options - Service name and path used to resolve the hub endpoint
30
+ */
13
31
  (
14
32
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
33
  configurator: IModulesConfigurator<any, any>,
@@ -19,31 +37,36 @@ export interface enableSignalR {
19
37
  }
20
38
 
21
39
  /**
40
+ * Register the SignalR module on a Fusion Framework configurator and add hub
41
+ * configuration in a single call.
42
+ *
43
+ * Accepts either a manual builder callback or a service-discovery shorthand.
44
+ * When the shorthand form is used, the hub URL and authentication token are
45
+ * resolved automatically via the service-discovery and MSAL modules.
46
+ *
47
+ * @param configurator - The module configurator to register the SignalR module on
48
+ * @param name - Hub name identifier (used as key in the hub registry)
49
+ * @param optionsOrCallback - A builder callback for custom configuration, or
50
+ * `{ service, path }` to resolve the hub endpoint through service-discovery
51
+ *
22
52
  * @example
23
- ```ts
24
- import {
25
- enableSignalR,
26
- configurePortalHub,
27
- configureNotificationHub
28
- } from '@equinor/fusion-framework-module-signalr';
29
-
30
- export const configure = (configurator) => {
31
- enableSignalR(configurator, {
32
- name: 'portal',
33
- service: 'portal',
34
- path: '/signalr/hubs/service-message'
35
- });
36
- enableSignalR(configurator, 'notifications');
37
- // custom
38
- enableSignalR(configurator, builder => {
39
- builder.addHub('myHub', {
40
- uri: 'https://foo.bar',
41
- path: '/my_messages'
42
- accessTokenFactory: () => makeToken(),
43
- })
44
- });
45
- }
46
- ```
53
+ * ```ts
54
+ * import { enableSignalR } from '@equinor/fusion-framework-module-signalr';
55
+ *
56
+ * // Using service-discovery shorthand
57
+ * enableSignalR(configurator, 'portal', {
58
+ * service: 'portal',
59
+ * path: '/signalr/hubs/service-message',
60
+ * });
61
+ *
62
+ * // Using a custom builder callback
63
+ * enableSignalR(configurator, 'custom', (builder) => {
64
+ * builder.addHub('custom', {
65
+ * url: 'https://my-service.example.com/hub',
66
+ * options: { accessTokenFactory: () => getToken() },
67
+ * });
68
+ * });
69
+ * ```
47
70
  */
48
71
  export function enableSignalR(
49
72
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '10.0.1';
2
+ export const version = '11.0.0';