UNPKG

19.9 kBJavaScriptView Raw
1/**
2 * @license Angular v14.1.2
3 * (c) 2010-2022 Google LLC. https://angular.io/
4 * License: MIT
5 */
6
7import { isPlatformBrowser } from '@angular/common';
8import * as i0 from '@angular/core';
9import { Injectable, InjectionToken, NgZone, ApplicationRef, PLATFORM_ID, APP_INITIALIZER, Injector, NgModule } from '@angular/core';
10import { defer, throwError, fromEvent, of, concat, Subject, NEVER, merge } from 'rxjs';
11import { map, filter, switchMap, publish, take, tap, delay } from 'rxjs/operators';
12
13/**
14 * @license
15 * Copyright Google LLC All Rights Reserved.
16 *
17 * Use of this source code is governed by an MIT-style license that can be
18 * found in the LICENSE file at https://angular.io/license
19 */
20const ERR_SW_NOT_SUPPORTED = 'Service workers are disabled or not supported by this browser';
21function errorObservable(message) {
22 return defer(() => throwError(new Error(message)));
23}
24/**
25 * @publicApi
26 */
27class NgswCommChannel {
28 constructor(serviceWorker) {
29 this.serviceWorker = serviceWorker;
30 if (!serviceWorker) {
31 this.worker = this.events = this.registration = errorObservable(ERR_SW_NOT_SUPPORTED);
32 }
33 else {
34 const controllerChangeEvents = fromEvent(serviceWorker, 'controllerchange');
35 const controllerChanges = controllerChangeEvents.pipe(map(() => serviceWorker.controller));
36 const currentController = defer(() => of(serviceWorker.controller));
37 const controllerWithChanges = concat(currentController, controllerChanges);
38 this.worker = controllerWithChanges.pipe(filter((c) => !!c));
39 this.registration = (this.worker.pipe(switchMap(() => serviceWorker.getRegistration())));
40 const rawEvents = fromEvent(serviceWorker, 'message');
41 const rawEventPayload = rawEvents.pipe(map(event => event.data));
42 const eventsUnconnected = rawEventPayload.pipe(filter(event => event && event.type));
43 const events = eventsUnconnected.pipe(publish());
44 events.connect();
45 this.events = events;
46 }
47 }
48 postMessage(action, payload) {
49 return this.worker
50 .pipe(take(1), tap((sw) => {
51 sw.postMessage(Object.assign({ action }, payload));
52 }))
53 .toPromise()
54 .then(() => undefined);
55 }
56 postMessageWithOperation(type, payload, operationNonce) {
57 const waitForOperationCompleted = this.waitForOperationCompleted(operationNonce);
58 const postMessage = this.postMessage(type, payload);
59 return Promise.all([postMessage, waitForOperationCompleted]).then(([, result]) => result);
60 }
61 generateNonce() {
62 return Math.round(Math.random() * 10000000);
63 }
64 eventsOfType(type) {
65 let filterFn;
66 if (typeof type === 'string') {
67 filterFn = (event) => event.type === type;
68 }
69 else {
70 filterFn = (event) => type.includes(event.type);
71 }
72 return this.events.pipe(filter(filterFn));
73 }
74 nextEventOfType(type) {
75 return this.eventsOfType(type).pipe(take(1));
76 }
77 waitForOperationCompleted(nonce) {
78 return this.eventsOfType('OPERATION_COMPLETED')
79 .pipe(filter(event => event.nonce === nonce), take(1), map(event => {
80 if (event.result !== undefined) {
81 return event.result;
82 }
83 throw new Error(event.error);
84 }))
85 .toPromise();
86 }
87 get isEnabled() {
88 return !!this.serviceWorker;
89 }
90}
91
92/**
93 * @license
94 * Copyright Google LLC All Rights Reserved.
95 *
96 * Use of this source code is governed by an MIT-style license that can be
97 * found in the LICENSE file at https://angular.io/license
98 */
99/**
100 * Subscribe and listen to
101 * [Web Push
102 * Notifications](https://developer.mozilla.org/en-US/docs/Web/API/Push_API/Best_Practices) through
103 * Angular Service Worker.
104 *
105 * @usageNotes
106 *
107 * You can inject a `SwPush` instance into any component or service
108 * as a dependency.
109 *
110 * <code-example path="service-worker/push/module.ts" region="inject-sw-push"
111 * header="app.component.ts"></code-example>
112 *
113 * To subscribe, call `SwPush.requestSubscription()`, which asks the user for permission.
114 * The call returns a `Promise` with a new
115 * [`PushSubscription`](https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription)
116 * instance.
117 *
118 * <code-example path="service-worker/push/module.ts" region="subscribe-to-push"
119 * header="app.component.ts"></code-example>
120 *
121 * A request is rejected if the user denies permission, or if the browser
122 * blocks or does not support the Push API or ServiceWorkers.
123 * Check `SwPush.isEnabled` to confirm status.
124 *
125 * Invoke Push Notifications by pushing a message with the following payload.
126 *
127 * ```ts
128 * {
129 * "notification": {
130 * "actions": NotificationAction[],
131 * "badge": USVString,
132 * "body": DOMString,
133 * "data": any,
134 * "dir": "auto"|"ltr"|"rtl",
135 * "icon": USVString,
136 * "image": USVString,
137 * "lang": DOMString,
138 * "renotify": boolean,
139 * "requireInteraction": boolean,
140 * "silent": boolean,
141 * "tag": DOMString,
142 * "timestamp": DOMTimeStamp,
143 * "title": DOMString,
144 * "vibrate": number[]
145 * }
146 * }
147 * ```
148 *
149 * Only `title` is required. See `Notification`
150 * [instance
151 * properties](https://developer.mozilla.org/en-US/docs/Web/API/Notification#Instance_properties).
152 *
153 * While the subscription is active, Service Worker listens for
154 * [PushEvent](https://developer.mozilla.org/en-US/docs/Web/API/PushEvent)
155 * occurrences and creates
156 * [Notification](https://developer.mozilla.org/en-US/docs/Web/API/Notification)
157 * instances in response.
158 *
159 * Unsubscribe using `SwPush.unsubscribe()`.
160 *
161 * An application can subscribe to `SwPush.notificationClicks` observable to be notified when a user
162 * clicks on a notification. For example:
163 *
164 * <code-example path="service-worker/push/module.ts" region="subscribe-to-notification-clicks"
165 * header="app.component.ts"></code-example>
166 *
167 * You can read more on handling notification clicks in the [Service worker notifications
168 * guide](guide/service-worker-notifications).
169 *
170 * @see [Push Notifications](https://developers.google.com/web/fundamentals/codelabs/push-notifications/)
171 * @see [Angular Push Notifications](https://blog.angular-university.io/angular-push-notifications/)
172 * @see [MDN: Push API](https://developer.mozilla.org/en-US/docs/Web/API/Push_API)
173 * @see [MDN: Notifications API](https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API)
174 * @see [MDN: Web Push API Notifications best practices](https://developer.mozilla.org/en-US/docs/Web/API/Push_API/Best_Practices)
175 *
176 * @publicApi
177 */
178class SwPush {
179 constructor(sw) {
180 this.sw = sw;
181 this.subscriptionChanges = new Subject();
182 if (!sw.isEnabled) {
183 this.messages = NEVER;
184 this.notificationClicks = NEVER;
185 this.subscription = NEVER;
186 return;
187 }
188 this.messages = this.sw.eventsOfType('PUSH').pipe(map(message => message.data));
189 this.notificationClicks =
190 this.sw.eventsOfType('NOTIFICATION_CLICK').pipe(map((message) => message.data));
191 this.pushManager = this.sw.registration.pipe(map(registration => registration.pushManager));
192 const workerDrivenSubscriptions = this.pushManager.pipe(switchMap(pm => pm.getSubscription()));
193 this.subscription = merge(workerDrivenSubscriptions, this.subscriptionChanges);
194 }
195 /**
196 * True if the Service Worker is enabled (supported by the browser and enabled via
197 * `ServiceWorkerModule`).
198 */
199 get isEnabled() {
200 return this.sw.isEnabled;
201 }
202 /**
203 * Subscribes to Web Push Notifications,
204 * after requesting and receiving user permission.
205 *
206 * @param options An object containing the `serverPublicKey` string.
207 * @returns A Promise that resolves to the new subscription object.
208 */
209 requestSubscription(options) {
210 if (!this.sw.isEnabled) {
211 return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED));
212 }
213 const pushOptions = { userVisibleOnly: true };
214 let key = this.decodeBase64(options.serverPublicKey.replace(/_/g, '/').replace(/-/g, '+'));
215 let applicationServerKey = new Uint8Array(new ArrayBuffer(key.length));
216 for (let i = 0; i < key.length; i++) {
217 applicationServerKey[i] = key.charCodeAt(i);
218 }
219 pushOptions.applicationServerKey = applicationServerKey;
220 return this.pushManager.pipe(switchMap(pm => pm.subscribe(pushOptions)), take(1))
221 .toPromise()
222 .then(sub => {
223 this.subscriptionChanges.next(sub);
224 return sub;
225 });
226 }
227 /**
228 * Unsubscribes from Service Worker push notifications.
229 *
230 * @returns A Promise that is resolved when the operation succeeds, or is rejected if there is no
231 * active subscription or the unsubscribe operation fails.
232 */
233 unsubscribe() {
234 if (!this.sw.isEnabled) {
235 return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED));
236 }
237 const doUnsubscribe = (sub) => {
238 if (sub === null) {
239 throw new Error('Not subscribed to push notifications.');
240 }
241 return sub.unsubscribe().then(success => {
242 if (!success) {
243 throw new Error('Unsubscribe failed!');
244 }
245 this.subscriptionChanges.next(null);
246 });
247 };
248 return this.subscription.pipe(take(1), switchMap(doUnsubscribe)).toPromise();
249 }
250 decodeBase64(input) {
251 return atob(input);
252 }
253}
254SwPush.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.1.2", ngImport: i0, type: SwPush, deps: [{ token: NgswCommChannel }], target: i0.ɵɵFactoryTarget.Injectable });
255SwPush.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.1.2", ngImport: i0, type: SwPush });
256i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.1.2", ngImport: i0, type: SwPush, decorators: [{
257 type: Injectable
258 }], ctorParameters: function () { return [{ type: NgswCommChannel }]; } });
259
260/**
261 * @license
262 * Copyright Google LLC All Rights Reserved.
263 *
264 * Use of this source code is governed by an MIT-style license that can be
265 * found in the LICENSE file at https://angular.io/license
266 */
267/**
268 * Subscribe to update notifications from the Service Worker, trigger update
269 * checks, and forcibly activate updates.
270 *
271 * @see {@link guide/service-worker-communications Service worker communication guide}
272 *
273 * @publicApi
274 */
275class SwUpdate {
276 constructor(sw) {
277 this.sw = sw;
278 if (!sw.isEnabled) {
279 this.versionUpdates = NEVER;
280 this.available = NEVER;
281 this.activated = NEVER;
282 this.unrecoverable = NEVER;
283 return;
284 }
285 this.versionUpdates = this.sw.eventsOfType([
286 'VERSION_DETECTED',
287 'VERSION_INSTALLATION_FAILED',
288 'VERSION_READY',
289 'NO_NEW_VERSION_DETECTED',
290 ]);
291 this.available = this.versionUpdates.pipe(filter((evt) => evt.type === 'VERSION_READY'), map(evt => ({
292 type: 'UPDATE_AVAILABLE',
293 current: evt.currentVersion,
294 available: evt.latestVersion,
295 })));
296 this.activated = this.sw.eventsOfType('UPDATE_ACTIVATED');
297 this.unrecoverable = this.sw.eventsOfType('UNRECOVERABLE_STATE');
298 }
299 /**
300 * True if the Service Worker is enabled (supported by the browser and enabled via
301 * `ServiceWorkerModule`).
302 */
303 get isEnabled() {
304 return this.sw.isEnabled;
305 }
306 /**
307 * Checks for an update and waits until the new version is downloaded from the server and ready
308 * for activation.
309 *
310 * @returns a promise that
311 * - resolves to `true` if a new version was found and is ready to be activated.
312 * - resolves to `false` if no new version was found
313 * - rejects if any error occurs
314 */
315 checkForUpdate() {
316 if (!this.sw.isEnabled) {
317 return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED));
318 }
319 const nonce = this.sw.generateNonce();
320 return this.sw.postMessageWithOperation('CHECK_FOR_UPDATES', { nonce }, nonce);
321 }
322 /**
323 * Updates the current client (i.e. browser tab) to the latest version that is ready for
324 * activation.
325 *
326 * @returns a promise that
327 * - resolves to `true` if an update was activated successfully
328 * - resolves to `false` if no update was available (for example, the client was already on the
329 * latest version).
330 * - rejects if any error occurs
331 */
332 activateUpdate() {
333 if (!this.sw.isEnabled) {
334 return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED));
335 }
336 const nonce = this.sw.generateNonce();
337 return this.sw.postMessageWithOperation('ACTIVATE_UPDATE', { nonce }, nonce);
338 }
339}
340SwUpdate.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.1.2", ngImport: i0, type: SwUpdate, deps: [{ token: NgswCommChannel }], target: i0.ɵɵFactoryTarget.Injectable });
341SwUpdate.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.1.2", ngImport: i0, type: SwUpdate });
342i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.1.2", ngImport: i0, type: SwUpdate, decorators: [{
343 type: Injectable
344 }], ctorParameters: function () { return [{ type: NgswCommChannel }]; } });
345
346/**
347 * @license
348 * Copyright Google LLC All Rights Reserved.
349 *
350 * Use of this source code is governed by an MIT-style license that can be
351 * found in the LICENSE file at https://angular.io/license
352 */
353/**
354 * Token that can be used to provide options for `ServiceWorkerModule` outside of
355 * `ServiceWorkerModule.register()`.
356 *
357 * You can use this token to define a provider that generates the registration options at runtime,
358 * for example via a function call:
359 *
360 * {@example service-worker/registration-options/module.ts region="registration-options"
361 * header="app.module.ts"}
362 *
363 * @publicApi
364 */
365class SwRegistrationOptions {
366}
367const SCRIPT = new InjectionToken('NGSW_REGISTER_SCRIPT');
368function ngswAppInitializer(injector, script, options, platformId) {
369 const initializer = () => {
370 if (!(isPlatformBrowser(platformId) && ('serviceWorker' in navigator) &&
371 options.enabled !== false)) {
372 return;
373 }
374 // Wait for service worker controller changes, and fire an INITIALIZE action when a new SW
375 // becomes active. This allows the SW to initialize itself even if there is no application
376 // traffic.
377 navigator.serviceWorker.addEventListener('controllerchange', () => {
378 if (navigator.serviceWorker.controller !== null) {
379 navigator.serviceWorker.controller.postMessage({ action: 'INITIALIZE' });
380 }
381 });
382 let readyToRegister$;
383 if (typeof options.registrationStrategy === 'function') {
384 readyToRegister$ = options.registrationStrategy();
385 }
386 else {
387 const [strategy, ...args] = (options.registrationStrategy || 'registerWhenStable:30000').split(':');
388 switch (strategy) {
389 case 'registerImmediately':
390 readyToRegister$ = of(null);
391 break;
392 case 'registerWithDelay':
393 readyToRegister$ = delayWithTimeout(+args[0] || 0);
394 break;
395 case 'registerWhenStable':
396 readyToRegister$ = !args[0] ? whenStable(injector) :
397 merge(whenStable(injector), delayWithTimeout(+args[0]));
398 break;
399 default:
400 // Unknown strategy.
401 throw new Error(`Unknown ServiceWorker registration strategy: ${options.registrationStrategy}`);
402 }
403 }
404 // Don't return anything to avoid blocking the application until the SW is registered.
405 // Also, run outside the Angular zone to avoid preventing the app from stabilizing (especially
406 // given that some registration strategies wait for the app to stabilize).
407 // Catch and log the error if SW registration fails to avoid uncaught rejection warning.
408 const ngZone = injector.get(NgZone);
409 ngZone.runOutsideAngular(() => readyToRegister$.pipe(take(1)).subscribe(() => navigator.serviceWorker.register(script, { scope: options.scope })
410 .catch(err => console.error('Service worker registration failed with:', err))));
411 };
412 return initializer;
413}
414function delayWithTimeout(timeout) {
415 return of(null).pipe(delay(timeout));
416}
417function whenStable(injector) {
418 const appRef = injector.get(ApplicationRef);
419 return appRef.isStable.pipe(filter(stable => stable));
420}
421function ngswCommChannelFactory(opts, platformId) {
422 return new NgswCommChannel(isPlatformBrowser(platformId) && opts.enabled !== false ? navigator.serviceWorker :
423 undefined);
424}
425/**
426 * @publicApi
427 */
428class ServiceWorkerModule {
429 /**
430 * Register the given Angular Service Worker script.
431 *
432 * If `enabled` is set to `false` in the given options, the module will behave as if service
433 * workers are not supported by the browser, and the service worker will not be registered.
434 */
435 static register(script, opts = {}) {
436 return {
437 ngModule: ServiceWorkerModule,
438 providers: [
439 { provide: SCRIPT, useValue: script },
440 { provide: SwRegistrationOptions, useValue: opts },
441 {
442 provide: NgswCommChannel,
443 useFactory: ngswCommChannelFactory,
444 deps: [SwRegistrationOptions, PLATFORM_ID]
445 },
446 {
447 provide: APP_INITIALIZER,
448 useFactory: ngswAppInitializer,
449 deps: [Injector, SCRIPT, SwRegistrationOptions, PLATFORM_ID],
450 multi: true,
451 },
452 ],
453 };
454 }
455}
456ServiceWorkerModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.1.2", ngImport: i0, type: ServiceWorkerModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
457ServiceWorkerModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.1.2", ngImport: i0, type: ServiceWorkerModule });
458ServiceWorkerModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.1.2", ngImport: i0, type: ServiceWorkerModule, providers: [SwPush, SwUpdate] });
459i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.1.2", ngImport: i0, type: ServiceWorkerModule, decorators: [{
460 type: NgModule,
461 args: [{
462 providers: [SwPush, SwUpdate],
463 }]
464 }] });
465
466/**
467 * @license
468 * Copyright Google LLC All Rights Reserved.
469 *
470 * Use of this source code is governed by an MIT-style license that can be
471 * found in the LICENSE file at https://angular.io/license
472 */
473
474/**
475 * @license
476 * Copyright Google LLC All Rights Reserved.
477 *
478 * Use of this source code is governed by an MIT-style license that can be
479 * found in the LICENSE file at https://angular.io/license
480 */
481// This file only reexports content of the `src` folder. Keep it that way.
482
483/**
484 * @license
485 * Copyright Google LLC All Rights Reserved.
486 *
487 * Use of this source code is governed by an MIT-style license that can be
488 * found in the LICENSE file at https://angular.io/license
489 */
490
491/**
492 * Generated bundle index. Do not edit.
493 */
494
495export { ServiceWorkerModule, SwPush, SwRegistrationOptions, SwUpdate };
496//# sourceMappingURL=service-worker.mjs.map