@yuuvis/client-framework 3.4.1 → 3.5.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.
@@ -1,9 +1,11 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, Injectable, signal, ChangeDetectionStrategy, Component, DestroyRef, makeEnvironmentProviders, provideAppInitializer, NgZone, NgModule } from '@angular/core';
2
+ import { inject, Injectable, ApplicationRef, DestroyRef, signal, ChangeDetectionStrategy, Component, makeEnvironmentProviders, provideAppInitializer, NgZone, NgModule } from '@angular/core';
3
3
  import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
4
+ import { SwUpdate } from '@angular/service-worker';
4
5
  import { TranslateService } from '@ngx-translate/core';
6
+ import { ConfirmService } from '@yuuvis/client-framework/common';
7
+ import { filter, first, concat, interval, finalize, timer, switchMap, map, debounceTime } from 'rxjs';
5
8
  import { AppCacheService, BackendService, UserService, EventService, YuvEventType } from '@yuuvis/client-core';
6
- import { finalize, timer, switchMap, map, debounceTime } from 'rxjs';
7
9
  import * as i1 from '@angular/material/button';
8
10
  import { MatButtonModule } from '@angular/material/button';
9
11
  import { MatSnackBar, MatSnackBarRef, MAT_SNACK_BAR_DATA, MatSnackBarLabel, MatSnackBarActions, MatSnackBarAction } from '@angular/material/snack-bar';
@@ -124,6 +126,22 @@ const haloFocusStyles = {
124
126
  transform: 'translateZ(0)'
125
127
  };
126
128
 
