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