@memberjunction/ng-explorer-service-worker
Service worker app-shell pre-cache + update-notification toast for MJ Explorer-style apps.
What this package gives you
Section titled “What this package gives you”ngsw-config.jsonshipped at the package root — pre-tuned cache strategy (app-shell, lazy assets, GraphQL exclusions) that updates withnpm update.MJServiceWorkerModule.forRoot({ enabled })— wrapsServiceWorkerModule.register(...)with sensible defaults (deferred registration so the SW doesn’t fight initial boot for bandwidth).UpdateNotificationService— RxJS-friendly wrapper aroundSwUpdate.<mj-update-notification>— standalone toast component, MJ design tokens, OnPush.
If you’re using @memberjunction/ng-explorer-app, MJExplorerAppModule.forRoot(environment) already wires this up internally. You only need to touch your shell directly if you’re building a non-Explorer Angular app.
Consumer setup
Section titled “Consumer setup”For a typical MJ Explorer-style app, you only edit two files:
1. angular.json — point the SW config at our shipped file
Section titled “1. angular.json — point the SW config at our shipped file”{ "projects": { "your-app": { "architect": { "build": { "configurations": { "production": { "serviceWorker": "node_modules/@memberjunction/ng-explorer-service-worker/ngsw-config.json" } } } } } }}2. environment.ts — opt-in kill switch
Section titled “2. environment.ts — opt-in kill switch”export const environment = { production: true, enableServiceWorker: true, // master kill switch — turn off in an emergency // ...};That’s it. MJExplorerAppModule.forRoot(environment) reads environment.enableServiceWorker and turns the SW on when (and only when) production && enableServiceWorker are both true.
If you skip the angular.json edit, no ngsw-worker.js is built and the runtime registration silently no-ops. App behaves exactly as it does today.
Optional consumer enhancements
Section titled “Optional consumer enhancements”These are recommended but not required. The SW works fine without them — they’re polish items that depend on consumer-side files we can’t reach from this npm package.
Backport for older versions: inline theme preload script
Section titled “Backport for older versions: inline theme preload script”MJExplorer ships this by default starting with the same release as this package — new installs get instant correct theming for free, no action required.
If you’re maintaining an older MJExplorer-based app (or a non-Explorer app that uses MJ’s theme system) and want the same benefit, add the snippet below to your app’s src/index.html inside <head>, anywhere before any stylesheets. We can’t inject HTML into your index.html from an npm package, so this lives on the consumer side.
Without this, dark-mode users see a brief light-mode flash on every load before MJ’s bootstrap code applies their saved theme. The Angular bootstrap path is too late to set the theme before first paint — the script must be inline in <head>.
<script> (function () { try { var saved = localStorage.getItem('mj-theme'); var isDark = saved ? saved === 'dark' : window.matchMedia('(prefers-color-scheme: dark)').matches; if (isDark) document.documentElement.setAttribute('data-theme', 'dark'); } catch (e) { /* swallow — theme just falls back to light */ } })();</script>Reads mj-theme, the unified theme key written by:
ThemeService.applyBaseThemeAttribute()post-login — mirrors the resolved base theme (lightordark), including the OS-resolved value for'system'preferenceMJExplorerAppComponent.ToggleTheme()when the user toggles theme on the login screen
The three readers (this inline script, MJExplorerAppComponent.applyLoginTheme, ThemeService) all share mj-theme as the single source of truth, so they paint in sync with no flash.
mj-theme is preserved across logout by MJExplorerAuthBase.preservedLocalStorageKeys so the login screen continues to show the user’s last theme without flashing back to OS default.
Falls back to OS prefers-color-scheme on true first visits with no saved preference. Wrapped in try/catch so localStorage exceptions (Safari private mode, disabled storage) can never block app load.
Combined with the SW app-shell pre-cache, dark-mode users see correct theme paint within ~100ms of navigation start.
Backport for older versions: build-time SW cache-shape gate
Section titled “Backport for older versions: build-time SW cache-shape gate”MJExplorer ships this by default starting with the same release as this package — new installs get the build-time defense automatically.
If you’re maintaining an older MJExplorer-based app and want the same safety net, add the snippet below to your package.json. The gate guards against future Angular releases that introduce new build-output filename prefixes (runtime-*, vendor-*, etc.) that our ngsw-config.json doesn’t classify. Without it, such a change would silently bypass the SW cache for those new files. The gate fails the build with a diagnostic listing the unrecognized filenames so the issue is caught at your build time, not in production.
Add to your app’s package.json scripts:
{ "scripts": { "postbuild": "node node_modules/@memberjunction/ng-explorer-service-worker/scripts/verify-sw-cache-shape.js dist/<your-app-name>/browser" }}Replace <your-app-name> with whatever Angular emits to (check your angular.json outputPath).
The script exits 0 when every root-level .js and .css file matches a known prefix (main-, polyfills-, chunk-, styles-) and ignores SW infrastructure files (ngsw-worker.js, ngsw.json, safety-worker.js, worker-basic.min.js). Exits 1 with a clear diagnostic on anything unrecognized.
Without this opt-in, the regression tests inside the SW package (ngsw-config-shape.test.ts, verify-sw-cache-shape.test.ts) still catch config-edit regressions at MJ-monorepo PR review time — but only consumer build-time invocation can catch downstream Angular naming changes.
Manual wiring (non-Explorer apps)
Section titled “Manual wiring (non-Explorer apps)”If you’re not using MJExplorerAppModule, wire it yourself:
import { MJServiceWorkerModule } from '@memberjunction/ng-explorer-service-worker';
@NgModule({ imports: [ MJServiceWorkerModule.forRoot({ enabled: environment.production && environment.enableServiceWorker }) ]})export class AppModule {}And drop the toast somewhere in your shell template:
<mj-update-notification />How update notifications work
Section titled “How update notifications work”- SW polls for new versions in the background (Angular default cadence).
- When a new version is fully downloaded,
SwUpdate.versionUpdatesemitsVERSION_READY. UpdateNotificationServiceflipsupdateAvailable$totrue.<mj-update-notification>renders a bottom-right toast with Reload / Later.- Reload triggers
document.location.reload(), which activates the new SW version. - Later dismisses for the session; next reload picks up the update naturally.
Customization
Section titled “Customization”To override the cache strategy for your app, copy ngsw-config.json into your repo and point angular.json at the local copy. You’ll lose automatic updates to the cache rules but can tune freely.
Ops runbook
Section titled “Ops runbook”See docs/operations/SERVICE_WORKER_RUNBOOK.md in the MJ repo for kill-switch procedures, cache invalidation, and rollback playbook.