ngx-pwa
Version:
Provides functionality around the progressive web app functionality in angular. Most notably the an approach to cache POST, UPDATE and DELETE requests.
1 lines • 62.3 kB
Source Map (JSON)
{"version":3,"file":"ngx-pwa.mjs","sources":["../../../projects/ngx-pwa/src/encapsulation/lodash.utilities.ts","../../../projects/ngx-pwa/src/models/http-method.enum.ts","../../../projects/ngx-pwa/src/services/offline.service.ts","../../../projects/ngx-pwa/src/encapsulation/purify.utilities.ts","../../../projects/ngx-pwa/src/models/synchronize-dialog-data-internal.model.ts","../../../projects/ngx-pwa/src/components/synchronize-dialog/synchronize-dialog.component.ts","../../../projects/ngx-pwa/src/components/synchronize-dialog/synchronize-dialog.component.html","../../../projects/ngx-pwa/src/components/synchronize-badge/synchronize-badge.component.ts","../../../projects/ngx-pwa/src/components/synchronize-badge/synchronize-badge.component.html","../../../projects/ngx-pwa/src/components/offline-status-bar/offline-status-bar.component.ts","../../../projects/ngx-pwa/src/components/offline-status-bar/offline-status-bar.component.html","../../../projects/ngx-pwa/src/models/version-ready-dialog-data-internal.model.ts","../../../projects/ngx-pwa/src/components/version-ready-dialog/version-ready-dialog.component.ts","../../../projects/ngx-pwa/src/components/version-ready-dialog/version-ready-dialog.component.html","../../../projects/ngx-pwa/src/models/request-metadata.model.ts","../../../projects/ngx-pwa/src/services/notification.service.ts","../../../projects/ngx-pwa/src/encapsulation/uuid.utilities.ts","../../../projects/ngx-pwa/src/models/request-metadata-internal.model.ts","../../../projects/ngx-pwa/src/services/offline-request.interceptor.ts","../../../projects/ngx-pwa/src/services/update.service.ts","../../../projects/ngx-pwa/src/public-api.ts","../../../projects/ngx-pwa/src/ngx-pwa.ts"],"sourcesContent":["import { cloneDeep, Many, omit } from 'lodash';\n\n/**\n * Encapsulates functionality of lodash.\n */\nexport abstract class LodashUtilities {\n /**\n * The opposite of `_.pick`; this method creates an object composed of the\n * own and inherited enumerable properties of `object` that are not omitted.\n * @param object - The source object.\n * @param paths - The property names to omit, specified\n * individually or in arrays.\n * @returns Returns the new object.\n */\n static omit<T extends object, K extends keyof T>(object: T | null | undefined, ...paths: Many<K>[]): Omit<T, K> {\n return omit(object, ...paths);\n }\n\n /**\n * This method is like _.clone except that it recursively clones value.\n * @param value - The value to recursively clone.\n * @returns Returns the deep cloned value.\n */\n static cloneDeep<T>(value: T): T {\n return cloneDeep(value);\n }\n}","/**\n * Some common http methods.\n */\nexport enum HttpMethod {\n POST = 'POST',\n GET = 'GET',\n PATCH = 'PATCH',\n DELETE = 'DELETE'\n}","import { isPlatformBrowser } from '@angular/common';\nimport { HttpClient, HttpRequest } from '@angular/common/http';\nimport { Inject, InjectionToken, NgZone, PLATFORM_ID } from '@angular/core';\nimport { MatSnackBar } from '@angular/material/snack-bar';\nimport { BehaviorSubject, Observable, firstValueFrom } from 'rxjs';\n\nimport { LodashUtilities } from '../encapsulation/lodash.utilities';\nimport { HttpMethod } from '../models/http-method.enum';\nimport { RequestMetadataInternal } from '../models/request-metadata-internal.model';\n\n/**\n * The Generic Base EntityType.\n */\nexport type BaseEntityType<T> = { [K in keyof T]: unknown };\n\n// eslint-disable-next-line jsdoc/require-jsdoc, typescript/typedef\nexport const NGX_PWA_OFFLINE_SERVICE = new InjectionToken(\n 'Provider for the OfflineService used eg. in the offline request interceptor.',\n {\n providedIn: 'root',\n factory: () => {\n // eslint-disable-next-line no-console\n console.error(\n // eslint-disable-next-line stylistic/max-len\n 'No OfflineService has been provided for the token NGX_OFFLINE_SERVICE\\nAdd this to your app.module.ts provider array:\\n{\\n provide: NGX_PWA_OFFLINE_SERVICE,\\n useExisting: MyOfflineService\\n}'\n );\n }\n }\n);\n\n/**\n * The type of a cached offline request.\n * Contains the http request as well as some metadata.\n */\nexport interface CachedRequest<T> {\n /**\n * The actual http request.\n */\n request: HttpRequest<T>,\n /**\n * The metadata for that request.\n */\n metadata: RequestMetadataInternal\n}\n\n/**\n * The base class for an offline service.\n */\nexport class NgxPwaOfflineService {\n /**\n * The key under which any requests are saved in local storage.\n */\n readonly CACHED_REQUESTS_KEY: string = 'requests';\n\n /**\n * The prefix of offline generated ids.\n * Is used to check if a request still has unresolved dependencies.\n */\n readonly OFFLINE_ID_PREFIX: string = 'offline';\n\n /**\n * A snackbar message to display when the synchronization of all cached requests has been finished.\n */\n protected readonly ALL_SYNC_FINISHED_SNACK_BAR_MESSAGE: string = 'Synchronization finished';\n\n /**\n * A snackbar message to display when the synchronization of all cached requests fails.\n */\n protected readonly ALL_SYNC_FAILED_SNACK_BAR_MESSAGE: string = 'Synchronization failed, please try again later';\n\n /**\n * A snackbar message to display when the synchronization of a single cached requests has been finished.\n */\n protected readonly SINGLE_SYNC_FINISHED_SNACK_BAR_MESSAGE: string = 'Synchronization finished';\n\n /**\n * A snackbar message to display when the synchronization of a single cached requests fails.\n */\n protected readonly SINGLE_SYNC_FAILED_SNACK_BAR_MESSAGE: string = 'Synchronization failed, please try again later';\n\n /**\n * Whether or not the user has no internet connection.\n */\n isOffline: boolean = false;\n\n /**\n * A subject of all the requests that have been done while offline.\n * Needs to be used for applying offline data or syncing the requests to the api.\n */\n private readonly cachedRequestsSubject: BehaviorSubject<CachedRequest<unknown>[]>;\n\n // eslint-disable-next-line jsdoc/require-returns\n /**\n * The currently stored cached requests (if there are any).\n */\n get cachedRequests(): CachedRequest<unknown>[] {\n return this.cachedRequestsSubject.value;\n }\n\n set cachedRequests(cachedRequests: CachedRequest<unknown>[]) {\n if (!isPlatformBrowser(this.platformId)) {\n return;\n }\n localStorage.setItem(this.CACHED_REQUESTS_KEY, JSON.stringify(cachedRequests));\n this.cachedRequestsSubject.next(cachedRequests);\n }\n\n constructor(\n private readonly http: HttpClient,\n private readonly snackBar: MatSnackBar,\n private readonly zone: NgZone,\n @Inject(PLATFORM_ID)\n private readonly platformId: Object\n ) {\n if (!isPlatformBrowser(platformId)) {\n this.isOffline = false;\n this.cachedRequestsSubject = new BehaviorSubject<CachedRequest<unknown>[]>([]);\n return;\n }\n this.isOffline = !navigator.onLine;\n window.ononline = () => this.isOffline = !navigator.onLine;\n window.onoffline = () => this.isOffline = !navigator.onLine;\n\n const stringData: string | null = localStorage.getItem(this.CACHED_REQUESTS_KEY);\n const requestsData: CachedRequest<unknown>[] = stringData ? JSON.parse(stringData) as CachedRequest<unknown>[] : [];\n this.cachedRequestsSubject = new BehaviorSubject(requestsData);\n }\n\n /**\n * Applies any offline data that has been cached to the given values.\n * @param type - The type of the provided entities. Is needed to check if any cached requests of the same type exist.\n * @param entities - The already existing data.\n * @returns The already existing entities extended/modified by the offline cached requests.\n */\n applyOfflineData<EntityType extends BaseEntityType<EntityType>>(\n type: string,\n entities: EntityType[]\n ): EntityType[] {\n if (!this.cachedRequests.length) {\n return entities;\n }\n const res: EntityType[] = Array.from(entities);\n const cachedRequests: CachedRequest<unknown>[] = this.cachedRequests.filter(req => req.metadata.type === type);\n for (const req of cachedRequests) {\n switch (req.request.method) {\n case HttpMethod.POST: {\n res.push(req.request.body as EntityType);\n break;\n }\n case HttpMethod.PATCH: {\n const patchIdKey: keyof EntityType = req.metadata.idKey;\n const index: number = res.findIndex(e => req.request.urlWithParams.includes(`${e[patchIdKey]}`));\n res[index] = this.updateOffline(req.request.body as EntityType, res[index]);\n break;\n }\n case HttpMethod.DELETE: {\n const deleteIdKey: keyof EntityType = req.metadata.idKey;\n res.splice(res.findIndex(e => req.request.urlWithParams.includes(`${e[deleteIdKey]}`)), 1);\n break;\n }\n default: {\n // eslint-disable-next-line no-console\n console.error('There was an unknown http-method in one of your cached offline requests:', req.request.method);\n break;\n }\n }\n }\n return res;\n }\n\n /**\n * Applies an UPDATE to an entity without sending a request to the server.\n * @param changes - The changes that should be made to the entity.\n * @param entity - The entity that should be updated.\n * @returns The updated entity.\n */\n protected updateOffline<EntityType extends BaseEntityType<EntityType>>(\n changes: Partial<EntityType>,\n entity: EntityType\n ): EntityType {\n for (const key in changes) {\n entity[key] = changes[key] as EntityType[Extract<keyof EntityType, string>];\n }\n return entity;\n }\n\n /**\n * Sends a specific cached request to the server.\n * @param request - The request that should be synced.\n */\n async sync<T>(request: CachedRequest<T>): Promise<void> {\n const cachedRequestsPriorChanges: CachedRequest<unknown>[] = LodashUtilities.cloneDeep(this.cachedRequests);\n try {\n const res: Awaited<T> = await this.syncSingleRequest(request);\n this.zone.run(() => {\n this.snackBar.open(this.SINGLE_SYNC_FINISHED_SNACK_BAR_MESSAGE, undefined, { duration: 2500 });\n });\n this.removeSingleRequest(request);\n this.updateOfflineIdsInRequests(request, res);\n }\n catch {\n this.zone.run(() => {\n this.snackBar.open(this.SINGLE_SYNC_FAILED_SNACK_BAR_MESSAGE, undefined, { duration: 2500 });\n });\n this.cachedRequests = cachedRequestsPriorChanges;\n }\n }\n\n /**\n * Sends all cached requests to the server. Tries to handle dependencies of requests on each other.\n */\n async syncAll(): Promise<void> {\n const cachedRequestsPriorChanges: CachedRequest<unknown>[] = LodashUtilities.cloneDeep(this.cachedRequests);\n try {\n await this.syncAllRecursive();\n this.zone.run(() => {\n this.snackBar.open(this.ALL_SYNC_FINISHED_SNACK_BAR_MESSAGE, undefined, { duration: 2500 });\n });\n this.cachedRequests = [];\n }\n catch {\n this.zone.run(() => {\n this.snackBar.open(this.ALL_SYNC_FAILED_SNACK_BAR_MESSAGE, undefined, { duration: 2500 });\n });\n this.cachedRequests = cachedRequestsPriorChanges;\n }\n }\n\n /**\n * The recursive method used to syn all requests to the api.\n */\n protected async syncAllRecursive(): Promise<void> {\n // eslint-disable-next-line stylistic/max-len\n const request: CachedRequest<BaseEntityType<unknown>> | undefined = this.cachedRequests.find(r => !this.hasUnresolvedDependency(r)) as CachedRequest<BaseEntityType<unknown>> | undefined;\n if (!request) {\n return;\n }\n const res: BaseEntityType<unknown> = await this.syncSingleRequest(request);\n this.updateOfflineIdsInRequests(request, res);\n await this.syncAllRecursive();\n }\n\n /**\n * Sends a single cached request to the server.\n * @param request - The request that should be synced.\n * @returns A promise of the request result.\n */\n protected async syncSingleRequest<T>(\n request: CachedRequest<T>\n ): Promise<T> {\n if (this.isOffline || this.hasUnresolvedDependency(request)) {\n throw new Error('Could not sync the request');\n }\n const requestObservable: Observable<T> | undefined = this.request(request);\n if (!requestObservable) {\n throw new Error('Could not sync the request');\n }\n return await firstValueFrom(requestObservable);\n }\n\n private updateOfflineIdsInRequests<T>(request: CachedRequest<T>, res: T): void {\n if (this.cachedRequests.length && request.request.body != undefined) {\n const idKey: keyof BaseEntityType<unknown> = request.metadata.idKey;\n if (res[idKey] != undefined) {\n // TODO: Investigate\n // eslint-disable-next-line typescript/no-base-to-string\n const requestsString: string = `${this.cachedRequests}`.split(request.request.body[idKey]).join(res[idKey]);\n this.cachedRequests = JSON.parse(requestsString) as CachedRequest<T>[];\n }\n }\n }\n\n /**\n * Calls http.post/patch/delete etc. On the provided request.\n * @param request - The request that should be sent.\n * @returns The observable of the request or undefined if something went wrong.\n */\n protected request<EntityType extends BaseEntityType<EntityType>>(\n request: CachedRequest<EntityType>\n ): Observable<EntityType> | undefined {\n switch (request.request.method) {\n case HttpMethod.POST: {\n return this.http.post<EntityType>(\n request.request.urlWithParams,\n LodashUtilities.omit(request.request.body, request.metadata.idKey)\n );\n }\n case HttpMethod.PATCH: {\n return this.http.patch<EntityType>(request.request.urlWithParams, request.request.body);\n }\n case HttpMethod.DELETE: {\n return this.http.delete<EntityType>(request.request.urlWithParams);\n }\n default: {\n return undefined;\n }\n }\n }\n\n /**\n * Checks if the given request has an unresolved dependency by looking for the keyword 'offline' inside of it.\n * @param request - The request that should be checked.\n * @returns Whether or no the given request has an unresolved dependency.\n */\n hasUnresolvedDependency(request: CachedRequest<unknown>): boolean {\n return request.request.urlWithParams.includes(this.OFFLINE_ID_PREFIX)\n || `${request.request.body}`.includes(this.OFFLINE_ID_PREFIX);\n }\n\n /**\n * Removes a single request from the cache.\n * @param request - The request that should be removed.\n */\n removeSingleRequest(request: CachedRequest<unknown>): void {\n this.cachedRequests.splice(this.cachedRequests.indexOf(request), 1);\n this.cachedRequests = this.cachedRequests;\n }\n}","import purify from 'dompurify';\n\n/**\n * Contains HelperMethods around handling the purification of html strings.\n * Is less strict than angular's own sanitizer.\n */\nexport abstract class PurifyUtilities {\n\n /**\n * Sanitizes the given source string.\n * @param source - The html value as a string.\n * @returns A sanitized string of the given source.\n */\n static sanitize(source: string): string {\n return purify.sanitize(source);\n }\n}","import { SynchronizeDialogData } from './synchronize-dialog-data.model';\n\n/**\n * The internal dialog data for the synchronize dialog.\n * Sets default values.\n */\nexport class SynchronizeDialogDataInternal implements SynchronizeDialogData {\n // eslint-disable-next-line jsdoc/require-jsdoc\n title: string;\n // eslint-disable-next-line jsdoc/require-jsdoc\n closeButtonLabel: string;\n // eslint-disable-next-line jsdoc/require-jsdoc\n syncAllButtonLabel: string;\n // eslint-disable-next-line jsdoc/require-jsdoc\n undoAllButtonLabel: string;\n\n constructor(data?: SynchronizeDialogData) {\n this.title = data?.title ?? 'Sync';\n this.closeButtonLabel = data?.closeButtonLabel ?? 'Ok';\n this.syncAllButtonLabel = data?.syncAllButtonLabel ?? 'Sync all';\n this.undoAllButtonLabel = data?.undoAllButtonLabel ?? 'Undo all';\n }\n}","import { CommonModule } from '@angular/common';\nimport { Component, Inject, OnInit } from '@angular/core';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';\nimport { MatDividerModule } from '@angular/material/divider';\nimport { DomSanitizer } from '@angular/platform-browser';\n\nimport { PurifyUtilities } from '../../encapsulation/purify.utilities';\nimport { SynchronizeDialogDataInternal } from '../../models/synchronize-dialog-data-internal.model';\nimport { SynchronizeDialogData } from '../../models/synchronize-dialog-data.model';\nimport { CachedRequest, NGX_PWA_OFFLINE_SERVICE, NgxPwaOfflineService } from '../../services/offline.service';\n\n/**\n * The dialog for syncing cached requests to the server.\n */\n@Component({\n selector: 'ngx-pwa-synchronize-dialog',\n templateUrl: './synchronize-dialog.component.html',\n styleUrls: ['./synchronize-dialog.component.scss'],\n standalone: true,\n imports: [\n CommonModule,\n MatButtonModule,\n MatDividerModule,\n MatDialogModule\n ]\n})\nexport class NgxPwaSynchronizeDialogComponent<OfflineServiceType extends NgxPwaOfflineService> implements OnInit {\n\n // eslint-disable-next-line jsdoc/require-jsdoc\n PurifyUtilities: typeof PurifyUtilities = PurifyUtilities;\n\n /**\n * The provided dialog data filled up with default values.\n */\n dialogData!: SynchronizeDialogDataInternal;\n\n constructor(\n @Inject(NGX_PWA_OFFLINE_SERVICE)\n readonly offlineService: OfflineServiceType,\n readonly sanitizer: DomSanitizer,\n private readonly dialogRef: MatDialogRef<NgxPwaSynchronizeDialogComponent<OfflineServiceType>>,\n @Inject(MAT_DIALOG_DATA)\n readonly data: SynchronizeDialogData\n ) { }\n\n ngOnInit(): void {\n this.dialogData = new SynchronizeDialogDataInternal(this.data);\n }\n\n /**\n * Sends a specific cached request to the server.\n * @param request - The request that should be synced.\n */\n async syncSingleRequest(request: CachedRequest<unknown>): Promise<void> {\n await this.offlineService.sync(request);\n if (!this.offlineService.cachedRequests.length) {\n this.dialogRef.close();\n }\n }\n\n /**\n * Removes a single request from the cache.\n * @param request - The request that should be removed.\n */\n removeSingleRequest(request: CachedRequest<unknown>): void {\n this.offlineService.removeSingleRequest(request);\n if (!this.offlineService.cachedRequests.length) {\n this.dialogRef.close();\n }\n }\n\n /**\n * Sends all cached requests to the server. Tries to handle dependencies of requests on each other.\n */\n async syncAll(): Promise<void> {\n await this.offlineService.syncAll();\n if (!this.offlineService.cachedRequests.length) {\n this.dialogRef.close();\n }\n }\n\n /**\n * Removes all locally cached requests.\n */\n undoAll(): void {\n this.offlineService.cachedRequests = [];\n this.dialogRef.close();\n }\n\n /**\n * Closes the dialog.\n */\n close(): void {\n this.dialogRef.close();\n }\n}","<h1 mat-dialog-title>{{dialogData.title}}</h1>\n<div mat-dialog-content>\n <div class=\"all-button-row\">\n <button type=\"button\" mat-raised-button [disabled]=\"offlineService.isOffline\" (click)=\"syncAll()\">\n {{dialogData.syncAllButtonLabel}}\n </button>\n <button type=\"button\" mat-raised-button (click)=\"undoAll()\">\n {{dialogData.undoAllButtonLabel}}\n </button>\n </div>\n <div class=\"mat-elevation-z4 request-box\">\n @for (request of offlineService.cachedRequests; track $index) {\n <div class=\"request-item\">\n <!-- eslint-disable-next-line angular/no-call-expression -->\n <div [innerHtml]=\"sanitizer.bypassSecurityTrustHtml(PurifyUtilities.sanitize(request.metadata.displayValue))\">\n </div>\n <span>\n <!-- eslint-disable-next-line angular/no-call-expression -->\n <button type=\"button\" mat-icon-button [disabled]=\"offlineService.isOffline || offlineService.hasUnresolvedDependency(request)\" (click)=\"syncSingleRequest(request)\">\n <i class=\"fas fa-upload\"></i>\n </button>\n <button type=\"button\" mat-icon-button color=\"warn\" (click)=\"removeSingleRequest(request)\">\n <i class=\"fas fa-trash\"></i>\n </button>\n </span>\n </div>\n <mat-divider></mat-divider>\n }\n </div>\n</div>\n<div mat-dialog-actions>\n <button type=\"button\" mat-raised-button class=\"cancel-button\" (click)=\"close()\">{{dialogData.closeButtonLabel}}</button>\n</div>","import { CommonModule } from '@angular/common';\nimport { Component, Inject, Input } from '@angular/core';\nimport { MatBadgeModule } from '@angular/material/badge';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatDialog, MatDialogModule } from '@angular/material/dialog';\n\nimport { SynchronizeDialogData } from '../../models/synchronize-dialog-data.model';\nimport { NGX_PWA_OFFLINE_SERVICE, NgxPwaOfflineService } from '../../services/offline.service';\nimport { NgxPwaSynchronizeDialogComponent } from '../synchronize-dialog/synchronize-dialog.component';\n\n/**\n * Displays a badge with the amount of cached offline request.\n * Can be clicked to open a dialog to sync cached requests to the server.\n */\n@Component({\n selector: 'ngx-pwa-synchronize-badge',\n templateUrl: './synchronize-badge.component.html',\n styleUrls: ['./synchronize-badge.component.scss'],\n standalone: true,\n imports: [\n CommonModule,\n MatButtonModule,\n MatBadgeModule,\n MatDialogModule\n ]\n})\nexport class NgxPwaSynchronizeBadgeComponent<OfflineServiceType extends NgxPwaOfflineService> {\n\n /**\n * Configuration data for the Synchronize Dialog.\n */\n @Input()\n synchronizeDialogData?: SynchronizeDialogData;\n\n constructor(\n @Inject(NGX_PWA_OFFLINE_SERVICE)\n readonly offlineService: OfflineServiceType,\n private readonly dialog: MatDialog\n ) { }\n\n /**\n * Opens the dialog for syncing cached requests to the server.\n */\n openSyncDialog(): void {\n this.dialog.open(\n NgxPwaSynchronizeDialogComponent,\n {\n autoFocus: false,\n restoreFocus: false,\n minWidth: '40%',\n data: this.synchronizeDialogData\n }\n );\n }\n}","@if (offlineService.cachedRequests.length) {\n <button type=\"button\" mat-button (click)=\"openSyncDialog()\">\n <i class=\"fas fa-rotate\" matBadgePosition=\"above after\" matBadgeColor=\"warn\" [matBadge]=\"offlineService.cachedRequests.length\"></i>\n </button>\n}","import { CommonModule } from '@angular/common';\nimport { Component, Inject, Input, OnInit } from '@angular/core';\n\nimport { SynchronizeDialogData } from '../../models/synchronize-dialog-data.model';\nimport { NgxPwaOfflineService, NGX_PWA_OFFLINE_SERVICE } from '../../services/offline.service';\nimport { NgxPwaSynchronizeBadgeComponent } from '../synchronize-badge/synchronize-badge.component';\n\n/**\n * Shows a offline warning when the user is not online.\n */\n@Component({\n selector: 'ngx-pwa-offline-status-bar',\n templateUrl: './offline-status-bar.component.html',\n styleUrls: ['./offline-status-bar.component.scss'],\n standalone: true,\n imports: [\n CommonModule,\n NgxPwaSynchronizeBadgeComponent\n ]\n})\nexport class NgxPwaOfflineStatusBarComponent<OfflineServiceType extends NgxPwaOfflineService> implements OnInit {\n /**\n * The message to display when the user is offline.\n * @default 'Offline'\n */\n @Input()\n offlineMessage!: string;\n\n /**\n * The message to display when the user has changes that aren't synced to the api.\n * @default 'Unsaved Changes'\n */\n @Input()\n unsavedChangesMessage!: string;\n\n /**\n * Whether or not to display a badge that shows the amount of cached requests and can open a dialog to sync changes to the server.\n */\n @Input()\n displayUnsavedChangesSynchronizeBadge!: boolean;\n\n /**\n * Configuration data for the Synchronize Dialog.\n */\n @Input()\n synchronizeDialogData?: SynchronizeDialogData;\n\n constructor(\n @Inject(NGX_PWA_OFFLINE_SERVICE)\n readonly offlineService: OfflineServiceType\n ) { }\n\n ngOnInit(): void {\n this.offlineMessage = this.offlineMessage ?? 'Offline';\n this.unsavedChangesMessage = this.unsavedChangesMessage ?? 'Unsaved Changes';\n this.displayUnsavedChangesSynchronizeBadge = this.displayUnsavedChangesSynchronizeBadge ?? true;\n }\n}","@if (offlineService.isOffline) {\n <div>\n {{offlineMessage}}\n @if (displayUnsavedChangesSynchronizeBadge) {\n <ngx-pwa-synchronize-badge [synchronizeDialogData]=\"synchronizeDialogData\"></ngx-pwa-synchronize-badge>\n }\n </div>\n}\n@else if (offlineService.cachedRequests.length) {\n <div>\n {{unsavedChangesMessage}}\n @if (displayUnsavedChangesSynchronizeBadge) {\n <ngx-pwa-synchronize-badge [synchronizeDialogData]=\"synchronizeDialogData\"></ngx-pwa-synchronize-badge>\n }\n </div>\n}","import { VersionReadyDialogData } from './version-ready-dialog-data.model';\n\n/**\n * The internal data to customize the Version Ready Dialog.\n * Sets default values.\n */\nexport class VersionReadyDialogDataInternal implements VersionReadyDialogData {\n // eslint-disable-next-line jsdoc/require-jsdoc\n title: string;\n // eslint-disable-next-line jsdoc/require-jsdoc\n message: string;\n // eslint-disable-next-line jsdoc/require-jsdoc\n confirmButtonLabel: string;\n // eslint-disable-next-line jsdoc/require-jsdoc\n cancelButtonLabel: string;\n\n constructor(data?: VersionReadyDialogData) {\n this.title = data?.title ?? 'New Update available';\n this.message = data?.message ?? 'A new version has been downloaded. Do you want to install it now?';\n this.confirmButtonLabel = data?.confirmButtonLabel ?? 'Reload';\n this.cancelButtonLabel = data?.cancelButtonLabel ?? 'Not now';\n }\n}","import { Component, Inject, OnInit } from '@angular/core';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';\n\nimport { VersionReadyDialogDataInternal } from '../../models/version-ready-dialog-data-internal.model';\nimport { VersionReadyDialogData } from '../../models/version-ready-dialog-data.model';\n\n/**\n * A dialog that gets displayed when a new version of the pwa has been downloaded and is ready for install.\n */\n@Component({\n selector: 'ngx-pwa-version-ready-dialog',\n templateUrl: './version-ready-dialog.component.html',\n styleUrls: ['./version-ready-dialog.component.scss'],\n standalone: true,\n imports: [\n MatButtonModule,\n MatDialogModule\n ]\n})\nexport class NgxPwaVersionReadyDialogComponent implements OnInit {\n\n /**\n * The data to customize the Version Ready Dialog.\n * Is built from the MAT_DIALOG_DATA input.\n */\n versionReadyDialogData!: VersionReadyDialogDataInternal;\n\n constructor(\n private readonly dialogRef: MatDialogRef<NgxPwaVersionReadyDialogComponent>,\n @Inject(MAT_DIALOG_DATA)\n readonly data?: VersionReadyDialogData\n ) { }\n\n ngOnInit(): void {\n this.versionReadyDialogData = new VersionReadyDialogDataInternal(this.data);\n }\n\n /**\n * Closes the dialog with data to trigger a reload of the app.\n */\n update(): void {\n this.dialogRef.close('update');\n }\n\n /**\n * Closes the dialog with data to not trigger anything.\n */\n cancel(): void {\n this.dialogRef.close('cancel');\n }\n}","<h1 mat-dialog-title>{{versionReadyDialogData.title}}</h1>\n<mat-dialog-content>\n {{versionReadyDialogData.message}}\n</mat-dialog-content>\n<mat-dialog-actions>\n <button type=\"button\" mat-raised-button (click)=\"update()\">{{versionReadyDialogData.confirmButtonLabel}}</button>\n <button type=\"button\" mat-raised-button (click)=\"cancel()\">{{versionReadyDialogData.cancelButtonLabel}}</button>\n</mat-dialog-actions>","import { HttpContextToken } from '@angular/common/http';\n\nimport { BaseEntityType } from '../services/offline.service';\n\n/**\n * Model for providing information about a request.\n * Is needed for various things when the request is cached locally.\n */\nexport interface RequestMetadata {\n /**\n * The idKey of the request.\n * @default 'id'\n */\n idKey?: keyof BaseEntityType<unknown>,\n /**\n * The type of the request body.\n * Is needed to apply offline request to local data.\n */\n type: string,\n /**\n * How to display the request inside the sync dialog.\n * Can use html.\n */\n displayValue?: string\n}\n\n// eslint-disable-next-line stylistic/max-len, jsdoc/require-jsdoc\nexport const NGX_PWA_HTTP_CONTEXT_METADATA: HttpContextToken<RequestMetadata | undefined> = new HttpContextToken<RequestMetadata | undefined>(() => undefined);","import { HttpClient } from '@angular/common/http';\nimport { SwPush } from '@angular/service-worker';\nimport { firstValueFrom } from 'rxjs';\n\n/**\n * A base service that provides functionality regarding notifications.\n */\nexport abstract class NgxPwaNotificationService {\n\n /**\n * The url to send a new push subscription to.\n */\n abstract readonly API_ENABLE_NOTIFICATIONS_URL: string;\n\n /**\n * The url to send a request to when wanting to disable notifications.\n */\n abstract readonly API_DISABLE_NOTIFICATIONS_URL: string;\n\n /**\n * The public key of your VAPID key pair.\n * Is needed to receive and display push notifications.\n */\n abstract readonly VAPID_PUBLIC_KEY: string;\n\n // eslint-disable-next-line jsdoc/require-returns\n /**\n * Whether or not the current user has notifications enabled.\n */\n get hasNotificationsEnabled(): boolean {\n return this.swPush.isEnabled;\n }\n\n constructor(private readonly swPush: SwPush, private readonly http: HttpClient) {}\n\n /**\n * Asks the user for permission to use push notifications.\n */\n async askForNotificationPermission(): Promise<void> {\n const pushSubscription: PushSubscription = await this.swPush.requestSubscription({ serverPublicKey: this.VAPID_PUBLIC_KEY });\n void this.enableNotifications(pushSubscription);\n }\n\n /**\n * Enables notifications by sending a push subscription to the server.\n * @param pushSubscription - The push subscription to send to the server.\n */\n protected async enableNotifications(pushSubscription: PushSubscription): Promise<void> {\n await firstValueFrom(this.http.post(this.API_ENABLE_NOTIFICATIONS_URL, pushSubscription));\n }\n\n /**\n * Disables notifications.\n */\n async disableNotifications(): Promise<void> {\n const pushSubscription: PushSubscription | null = await firstValueFrom(this.swPush.subscription);\n if (pushSubscription == undefined) {\n return;\n }\n await firstValueFrom(this.http.post(this.API_DISABLE_NOTIFICATIONS_URL, pushSubscription));\n await this.swPush.unsubscribe();\n }\n}","import { v4 } from 'uuid';\n/**\n * Encapsulates functionality of uuid.\n */\nexport abstract class UuidUtilities {\n /**\n * Generates a uuid.\n * @returns A random new uuid.\n */\n static generate(): string {\n return v4();\n }\n}","/* eslint-disable jsdoc/require-jsdoc */\nimport { HttpRequest } from '@angular/common/http';\n\nimport { HttpMethod } from './http-method.enum';\nimport { RequestMetadata } from './request-metadata.model';\nimport { BaseEntityType } from '../services/offline.service';\n\n/**\n * The internal request metadata.\n * Sets default values.\n */\nexport class RequestMetadataInternal implements RequestMetadata {\n idKey: keyof BaseEntityType<unknown>;\n type: string;\n displayValue: string;\n\n constructor(request: HttpRequest<unknown>, data?: RequestMetadata) {\n this.idKey = data?.idKey ?? 'id' as keyof BaseEntityType<unknown>;\n this.type = data?.type ?? '';\n this.displayValue = data?.displayValue ?? defaultCachedRequestMetadata(request);\n }\n}\n\nfunction defaultCachedRequestMetadata(req: HttpRequest<unknown>): string {\n const color: string = getColorForHttpMethod(req.method);\n return `<b style=\"color: ${color};\">${req.method}</b> ${req.url}`;\n}\n\nfunction getColorForHttpMethod(method: string): string {\n switch (method) {\n case HttpMethod.POST: {\n return 'green';\n }\n case HttpMethod.PATCH: {\n return 'black';\n }\n case HttpMethod.DELETE: {\n return 'red';\n }\n default: {\n return 'black';\n }\n }\n}","import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';\nimport { Inject, Injectable } from '@angular/core';\nimport { Observable } from 'rxjs';\n\nimport { CachedRequest, NgxPwaOfflineService, NGX_PWA_OFFLINE_SERVICE } from './offline.service';\nimport { UuidUtilities } from '../encapsulation/uuid.utilities';\nimport { HttpMethod } from '../models/http-method.enum';\nimport { RequestMetadataInternal } from '../models/request-metadata-internal.model';\nimport { NGX_PWA_HTTP_CONTEXT_METADATA, RequestMetadata } from '../models/request-metadata.model';\n\n/**\n * An interceptor that caches any POST, UPDATE or DELETE requests when the user is offline.\n */\n@Injectable()\nexport class OfflineRequestInterceptor<OfflineServiceType extends NgxPwaOfflineService> implements HttpInterceptor {\n\n constructor(\n @Inject(NGX_PWA_OFFLINE_SERVICE)\n private readonly offlineService: OfflineServiceType\n ) { }\n\n // eslint-disable-next-line jsdoc/require-jsdoc\n intercept<T>(req: HttpRequest<T>, next: HttpHandler): Observable<HttpEvent<T>> {\n if (!this.requestShouldBeCached(req)) {\n return next.handle(req);\n }\n const metadata: RequestMetadataInternal = this.getRequestMetadata(req);\n if (req.method === HttpMethod.POST && req.body != undefined) {\n (req.body[metadata.idKey] as unknown as string) = `${this.offlineService.OFFLINE_ID_PREFIX} ${UuidUtilities.generate()}`;\n }\n const cachedRequest: CachedRequest<T> = {\n request: req,\n metadata: metadata\n };\n this.offlineService.cachedRequests = this.offlineService.cachedRequests.concat(cachedRequest);\n return next.handle(req);\n }\n\n private getRequestMetadata(request: HttpRequest<unknown>): RequestMetadataInternal {\n const metadata: RequestMetadata | undefined = request.context.get(NGX_PWA_HTTP_CONTEXT_METADATA);\n if (!metadata) {\n // eslint-disable-next-line no-console\n console.error('No metadata for the request', request.urlWithParams, ' was found.\\nUsing fallback default values.');\n }\n const internalMetadata: RequestMetadataInternal = new RequestMetadataInternal(request, metadata);\n return internalMetadata;\n }\n\n private requestShouldBeCached(req: HttpRequest<unknown>): boolean {\n return this.offlineService.isOffline\n && this.requestMethodIsPostPatchOrDelete(req)\n && !this.urlShouldNotBeCached(req.url);\n }\n\n private requestMethodIsPostPatchOrDelete(req: HttpRequest<unknown>): boolean {\n return req.method === HttpMethod.POST || req.method === HttpMethod.PATCH || req.method === HttpMethod.DELETE;\n }\n\n private urlShouldNotBeCached(url: string): boolean {\n return url.endsWith('/login')\n || url.endsWith('/register')\n || url.endsWith('/refresh-token')\n || url.endsWith('/request-reset-password')\n || url.endsWith('/confirm-reset-password')\n || url.endsWith('/verify-password-reset-token');\n }\n}","import { MatDialog, MatDialogRef } from '@angular/material/dialog';\nimport { SwUpdate } from '@angular/service-worker';\nimport { firstValueFrom } from 'rxjs';\n\nimport { NgxPwaVersionReadyDialogComponent } from '../components/version-ready-dialog/version-ready-dialog.component';\n\n/**\n * Provides helpers for handling pwa version updates.\n */\nexport class NgxPwaUpdateService {\n\n constructor(private readonly swUpdate: SwUpdate, private readonly dialog: MatDialog) { }\n\n /**\n * Subscribes to any version update events.\n */\n subscribeToUpdateEvents(): void {\n if (!this.swUpdate.isEnabled) {\n return;\n }\n this.swUpdate.versionUpdates.subscribe(e => {\n switch (e.type) {\n case 'VERSION_READY': {\n void this.onVersionReady();\n break;\n }\n case 'VERSION_DETECTED': {\n this.onVersionDetected();\n break;\n }\n case 'VERSION_INSTALLATION_FAILED': {\n this.onVersionInstallationFailed();\n break;\n }\n case 'NO_NEW_VERSION_DETECTED': {\n this.onNoNewVersionDetected();\n break;\n }\n case 'VERSION_FAILED': {\n this.onVersionFailed();\n break;\n }\n }\n });\n }\n\n /**\n * Gets called when no new version was found.\n */\n protected onNoNewVersionDetected(): void {\n return;\n }\n\n /**\n * Gets called when the installation of a new version fails.\n */\n protected onVersionInstallationFailed(): void {\n return;\n }\n\n /**\n * Gets called when a new version has been found.\n */\n protected onVersionDetected(): void {\n return;\n }\n\n /**\n * Gets called when the version has a critical error.\n */\n protected onVersionFailed(): void {\n return;\n }\n\n /**\n * Gets called when a new version has been installed.\n */\n protected async onVersionReady(): Promise<void> {\n const dialogRef: MatDialogRef<NgxPwaVersionReadyDialogComponent> = this.dialog.open(\n NgxPwaVersionReadyDialogComponent,\n {\n autoFocus: false,\n restoreFocus: false\n }\n );\n const res: 'update' | 'cancel' = await firstValueFrom(dialogRef.afterClosed()) as 'update' | 'cancel';\n if (res === 'update') {\n window.location.reload();\n }\n }\n\n /**\n * Manually checks for updates.\n * @returns Whether or not new updates are available.\n */\n async checkForUpdates(): Promise<boolean> {\n return await this.swUpdate.checkForUpdate();\n }\n}","/*\n * Public API Surface of ngx-pwa\n */\nexport * from './components/offline-status-bar/offline-status-bar.component';\n\nexport * from './components/synchronize-badge/synchronize-badge.component';\n\nexport * from './components/synchronize-dialog/synchronize-dialog.component';\n\nexport * from './components/version-ready-dialog/version-ready-dialog.component';\n\nexport * from './models/http-method.enum';\nexport * from './models/request-metadata.model';\nexport * from './models/synchronize-dialog-data.model';\nexport * from './models/version-ready-dialog-data.model';\n\nexport * from './services/notification.service';\nexport * from './services/offline-request.interceptor';\nexport * from './services/offline.service';\nexport * from './services/update.service';","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i2","i1","i3"],"mappings":"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