129
+ /**
130
+ * Default interval (in milliseconds) for periodic PWA update checks.
131
+ *
132
+ * After the application stabilizes, {@link PwaUpdateService} polls the service
133
+ * worker for a newer deployed version on this interval. This complements the
134
+ * `VERSION_READY` event, which only fires while the tab is open and the service
135
+ * worker happens to detect a change — long-running sessions would otherwise
136
+ * never learn about a new release.
137
+ *
138
+ * Override via `providePwaUpdate({ checkInterval })`. Set `checkInterval` to `0`
139
+ * to disable polling and rely solely on `VERSION_READY`.
140
+ *
141
+ * @default 6 hours (21600000 milliseconds)
142
+ */
143
+ const pwaUpdateDefaultCheckInterval = 6 * 60 * 60 * 1000; // 6 hours
144
+
127
145
  /**
128
146
  * Default session duration (in milliseconds) when no explicit duration is provided.
129
147
  *
@@ -948,6 +966,115 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
948
966
  type: Injectable
949
967
  }] });
950
968
 
969
+ /**
970
+ * Detects newly deployed versions of the application (PWA) and lets the user
971
+ * decide when to apply them.
972
+ *
973
+ * **Flow (per the Angular `SwUpdate` documentation):**
974
+ * 1. **Check** — listens for the `VERSION_READY` event and, additionally, polls
975
+ * `checkForUpdate()` on an interval once the app is stable. The update is
976
+ * *not* applied automatically.
977
+ * 2. **Ask** — when a new version is ready, prompts the user via the framework
978
+ * {@link ConfirmService} dialog instead of reloading silently.
979
+ * 3. **Apply** — only after the user confirms, calls `activateUpdate()` and then
980
+ * reloads the page so the new version takes effect.
981
+ *
982
+ * The service is a no-op when the service worker is disabled (e.g. dev mode),
983
+ * so it is safe to provide unconditionally.
984
+ *
985
+ * Wire it up via `providePwaUpdate()` in `app.config.ts`. The service worker
986
+ * itself must still be registered separately via `provideServiceWorker(...)`.
987
+ *
988
+ * @see https://angular.dev/ecosystem/service-workers/communications
989
+ */
990
+ class PwaUpdateService {
991
+ #swUpdate = inject(SwUpdate);
992
+ #confirm = inject(ConfirmService);
993
+ #translate = inject(TranslateService);
994
+ #appRef = inject(ApplicationRef);
995
+ #destroyRef = inject(DestroyRef);
996
+ /** Guards against opening multiple confirm dialogs while one is already open. */
997
+ #prompting = false;
998
+ /**
999
+ * Starts listening for updates. Called automatically by `providePwaUpdate()`.
1000
+ * Does nothing when the service worker is not enabled.
1001
+ */
1002
+ init(config) {
1003
+ if (!this.#swUpdate.isEnabled) {
1004
+ return;
1005
+ }
1006
+ // 1a. React when the service worker has already downloaded a new version.
1007
+ this.#swUpdate.versionUpdates
1008
+ .pipe(filter((event) => event.type === 'VERSION_READY'), takeUntilDestroyed(this.#destroyRef))
1009
+ .subscribe(() => this.#promptUpdate(config));
1010
+ // 1b. Poll for updates: first once the app is stable, then on an interval.
1011
+ const checkInterval = config?.checkInterval ?? pwaUpdateDefaultCheckInterval;
1012
+ if (checkInterval > 0) {
1013
+ const appStable$ = this.#appRef.isStable.pipe(first((stable) => stable));
1014
+ concat(appStable$, interval(checkInterval))
1015
+ .pipe(takeUntilDestroyed(this.#destroyRef))
1016
+ .subscribe(() => void this.checkForUpdate());
1017
+ }
1018
+ // 1c. Offer a reload when the service worker is in a broken, unrecoverable state.
1019
+ this.#swUpdate.unrecoverable
1020
+ .pipe(takeUntilDestroyed(this.#destroyRef))
1021
+ .subscribe(() => this.#promptUpdate(config, true));
1022
+ }
1023
+ /**
1024
+ * Manually triggers an update check (e.g. from a "check for updates" button).
1025
+ * Resolves to `true` when a new version was found. Network errors are swallowed
1026
+ * and resolve to `false`.
1027
+ */
1028
+ checkForUpdate() {
1029
+ if (!this.#swUpdate.isEnabled) {
1030
+ return Promise.resolve(false);
1031
+ }
1032
+ return this.#swUpdate.checkForUpdate().catch(() => false);
1033
+ }
1034
+ #promptUpdate(config, unrecoverable = false) {
1035
+ if (this.#prompting) {
1036
+ return;
1037
+ }
1038
+ this.#prompting = true;
1039
+ const messageKey = unrecoverable
1040
+ ? (config?.unrecoverableMessage ?? 'yuv.pwa.update.unrecoverable.message')
1041
+ : (config?.message ?? 'yuv.pwa.update.message');
1042
+ this.#confirm
1043
+ .confirm({
1044
+ title: this.#translate.instant(config?.title ?? 'yuv.pwa.update.title'),
1045
+ message: this.#translate.instant(messageKey),
1046
+ confirmLabel: this.#translate.instant(config?.confirmLabel ?? 'yuv.pwa.update.confirm'),
1047
+ cancelLabel: this.#translate.instant(config?.cancelLabel ?? 'yuv.pwa.update.cancel'),
1048
+ // An unrecoverable worker cannot keep serving the app, so reloading is the only option.
1049
+ hideCancelButton: unrecoverable,
1050
+ level: 'info'
1051
+ })
1052
+ .subscribe((confirmed) => {
1053
+ this.#prompting = false;
1054
+ if (confirmed) {
1055
+ this.#applyUpdate(unrecoverable);
1056
+ }
1057
+ });
1058
+ }
1059
+ #applyUpdate(unrecoverable) {
1060
+ // In an unrecoverable state there is no pending version to activate — just reload.
1061
+ if (unrecoverable) {
1062
+ document.location.reload();
1063
+ return;
1064
+ }
1065
+ void this.#swUpdate
1066
+ .activateUpdate()
1067
+ .catch(() => false)
1068
+ .finally(() => document.location.reload());
1069
+ }
1070
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: PwaUpdateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1071
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: PwaUpdateService, providedIn: 'root' }); }
1072
+ }
1073
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: PwaUpdateService, decorators: [{
1074
+ type: Injectable,
1075
+ args: [{ providedIn: 'root' }]
1076
+ }] });
1077
+
951
1078
  const SNACK_BAR_DEFAULT_DURATION = 3000;
952
1079
  class SnackBarService {
953
1080
  #snackBar;
@@ -1520,6 +1647,48 @@ function provideHaloFocus(config) {
1520
1647
  ]);
1521
1648
  }
