@memberjunction/ng-explorer-service-worker 6.2.0-edge.0 → 6.2.0-edge.2
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/dist/lib/mj-service-worker.module.d.ts +2 -0
- package/dist/lib/mj-service-worker.module.d.ts.map +1 -1
- package/dist/lib/mj-service-worker.module.js +5 -1
- package/dist/lib/mj-service-worker.module.js.map +1 -1
- package/dist/lib/update-notification.component.d.ts +7 -1
- package/dist/lib/update-notification.component.d.ts.map +1 -1
- package/dist/lib/update-notification.component.js +15 -3
- package/dist/lib/update-notification.component.js.map +1 -1
- package/dist/lib/update-notification.service.d.ts +16 -0
- package/dist/lib/update-notification.service.d.ts.map +1 -1
- package/dist/lib/update-notification.service.js +44 -12
- package/dist/lib/update-notification.service.js.map +1 -1
- package/package.json +1 -1
|
@@ -60,6 +60,8 @@ export interface MJServiceWorkerOptions {
|
|
|
60
60
|
* The app keeps working exactly as if the SW didn't exist.
|
|
61
61
|
*/
|
|
62
62
|
export declare class MJServiceWorkerModule {
|
|
63
|
+
static ForRoot(options: MJServiceWorkerOptions): ModuleWithProviders<MJServiceWorkerModule>;
|
|
64
|
+
/** @deprecated Use {@link ForRoot}. */
|
|
63
65
|
static forRoot(options: MJServiceWorkerOptions): ModuleWithProviders<MJServiceWorkerModule>;
|
|
64
66
|
static ɵfac: i0.ɵɵFactoryDeclaration<MJServiceWorkerModule, never>;
|
|
65
67
|
static ɵmod: i0.ɵɵNgModuleDeclaration<MJServiceWorkerModule, never, [typeof i1.UpdateNotificationComponent], [typeof i1.UpdateNotificationComponent]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mj-service-worker.module.d.ts","sourceRoot":"","sources":["../../src/lib/mj-service-worker.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,mBAAmB,EAAE,MAAM,eAAe,CAAC;;;AAI9D;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACnC;;;;;;;OAOG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B;;;;;;;;;;OAUG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAIa,qBAAqB;IAC9B,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,sBAAsB,GAAG,mBAAmB,CAAC,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"mj-service-worker.module.d.ts","sourceRoot":"","sources":["../../src/lib/mj-service-worker.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,mBAAmB,EAAE,MAAM,eAAe,CAAC;;;AAI9D;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACnC;;;;;;;OAOG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B;;;;;;;;;;OAUG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAIa,qBAAqB;IAC9B,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,sBAAsB,GAAG,mBAAmB,CAAC,qBAAqB,CAAC;IAmB3F,uCAAuC;IACvC,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,sBAAsB,GAAG,mBAAmB,CAAC,qBAAqB,CAAC;yCArBlF,qBAAqB;0CAArB,qBAAqB;0CAArB,qBAAqB;CAwBjC"}
|
|
@@ -22,7 +22,7 @@ import * as i0 from "@angular/core";
|
|
|
22
22
|
* The app keeps working exactly as if the SW didn't exist.
|
|
23
23
|
*/
|
|
24
24
|
export class MJServiceWorkerModule {
|
|
25
|
-
static
|
|
25
|
+
static ForRoot(options) {
|
|
26
26
|
// ServiceWorkerModule.register is always called — the `enabled` flag
|
|
27
27
|
// tells Angular whether to actually attempt registration. We pass the
|
|
28
28
|
// module reference into the providers chain so the SwUpdate token tree
|
|
@@ -39,6 +39,10 @@ export class MJServiceWorkerModule {
|
|
|
39
39
|
]
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
|
+
/** @deprecated Use {@link ForRoot}. */
|
|
43
|
+
static forRoot(options) {
|
|
44
|
+
return this.ForRoot(options);
|
|
45
|
+
}
|
|
42
46
|
static ɵfac = function MJServiceWorkerModule_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MJServiceWorkerModule)(); };
|
|
43
47
|
static ɵmod = /*@__PURE__*/ i0.ɵɵdefineNgModule({ type: MJServiceWorkerModule });
|
|
44
48
|
static ɵinj = /*@__PURE__*/ i0.ɵɵdefineInjector({ imports: [UpdateNotificationComponent] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mj-service-worker.module.js","sourceRoot":"","sources":["../../src/lib/mj-service-worker.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAuB,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,2BAA2B,EAAE,MAAM,iCAAiC,CAAC;;AA6C9E;;;;;;;;;;;;;;;;;;GAkBG;AAKH,MAAM,OAAO,qBAAqB;IAC9B,MAAM,CAAC,OAAO,CAAC,OAA+B;QAC1C,qEAAqE;QACrE,sEAAsE;QACtE,uEAAuE;QACvE,sEAAsE;QACtE,oCAAoC;QACpC,MAAM,QAAQ,GAAG,mBAAmB,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,IAAI,gBAAgB,EAAE;YAClF,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,oBAAoB,EAAE,OAAO,CAAC,oBAAoB,IAAI,0BAA0B;SACnF,CAAC,CAAC;QAEH,OAAO;YACH,QAAQ,EAAE,qBAAqB;YAC/B,SAAS,EAAE;gBACP,GAAG,CAAC,QAAQ,CAAC,SAAS,IAAI,EAAE,CAAC;aAChC;SACJ,CAAC;IACN,CAAC;+
|
|
1
|
+
{"version":3,"file":"mj-service-worker.module.js","sourceRoot":"","sources":["../../src/lib/mj-service-worker.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAuB,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,2BAA2B,EAAE,MAAM,iCAAiC,CAAC;;AA6C9E;;;;;;;;;;;;;;;;;;GAkBG;AAKH,MAAM,OAAO,qBAAqB;IAC9B,MAAM,CAAC,OAAO,CAAC,OAA+B;QAC1C,qEAAqE;QACrE,sEAAsE;QACtE,uEAAuE;QACvE,sEAAsE;QACtE,oCAAoC;QACpC,MAAM,QAAQ,GAAG,mBAAmB,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,IAAI,gBAAgB,EAAE;YAClF,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,oBAAoB,EAAE,OAAO,CAAC,oBAAoB,IAAI,0BAA0B;SACnF,CAAC,CAAC;QAEH,OAAO;YACH,QAAQ,EAAE,qBAAqB;YAC/B,SAAS,EAAE;gBACP,GAAG,CAAC,QAAQ,CAAC,SAAS,IAAI,EAAE,CAAC;aAChC;SACJ,CAAC;IACN,CAAC;IAED,uCAAuC;IACvC,MAAM,CAAC,OAAO,CAAC,OAA+B;QAC1C,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC;+GAvBQ,qBAAqB;4DAArB,qBAAqB;gEAHpB,2BAA2B;;iFAG5B,qBAAqB;cAJjC,QAAQ;eAAC;gBACN,OAAO,EAAE,CAAC,2BAA2B,CAAC;gBACtC,OAAO,EAAE,CAAC,2BAA2B,CAAC;aACzC;;wFACY,qBAAqB,cAHpB,2BAA2B,aAC3B,2BAA2B","sourcesContent":["import { NgModule, ModuleWithProviders } from '@angular/core';\nimport { ServiceWorkerModule } from '@angular/service-worker';\nimport { UpdateNotificationComponent } from './update-notification.component';\n\n/**\n * Options for configuring `MJServiceWorkerModule.forRoot()`.\n */\nexport interface MJServiceWorkerOptions {\n /**\n * Master enable flag — when `false`, no service worker is registered and\n * `SwUpdate.isEnabled` resolves to `false`. The toast component is always\n * exported, but it renders nothing when SW is disabled.\n *\n * Wire this to something like `environment.production && environment.enableServiceWorker`\n * so dev builds and ops kill-switches both turn it off cleanly.\n */\n enabled: boolean;\n\n /**\n * The path to the generated SW worker script, relative to the deployed\n * app root. Defaults to `'ngsw-worker.js'` (the Angular CLI default).\n * Override only if you've customized the build output.\n */\n scriptPath?: string;\n\n /**\n * Registration strategy passed to `ServiceWorkerModule.register`.\n * Defaults to `'registerWhenStable:30000'` — defer registration until\n * the app is idle for 30s OR 30s have elapsed, whichever first. Avoids\n * competing with initial app boot for network bandwidth.\n */\n registrationStrategy?: string;\n\n /**\n * How often (in ms) UpdateNotificationService should ask the SW to check\n * for a new manifest while the tab is visible. Defaults to 15 minutes.\n * Pass `0` to disable periodic checking entirely (the SW's internal poll\n * still runs, but on a much longer cadence — typically hours).\n *\n * Note: this is wired by `UpdateNotificationService` itself in its\n * constructor; we don't currently provide a DI token for it. To override\n * at runtime, inject `UpdateNotificationService` and call\n * `service.startAutoCheck(yourIntervalMs)`.\n */\n pollIntervalMs?: number;\n}\n\n/**\n * Bundles MJ Explorer's service worker concerns into one module:\n * - Conditionally registers `@angular/service-worker` based on `enabled`\n * - Exports `UpdateNotificationComponent` (standalone toast)\n * - Exposes `UpdateNotificationService` via `providedIn: 'root'`\n *\n * Consumers should call `MJServiceWorkerModule.forRoot({ enabled: ... })`\n * exactly once in their root NgModule. Typically this is done internally by\n * `MJExplorerAppModule.forRoot(environment)` so MJExplorer (and downstream\n * Explorer-style apps) don't have to wire it themselves.\n *\n * Pre-built `ngsw-config.json` ships at the package root; consumers point\n * their `angular.json` `serviceWorker` field at:\n * `node_modules/@memberjunction/ng-explorer-service-worker/ngsw-config.json`\n *\n * If a consumer doesn't update `angular.json` (no SW worker built) AND\n * doesn't flip `enabled` true in their environment, this module is a no-op.\n * The app keeps working exactly as if the SW didn't exist.\n */\n@NgModule({\n imports: [UpdateNotificationComponent],\n exports: [UpdateNotificationComponent]\n})\nexport class MJServiceWorkerModule {\n static ForRoot(options: MJServiceWorkerOptions): ModuleWithProviders<MJServiceWorkerModule> {\n // ServiceWorkerModule.register is always called — the `enabled` flag\n // tells Angular whether to actually attempt registration. We pass the\n // module reference into the providers chain so the SwUpdate token tree\n // is set up regardless (so injection always succeeds; it just reports\n // isEnabled = false when disabled).\n const swModule = ServiceWorkerModule.register(options.scriptPath ?? 'ngsw-worker.js', {\n enabled: options.enabled,\n registrationStrategy: options.registrationStrategy ?? 'registerWhenStable:30000'\n });\n\n return {\n ngModule: MJServiceWorkerModule,\n providers: [\n ...(swModule.providers ?? [])\n ]\n };\n }\n\n /** @deprecated Use {@link ForRoot}. */\n static forRoot(options: MJServiceWorkerOptions): ModuleWithProviders<MJServiceWorkerModule> {\n return this.ForRoot(options);\n }\n}\n"]}
|
|
@@ -12,8 +12,14 @@ import * as i0 from "@angular/core";
|
|
|
12
12
|
* + subtle pulse on the icon to draw attention without being aggressive.
|
|
13
13
|
*/
|
|
14
14
|
export declare class UpdateNotificationComponent {
|
|
15
|
-
readonly
|
|
15
|
+
readonly UpdateService: UpdateNotificationService;
|
|
16
|
+
/** @deprecated Use {@link UpdateService}. */
|
|
17
|
+
get updateService(): UpdateNotificationService;
|
|
18
|
+
OnReload(): void;
|
|
19
|
+
/** @deprecated Use {@link OnReload}. */
|
|
16
20
|
onReload(): void;
|
|
21
|
+
OnDismiss(): void;
|
|
22
|
+
/** @deprecated Use {@link OnDismiss}. */
|
|
17
23
|
onDismiss(): void;
|
|
18
24
|
static ɵfac: i0.ɵɵFactoryDeclaration<UpdateNotificationComponent, never>;
|
|
19
25
|
static ɵcmp: i0.ɵɵComponentDeclaration<UpdateNotificationComponent, "mj-update-notification", never, {}, {}, never, never, true, never>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update-notification.component.d.ts","sourceRoot":"","sources":["../../src/lib/update-notification.component.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;;AAE1E;;;;;;;;;;GAUG;AACH,qBA8Na,2BAA2B;IACpC,SAAgB,aAAa,4BAAqC;IAElE,QAAQ,IAAI,IAAI;IAIhB,SAAS,IAAI,IAAI;
|
|
1
|
+
{"version":3,"file":"update-notification.component.d.ts","sourceRoot":"","sources":["../../src/lib/update-notification.component.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;;AAE1E;;;;;;;;;;GAUG;AACH,qBA8Na,2BAA2B;IACpC,SAAgB,aAAa,4BAAqC;IAElE,6CAA6C;IAC7C,IAAW,aAAa,8BAEvB;IAED,QAAQ,IAAI,IAAI;IAIhB,wCAAwC;IACxC,QAAQ,IAAI,IAAI;IAIhB,SAAS,IAAI,IAAI;IAIjB,yCAAyC;IACzC,SAAS,IAAI,IAAI;yCAtBR,2BAA2B;2CAA3B,2BAA2B;CAyBvC"}
|
|
@@ -41,12 +41,24 @@ function UpdateNotificationComponent_Conditional_0_Template(rf, ctx) { if (rf &
|
|
|
41
41
|
* + subtle pulse on the icon to draw attention without being aggressive.
|
|
42
42
|
*/
|
|
43
43
|
export class UpdateNotificationComponent {
|
|
44
|
-
|
|
44
|
+
UpdateService = inject(UpdateNotificationService);
|
|
45
|
+
/** @deprecated Use {@link UpdateService}. */
|
|
46
|
+
get updateService() {
|
|
47
|
+
return this.UpdateService;
|
|
48
|
+
}
|
|
49
|
+
OnReload() {
|
|
50
|
+
this.UpdateService.applyUpdate();
|
|
51
|
+
}
|
|
52
|
+
/** @deprecated Use {@link OnReload}. */
|
|
45
53
|
onReload() {
|
|
46
|
-
this.
|
|
54
|
+
return this.OnReload();
|
|
55
|
+
}
|
|
56
|
+
OnDismiss() {
|
|
57
|
+
this.UpdateService.dismissForSession();
|
|
47
58
|
}
|
|
59
|
+
/** @deprecated Use {@link OnDismiss}. */
|
|
48
60
|
onDismiss() {
|
|
49
|
-
this.
|
|
61
|
+
return this.OnDismiss();
|
|
50
62
|
}
|
|
51
63
|
static ɵfac = function UpdateNotificationComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || UpdateNotificationComponent)(); };
|
|
52
64
|
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: UpdateNotificationComponent, selectors: [["mj-update-notification"]], decls: 2, vars: 3, consts: [["role", "status", "aria-live", "polite", 1, "mj-sw-update-toast"], ["aria-hidden", "true", 1, "mj-sw-update-toast__icon"], [1, "fa-solid", "fa-arrows-rotate"], [1, "mj-sw-update-toast__content"], [1, "mj-sw-update-toast__title"], [1, "mj-sw-update-toast__subtitle"], [1, "mj-sw-update-toast__actions"], ["type", "button", 1, "mj-sw-update-toast__btn", "mj-sw-update-toast__btn--primary", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-rotate-right"], ["type", "button", 1, "mj-sw-update-toast__btn", "mj-sw-update-toast__btn--secondary", 3, "click"], ["type", "button", "aria-label", "Dismiss", 1, "mj-sw-update-toast__close", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-xmark"]], template: function UpdateNotificationComponent_Template(rf, ctx) { if (rf & 1) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update-notification.component.js","sourceRoot":"","sources":["../../src/lib/update-notification.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;;;;;IAyB1D,AALJ,iCAIC,aAC4D;IACrD,0BAAyC;IAC7C,oBAAM;IAEF,AADJ,iCAAyC,aACE;IACnC,kDACJ;IAAA,oBAAM;IACN,iCAA0C;IACtC,mJACJ;IAAA,oBAAM;IAEF,AADJ,iCAAyC,gBAKpC;IADG,kMAAS,iBAAU,KAAC;IAEpB,2BAA2D;IAC3D,gCAAM;IAAA,2BAAU;IACpB,AADoB,oBAAO,EAClB;IACT,qCAIC;IADG,mMAAS,kBAAW,KAAC;IAErB,wBACJ;IAER,AADI,AADI,oBAAS,EACP,EACJ;IACN,sCAKC;IADG,mMAAS,kBAAW,KAAC;IAErB,4BAAoD;IAE5D,AADI,oBAAS,EACP;;AA3DlB;;;;;;;;;;GAUG;AA+NH,MAAM,OAAO,2BAA2B;IACpB,aAAa,GAAG,MAAM,CAAC,yBAAyB,CAAC,CAAC;IAElE,QAAQ;QACJ,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,CAAC;IACrC,CAAC;IAED,SAAS;QACL,IAAI,CAAC,aAAa,CAAC,iBAAiB,EAAE,CAAC;IAC3C,CAAC;qHATQ,2BAA2B;6DAA3B,2BAA2B;YAxNhC,8FAA8C;;;YAA9C,mFA2CC;4BA9CK,YAAY;;iFA2Nb,2BAA2B;cA9NvC,SAAS;6BACM,IAAI,YACN,wBAAwB,WACzB,CAAC,YAAY,CAAC,mBACN,uBAAuB,CAAC,MAAM,YACrC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA6CT;;kFA4KQ,2BAA2B","sourcesContent":["import { ChangeDetectionStrategy, Component, inject } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { UpdateNotificationService } from './update-notification.service';\n\n/**\n * Renders a small bottom-right toast when a new app version is available.\n * Two actions: \"Reload\" applies the update; \"Later\" dismisses for the session.\n *\n * Standalone component — no NgModule needed, drop into the shell template\n * with `<mj-update-notification></mj-update-notification>`.\n *\n * Visual design uses MJ design tokens (no hardcoded colors) so it picks up\n * dark mode + brand customization automatically. Slide-up entrance animation\n * + subtle pulse on the icon to draw attention without being aggressive.\n */\n@Component({\n standalone: true,\n selector: 'mj-update-notification',\n imports: [CommonModule],\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n @if (updateService.updateAvailable$ | async) {\n <div\n class=\"mj-sw-update-toast\"\n role=\"status\"\n aria-live=\"polite\"\n >\n <div class=\"mj-sw-update-toast__icon\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-arrows-rotate\"></i>\n </div>\n <div class=\"mj-sw-update-toast__content\">\n <div class=\"mj-sw-update-toast__title\">\n Update available (test build #7)\n </div>\n <div class=\"mj-sw-update-toast__subtitle\">\n A newer version of MemberJunction has been downloaded. Reload now to apply, or keep working and we'll prompt you again next time.\n </div>\n <div class=\"mj-sw-update-toast__actions\">\n <button\n type=\"button\"\n class=\"mj-sw-update-toast__btn mj-sw-update-toast__btn--primary\"\n (click)=\"onReload()\"\n >\n <i class=\"fa-solid fa-rotate-right\" aria-hidden=\"true\"></i>\n <span>Reload now</span>\n </button>\n <button\n type=\"button\"\n class=\"mj-sw-update-toast__btn mj-sw-update-toast__btn--secondary\"\n (click)=\"onDismiss()\"\n >\n Later\n </button>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"mj-sw-update-toast__close\"\n aria-label=\"Dismiss\"\n (click)=\"onDismiss()\"\n >\n <i class=\"fa-solid fa-xmark\" aria-hidden=\"true\"></i>\n </button>\n </div>\n }\n `,\n styles: [`\n :host {\n position: fixed;\n bottom: 24px;\n right: 24px;\n z-index: 1000;\n pointer-events: none;\n max-width: 100%;\n }\n\n @keyframes mj-sw-toast-in {\n from {\n opacity: 0;\n transform: translateY(16px) scale(0.98);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n }\n\n @keyframes mj-sw-toast-pulse {\n 0%, 100% {\n box-shadow: 0 0 0 0 color-mix(in srgb, var(--mj-brand-primary) 35%, transparent);\n }\n 50% {\n box-shadow: 0 0 0 8px color-mix(in srgb, var(--mj-brand-primary) 0%, transparent);\n }\n }\n\n .mj-sw-update-toast {\n display: flex;\n align-items: flex-start;\n gap: 14px;\n position: relative;\n background: var(--mj-bg-surface-elevated);\n color: var(--mj-text-primary);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n padding: 16px 18px 16px 16px;\n width: 380px;\n max-width: calc(100vw - 48px);\n box-shadow:\n 0 1px 2px color-mix(in srgb, var(--mj-text-primary) 6%, transparent),\n 0 12px 32px color-mix(in srgb, var(--mj-text-primary) 16%, transparent);\n pointer-events: auto;\n font-size: 13.5px;\n line-height: 1.45;\n animation: mj-sw-toast-in 220ms cubic-bezier(0.2, 0.9, 0.32, 1.15) both;\n }\n\n .mj-sw-update-toast__icon {\n flex: 0 0 auto;\n width: 36px;\n height: 36px;\n border-radius: 50%;\n display: grid;\n place-items: center;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 16px;\n animation: mj-sw-toast-pulse 2.4s ease-in-out 0.4s 2;\n }\n\n .mj-sw-update-toast__content {\n flex: 1 1 auto;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n\n .mj-sw-update-toast__title {\n color: var(--mj-text-primary);\n font-weight: 600;\n font-size: 14px;\n letter-spacing: -0.005em;\n }\n\n .mj-sw-update-toast__subtitle {\n color: var(--mj-text-secondary);\n font-size: 12.5px;\n line-height: 1.5;\n margin-bottom: 4px;\n }\n\n .mj-sw-update-toast__actions {\n display: flex;\n gap: 8px;\n margin-top: 8px;\n }\n\n .mj-sw-update-toast__btn {\n font: inherit;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n padding: 7px 14px;\n border-radius: 8px;\n border: 1px solid transparent;\n line-height: 1.2;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, transform 0.08s ease;\n }\n .mj-sw-update-toast__btn:active {\n transform: translateY(1px);\n }\n .mj-sw-update-toast__btn:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 2px;\n }\n\n .mj-sw-update-toast__btn--primary {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n }\n .mj-sw-update-toast__btn--primary:hover {\n background: var(--mj-brand-primary-hover);\n }\n .mj-sw-update-toast__btn--primary:active {\n background: var(--mj-brand-primary-active);\n }\n\n .mj-sw-update-toast__btn--secondary {\n background: transparent;\n border-color: var(--mj-border-default);\n color: var(--mj-text-secondary);\n }\n .mj-sw-update-toast__btn--secondary:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n border-color: var(--mj-border-strong);\n }\n\n .mj-sw-update-toast__close {\n position: absolute;\n top: 8px;\n right: 8px;\n width: 24px;\n height: 24px;\n border: none;\n background: transparent;\n color: var(--mj-text-muted);\n border-radius: 6px;\n cursor: pointer;\n display: grid;\n place-items: center;\n font-size: 12px;\n transition: background 0.15s ease, color 0.15s ease;\n }\n .mj-sw-update-toast__close:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n .mj-sw-update-toast__close:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 1px;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .mj-sw-update-toast {\n animation: none;\n }\n .mj-sw-update-toast__icon {\n animation: none;\n }\n }\n `]\n})\nexport class UpdateNotificationComponent {\n public readonly updateService = inject(UpdateNotificationService);\n\n onReload(): void {\n this.updateService.applyUpdate();\n }\n\n onDismiss(): void {\n this.updateService.dismissForSession();\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"update-notification.component.js","sourceRoot":"","sources":["../../src/lib/update-notification.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;;;;;IAyB1D,AALJ,iCAIC,aAC4D;IACrD,0BAAyC;IAC7C,oBAAM;IAEF,AADJ,iCAAyC,aACE;IACnC,kDACJ;IAAA,oBAAM;IACN,iCAA0C;IACtC,mJACJ;IAAA,oBAAM;IAEF,AADJ,iCAAyC,gBAKpC;IADG,kMAAS,iBAAU,KAAC;IAEpB,2BAA2D;IAC3D,gCAAM;IAAA,2BAAU;IACpB,AADoB,oBAAO,EAClB;IACT,qCAIC;IADG,mMAAS,kBAAW,KAAC;IAErB,wBACJ;IAER,AADI,AADI,oBAAS,EACP,EACJ;IACN,sCAKC;IADG,mMAAS,kBAAW,KAAC;IAErB,4BAAoD;IAE5D,AADI,oBAAS,EACP;;AA3DlB;;;;;;;;;;GAUG;AA+NH,MAAM,OAAO,2BAA2B;IACpB,aAAa,GAAG,MAAM,CAAC,yBAAyB,CAAC,CAAC;IAElE,6CAA6C;IAC7C,IAAW,aAAa;QACpB,OAAO,IAAI,CAAC,aAAa,CAAC;IAC9B,CAAC;IAED,QAAQ;QACJ,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,CAAC;IACrC,CAAC;IAED,wCAAwC;IACxC,QAAQ;QACJ,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC;IAC3B,CAAC;IAED,SAAS;QACL,IAAI,CAAC,aAAa,CAAC,iBAAiB,EAAE,CAAC;IAC3C,CAAC;IAED,yCAAyC;IACzC,SAAS;QACL,OAAO,IAAI,CAAC,SAAS,EAAE,CAAC;IAC5B,CAAC;qHAxBQ,2BAA2B;6DAA3B,2BAA2B;YAxNhC,8FAA8C;;;YAA9C,mFA2CC;4BA9CK,YAAY;;iFA2Nb,2BAA2B;cA9NvC,SAAS;6BACM,IAAI,YACN,wBAAwB,WACzB,CAAC,YAAY,CAAC,mBACN,uBAAuB,CAAC,MAAM,YACrC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA6CT;;kFA4KQ,2BAA2B","sourcesContent":["import { ChangeDetectionStrategy, Component, inject } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { UpdateNotificationService } from './update-notification.service';\n\n/**\n * Renders a small bottom-right toast when a new app version is available.\n * Two actions: \"Reload\" applies the update; \"Later\" dismisses for the session.\n *\n * Standalone component — no NgModule needed, drop into the shell template\n * with `<mj-update-notification></mj-update-notification>`.\n *\n * Visual design uses MJ design tokens (no hardcoded colors) so it picks up\n * dark mode + brand customization automatically. Slide-up entrance animation\n * + subtle pulse on the icon to draw attention without being aggressive.\n */\n@Component({\n standalone: true,\n selector: 'mj-update-notification',\n imports: [CommonModule],\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n @if (updateService.updateAvailable$ | async) {\n <div\n class=\"mj-sw-update-toast\"\n role=\"status\"\n aria-live=\"polite\"\n >\n <div class=\"mj-sw-update-toast__icon\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-arrows-rotate\"></i>\n </div>\n <div class=\"mj-sw-update-toast__content\">\n <div class=\"mj-sw-update-toast__title\">\n Update available (test build #7)\n </div>\n <div class=\"mj-sw-update-toast__subtitle\">\n A newer version of MemberJunction has been downloaded. Reload now to apply, or keep working and we'll prompt you again next time.\n </div>\n <div class=\"mj-sw-update-toast__actions\">\n <button\n type=\"button\"\n class=\"mj-sw-update-toast__btn mj-sw-update-toast__btn--primary\"\n (click)=\"onReload()\"\n >\n <i class=\"fa-solid fa-rotate-right\" aria-hidden=\"true\"></i>\n <span>Reload now</span>\n </button>\n <button\n type=\"button\"\n class=\"mj-sw-update-toast__btn mj-sw-update-toast__btn--secondary\"\n (click)=\"onDismiss()\"\n >\n Later\n </button>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"mj-sw-update-toast__close\"\n aria-label=\"Dismiss\"\n (click)=\"onDismiss()\"\n >\n <i class=\"fa-solid fa-xmark\" aria-hidden=\"true\"></i>\n </button>\n </div>\n }\n `,\n styles: [`\n :host {\n position: fixed;\n bottom: 24px;\n right: 24px;\n z-index: 1000;\n pointer-events: none;\n max-width: 100%;\n }\n\n @keyframes mj-sw-toast-in {\n from {\n opacity: 0;\n transform: translateY(16px) scale(0.98);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n }\n\n @keyframes mj-sw-toast-pulse {\n 0%, 100% {\n box-shadow: 0 0 0 0 color-mix(in srgb, var(--mj-brand-primary) 35%, transparent);\n }\n 50% {\n box-shadow: 0 0 0 8px color-mix(in srgb, var(--mj-brand-primary) 0%, transparent);\n }\n }\n\n .mj-sw-update-toast {\n display: flex;\n align-items: flex-start;\n gap: 14px;\n position: relative;\n background: var(--mj-bg-surface-elevated);\n color: var(--mj-text-primary);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n padding: 16px 18px 16px 16px;\n width: 380px;\n max-width: calc(100vw - 48px);\n box-shadow:\n 0 1px 2px color-mix(in srgb, var(--mj-text-primary) 6%, transparent),\n 0 12px 32px color-mix(in srgb, var(--mj-text-primary) 16%, transparent);\n pointer-events: auto;\n font-size: 13.5px;\n line-height: 1.45;\n animation: mj-sw-toast-in 220ms cubic-bezier(0.2, 0.9, 0.32, 1.15) both;\n }\n\n .mj-sw-update-toast__icon {\n flex: 0 0 auto;\n width: 36px;\n height: 36px;\n border-radius: 50%;\n display: grid;\n place-items: center;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 16px;\n animation: mj-sw-toast-pulse 2.4s ease-in-out 0.4s 2;\n }\n\n .mj-sw-update-toast__content {\n flex: 1 1 auto;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n\n .mj-sw-update-toast__title {\n color: var(--mj-text-primary);\n font-weight: 600;\n font-size: 14px;\n letter-spacing: -0.005em;\n }\n\n .mj-sw-update-toast__subtitle {\n color: var(--mj-text-secondary);\n font-size: 12.5px;\n line-height: 1.5;\n margin-bottom: 4px;\n }\n\n .mj-sw-update-toast__actions {\n display: flex;\n gap: 8px;\n margin-top: 8px;\n }\n\n .mj-sw-update-toast__btn {\n font: inherit;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n padding: 7px 14px;\n border-radius: 8px;\n border: 1px solid transparent;\n line-height: 1.2;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, transform 0.08s ease;\n }\n .mj-sw-update-toast__btn:active {\n transform: translateY(1px);\n }\n .mj-sw-update-toast__btn:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 2px;\n }\n\n .mj-sw-update-toast__btn--primary {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n }\n .mj-sw-update-toast__btn--primary:hover {\n background: var(--mj-brand-primary-hover);\n }\n .mj-sw-update-toast__btn--primary:active {\n background: var(--mj-brand-primary-active);\n }\n\n .mj-sw-update-toast__btn--secondary {\n background: transparent;\n border-color: var(--mj-border-default);\n color: var(--mj-text-secondary);\n }\n .mj-sw-update-toast__btn--secondary:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n border-color: var(--mj-border-strong);\n }\n\n .mj-sw-update-toast__close {\n position: absolute;\n top: 8px;\n right: 8px;\n width: 24px;\n height: 24px;\n border: none;\n background: transparent;\n color: var(--mj-text-muted);\n border-radius: 6px;\n cursor: pointer;\n display: grid;\n place-items: center;\n font-size: 12px;\n transition: background 0.15s ease, color 0.15s ease;\n }\n .mj-sw-update-toast__close:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n .mj-sw-update-toast__close:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 1px;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .mj-sw-update-toast {\n animation: none;\n }\n .mj-sw-update-toast__icon {\n animation: none;\n }\n }\n `]\n})\nexport class UpdateNotificationComponent {\n public readonly UpdateService = inject(UpdateNotificationService);\n\n /** @deprecated Use {@link UpdateService}. */\n public get updateService() {\n return this.UpdateService;\n }\n\n OnReload(): void {\n this.UpdateService.applyUpdate();\n }\n\n /** @deprecated Use {@link OnReload}. */\n onReload(): void {\n return this.OnReload();\n }\n\n OnDismiss(): void {\n this.UpdateService.dismissForSession();\n }\n\n /** @deprecated Use {@link OnDismiss}. */\n onDismiss(): void {\n return this.OnDismiss();\n }\n}\n"]}
|
|
@@ -27,6 +27,8 @@ export declare class UpdateNotificationService implements OnDestroy {
|
|
|
27
27
|
*/
|
|
28
28
|
startAutoCheck(intervalMs: number): void;
|
|
29
29
|
/** Stop the periodic poll started by `startAutoCheck()`. */
|
|
30
|
+
StopAutoCheck(): void;
|
|
31
|
+
/** @deprecated Use {@link StopAutoCheck}. */
|
|
30
32
|
stopAutoCheck(): void;
|
|
31
33
|
/**
|
|
32
34
|
* Attach this service to `window.__mjUpdateNotificationService__` so it can
|
|
@@ -41,12 +43,20 @@ export declare class UpdateNotificationService implements OnDestroy {
|
|
|
41
43
|
* Stays `true` until either `applyUpdate()` (which reloads the page) or
|
|
42
44
|
* `dismissForSession()` (which resets the flag without reloading).
|
|
43
45
|
*/
|
|
46
|
+
get UpdateAvailable$(): Observable<boolean>;
|
|
47
|
+
/** @deprecated Use {@link UpdateAvailable$}. */
|
|
44
48
|
get updateAvailable$(): Observable<boolean>;
|
|
45
49
|
/** Synchronous accessor for current update-available state. */
|
|
50
|
+
get IsUpdateAvailable(): boolean;
|
|
51
|
+
/** @deprecated Use {@link IsUpdateAvailable}. */
|
|
46
52
|
get isUpdateAvailable(): boolean;
|
|
47
53
|
/** True if the underlying SwUpdate API is active (SW registered + supported). */
|
|
54
|
+
get IsServiceWorkerEnabled(): boolean;
|
|
55
|
+
/** @deprecated Use {@link IsServiceWorkerEnabled}. */
|
|
48
56
|
get isServiceWorkerEnabled(): boolean;
|
|
49
57
|
/** The most recent `VERSION_READY` event, if any. Useful for diagnostics. */
|
|
58
|
+
get LastVersionEvent(): VersionReadyEvent | null;
|
|
59
|
+
/** @deprecated Use {@link LastVersionEvent}. */
|
|
50
60
|
get lastVersionEvent(): VersionReadyEvent | null;
|
|
51
61
|
/**
|
|
52
62
|
* Reload the page to activate the newly-downloaded SW version.
|
|
@@ -59,6 +69,8 @@ export declare class UpdateNotificationService implements OnDestroy {
|
|
|
59
69
|
* This method is split out (vs. inlining `location.reload()`) so consumers
|
|
60
70
|
* can be tested without actually reloading the page.
|
|
61
71
|
*/
|
|
72
|
+
ApplyUpdate(): void;
|
|
73
|
+
/** @deprecated Use {@link ApplyUpdate}. */
|
|
62
74
|
applyUpdate(): void;
|
|
63
75
|
/**
|
|
64
76
|
* Dismiss the prompt for this session without reloading. The flag will
|
|
@@ -68,6 +80,8 @@ export declare class UpdateNotificationService implements OnDestroy {
|
|
|
68
80
|
* Use case: a power user is in the middle of editing something and doesn't
|
|
69
81
|
* want to interrupt; they'll get prompted again on their next reload.
|
|
70
82
|
*/
|
|
83
|
+
DismissForSession(): void;
|
|
84
|
+
/** @deprecated Use {@link DismissForSession}. */
|
|
71
85
|
dismissForSession(): void;
|
|
72
86
|
/**
|
|
73
87
|
* Manually nudge the SW to check for an update. The Angular SW polls
|
|
@@ -77,6 +91,8 @@ export declare class UpdateNotificationService implements OnDestroy {
|
|
|
77
91
|
* Returns `true` if a new version was found and downloaded; `false` if
|
|
78
92
|
* we're already on the latest. Resolves to `false` if the SW is disabled.
|
|
79
93
|
*/
|
|
94
|
+
CheckForUpdate(): Promise<boolean>;
|
|
95
|
+
/** @deprecated Use {@link CheckForUpdate}. */
|
|
80
96
|
checkForUpdate(): Promise<boolean>;
|
|
81
97
|
/**
|
|
82
98
|
* Indirection for test injection — overriding `location.reload` directly
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update-notification.service.d.ts","sourceRoot":"","sources":["../../src/lib/update-notification.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,SAAS,EAAU,MAAM,eAAe,CAAC;AAC9D,OAAO,EAA0B,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACpF,OAAO,EAAmB,UAAU,EAAkB,MAAM,MAAM,CAAC;;AA2CnE,qBACa,yBAA0B,YAAW,SAAS;IACvD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAuC;IACzE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAuB;IACjD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoB;IAE9C,qEAAqE;IACrE,OAAO,CAAC,iBAAiB,CAAkC;IAE3D,+EAA+E;IAC/E,OAAO,CAAC,WAAW,CAA+C;IAElE,mFAAmF;IACnF,OAAO,CAAC,mBAAmB,CAA6B;IAExD,2DAA2D;IAC3D,OAAO,CAAC,eAAe,CAA4B;;IA6BnD;;;;;;;;;OASG;IACI,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAqB/C,4DAA4D;IACrD,aAAa,IAAI,IAAI;IAW5B;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAMzB;;;;OAIG;IACH,IAAW,gBAAgB,IAAI,UAAU,CAAC,OAAO,CAAC,CAEjD;IAED,+DAA+D;IAC/D,IAAW,iBAAiB,IAAI,OAAO,CAEtC;IAED,iFAAiF;IACjF,IAAW,sBAAsB,IAAI,OAAO,CAE3C;IAED,6EAA6E;IAC7E,IAAW,gBAAgB,IAAI,iBAAiB,GAAG,IAAI,CAEtD;IAED;;;;;;;;;;OAUG;IACI,WAAW,IAAI,IAAI;IAQ1B;;;;;;;OAOG;IACI,iBAAiB,IAAI,IAAI;IAIhC;;;;;;;OAOG;IACU,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;IAW/C;;;;;OAKG;IAEH,SAAS,CAAC,aAAa,IAAI,IAAI;IAO/B,WAAW,IAAI,IAAI;
|
|
1
|
+
{"version":3,"file":"update-notification.service.d.ts","sourceRoot":"","sources":["../../src/lib/update-notification.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,SAAS,EAAU,MAAM,eAAe,CAAC;AAC9D,OAAO,EAA0B,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACpF,OAAO,EAAmB,UAAU,EAAkB,MAAM,MAAM,CAAC;;AA2CnE,qBACa,yBAA0B,YAAW,SAAS;IACvD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAuC;IACzE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAuB;IACjD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoB;IAE9C,qEAAqE;IACrE,OAAO,CAAC,iBAAiB,CAAkC;IAE3D,+EAA+E;IAC/E,OAAO,CAAC,WAAW,CAA+C;IAElE,mFAAmF;IACnF,OAAO,CAAC,mBAAmB,CAA6B;IAExD,2DAA2D;IAC3D,OAAO,CAAC,eAAe,CAA4B;;IA6BnD;;;;;;;;;OASG;IACI,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAqB/C,4DAA4D;IACrD,aAAa,IAAI,IAAI;IAW5B,6CAA6C;IACtC,aAAa,IAAI,IAAI;IAI5B;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAMzB;;;;OAIG;IACH,IAAW,gBAAgB,IAAI,UAAU,CAAC,OAAO,CAAC,CAEjD;IAED,gDAAgD;IAChD,IAAW,gBAAgB,IAAI,UAAU,CAAC,OAAO,CAAC,CAEjD;IAED,+DAA+D;IAC/D,IAAW,iBAAiB,IAAI,OAAO,CAEtC;IAED,iDAAiD;IACjD,IAAW,iBAAiB,IAAI,OAAO,CAEtC;IAED,iFAAiF;IACjF,IAAW,sBAAsB,IAAI,OAAO,CAE3C;IAED,sDAAsD;IACtD,IAAW,sBAAsB,IAAI,OAAO,CAE3C;IAED,6EAA6E;IAC7E,IAAW,gBAAgB,IAAI,iBAAiB,GAAG,IAAI,CAEtD;IAED,gDAAgD;IAChD,IAAW,gBAAgB,IAAI,iBAAiB,GAAG,IAAI,CAEtD;IAED;;;;;;;;;;OAUG;IACI,WAAW,IAAI,IAAI;IAQ1B,2CAA2C;IACpC,WAAW,IAAI,IAAI;IAI1B;;;;;;;OAOG;IACI,iBAAiB,IAAI,IAAI;IAIhC,iDAAiD;IAC1C,iBAAiB,IAAI,IAAI;IAIhC;;;;;;;OAOG;IACU,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;IAW/C,8CAA8C;IACjC,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;IAI/C;;;;;OAKG;IAEH,SAAS,CAAC,aAAa,IAAI,IAAI;IAO/B,WAAW,IAAI,IAAI;yCApOV,yBAAyB;6CAAzB,yBAAyB;CA0OrC"}
|
|
@@ -85,7 +85,7 @@ export class UpdateNotificationService {
|
|
|
85
85
|
* (with an immediate check) when the tab becomes visible again.
|
|
86
86
|
*/
|
|
87
87
|
startAutoCheck(intervalMs) {
|
|
88
|
-
this.
|
|
88
|
+
this.StopAutoCheck();
|
|
89
89
|
this._pollIntervalMs = intervalMs;
|
|
90
90
|
if (!this._swUpdate.isEnabled || intervalMs <= 0)
|
|
91
91
|
return;
|
|
@@ -93,19 +93,19 @@ export class UpdateNotificationService {
|
|
|
93
93
|
// Only fire when visible. Hidden tabs catch up via the
|
|
94
94
|
// visibilitychange handler.
|
|
95
95
|
if (typeof document === 'undefined' || !document.hidden) {
|
|
96
|
-
void this.
|
|
96
|
+
void this.CheckForUpdate();
|
|
97
97
|
}
|
|
98
98
|
}, intervalMs);
|
|
99
99
|
if (typeof document !== 'undefined') {
|
|
100
100
|
this._visibilityListener = () => {
|
|
101
101
|
if (!document.hidden)
|
|
102
|
-
void this.
|
|
102
|
+
void this.CheckForUpdate();
|
|
103
103
|
};
|
|
104
104
|
document.addEventListener('visibilitychange', this._visibilityListener);
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
/** Stop the periodic poll started by `startAutoCheck()`. */
|
|
108
|
-
|
|
108
|
+
StopAutoCheck() {
|
|
109
109
|
if (this._pollHandle !== null) {
|
|
110
110
|
clearInterval(this._pollHandle);
|
|
111
111
|
this._pollHandle = null;
|
|
@@ -115,6 +115,10 @@ export class UpdateNotificationService {
|
|
|
115
115
|
this._visibilityListener = null;
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
+
/** @deprecated Use {@link StopAutoCheck}. */
|
|
119
|
+
stopAutoCheck() {
|
|
120
|
+
return this.StopAutoCheck();
|
|
121
|
+
}
|
|
118
122
|
/**
|
|
119
123
|
* Attach this service to `window.__mjUpdateNotificationService__` so it can
|
|
120
124
|
* be poked from the DevTools console. Useful for ops/QA verification of the
|
|
@@ -133,21 +137,37 @@ export class UpdateNotificationService {
|
|
|
133
137
|
* Stays `true` until either `applyUpdate()` (which reloads the page) or
|
|
134
138
|
* `dismissForSession()` (which resets the flag without reloading).
|
|
135
139
|
*/
|
|
136
|
-
get
|
|
140
|
+
get UpdateAvailable$() {
|
|
137
141
|
return this._updateAvailable$.asObservable();
|
|
138
142
|
}
|
|
143
|
+
/** @deprecated Use {@link UpdateAvailable$}. */
|
|
144
|
+
get updateAvailable$() {
|
|
145
|
+
return this.UpdateAvailable$;
|
|
146
|
+
}
|
|
139
147
|
/** Synchronous accessor for current update-available state. */
|
|
140
|
-
get
|
|
148
|
+
get IsUpdateAvailable() {
|
|
141
149
|
return this._updateAvailable$.value;
|
|
142
150
|
}
|
|
151
|
+
/** @deprecated Use {@link IsUpdateAvailable}. */
|
|
152
|
+
get isUpdateAvailable() {
|
|
153
|
+
return this.IsUpdateAvailable;
|
|
154
|
+
}
|
|
143
155
|
/** True if the underlying SwUpdate API is active (SW registered + supported). */
|
|
144
|
-
get
|
|
156
|
+
get IsServiceWorkerEnabled() {
|
|
145
157
|
return this._swUpdate.isEnabled;
|
|
146
158
|
}
|
|
159
|
+
/** @deprecated Use {@link IsServiceWorkerEnabled}. */
|
|
160
|
+
get isServiceWorkerEnabled() {
|
|
161
|
+
return this.IsServiceWorkerEnabled;
|
|
162
|
+
}
|
|
147
163
|
/** The most recent `VERSION_READY` event, if any. Useful for diagnostics. */
|
|
148
|
-
get
|
|
164
|
+
get LastVersionEvent() {
|
|
149
165
|
return this._lastVersionEvent;
|
|
150
166
|
}
|
|
167
|
+
/** @deprecated Use {@link LastVersionEvent}. */
|
|
168
|
+
get lastVersionEvent() {
|
|
169
|
+
return this.LastVersionEvent;
|
|
170
|
+
}
|
|
151
171
|
/**
|
|
152
172
|
* Reload the page to activate the newly-downloaded SW version.
|
|
153
173
|
*
|
|
@@ -159,13 +179,17 @@ export class UpdateNotificationService {
|
|
|
159
179
|
* This method is split out (vs. inlining `location.reload()`) so consumers
|
|
160
180
|
* can be tested without actually reloading the page.
|
|
161
181
|
*/
|
|
162
|
-
|
|
182
|
+
ApplyUpdate() {
|
|
163
183
|
// Hide the prompt before reloading so it doesn't briefly flash again
|
|
164
184
|
// on the new page if anything in the shell renders before the SW
|
|
165
185
|
// activates.
|
|
166
186
|
this._updateAvailable$.next(false);
|
|
167
187
|
this.performReload();
|
|
168
188
|
}
|
|
189
|
+
/** @deprecated Use {@link ApplyUpdate}. */
|
|
190
|
+
applyUpdate() {
|
|
191
|
+
return this.ApplyUpdate();
|
|
192
|
+
}
|
|
169
193
|
/**
|
|
170
194
|
* Dismiss the prompt for this session without reloading. The flag will
|
|
171
195
|
* re-fire on the next page load if the SW is still pointing at the
|
|
@@ -174,9 +198,13 @@ export class UpdateNotificationService {
|
|
|
174
198
|
* Use case: a power user is in the middle of editing something and doesn't
|
|
175
199
|
* want to interrupt; they'll get prompted again on their next reload.
|
|
176
200
|
*/
|
|
177
|
-
|
|
201
|
+
DismissForSession() {
|
|
178
202
|
this._updateAvailable$.next(false);
|
|
179
203
|
}
|
|
204
|
+
/** @deprecated Use {@link DismissForSession}. */
|
|
205
|
+
dismissForSession() {
|
|
206
|
+
return this.DismissForSession();
|
|
207
|
+
}
|
|
180
208
|
/**
|
|
181
209
|
* Manually nudge the SW to check for an update. The Angular SW polls
|
|
182
210
|
* automatically on a schedule, but consumers can call this to force an
|
|
@@ -185,7 +213,7 @@ export class UpdateNotificationService {
|
|
|
185
213
|
* Returns `true` if a new version was found and downloaded; `false` if
|
|
186
214
|
* we're already on the latest. Resolves to `false` if the SW is disabled.
|
|
187
215
|
*/
|
|
188
|
-
async
|
|
216
|
+
async CheckForUpdate() {
|
|
189
217
|
if (!this._swUpdate.isEnabled)
|
|
190
218
|
return false;
|
|
191
219
|
try {
|
|
@@ -197,6 +225,10 @@ export class UpdateNotificationService {
|
|
|
197
225
|
return false;
|
|
198
226
|
}
|
|
199
227
|
}
|
|
228
|
+
/** @deprecated Use {@link CheckForUpdate}. */
|
|
229
|
+
async checkForUpdate() {
|
|
230
|
+
return this.CheckForUpdate();
|
|
231
|
+
}
|
|
200
232
|
/**
|
|
201
233
|
* Indirection for test injection — overriding `location.reload` directly
|
|
202
234
|
* is not possible in a JSDOM/happy-dom environment without warnings, and
|
|
@@ -211,7 +243,7 @@ export class UpdateNotificationService {
|
|
|
211
243
|
document.location.reload();
|
|
212
244
|
}
|
|
213
245
|
ngOnDestroy() {
|
|
214
|
-
this.
|
|
246
|
+
this.StopAutoCheck();
|
|
215
247
|
this._destroy$.next();
|
|
216
248
|
this._destroy$.complete();
|
|
217
249
|
this._updateAvailable$.complete();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update-notification.service.js","sourceRoot":"","sources":["../../src/lib/update-notification.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable, OnDestroy, inject } from '@angular/core';\nimport { SwUpdate, VersionEvent, VersionReadyEvent } from '@angular/service-worker';\nimport { BehaviorSubject, Observable, Subject, EMPTY } from 'rxjs';\nimport { catchError, filter, takeUntil } from 'rxjs/operators';\n\n/**\n * Coordinates notifying the user when a new app-shell version has been\n * downloaded by the service worker and is waiting to activate.\n *\n * Background — the Angular service worker downloads new versions silently in\n * the background; the currently-loaded page keeps using the old cached version\n * until a navigation reload activates the new one. This service watches for\n * `VERSION_READY` events from `SwUpdate` and exposes them via an observable\n * the shell can subscribe to (e.g. to render an \"Update available\" toast).\n *\n * Use in MJExplorer:\n * 1. Inject this service in `AppComponent` (or wherever the shell lives)\n * 2. Subscribe to `updateAvailable$` and render UI when it emits\n * 3. Call `applyUpdate()` when the user accepts the prompt — this performs\n * a full page reload, which causes the SW to activate the new version\n *\n * **Dev note**: when the service worker is disabled (development builds, or\n * `enableServiceWorker = false` in the environment), `SwUpdate.isEnabled`\n * returns `false` and this service is effectively a no-op. The shell can\n * always subscribe; it just never receives an emission.\n */\n/**\n * How often (in ms) to ask the SW to check for a new manifest while the tab\n * is visible. 15 minutes balances responsiveness against pointless network\n * chatter on idle tabs. Checks are skipped while the tab is hidden (Page\n * Visibility API), and a check is also fired immediately when the tab becomes\n * visible after being backgrounded.\n *\n * Override via `MJServiceWorkerOptions.pollIntervalMs`.\n */\nconst DEFAULT_POLL_INTERVAL_MS = 15 * 60 * 1000;\n\n/**\n * Window-attached debug handle. Type-safe accessor avoids polluting the global\n * namespace with `any`. Use from DevTools console:\n * __mjUpdateNotificationService__.checkForUpdate()\n * __mjUpdateNotificationService__.isUpdateAvailable\n */\nconst WINDOW_DEBUG_KEY = '__mjUpdateNotificationService__';\n\n@Injectable({ providedIn: 'root' })\nexport class UpdateNotificationService implements OnDestroy {\n private readonly _updateAvailable$ = new BehaviorSubject<boolean>(false);\n private readonly _destroy$ = new Subject<void>();\n private readonly _swUpdate = inject(SwUpdate);\n\n /** Last `VERSION_READY` event captured (for diagnostics / tests). */\n private _lastVersionEvent: VersionReadyEvent | null = null;\n\n /** Handle from `setInterval` for the periodic poll; cleared in ngOnDestroy. */\n private _pollHandle: ReturnType<typeof setInterval> | null = null;\n\n /** Bound listener for `visibilitychange`; needs the same reference for removal. */\n private _visibilityListener: (() => void) | null = null;\n\n /** Effective poll interval — set in `startAutoCheck()`. */\n private _pollIntervalMs = DEFAULT_POLL_INTERVAL_MS;\n\n constructor() {\n if (this._swUpdate.isEnabled) {\n // Subscribe to all version events; filter to ones that mean a new\n // version is fully downloaded and waiting to activate.\n this._swUpdate.versionUpdates\n .pipe(\n filter((e: VersionEvent): e is VersionReadyEvent => e.type === 'VERSION_READY'),\n catchError(err => {\n // Defensive — observable should never error in practice, but if the\n // SW connection breaks for any reason, swallow it. Caller can re-init\n // the service later if needed.\n // eslint-disable-next-line no-console\n console.warn('[UpdateNotificationService] versionUpdates errored:', err);\n return EMPTY;\n }),\n takeUntil(this._destroy$)\n )\n .subscribe((event: VersionReadyEvent) => {\n this._lastVersionEvent = event;\n this._updateAvailable$.next(true);\n });\n\n this.startAutoCheck(DEFAULT_POLL_INTERVAL_MS);\n this.exposeDebugHandle();\n }\n }\n\n /**\n * Start a periodic update check on the given interval. Called automatically\n * by the constructor with `DEFAULT_POLL_INTERVAL_MS`. Consumers may call\n * this again with a different interval to retune at runtime — the previous\n * timer is cleared first.\n *\n * Pass `0` (or any non-positive number) to disable periodic checking.\n * Polling is automatically suspended while the tab is hidden and resumed\n * (with an immediate check) when the tab becomes visible again.\n */\n public startAutoCheck(intervalMs: number): void {\n this.stopAutoCheck();\n this._pollIntervalMs = intervalMs;\n if (!this._swUpdate.isEnabled || intervalMs <= 0) return;\n\n this._pollHandle = setInterval(() => {\n // Only fire when visible. Hidden tabs catch up via the\n // visibilitychange handler.\n if (typeof document === 'undefined' || !document.hidden) {\n void this.checkForUpdate();\n }\n }, intervalMs);\n\n if (typeof document !== 'undefined') {\n this._visibilityListener = () => {\n if (!document.hidden) void this.checkForUpdate();\n };\n document.addEventListener('visibilitychange', this._visibilityListener);\n }\n }\n\n /** Stop the periodic poll started by `startAutoCheck()`. */\n public stopAutoCheck(): void {\n if (this._pollHandle !== null) {\n clearInterval(this._pollHandle);\n this._pollHandle = null;\n }\n if (this._visibilityListener && typeof document !== 'undefined') {\n document.removeEventListener('visibilitychange', this._visibilityListener);\n this._visibilityListener = null;\n }\n }\n\n /**\n * Attach this service to `window.__mjUpdateNotificationService__` so it can\n * be poked from the DevTools console. Useful for ops/QA verification of the\n * update-detection roundtrip without code changes.\n *\n * No-op in non-browser contexts.\n */\n private exposeDebugHandle(): void {\n if (typeof window === 'undefined') return;\n // Cast through unknown to avoid a global declaration just for the debug hook.\n (window as unknown as Record<string, unknown>)[WINDOW_DEBUG_KEY] = this;\n }\n\n /**\n * Emits `true` once a new SW-cached version is ready to activate.\n * Stays `true` until either `applyUpdate()` (which reloads the page) or\n * `dismissForSession()` (which resets the flag without reloading).\n */\n public get updateAvailable$(): Observable<boolean> {\n return this._updateAvailable$.asObservable();\n }\n\n /** Synchronous accessor for current update-available state. */\n public get isUpdateAvailable(): boolean {\n return this._updateAvailable$.value;\n }\n\n /** True if the underlying SwUpdate API is active (SW registered + supported). */\n public get isServiceWorkerEnabled(): boolean {\n return this._swUpdate.isEnabled;\n }\n\n /** The most recent `VERSION_READY` event, if any. Useful for diagnostics. */\n public get lastVersionEvent(): VersionReadyEvent | null {\n return this._lastVersionEvent;\n }\n\n /**\n * Reload the page to activate the newly-downloaded SW version.\n *\n * Implementation note: the SW activates new versions on the next\n * navigation, not via any in-process API. So we just reload — `SwUpdate`\n * doesn't expose a non-reload way to swap the running JS without losing\n * application state, and any such mechanism would be unsafe anyway.\n *\n * This method is split out (vs. inlining `location.reload()`) so consumers\n * can be tested without actually reloading the page.\n */\n public applyUpdate(): void {\n // Hide the prompt before reloading so it doesn't briefly flash again\n // on the new page if anything in the shell renders before the SW\n // activates.\n this._updateAvailable$.next(false);\n this.performReload();\n }\n\n /**\n * Dismiss the prompt for this session without reloading. The flag will\n * re-fire on the next page load if the SW is still pointing at the\n * pending version (which it will be, until something triggers a reload).\n *\n * Use case: a power user is in the middle of editing something and doesn't\n * want to interrupt; they'll get prompted again on their next reload.\n */\n public dismissForSession(): void {\n this._updateAvailable$.next(false);\n }\n\n /**\n * Manually nudge the SW to check for an update. The Angular SW polls\n * automatically on a schedule, but consumers can call this to force an\n * immediate check (e.g., from a \"Check for updates\" menu item).\n *\n * Returns `true` if a new version was found and downloaded; `false` if\n * we're already on the latest. Resolves to `false` if the SW is disabled.\n */\n public async checkForUpdate(): Promise<boolean> {\n if (!this._swUpdate.isEnabled) return false;\n try {\n return await this._swUpdate.checkForUpdate();\n } catch (err) {\n // eslint-disable-next-line no-console\n console.warn('[UpdateNotificationService] checkForUpdate failed:', err);\n return false;\n }\n }\n\n /**\n * Indirection for test injection — overriding `location.reload` directly\n * is not possible in a JSDOM/happy-dom environment without warnings, and\n * spying on `globalThis.location` is brittle. This wrapper lets tests\n * mock the reload via a subclass / `vi.spyOn(service as any, 'performReload')`.\n */\n /* istanbul ignore next — exercised manually, hard to unit-test cleanly */\n protected performReload(): void {\n // Use document.location for cross-environment reliability. `window`\n // can be shadowed in test environments; `document.location` is more\n // consistently available.\n document.location.reload();\n }\n\n ngOnDestroy(): void {\n this.stopAutoCheck();\n this._destroy$.next();\n this._destroy$.complete();\n this._updateAvailable$.complete();\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"update-notification.service.js","sourceRoot":"","sources":["../../src/lib/update-notification.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable, OnDestroy, inject } from '@angular/core';\nimport { SwUpdate, VersionEvent, VersionReadyEvent } from '@angular/service-worker';\nimport { BehaviorSubject, Observable, Subject, EMPTY } from 'rxjs';\nimport { catchError, filter, takeUntil } from 'rxjs/operators';\n\n/**\n * Coordinates notifying the user when a new app-shell version has been\n * downloaded by the service worker and is waiting to activate.\n *\n * Background — the Angular service worker downloads new versions silently in\n * the background; the currently-loaded page keeps using the old cached version\n * until a navigation reload activates the new one. This service watches for\n * `VERSION_READY` events from `SwUpdate` and exposes them via an observable\n * the shell can subscribe to (e.g. to render an \"Update available\" toast).\n *\n * Use in MJExplorer:\n * 1. Inject this service in `AppComponent` (or wherever the shell lives)\n * 2. Subscribe to `updateAvailable$` and render UI when it emits\n * 3. Call `applyUpdate()` when the user accepts the prompt — this performs\n * a full page reload, which causes the SW to activate the new version\n *\n * **Dev note**: when the service worker is disabled (development builds, or\n * `enableServiceWorker = false` in the environment), `SwUpdate.isEnabled`\n * returns `false` and this service is effectively a no-op. The shell can\n * always subscribe; it just never receives an emission.\n */\n/**\n * How often (in ms) to ask the SW to check for a new manifest while the tab\n * is visible. 15 minutes balances responsiveness against pointless network\n * chatter on idle tabs. Checks are skipped while the tab is hidden (Page\n * Visibility API), and a check is also fired immediately when the tab becomes\n * visible after being backgrounded.\n *\n * Override via `MJServiceWorkerOptions.pollIntervalMs`.\n */\nconst DEFAULT_POLL_INTERVAL_MS = 15 * 60 * 1000;\n\n/**\n * Window-attached debug handle. Type-safe accessor avoids polluting the global\n * namespace with `any`. Use from DevTools console:\n * __mjUpdateNotificationService__.checkForUpdate()\n * __mjUpdateNotificationService__.isUpdateAvailable\n */\nconst WINDOW_DEBUG_KEY = '__mjUpdateNotificationService__';\n\n@Injectable({ providedIn: 'root' })\nexport class UpdateNotificationService implements OnDestroy {\n private readonly _updateAvailable$ = new BehaviorSubject<boolean>(false);\n private readonly _destroy$ = new Subject<void>();\n private readonly _swUpdate = inject(SwUpdate);\n\n /** Last `VERSION_READY` event captured (for diagnostics / tests). */\n private _lastVersionEvent: VersionReadyEvent | null = null;\n\n /** Handle from `setInterval` for the periodic poll; cleared in ngOnDestroy. */\n private _pollHandle: ReturnType<typeof setInterval> | null = null;\n\n /** Bound listener for `visibilitychange`; needs the same reference for removal. */\n private _visibilityListener: (() => void) | null = null;\n\n /** Effective poll interval — set in `startAutoCheck()`. */\n private _pollIntervalMs = DEFAULT_POLL_INTERVAL_MS;\n\n constructor() {\n if (this._swUpdate.isEnabled) {\n // Subscribe to all version events; filter to ones that mean a new\n // version is fully downloaded and waiting to activate.\n this._swUpdate.versionUpdates\n .pipe(\n filter((e: VersionEvent): e is VersionReadyEvent => e.type === 'VERSION_READY'),\n catchError(err => {\n // Defensive — observable should never error in practice, but if the\n // SW connection breaks for any reason, swallow it. Caller can re-init\n // the service later if needed.\n // eslint-disable-next-line no-console\n console.warn('[UpdateNotificationService] versionUpdates errored:', err);\n return EMPTY;\n }),\n takeUntil(this._destroy$)\n )\n .subscribe((event: VersionReadyEvent) => {\n this._lastVersionEvent = event;\n this._updateAvailable$.next(true);\n });\n\n this.startAutoCheck(DEFAULT_POLL_INTERVAL_MS);\n this.exposeDebugHandle();\n }\n }\n\n /**\n * Start a periodic update check on the given interval. Called automatically\n * by the constructor with `DEFAULT_POLL_INTERVAL_MS`. Consumers may call\n * this again with a different interval to retune at runtime — the previous\n * timer is cleared first.\n *\n * Pass `0` (or any non-positive number) to disable periodic checking.\n * Polling is automatically suspended while the tab is hidden and resumed\n * (with an immediate check) when the tab becomes visible again.\n */\n public startAutoCheck(intervalMs: number): void { // case-violation-ok-legacy-back-compat: a subclass overrides this; a stub preserves CALLING the old name but not OVERRIDING it\n this.StopAutoCheck();\n this._pollIntervalMs = intervalMs;\n if (!this._swUpdate.isEnabled || intervalMs <= 0) return;\n\n this._pollHandle = setInterval(() => {\n // Only fire when visible. Hidden tabs catch up via the\n // visibilitychange handler.\n if (typeof document === 'undefined' || !document.hidden) {\n void this.CheckForUpdate();\n }\n }, intervalMs);\n\n if (typeof document !== 'undefined') {\n this._visibilityListener = () => {\n if (!document.hidden) void this.CheckForUpdate();\n };\n document.addEventListener('visibilitychange', this._visibilityListener);\n }\n }\n\n /** Stop the periodic poll started by `startAutoCheck()`. */\n public StopAutoCheck(): void {\n if (this._pollHandle !== null) {\n clearInterval(this._pollHandle);\n this._pollHandle = null;\n }\n if (this._visibilityListener && typeof document !== 'undefined') {\n document.removeEventListener('visibilitychange', this._visibilityListener);\n this._visibilityListener = null;\n }\n }\n\n /** @deprecated Use {@link StopAutoCheck}. */\n public stopAutoCheck(): void {\n return this.StopAutoCheck();\n }\n\n /**\n * Attach this service to `window.__mjUpdateNotificationService__` so it can\n * be poked from the DevTools console. Useful for ops/QA verification of the\n * update-detection roundtrip without code changes.\n *\n * No-op in non-browser contexts.\n */\n private exposeDebugHandle(): void {\n if (typeof window === 'undefined') return;\n // Cast through unknown to avoid a global declaration just for the debug hook.\n (window as unknown as Record<string, unknown>)[WINDOW_DEBUG_KEY] = this;\n }\n\n /**\n * Emits `true` once a new SW-cached version is ready to activate.\n * Stays `true` until either `applyUpdate()` (which reloads the page) or\n * `dismissForSession()` (which resets the flag without reloading).\n */\n public get UpdateAvailable$(): Observable<boolean> {\n return this._updateAvailable$.asObservable();\n }\n\n /** @deprecated Use {@link UpdateAvailable$}. */\n public get updateAvailable$(): Observable<boolean> {\n return this.UpdateAvailable$;\n }\n\n /** Synchronous accessor for current update-available state. */\n public get IsUpdateAvailable(): boolean {\n return this._updateAvailable$.value;\n }\n\n /** @deprecated Use {@link IsUpdateAvailable}. */\n public get isUpdateAvailable(): boolean {\n return this.IsUpdateAvailable;\n }\n\n /** True if the underlying SwUpdate API is active (SW registered + supported). */\n public get IsServiceWorkerEnabled(): boolean {\n return this._swUpdate.isEnabled;\n }\n\n /** @deprecated Use {@link IsServiceWorkerEnabled}. */\n public get isServiceWorkerEnabled(): boolean {\n return this.IsServiceWorkerEnabled;\n }\n\n /** The most recent `VERSION_READY` event, if any. Useful for diagnostics. */\n public get LastVersionEvent(): VersionReadyEvent | null {\n return this._lastVersionEvent;\n }\n\n /** @deprecated Use {@link LastVersionEvent}. */\n public get lastVersionEvent(): VersionReadyEvent | null {\n return this.LastVersionEvent;\n }\n\n /**\n * Reload the page to activate the newly-downloaded SW version.\n *\n * Implementation note: the SW activates new versions on the next\n * navigation, not via any in-process API. So we just reload — `SwUpdate`\n * doesn't expose a non-reload way to swap the running JS without losing\n * application state, and any such mechanism would be unsafe anyway.\n *\n * This method is split out (vs. inlining `location.reload()`) so consumers\n * can be tested without actually reloading the page.\n */\n public ApplyUpdate(): void {\n // Hide the prompt before reloading so it doesn't briefly flash again\n // on the new page if anything in the shell renders before the SW\n // activates.\n this._updateAvailable$.next(false);\n this.performReload();\n }\n\n /** @deprecated Use {@link ApplyUpdate}. */\n public applyUpdate(): void {\n return this.ApplyUpdate();\n }\n\n /**\n * Dismiss the prompt for this session without reloading. The flag will\n * re-fire on the next page load if the SW is still pointing at the\n * pending version (which it will be, until something triggers a reload).\n *\n * Use case: a power user is in the middle of editing something and doesn't\n * want to interrupt; they'll get prompted again on their next reload.\n */\n public DismissForSession(): void {\n this._updateAvailable$.next(false);\n }\n\n /** @deprecated Use {@link DismissForSession}. */\n public dismissForSession(): void {\n return this.DismissForSession();\n }\n\n /**\n * Manually nudge the SW to check for an update. The Angular SW polls\n * automatically on a schedule, but consumers can call this to force an\n * immediate check (e.g., from a \"Check for updates\" menu item).\n *\n * Returns `true` if a new version was found and downloaded; `false` if\n * we're already on the latest. Resolves to `false` if the SW is disabled.\n */\n public async CheckForUpdate(): Promise<boolean> {\n if (!this._swUpdate.isEnabled) return false;\n try {\n return await this._swUpdate.checkForUpdate();\n } catch (err) {\n // eslint-disable-next-line no-console\n console.warn('[UpdateNotificationService] checkForUpdate failed:', err);\n return false;\n }\n }\n\n /** @deprecated Use {@link CheckForUpdate}. */\n public async checkForUpdate(): Promise<boolean> {\n return this.CheckForUpdate();\n }\n\n /**\n * Indirection for test injection — overriding `location.reload` directly\n * is not possible in a JSDOM/happy-dom environment without warnings, and\n * spying on `globalThis.location` is brittle. This wrapper lets tests\n * mock the reload via a subclass / `vi.spyOn(service as any, 'performReload')`.\n */\n /* istanbul ignore next — exercised manually, hard to unit-test cleanly */\n protected performReload(): void {\n // Use document.location for cross-environment reliability. `window`\n // can be shadowed in test environments; `document.location` is more\n // consistently available.\n document.location.reload();\n }\n\n ngOnDestroy(): void {\n this.StopAutoCheck();\n this._destroy$.next();\n this._destroy$.complete();\n this._updateAvailable$.complete();\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ng-explorer-service-worker",
|
|
3
|
-
"version": "6.2.0-edge.
|
|
3
|
+
"version": "6.2.0-edge.2",
|
|
4
4
|
"description": "MemberJunction: Service worker app-shell pre-cache + update notification UI for MJ Explorer",
|
|
5
5
|
"main": "./dist/public-api.js",
|
|
6
6
|
"typings": "./dist/public-api.d.ts",
|