UNPKG

@ricardojbd/ngrx-signals-broadcast-sync

Version:

Enables seamless synchronization of state between browser contexts

103 lines (99 loc) 4.48 kB
import { signalStoreFeature, withMethods, getState, patchState, withHooks } from '@ngrx/signals'; import { inject, PLATFORM_ID, effect } from '@angular/core'; import { isPlatformServer } from '@angular/common'; import isEqual from 'lodash.isequal'; const NOOP = () => null; const BroadcastSyncStub = { getBroadcastChannel: NOOP, broadcastState: NOOP, _requestBroadcastState: NOOP, _patchStateFromBroadcast: NOOP }; /** * Adds broadcast synchronization capabilities to a Signal Store. * * This function enables state synchronization between multiple clients using the `BroadcastChannel` API. * It can be invoked either with a broadcast channel name or with a configuration object (`Options`). * It allows clients to request, send, and patch the state across multiple contexts. * * @template Input The type of the store feature result, extending `SignalStoreFeatureResult`. * @param channelOrOptions Either the name of the broadcast channel or a configuration object. * If a string is provided, it is treated as the broadcast channel name. * If an `Options` object is provided, it allows further configuration of the broadcast behavior. * @returns A Signal Store feature that adds methods for broadcast synchronization. */ function withBroadcastSync(channelOrOptions) { const { channel, requestState = false, select = (state) => state, messageEventInterceptor = (event) => event.data, broadcastStateInterceptor = (state) => state, onMessageError = () => null } = typeof channelOrOptions === 'string' ? { channel: channelOrOptions } : channelOrOptions; const runGuard = (platformId) => isPlatformServer(platformId) || typeof window?.BroadcastChannel === 'undefined'; let stateChannel; let lastSyncedState = null; return signalStoreFeature(withMethods((store, platformId = inject(PLATFORM_ID)) => { if (runGuard(platformId)) { console.warn(`'withBroadcastSync' provides non-functional implementation due to server-side execution`); return BroadcastSyncStub; } return { getBroadcastChannel() { return stateChannel; }, broadcastState() { let state = select(getState(store)); if (isEqual(state, lastSyncedState)) { return; } state = broadcastStateInterceptor(state); if (stateChannel != null) { lastSyncedState = state; // Avoid broadcast duplicated state stateChannel.postMessage({ type: 'UPDATE_STATE', state }); } }, _requestBroadcastState() { lastSyncedState = select(getState(store)); // Avoid broadcast initial state stateChannel?.postMessage({ type: 'REQUEST_STATE' }); }, _patchStateFromBroadcast(state) { lastSyncedState = state; // Avoid broadcast this state change patchState(store, state); } }; }), withHooks({ onInit(store, platformId = inject(PLATFORM_ID)) { if (runGuard(platformId)) { return; } stateChannel = new BroadcastChannel(channel); if (requestState) { store._requestBroadcastState(); } stateChannel.onmessage = (event) => { const message = messageEventInterceptor(event); switch (message.type) { case 'REQUEST_STATE': { lastSyncedState = null; // Force broadcast state store.broadcastState(); break; } case 'UPDATE_STATE': { store._patchStateFromBroadcast(message.state); break; } default: { console.warn(`Unknown message type "${message}" in the broadcast channel "${channel}"`); } } }; stateChannel.onmessageerror = (event) => onMessageError(event); effect(() => { store.broadcastState(); }); }, onDestroy() { stateChannel?.close(); } })); } /** * Generated bundle index. Do not edit. */ export { withBroadcastSync }; //# sourceMappingURL=ricardojbd-ngrx-signals-broadcast-sync.mjs.map