1522
1649
 
1650
+ /**
1651
+ * Provides and initializes PWA update detection for the application.
1652
+ *
1653
+ * On startup it boots {@link PwaUpdateService}, which watches for newly deployed
1654
+ * versions of the app and, instead of reloading silently, asks the user to
1655
+ * confirm before activating the update and reloading the page.
1656
+ *
1657
+ * **Detection:** reacts to the service worker `VERSION_READY` event and also
1658
+ * polls `checkForUpdate()` on an interval (default 6 hours) once the app is
1659
+ * stable, so long-running sessions still pick up new releases.
1660
+ *
1661
+ * **Prerequisite:** the service worker must be registered separately via
1662
+ * `provideServiceWorker('ngsw-worker.js', { enabled: !isDevMode() })`. When the
1663
+ * service worker is disabled (e.g. dev mode) this provider is a harmless no-op.
1664
+ *
1665
+ * @param config - Optional configuration (check interval, custom dialog labels/messages).
1666
+ * @returns EnvironmentProviders for the PWA update feature.
1667
+ *
1668
+ * @example
1669
+ * // app.config.ts
1670
+ * export const appConfig: ApplicationConfig = {
1671
+ * providers: [
1672
+ * provideServiceWorker('ngsw-worker.js', { enabled: !isDevMode() }),
1673
+ * providePwaUpdate()
1674
+ * ]
1675
+ * };
1676
+ *
1677
+ * @example
1678
+ * // Custom check interval (1 hour) and labels
1679
+ * providePwaUpdate({
1680
+ * checkInterval: 60 * 60 * 1000,
1681
+ * message: 'A new version is available. Reload now?'
1682
+ * });
1683
+ */
1684
+ function providePwaUpdate(config) {
1685
+ return makeEnvironmentProviders([
1686
+ provideAppInitializer(() => {
1687
+ inject(PwaUpdateService).init(config);
1688
+ })
1689
+ ]);
1690
+ }
1691
+
1523
1692
  /**
1524
1693
  * Provides and initializes the SessionService at application startup.
1525
1694
  *
@@ -1586,5 +1755,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
1586
1755
  * Generated bundle index. Do not edit.
1587
1756
  */
1588
1757
 
1589
- export { ChannelMessage, HaloFocusService, HaloUtilityService, SessionService, SnackBarComponent, SnackBarService, YuuvisClientFrameworkModule, defaultHaloFocusOffset, haloExcludedElementsInMatFormField, haloFocusNavigationKeys, haloFocusStyles, provideHaloFocus, provideSession, sessionActivityWindowBeforeEnd, sessionDefaultDuration, sessionPopupBeforeEnd };
1758
+ export { ChannelMessage, HaloFocusService, HaloUtilityService, PwaUpdateService, SessionService, SnackBarComponent, SnackBarService, YuuvisClientFrameworkModule, defaultHaloFocusOffset, haloExcludedElementsInMatFormField, haloFocusNavigationKeys, haloFocusStyles, provideHaloFocus, providePwaUpdate, provideSession, pwaUpdateDefaultCheckInterval, sessionActivityWindowBeforeEnd, sessionDefaultDuration, sessionPopupBeforeEnd };
1590
1759
  //# sourceMappingURL=yuuvis-client-framework.mjs.map