UNPKG

16.9 kBJavaScriptView Raw
1/**
2 * @license Angular v10.2.3
3 * (c) 2010-2020 Google LLC. https://angular.io/
4 * License: MIT
5 */
6
7import { isPlatformBrowser } from '@angular/common';
8import { Injectable, InjectionToken, NgZone, ApplicationRef, PLATFORM_ID, APP_INITIALIZER, Injector, NgModule } from '@angular/core';
9import { defer, throwError, fromEvent, of, concat, Subject, NEVER, merge } from 'rxjs';
10import { 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 */
19const ERR_SW_NOT_SUPPORTED = 'Service workers are disabled or not supported by this browser';
20function errorObservable(message) {
21 return defer(() => throwError(new Error(message)));
22}
23/**
24 * @publicApi
25 */
26class 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 */
168class 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}
244SwPush.decorators = [
245 { type: Injectable }
246];
247SwPush.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 */
264class 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}
297SwUpdate.decorators = [
298 { type: Injectable }
299];
300SwUpdate.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 */
323class SwRegistrationOptions {
324}
325const SCRIPT = new InjectionToken('NGSW_REGISTER_SCRIPT');
326function 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}
372function delayWithTimeout(timeout) {
373 return of(null).pipe(delay(timeout));
374}
375function whenStable(injector) {
376 const appRef = injector.get(ApplicationRef);
377 return appRef.isStable.pipe(filter(stable => stable));
378}
379function ngswCommChannelFactory(opts, platformId) {
380 return new NgswCommChannel(isPlatformBrowser(platformId) && opts.enabled !== false ? navigator.serviceWorker :
381 undefined);
382}
383/**
384 * @publicApi
385 */
386class 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}
414ServiceWorkerModule.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
449export { 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