UNPKG

@angular/service-worker

Version:

Angular - service worker tooling!

396 lines (385 loc) 12.7 kB
/** * @license Angular v7.0.2 * (c) 2010-2018 Google, Inc. https://angular.io/ * License: MIT */ import { concat, defer, fromEvent, of, throwError, NEVER, Subject, merge } from 'rxjs'; import { filter, map, publish, switchMap, take, tap } from 'rxjs/operators'; import { Injectable, APP_INITIALIZER, ApplicationRef, InjectionToken, Injector, NgModule, PLATFORM_ID } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ /** @type {?} */ const ERR_SW_NOT_SUPPORTED = 'Service workers are disabled or not supported by this browser'; /** * @param {?} message * @return {?} */ function errorObservable(message) { return defer(() => throwError(new Error(message))); } /** * \@publicApi */ class NgswCommChannel { /** * @param {?} serviceWorker */ constructor(serviceWorker) { this.serviceWorker = serviceWorker; if (!serviceWorker) { this.worker = this.events = this.registration = errorObservable(ERR_SW_NOT_SUPPORTED); } else { /** @type {?} */ const controllerChangeEvents = fromEvent(serviceWorker, 'controllerchange'); /** @type {?} */ const controllerChanges = controllerChangeEvents.pipe(map(() => serviceWorker.controller)); /** @type {?} */ const currentController = defer(() => of(serviceWorker.controller)); /** @type {?} */ const controllerWithChanges = concat(currentController, controllerChanges); this.worker = controllerWithChanges.pipe(filter(c => !!c)); this.registration = /** @type {?} */ ((this.worker.pipe(switchMap(() => serviceWorker.getRegistration())))); /** @type {?} */ const rawEvents = fromEvent(serviceWorker, 'message'); /** @type {?} */ const rawEventPayload = rawEvents.pipe(map(event => event.data)); /** @type {?} */ const eventsUnconnected = rawEventPayload.pipe(filter(event => event && event.type)); /** @type {?} */ const events = /** @type {?} */ (eventsUnconnected.pipe(publish())); events.connect(); this.events = events; } } /** * @param {?} action * @param {?} payload * @return {?} */ postMessage(action, payload) { return this.worker .pipe(take(1), tap((sw) => { sw.postMessage(Object.assign({ action }, payload)); })) .toPromise() .then(() => undefined); } /** * @param {?} type * @param {?} payload * @param {?} nonce * @return {?} */ postMessageWithStatus(type, payload, nonce) { /** @type {?} */ const waitForStatus = this.waitForStatus(nonce); /** @type {?} */ const postMessage = this.postMessage(type, payload); return Promise.all([waitForStatus, postMessage]).then(() => undefined); } /** * @return {?} */ generateNonce() { return Math.round(Math.random() * 10000000); } /** * @template T * @param {?} type * @return {?} */ eventsOfType(type) { /** @type {?} */ const filterFn = (event) => event.type === type; return this.events.pipe(filter(filterFn)); } /** * @template T * @param {?} type * @return {?} */ nextEventOfType(type) { return this.eventsOfType(type).pipe(take(1)); } /** * @param {?} nonce * @return {?} */ waitForStatus(nonce) { return this.eventsOfType('STATUS') .pipe(filter(event => event.nonce === nonce), take(1), map(event => { if (event.status) { return undefined; } throw new Error(/** @type {?} */ ((event.error))); })) .toPromise(); } /** * @return {?} */ get isEnabled() { return !!this.serviceWorker; } } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ /** * Subscribe and listen to push notifications from the Service Worker. * * \@publicApi */ class SwPush { /** * @param {?} sw */ constructor(sw) { this.sw = sw; this.subscriptionChanges = new Subject(); if (!sw.isEnabled) { this.messages = NEVER; this.subscription = NEVER; return; } this.messages = this.sw.eventsOfType('PUSH').pipe(map(message => message.data)); this.pushManager = this.sw.registration.pipe(map(registration => registration.pushManager)); /** @type {?} */ const workerDrivenSubscriptions = this.pushManager.pipe(switchMap(pm => pm.getSubscription())); this.subscription = merge(workerDrivenSubscriptions, this.subscriptionChanges); } /** * True if the Service Worker is enabled (supported by the browser and enabled via * `ServiceWorkerModule`). * @return {?} */ get isEnabled() { return this.sw.isEnabled; } /** * @param {?} options * @return {?} */ requestSubscription(options) { if (!this.sw.isEnabled) { return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED)); } /** @type {?} */ const pushOptions = { userVisibleOnly: true }; /** @type {?} */ let key = this.decodeBase64(options.serverPublicKey.replace(/_/g, '/').replace(/-/g, '+')); /** @type {?} */ let applicationServerKey = new Uint8Array(new ArrayBuffer(key.length)); for (let i = 0; i < key.length; i++) { applicationServerKey[i] = key.charCodeAt(i); } pushOptions.applicationServerKey = applicationServerKey; return this.pushManager.pipe(switchMap(pm => pm.subscribe(pushOptions)), take(1)) .toPromise() .then(sub => { this.subscriptionChanges.next(sub); return sub; }); } /** * @return {?} */ unsubscribe() { if (!this.sw.isEnabled) { return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED)); } /** @type {?} */ const doUnsubscribe = (sub) => { if (sub === null) { throw new Error('Not subscribed to push notifications.'); } return sub.unsubscribe().then(success => { if (!success) { throw new Error('Unsubscribe failed!'); } this.subscriptionChanges.next(null); }); }; return this.subscription.pipe(take(1), switchMap(doUnsubscribe)).toPromise(); } /** * @param {?} input * @return {?} */ decodeBase64(input) { return atob(input); } } SwPush.decorators = [ { type: Injectable } ]; /** @nocollapse */ SwPush.ctorParameters = () => [ { type: NgswCommChannel } ]; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ /** * Subscribe to update notifications from the Service Worker, trigger update * checks, and forcibly activate updates. * * \@publicApi */ class SwUpdate { /** * @param {?} sw */ constructor(sw) { this.sw = sw; if (!sw.isEnabled) { this.available = NEVER; this.activated = NEVER; return; } this.available = this.sw.eventsOfType('UPDATE_AVAILABLE'); this.activated = this.sw.eventsOfType('UPDATE_ACTIVATED'); } /** * True if the Service Worker is enabled (supported by the browser and enabled via * `ServiceWorkerModule`). * @return {?} */ get isEnabled() { return this.sw.isEnabled; } /** * @return {?} */ checkForUpdate() { if (!this.sw.isEnabled) { return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED)); } /** @type {?} */ const statusNonce = this.sw.generateNonce(); return this.sw.postMessageWithStatus('CHECK_FOR_UPDATES', { statusNonce }, statusNonce); } /** * @return {?} */ activateUpdate() { if (!this.sw.isEnabled) { return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED)); } /** @type {?} */ const statusNonce = this.sw.generateNonce(); return this.sw.postMessageWithStatus('ACTIVATE_UPDATE', { statusNonce }, statusNonce); } } SwUpdate.decorators = [ { type: Injectable } ]; /** @nocollapse */ SwUpdate.ctorParameters = () => [ { type: NgswCommChannel } ]; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ /** * @abstract */ class RegistrationOptions { } /** @type {?} */ const SCRIPT = new InjectionToken('NGSW_REGISTER_SCRIPT'); /** * @param {?} injector * @param {?} script * @param {?} options * @param {?} platformId * @return {?} */ function ngswAppInitializer(injector, script, options, platformId) { /** @type {?} */ const initializer = () => { /** @type {?} */ const app = injector.get(ApplicationRef); if (!(isPlatformBrowser(platformId) && ('serviceWorker' in navigator) && options.enabled !== false)) { return; } /** @type {?} */ const whenStable = app.isStable.pipe(filter((stable) => !!stable), take(1)).toPromise(); // Wait for service worker controller changes, and fire an INITIALIZE action when a new SW // becomes active. This allows the SW to initialize itself even if there is no application // traffic. navigator.serviceWorker.addEventListener('controllerchange', () => { if (navigator.serviceWorker.controller !== null) { navigator.serviceWorker.controller.postMessage({ action: 'INITIALIZE' }); } }); // Don't return the Promise, as that will block the application until the SW is registered, and // cause a crash if the SW registration fails. whenStable.then(() => navigator.serviceWorker.register(script, { scope: options.scope })); }; return initializer; } /** * @param {?} opts * @param {?} platformId * @return {?} */ function ngswCommChannelFactory(opts, platformId) { return new NgswCommChannel(isPlatformBrowser(platformId) && opts.enabled !== false ? navigator.serviceWorker : undefined); } /** * \@publicApi */ class ServiceWorkerModule { /** * Register the given Angular Service Worker script. * * If `enabled` is set to `false` in the given options, the module will behave as if service * workers are not supported by the browser, and the service worker will not be registered. * @param {?} script * @param {?=} opts * @return {?} */ static register(script, opts = {}) { return { ngModule: ServiceWorkerModule, providers: [ { provide: SCRIPT, useValue: script }, { provide: RegistrationOptions, useValue: opts }, { provide: NgswCommChannel, useFactory: ngswCommChannelFactory, deps: [RegistrationOptions, PLATFORM_ID] }, { provide: APP_INITIALIZER, useFactory: ngswAppInitializer, deps: [Injector, SCRIPT, RegistrationOptions, PLATFORM_ID], multi: true, }, ], }; } } ServiceWorkerModule.decorators = [ { type: NgModule, args: [{ providers: [SwPush, SwUpdate], },] } ]; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ // This file only reexports content of the `src` folder. Keep it that way. /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ /** * Generated bundle index. Do not edit. */ export { NgswCommChannel as ɵangular_packages_service_worker_service_worker_a, RegistrationOptions as ɵangular_packages_service_worker_service_worker_b, SCRIPT as ɵangular_packages_service_worker_service_worker_c, ngswAppInitializer as ɵangular_packages_service_worker_service_worker_d, ngswCommChannelFactory as ɵangular_packages_service_worker_service_worker_e, ServiceWorkerModule, SwPush, SwUpdate }; //# sourceMappingURL=service-worker.js.map