diff --git a/packages/messaging/src/controllers/sw-controller.ts b/packages/messaging/src/controllers/sw-controller.ts index 1d8db897edf..104ea364297 100644 --- a/packages/messaging/src/controllers/sw-controller.ts +++ b/packages/messaging/src/controllers/sw-controller.ts @@ -14,16 +14,34 @@ * limitations under the License. */ +import './sw-types'; + import { FirebaseApp } from '@firebase/app-types'; import { ERROR_CODES } from '../models/errors'; import { DEFAULT_PUBLIC_VAPID_KEY } from '../models/fcm-details'; -import * as WorkerPageMessage from '../models/worker-page-message'; +import { + InternalMessage, + MessageParameter, + MessageType +} from '../models/worker-page-message'; import { ControllerInterface } from './controller-interface'; +// Let TS know that this is a service worker +declare const self: ServiceWorkerGlobalScope; + const FCM_MSG = 'FCM_MSG'; -export type BgMessageHandler = (input: any) => Promise; +export type BgMessageHandler = (input: Payload) => Promise; + +export interface NotificationDetails extends NotificationOptions { + title: string; + click_action?: string; +} + +export interface Payload { + notification?: NotificationDetails; +} export class SWController extends ControllerInterface { private bgMessageHandler_: BgMessageHandler | null = null; @@ -31,44 +49,32 @@ export class SWController extends ControllerInterface { constructor(app: FirebaseApp) { super(app); - self.addEventListener( - 'push', - (e: any) => { - this.onPush(e); - }, - false - ); - self.addEventListener( - 'pushsubscriptionchange', - (e: any) => { - this.onSubChange(e); - }, - false - ); - self.addEventListener( - 'notificationclick', - (e: any) => { - this.onNotificationClick(e); - }, - false - ); + self.addEventListener('push', e => { + this.onPush(e); + }); + self.addEventListener('pushsubscriptionchange', e => { + this.onSubChange(e); + }); + self.addEventListener('notificationclick', e => { + this.onNotificationClick(e); + }); } // Visible for testing // TODO: Make private - onPush(event: any): void { + onPush(event: PushEvent): void { event.waitUntil(this.onPush_(event)); } // Visible for testing // TODO: Make private - onSubChange(event: any): void { + onSubChange(event: PushSubscriptionChangeEvent): void { event.waitUntil(this.onSubChange_(event)); } // Visible for testing // TODO: Make private - onNotificationClick(event: any): void { + onNotificationClick(event: NotificationEvent): void { event.waitUntil(this.onNotificationClick_(event)); } @@ -84,8 +90,12 @@ export class SWController extends ControllerInterface { * If there is no notification data in the payload then no notification will be * shown. */ - private async onPush_(event: any): Promise { - let msgPayload: any; + private async onPush_(event: PushEvent): Promise { + if (!event.data) { + return; + } + + let msgPayload: Payload; try { msgPayload = event.data.json(); } catch (err) { @@ -105,15 +115,18 @@ export class SWController extends ControllerInterface { const notificationDetails = this.getNotificationData_(msgPayload); if (notificationDetails) { - const notificationTitle = (notificationDetails as any).title || ''; + const notificationTitle = notificationDetails.title || ''; const reg = await this.getSWRegistration_(); return reg.showNotification(notificationTitle, notificationDetails); } else if (this.bgMessageHandler_) { - return this.bgMessageHandler_(msgPayload); + await this.bgMessageHandler_(msgPayload); + return; } } - private async onSubChange_(event: any): Promise { + private async onSubChange_( + event: PushSubscriptionChangeEvent + ): Promise { let registration: ServiceWorkerRegistration; try { registration = await this.getSWRegistration_(); @@ -140,12 +153,12 @@ export class SWController extends ControllerInterface { } // Attempt to delete the token if we know it's bad - await this.deleteToken(tokenDetails['fcmToken']); + await this.deleteToken(tokenDetails.fcmToken); throw err; } } - private async onNotificationClick_(event: any): Promise { + private async onNotificationClick_(event: NotificationEvent): Promise { if ( !event.notification || !event.notification.data || @@ -160,13 +173,13 @@ export class SWController extends ControllerInterface { event.notification.close(); - const msgPayload = event.notification.data[FCM_MSG]; - if (!msgPayload['notification']) { + const msgPayload: Payload = event.notification.data[FCM_MSG]; + if (!msgPayload.notification) { // Nothing to do. return; } - const clickAction = msgPayload['notification']['click_action']; + const clickAction = msgPayload.notification.click_action; if (!clickAction) { // Nothing to do. return; @@ -175,7 +188,7 @@ export class SWController extends ControllerInterface { let windowClient = await this.getWindowClient_(clickAction); if (!windowClient) { // Unable to find window client so need to open one. - windowClient = await (self as any).clients.openWindow(clickAction); + windowClient = await self.clients.openWindow(clickAction); } else { windowClient = await windowClient.focus(); } @@ -186,10 +199,10 @@ export class SWController extends ControllerInterface { } // Delete notification data from payload before sending to the page. - delete msgPayload['notification']; + delete msgPayload.notification; - const internalMsg = WorkerPageMessage.createNewMsg( - WorkerPageMessage.TYPES_OF_MSG.NOTIFICATION_CLICKED, + const internalMsg = createNewMsg( + MessageType.NOTIFICATION_CLICKED, msgPayload ); @@ -200,7 +213,7 @@ export class SWController extends ControllerInterface { // Visible for testing // TODO: Make private - getNotificationData_(msgPayload: any): NotificationOptions | undefined { + getNotificationData_(msgPayload: Payload): NotificationDetails | undefined { if (!msgPayload) { return; } @@ -250,16 +263,16 @@ export class SWController extends ControllerInterface { */ // Visible for testing // TODO: Make private - async getWindowClient_(url: string): Promise { + async getWindowClient_(url: string): Promise { // Use URL to normalize the URL when comparing to windowClients. // This at least handles whether to include trailing slashes or not - const parsedURL = new URL(url, (self as any).location).href; + const parsedURL = new URL(url, self.location.href).href; const clientList = await getClientList(); - let suitableClient = null; + let suitableClient: WindowClient | null = null; for (let i = 0; i < clientList.length; i++) { - const parsedClientUrl = new URL(clientList[i].url, (self as any).location) + const parsedClientUrl = new URL(clientList[i].url, self.location.href) .href; if (parsedClientUrl === parsedURL) { suitableClient = clientList[i]; @@ -279,7 +292,10 @@ export class SWController extends ControllerInterface { */ // Visible for testing // TODO: Make private - async attemptToMessageClient_(client: any, message: any): Promise { + async attemptToMessageClient_( + client: WindowClient, + message: InternalMessage + ): Promise { // NOTE: This returns a promise in case this API is abstracted later on to // do additional work if (!client) { @@ -299,7 +315,7 @@ export class SWController extends ControllerInterface { const clientList = await getClientList(); return clientList.some( - (client: any) => client.visibilityState === 'visible' + (client: WindowClient) => client.visibilityState === 'visible' ); } @@ -311,16 +327,13 @@ export class SWController extends ControllerInterface { */ // Visible for testing // TODO: Make private - async sendMessageToWindowClients_(msgPayload: any): Promise { + async sendMessageToWindowClients_(msgPayload: Payload): Promise { const clientList = await getClientList(); - const internalMsg = WorkerPageMessage.createNewMsg( - WorkerPageMessage.TYPES_OF_MSG.PUSH_MSG_RECEIVED, - msgPayload - ); + const internalMsg = createNewMsg(MessageType.PUSH_MSG_RECEIVED, msgPayload); await Promise.all( - clientList.map((client: any) => + clientList.map(client => this.attemptToMessageClient_(client, internalMsg) ) ); @@ -331,7 +344,7 @@ export class SWController extends ControllerInterface { * @return he service worker registration to be used for the push service. */ async getSWRegistration_(): Promise { - return (self as any).registration; + return self.registration; } /** @@ -355,9 +368,17 @@ export class SWController extends ControllerInterface { } } -function getClientList(): Promise { - return (self as any).clients.matchAll({ +function getClientList(): Promise { + return self.clients.matchAll({ type: 'window', includeUncontrolled: true - }); + // TS doesn't know that "type: 'window'" means it'll return WindowClient[] + }) as Promise; +} + +function createNewMsg(msgType: MessageType, msgData: Payload): InternalMessage { + return { + [MessageParameter.TYPE_OF_MSG]: msgType, + [MessageParameter.DATA]: msgData + }; } diff --git a/packages/messaging/src/controllers/sw-types.ts b/packages/messaging/src/controllers/sw-types.ts new file mode 100644 index 00000000000..9ac1c6d01dc --- /dev/null +++ b/packages/messaging/src/controllers/sw-types.ts @@ -0,0 +1,122 @@ +/** + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Subset of Web Worker types from lib.webworker.d.ts + * https://github.com/Microsoft/TypeScript/blob/master/lib/lib.webworker.d.ts + * + * Since it's not possible to have both "dom" and "webworker" libs in a single + * project, we have to manually declare the web worker types we need. + */ + +// Not the whole interface, just the parts we're currently using. +// If TS claims that something does not exist on this, feel free to add it. +interface ServiceWorkerGlobalScope { + readonly location: WorkerLocation; + readonly clients: Clients; + readonly registration: ServiceWorkerRegistration; + addEventListener( + type: K, + listener: ( + this: ServiceWorkerGlobalScope, + ev: ServiceWorkerGlobalScopeEventMap[K] + ) => any, + options?: boolean | AddEventListenerOptions + ): void; +} + +// Same as the previous interface +interface ServiceWorkerGlobalScopeEventMap { + notificationclick: NotificationEvent; + push: PushEvent; + pushsubscriptionchange: PushSubscriptionChangeEvent; +} + +interface Client { + readonly id: string; + readonly reserved: boolean; + readonly type: ClientTypes; + readonly url: string; + postMessage(message: any, transfer?: any[]): void; +} + +interface ClientQueryOptions { + includeReserved?: boolean; + includeUncontrolled?: boolean; + type?: ClientTypes; +} + +interface WindowClient extends Client { + readonly ancestorOrigins: ReadonlyArray; + readonly focused: boolean; + readonly visibilityState: VisibilityState; + focus(): Promise; + navigate(url: string): Promise; +} + +interface Clients { + claim(): Promise; + get(id: string): Promise; + matchAll(options?: ClientQueryOptions): Promise; + openWindow(url: string): Promise; +} + +interface NotificationOptions { + body?: string; + data?: any; + dir?: NotificationDirection; + icon?: string; + lang?: string; + tag?: string; +} + +interface ExtendableEvent extends Event { + waitUntil(f: Promise): void; +} + +interface NotificationEvent extends ExtendableEvent { + readonly action: string; + readonly notification: Notification; +} + +interface PushMessageData { + arrayBuffer(): ArrayBuffer; + blob(): Blob; + json(): any; + text(): string; +} + +interface PushEvent extends ExtendableEvent { + readonly data: PushMessageData | null; +} + +interface PushSubscriptionChangeEvent extends ExtendableEvent { + readonly newSubscription: PushSubscription | null; + readonly oldSubscription: PushSubscription | null; +} + +interface WorkerLocation { + readonly hash: string; + readonly host: string; + readonly hostname: string; + readonly href: string; + readonly origin: string; + readonly pathname: string; + readonly port: string; + readonly protocol: string; + readonly search: string; + toString(): string; +} diff --git a/packages/messaging/src/controllers/window-controller.ts b/packages/messaging/src/controllers/window-controller.ts index aa1a2f325d1..4ff73609353 100644 --- a/packages/messaging/src/controllers/window-controller.ts +++ b/packages/messaging/src/controllers/window-controller.ts @@ -29,7 +29,7 @@ import { base64ToArrayBuffer } from '../helpers/base64-to-array-buffer'; import { DEFAULT_SW_PATH, DEFAULT_SW_SCOPE } from '../models/default-sw'; import { ERROR_CODES } from '../models/errors'; import { DEFAULT_PUBLIC_VAPID_KEY } from '../models/fcm-details'; -import * as WorkerPageMessage from '../models/worker-page-message'; +import { MessageParameter, MessageType } from '../models/worker-page-message'; import { ControllerInterface } from './controller-interface'; export class WindowController extends ControllerInterface @@ -360,20 +360,19 @@ export class WindowController extends ControllerInterface return; } - (navigator as any).serviceWorker.addEventListener( + navigator.serviceWorker.addEventListener( 'message', - (event: MessageEvent) => { - if (!event.data || !event.data[WorkerPageMessage.PARAMS.TYPE_OF_MSG]) { + event => { + if (!event.data || !event.data[MessageParameter.TYPE_OF_MSG]) { // Not a message from FCM return; } const workerPageMessage = event.data; - switch (workerPageMessage[WorkerPageMessage.PARAMS.TYPE_OF_MSG]) { - case WorkerPageMessage.TYPES_OF_MSG.PUSH_MSG_RECEIVED: - case WorkerPageMessage.TYPES_OF_MSG.NOTIFICATION_CLICKED: - const pushMessage = - workerPageMessage[WorkerPageMessage.PARAMS.DATA]; + switch (workerPageMessage[MessageParameter.TYPE_OF_MSG]) { + case MessageType.PUSH_MSG_RECEIVED: + case MessageType.NOTIFICATION_CLICKED: + const pushMessage = workerPageMessage[MessageParameter.DATA]; if (this.messageObserver_) { this.messageObserver_.next(pushMessage); } diff --git a/packages/messaging/src/models/worker-page-message.ts b/packages/messaging/src/models/worker-page-message.ts index 868b6f0ba67..676cca5ab36 100644 --- a/packages/messaging/src/models/worker-page-message.ts +++ b/packages/messaging/src/models/worker-page-message.ts @@ -14,26 +14,17 @@ * limitations under the License. */ -// These fields are strings to prevent closure from thinking goog.getMsg -// should be used to initialise the values -export const PARAMS = { - TYPE_OF_MSG: 'firebase-messaging-msg-type', - DATA: 'firebase-messaging-msg-data' -}; +export enum MessageParameter { + TYPE_OF_MSG = 'firebase-messaging-msg-type', + DATA = 'firebase-messaging-msg-data' +} -// This value isn't using the TYPE_OF_MSG short hand as closure -// expects the variable to be defined via goog.getMsg -export const TYPES_OF_MSG = { - PUSH_MSG_RECEIVED: 'push-msg-received', - NOTIFICATION_CLICKED: 'notification-clicked' -}; +export enum MessageType { + PUSH_MSG_RECEIVED = 'push-msg-received', + NOTIFICATION_CLICKED = 'notification-clicked' +} -export function createNewMsg( - msgType: any, - msgData: any -): { [key: string]: any } { - return { - [PARAMS.TYPE_OF_MSG]: msgType, - [PARAMS.DATA]: msgData - }; +export interface InternalMessage { + [MessageParameter.TYPE_OF_MSG]: MessageType; + [MessageParameter.DATA]: any; // tslint:disable-line no-any ¯\_(ツ)_/¯ } diff --git a/packages/messaging/test/sw-controller.test.ts b/packages/messaging/test/sw-controller.test.ts index d146467db9b..692ff661d44 100644 --- a/packages/messaging/test/sw-controller.test.ts +++ b/packages/messaging/test/sw-controller.test.ts @@ -51,27 +51,27 @@ describe('Firebase Messaging > *SWController', () => { }); describe('onPush', () => { - it('should handle a push event with no data', () => { + it('should handle a push event with no data', async () => { const swController = new SWController(app); - swController.onPush({ + await swController.onPush({ waitUntil: sandbox.spy(), data: undefined - }); + } as any); }); - it('should handle a push event where .json() throws', () => { + it('should handle a push event where .json() throws', async () => { const swController = new SWController(app); - swController.onPush({ + await swController.onPush({ waitUntil: sandbox.spy(), data: { json: () => { throw new Error('Injected Error'); } } - }); + } as any); }); - it('should not do anything if window is in focus', () => { + it('should not do anything if window is in focus', async () => { const waitUntilSpy = sandbox.spy(); sandbox.stub(SWController.prototype, 'sendMessageToWindowClients_'); sandbox @@ -79,14 +79,14 @@ describe('Firebase Messaging > *SWController', () => { .callsFake(async () => true); const swController = new SWController(app); - swController.onPush({ + await swController.onPush({ waitUntil: waitUntilSpy, data: { json: () => { return {}; } } - }); + } as any); return waitUntilSpy.getCall(0).args[0].then(() => { expect(swController.sendMessageToWindowClients_['callCount']).to.equal( @@ -95,7 +95,7 @@ describe('Firebase Messaging > *SWController', () => { }); }); - it('should send to window if a notification payload', () => { + it('should send to window if a notification payload', async () => { const waitUntilSpy = sandbox.spy(); sandbox.stub(SWController.prototype, 'sendMessageToWindowClients_'); sandbox @@ -103,7 +103,7 @@ describe('Firebase Messaging > *SWController', () => { .callsFake(async () => true); const swController = new SWController(app); - swController.onPush({ + await swController.onPush({ waitUntil: waitUntilSpy, data: { json: () => { @@ -112,7 +112,7 @@ describe('Firebase Messaging > *SWController', () => { }; } } - }); + } as any); return waitUntilSpy.getCall(0).args[0].then(() => { expect(swController.sendMessageToWindowClients_['callCount']).to.equal( @@ -121,7 +121,7 @@ describe('Firebase Messaging > *SWController', () => { }); }); - it('should send to window if a bgMessageHandler is defined', () => { + it('should send to window if a bgMessageHandler is defined', async () => { const waitUntilSpy = sandbox.spy(); sandbox.stub(SWController.prototype, 'sendMessageToWindowClients_'); sandbox @@ -130,14 +130,14 @@ describe('Firebase Messaging > *SWController', () => { const swController = new SWController(app); swController.setBackgroundMessageHandler((() => {}) as any); - swController.onPush({ + await swController.onPush({ waitUntil: waitUntilSpy, data: { json: () => { return {}; } } - }); + } as any); return waitUntilSpy.getCall(0).args[0].then(() => { expect(swController.sendMessageToWindowClients_['callCount']).to.equal( @@ -146,7 +146,7 @@ describe('Firebase Messaging > *SWController', () => { }); }); - it('should generate notification without options and show notification if no visible clients', () => { + it('should generate notification without options and show notification if no visible clients', async () => { const registration = makeFakeSWReg(); self['registration'] = registration; @@ -157,7 +157,7 @@ describe('Firebase Messaging > *SWController', () => { sandbox.spy(registration, 'showNotification'); const swController = new SWController(app); - swController.onPush({ + await swController.onPush({ waitUntil: waitUntilSpy, data: { json: () => { @@ -166,7 +166,7 @@ describe('Firebase Messaging > *SWController', () => { }; } } - }); + } as any); return waitUntilSpy.getCall(0).args[0].then(() => { expect(registration.showNotification['callCount']).to.equal(1); @@ -181,7 +181,7 @@ describe('Firebase Messaging > *SWController', () => { }); }); - it('should generate notification with options and show notification if no visible clients', () => { + it('should generate notification with options and show notification if no visible clients', async () => { const registration = makeFakeSWReg(); self['registration'] = registration; @@ -196,14 +196,14 @@ describe('Firebase Messaging > *SWController', () => { const swController = new SWController(app); swController.setBackgroundMessageHandler(bgMessageHandlerSpy); - swController.onPush({ + await swController.onPush({ waitUntil: waitUntilSpy, data: { json: () => { return payloadData; } } - }); + } as any); return waitUntilSpy.getCall(0).args[0].then(() => { expect(bgMessageHandlerSpy['callCount']).to.equal(1); @@ -211,7 +211,7 @@ describe('Firebase Messaging > *SWController', () => { }); }); - it('should fall back to background message handler otherwise', () => { + it('should fall back to background message handler otherwise', async () => { const registration = makeFakeSWReg(); self['registration'] = registration; @@ -228,7 +228,7 @@ describe('Firebase Messaging > *SWController', () => { }; const swController = new SWController(app); - swController.onPush({ + await swController.onPush({ waitUntil: waitUntilSpy, data: { json: () => { @@ -237,7 +237,7 @@ describe('Firebase Messaging > *SWController', () => { }; } } - }); + } as any); return waitUntilSpy.getCall(0).args[0].then(() => { expect(registration.showNotification['callCount']).to.equal(1); @@ -257,21 +257,21 @@ describe('Firebase Messaging > *SWController', () => { }); }); - it('should do nothing if no background message handler and no notification', () => { + it('should do nothing if no background message handler and no notification', async () => { const waitUntilSpy = sandbox.spy(); sandbox .stub(SWController.prototype, 'hasVisibleClients_') .callsFake(async () => false); const swController = new SWController(app); - swController.onPush({ + await swController.onPush({ waitUntil: waitUntilSpy, data: { json: () => { return {}; } } - }); + } as any); return waitUntilSpy.getCall(0).args[0]; }); @@ -454,33 +454,33 @@ describe('Firebase Messaging > *SWController', () => { }); describe('onNotificationClick', () => { - it('should do nothing for no notification', () => { - const event = { + it('should do nothing for no notification', async () => { + const event: any = { waitUntil: sandbox.spy(), stopImmediatePropagation: sandbox.spy() }; const swController = new SWController(app); - swController.onNotificationClick(event); + await swController.onNotificationClick(event); expect(event.stopImmediatePropagation.callCount).to.equal(0); }); - it('should do nothing for notification with no data', () => { - const event = { + it('should do nothing for notification with no data', async () => { + const event: any = { notification: {}, waitUntil: sandbox.spy(), stopImmediatePropagation: sandbox.spy() }; const swController = new SWController(app); - swController.onNotificationClick(event); + await swController.onNotificationClick(event); expect(event.stopImmediatePropagation.callCount).to.equal(0); }); - it('should do nothing for notification with no FCM data', () => { - const event = { + it('should do nothing for notification with no FCM data', async () => { + const event: any = { notification: { data: {} }, @@ -489,13 +489,13 @@ describe('Firebase Messaging > *SWController', () => { }; const swController = new SWController(app); - swController.onNotificationClick(event); + await swController.onNotificationClick(event); expect(event.stopImmediatePropagation.callCount).to.equal(0); }); - it('should handle FCM notification without a notification data field', () => { - const event = { + it('should handle FCM notification without a notification data field', async () => { + const event: any = { notification: { data: { FCM_MSG: {} @@ -507,14 +507,14 @@ describe('Firebase Messaging > *SWController', () => { }; const swController = new SWController(app); - swController.onNotificationClick(event); + await swController.onNotificationClick(event); expect(event.stopImmediatePropagation.callCount).to.equal(1); expect(event.notification.close.callCount).to.equal(1); }); - it('should handle FCM notification without a click_action field', () => { - const event = { + it('should handle FCM notification without a click_action field', async () => { + const event: any = { notification: { data: { FCM_MSG: { @@ -528,15 +528,15 @@ describe('Firebase Messaging > *SWController', () => { }; const swController = new SWController(app); - swController.onNotificationClick(event); + await swController.onNotificationClick(event); expect(event.stopImmediatePropagation.callCount).to.equal(1); expect(event.notification.close.callCount).to.equal(1); }); - it('should open click_action URL for FCM notification (third-party so no window client access)', () => { + it('should open click_action URL for FCM notification (third-party so no window client access)', async () => { const clickAction = '/test-click-action'; - const event = { + const event: any = { notification: { data: { FCM_MSG: { @@ -564,7 +564,7 @@ describe('Firebase Messaging > *SWController', () => { .stub(swController, 'getWindowClient_') .callsFake(async () => null); - swController.onNotificationClick(event); + await swController.onNotificationClick(event); expect(event.stopImmediatePropagation.callCount).to.equal(1); expect(event.notification.close.callCount).to.equal(1); @@ -577,10 +577,10 @@ describe('Firebase Messaging > *SWController', () => { }); }); - it('should open click_action URL for FCM notification (same origin will include window client access)', () => { + it('should open click_action URL for FCM notification (same origin will include window client access)', async () => { const fakeWindowClient = {}; const clickAction = '/test-click-action'; - const event = { + const event: any = { notification: { data: { FCM_MSG: { @@ -611,7 +611,7 @@ describe('Firebase Messaging > *SWController', () => { .stub(swController, 'attemptToMessageClient_') .callsFake(async () => {}); - swController.onNotificationClick(event); + await swController.onNotificationClick(event); expect(event.stopImmediatePropagation.callCount).to.equal(1); expect(event.notification.close.callCount).to.equal(1); @@ -635,12 +635,12 @@ describe('Firebase Messaging > *SWController', () => { }); }); - it('should not open a window if one exists, instead focus is', () => { + it('should not open a window if one exists, instead focus is', async () => { const fakeWindowClient = { focus: sandbox.stub().callsFake(() => fakeWindowClient) }; const clickAction = '/test-click-action'; - const event = { + const event: any = { notification: { data: { FCM_MSG: { @@ -671,7 +671,7 @@ describe('Firebase Messaging > *SWController', () => { .stub(swController, 'attemptToMessageClient_') .callsFake(async () => {}); - swController.onNotificationClick(event); + await swController.onNotificationClick(event); expect(event.stopImmediatePropagation.callCount).to.equal(1); expect(event.notification.close.callCount).to.equal(1); @@ -698,14 +698,16 @@ describe('Firebase Messaging > *SWController', () => { describe('getNotificationData_', () => { it('should return nothing for no payload', () => { const swController = new SWController(app); - expect(swController.getNotificationData_(undefined)).to.equal(undefined); + expect(swController.getNotificationData_(undefined as any)).to.equal( + undefined + ); }); }); describe('attemptToMessageClient_', () => { it('should reject when no window client provided', () => { const swController = new SWController(app); - return swController.attemptToMessageClient_(null, {}).then( + return swController.attemptToMessageClient_(null as any, {} as any).then( () => { throw new Error('Expected error to be thrown'); }, @@ -717,8 +719,8 @@ describe('Firebase Messaging > *SWController', () => { }); it('should message window client', () => { - const msg = {}; - const client = { + const msg: any = {}; + const client: any = { postMessage: sandbox.spy() }; const swController = new SWController(app); @@ -759,7 +761,7 @@ describe('Firebase Messaging > *SWController', () => { .stub(swController, 'attemptToMessageClient_') .callsFake(async () => {}); - const payload = { + const payload: any = { example: 'test' }; @@ -856,7 +858,7 @@ describe('Firebase Messaging > *SWController', () => { it('should update token if the subscription has changed', () => {}); - it('should handle errors and delete token', () => { + it('should handle errors and delete token', async () => { const registration = makeFakeSWReg(); self['registration'] = registration; @@ -865,12 +867,12 @@ describe('Firebase Messaging > *SWController', () => { .callsFake(async () => { throw new Error('Injected Error'); }); - const event = { + const event: any = { waitUntil: sandbox.spy() }; const swController = new SWController(app); - swController.onSubChange(event); + await swController.onSubChange(event); return event.waitUntil .getCall(0)