UNPKG

@adaskothebeast/ngxs-signalr-plugin

Version:
391 lines (371 loc) 13.2 kB
import { CommonModule } from '@angular/common'; import * as i0 from '@angular/core'; import { InjectionToken, Injectable, Inject, provideAppInitializer, inject, NgModule } from '@angular/core'; import { HubConnectionBuilder, HubConnectionState, HttpTransportType, JsonHubProtocol } from '@microsoft/signalr'; import * as i1 from '@ngxs/store'; import { ofActionDispatched } from '@ngxs/store'; import { Subscription } from 'rxjs/internal/Subscription'; /** * Action to connect to the websocket. Optionally pass a URL. */ class ConnectSignalR { static { this.type = '[SignalR] Connect'; } constructor(payload) { this.payload = payload; } } /** * Action to disconnect the SignalR. */ class DisconnectSignalR { static { this.type = '[SignalR] Disconnect'; } } /** * Action to invoke to the server. */ class InvokeSignalRMessage { static { this.type = '[SignalR] Invoke Message'; } constructor(name, payload) { this.name = name; this.payload = payload; } } /** * Action to send to the server. */ class SendSignalRMessage { static { this.type = '[SignalR] Send Message'; } constructor(name, payload) { this.name = name; this.payload = payload; } } /** * Action triggered when SignalR is connected */ class SignalRConnected { static { this.type = '[SignalR] Connected'; } } /** * Action dispatched when the user tries to connect if the connection already exists. */ class SignalRConnectionUpdated { static { this.type = '[SignalR] Connection Updated'; } } /** * Action triggered when SignalR is disconnected */ class SignalRDisconnected { static { this.type = '[SignalR] Disconnected'; } constructor(payload) { this.payload = payload; } } /** * Action triggered when a error ocurrs */ class SignalRMessageError { static { this.type = '[SignalR] Message Error'; } constructor(payload) { this.payload = payload; } } /** * Action triggered when SignalR is connected */ class SignalRReconnected { static { this.type = '[SignalR] Reconnected'; } constructor(connectionId) { this.connectionId = connectionId; } } /** * Action triggered when SignalR is connected */ class SignalRReconnecting { static { this.type = '[SignalR] Reconnecting'; } constructor(payload) { this.payload = payload; } } /** * Action triggered when SignalR is connected */ class SignalRStreamCompleted { static { this.type = '[SignalR] Stream Completed'; } } /** * Action triggered when a error ocurrs */ class SignalRStreamError { static { this.type = '[SignalR] Stream Error'; } constructor(payload) { this.payload = payload; } } /** * Action to stream from the server. */ class StreamSignalRMessage { static { this.type = '[SignalR] Stream Message'; } constructor(name, payload) { this.name = name; this.payload = payload; } } const NGXS_SIGNALR_OPTIONS = new InjectionToken('NGXS_SIGNALR_OPTIONS'); // eslint-disable-next-line @typescript-eslint/no-unused-vars function noop(..._args) { return function () { // noop }; } /** * This error is thrown where there is no `type` (or custom `typeKey`) property * on the message that came from the server side socket */ class TypeKeyPropertyMissingError extends Error { constructor(typeKey) { super(`Property ${typeKey} is missing on the socket message`); } } class SignalRHandler { constructor(store, actions$, options) { this.store = store; this.actions$ = actions$; this.options = options; this.connection = null; this.subscription = new Subscription(); this.streamSubscriptions = []; this.typeKey = 'type'; this.setupActionsListeners(); if (this.options.typeKey) { this.typeKey = this.options.typeKey; } } ngOnDestroy() { this.closeConnection(); this.subscription.unsubscribe(); } setupActionsListeners() { this.subscription.add(this.actions$ .pipe(ofActionDispatched(ConnectSignalR)) .subscribe(async ({ payload }) => { await this.connect(payload); })); this.subscription.add(this.actions$ .pipe(ofActionDispatched(DisconnectSignalR)) .subscribe(() => { this.disconnect(); })); this.subscription.add(this.actions$ .pipe(ofActionDispatched(SendSignalRMessage)) .subscribe(async ({ name, payload }) => { await this.send(name, payload); })); this.subscription.add(this.actions$ .pipe(ofActionDispatched(InvokeSignalRMessage)) .subscribe(async ({ name, payload }) => { await this.invoke(name, payload); })); this.subscription.add(this.actions$ .pipe(ofActionDispatched(StreamSignalRMessage)) .subscribe(async ({ name, payload }) => { const resultStream = await this.stream(name, payload); this.streamSubscriptions.push(resultStream.subscribe({ next: (item) => { const type = item[this.typeKey]; if (!type) { throw new TypeKeyPropertyMissingError(this.typeKey); } this.store.dispatch({ ...item, type }); }, complete: () => { this.store.dispatch(new SignalRStreamCompleted()); }, error: (err) => { this.store.dispatch(new SignalRStreamError(err)); }, })); })); } setupConnection() { let builder = new HubConnectionBuilder(); if (this.options.url) { if (this.options.httpConnectionOptions) { builder = builder.withUrl(this.options.url, this.options.httpConnectionOptions); } else if (this.options.transportType) { builder = builder.withUrl(this.options.url, this.options.transportType); } else { builder = builder.withUrl(this.options.url); } } if (this.options.reconnectPolicy) { builder = builder.withAutomaticReconnect(this.options.reconnectPolicy); } else if (this.options.retryDelays) { builder = builder.withAutomaticReconnect(this.options.retryDelays); } else if (this.options.automaticReconnect) { builder = builder.withAutomaticReconnect(); } if (this.options.protocol) { builder = builder.withHubProtocol(this.options.protocol); } if (this.options.logging) { builder = builder.configureLogging(this.options.logging); } this.connection = builder.build(); if (this.options.baseUrl) { this.connection.baseUrl = this.options.baseUrl; } if (this.options.keepAliveIntervalInMilliseconds) { this.connection.keepAliveIntervalInMilliseconds = this.options.keepAliveIntervalInMilliseconds; } if (this.options.serverTimeoutInMilliseconds) { this.connection.serverTimeoutInMilliseconds = this.options.serverTimeoutInMilliseconds; } this.connection.onclose((error) => { this.store.dispatch(new SignalRDisconnected(error)); }); this.connection.onreconnecting((error) => { this.store.dispatch(new SignalRReconnecting(error)); }); this.connection.onreconnected((connectionId) => { this.store.dispatch(new SignalRReconnected(connectionId)); }); } setupHandlers() { this.connection?.on('send', (message) => { const type = message[this.typeKey]; if (!type) { throw new TypeKeyPropertyMissingError(this.typeKey); } this.store.dispatch({ ...message, type }); }); } async connect(options) { this.updateConnection(); // Users can pass the options in the connect method so // if options aren't available at DI bootstrap they have access // to pass them here if (options) { this.options = { ...this.options, ...options }; } this.setupConnection(); this.setupHandlers(); try { await this.connection?.start(); this.store.dispatch(new SignalRConnected()); } catch (err) { this.store.dispatch(new SignalRMessageError(err)); } } disconnect() { if (this.connection) { this.closeConnection(); } } async send(name, data) { if (this.connection?.state !== HubConnectionState.Connected) { throw new Error('You must connect to the SignalR before sending any data'); } await this.connection.send(name, data); } async invoke(name, data) { if (this.connection?.state !== HubConnectionState.Connected) { throw new Error('You must connect to the SignalR before sending any data'); } await this.connection.invoke(name, data); } async stream(name, data) { if (this.connection?.state !== HubConnectionState.Connected) { throw new Error('You must connect to the SignalR before sending any data'); } return this.connection.stream(name, data); } /** * To ensure we don't have any memory leaks * e.g. if the user occasionally dispatched `ConnectWebSocket` twice * then the previous subscription will still live in the memory * to prevent such behavior - we close the previous connection if it exists */ updateConnection() { if (this.connection) { this.closeConnection(); this.store.dispatch(new SignalRConnectionUpdated()); } } async closeConnection() { this.streamSubscriptions.forEach((s) => { s.dispose(); }); this.streamSubscriptions = []; if (this.connection !== null) { await this.connection.stop(); this.connection = null; } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: SignalRHandler, deps: [{ token: i1.Store }, { token: i1.Actions }, { token: NGXS_SIGNALR_OPTIONS }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: SignalRHandler }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: SignalRHandler, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: i1.Store }, { type: i1.Actions }, { type: undefined, decorators: [{ type: Inject, args: [NGXS_SIGNALR_OPTIONS] }] }] }); function signalrOptionsFactory(options) { return { keepAliveIntervalInMilliseconds: 15000, serverTimeoutInMilliseconds: 30000, typeKey: 'type', automaticReconnect: true, transportType: HttpTransportType.WebSockets, protocol: new JsonHubProtocol(), ...options, }; } const NGXS_SIGNALR_USER_OPTIONS = new InjectionToken('NGXS_SIGNALR_USER_OPTIONS'); class NgxsSignalrPluginModule { static forRoot(options) { return { ngModule: NgxsSignalrPluginModule, providers: [ SignalRHandler, { provide: NGXS_SIGNALR_USER_OPTIONS, useValue: options, }, { provide: NGXS_SIGNALR_OPTIONS, useFactory: signalrOptionsFactory, deps: [NGXS_SIGNALR_USER_OPTIONS], }, provideAppInitializer(() => { const initializerFn = (noop)(inject(SignalRHandler)); return initializerFn(); }), ], }; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: NgxsSignalrPluginModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.0.5", ngImport: i0, type: NgxsSignalrPluginModule, imports: [CommonModule] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: NgxsSignalrPluginModule, imports: [CommonModule] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: NgxsSignalrPluginModule, decorators: [{ type: NgModule, args: [{ imports: [CommonModule], }] }] }); /** * Generated bundle index. Do not edit. */ export { NGXS_SIGNALR_USER_OPTIONS, NgxsSignalrPluginModule, signalrOptionsFactory }; //# sourceMappingURL=adaskothebeast-ngxs-signalr-plugin.mjs.map