/** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import {Injectable} from '@angular/core'; import {NEVER, Observable, Subject, merge} from 'rxjs'; import {map, switchMap, take} from 'rxjs/operators'; import {ERR_SW_NOT_SUPPORTED, NgswCommChannel, PushEvent} from './low_level'; /** * Subscribe and listen to push notifications from the Service Worker. * * @publicApi */ @Injectable() export class SwPush { /** * Emits the payloads of the received push notification messages. */ readonly messages: Observable; /** * Emits the currently active * [PushSubscription](https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription) * associated to the Service Worker registration or `null` if there is no subscription. */ readonly subscription: Observable; /** * True if the Service Worker is enabled (supported by the browser and enabled via * `ServiceWorkerModule`). */ get isEnabled(): boolean { return this.sw.isEnabled; } // TODO(issue/24571): remove '!'. private pushManager !: Observable; private subscriptionChanges = new Subject(); constructor(private sw: NgswCommChannel) { 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)); const workerDrivenSubscriptions = this.pushManager.pipe(switchMap(pm => pm.getSubscription())); this.subscription = merge(workerDrivenSubscriptions, this.subscriptionChanges); } requestSubscription(options: {serverPublicKey: string}): Promise { if (!this.sw.isEnabled) { return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED)); } const pushOptions: PushSubscriptionOptionsInit = {userVisibleOnly: true}; let key = this.decodeBase64(options.serverPublicKey.replace(/_/g, '/').replace(/-/g, '+')); 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; }); } unsubscribe(): Promise { if (!this.sw.isEnabled) { return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED)); } const doUnsubscribe = (sub: PushSubscription | null) => { 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(); } private decodeBase64(input: string): string { return atob(input); } }