Add types (#637)

* Add types to @firebase/messaging

This makes src/ compatible with --noImplicitAny. test/ still needs work.

* [AUTOMATED]: License Headers

* Remove casts
This commit is contained in:
Mertcan Mermerkaya
2018-04-05 22:14:35 +01:00
committed by GitHub
parent 684bf6ed82
commit f6d5504196
20 changed files with 349 additions and 409 deletions
+8 -3
View File
@@ -17,11 +17,16 @@
import { WindowController } from './src/controllers/window-controller';
import { SWController } from './src/controllers/sw-controller';
import { firebase } from '@firebase/app';
import {
_FirebaseNamespace,
FirebaseServiceFactory
} from '@firebase/app-types/private';
import * as types from '@firebase/messaging-types';
export function registerMessaging(instance) {
export function registerMessaging(instance: _FirebaseNamespace) {
const messagingName = 'messaging';
const factoryMethod = app => {
const factoryMethod: FirebaseServiceFactory = app => {
if (self && 'ServiceWorkerGlobalScope' in self) {
return new SWController(app);
}
@@ -42,7 +47,7 @@ export function registerMessaging(instance) {
);
}
registerMessaging(firebase);
registerMessaging(firebase as _FirebaseNamespace);
/**
* Define extension behavior of `registerMessaging`
+2 -1
View File
@@ -14,7 +14,8 @@
},
"license": "Apache-2.0",
"peerDependencies": {
"@firebase/app": "^0.1.0"
"@firebase/app": "^0.1.0",
"@firebase/app-types": "^0.1.0"
},
"dependencies": {
"@firebase/messaging-types": "0.1.2",
@@ -18,18 +18,25 @@ import { ErrorFactory } from '@firebase/util';
import { ERROR_CODES, ERROR_MAP } from '../models/errors';
import { TokenDetailsModel } from '../models/token-details-model';
import { VapidDetailsModel } from '../models/vapid-details-model';
import { NotificationPermission } from '../models/notification-permission';
import { IIDModel } from '../models/iid-model';
import { arrayBufferToBase64 } from '../helpers/array-buffer-to-base64';
import { FirebaseApp } from '@firebase/app-types';
import { TokenDetails } from '../interfaces/token-details';
import {
createSubscribe,
Observer,
NextFn,
PartialObserver
} from '@firebase/util';
const SENDER_ID_OPTION_NAME = 'messagingSenderId';
// Database cache should be invalidated once a week.
export const TOKEN_EXPIRATION_MILLIS = 7 * 24 * 60 * 60 * 1000; // 7 days
export class ControllerInterface {
public app;
public INTERNAL;
protected errorFactory_;
export abstract class ControllerInterface {
public app: FirebaseApp;
public INTERNAL: any;
protected errorFactory_: ErrorFactory<string>;
private messagingSenderId_: string;
private tokenDetailsModel_: TokenDetailsModel;
private vapidDetailsModel_: VapidDetailsModel;
@@ -37,9 +44,8 @@ export class ControllerInterface {
/**
* An interface of the Messaging Service API
* @param {!firebase.app.App} app
*/
constructor(app) {
constructor(app: FirebaseApp) {
this.errorFactory_ = new ErrorFactory('messaging', 'Messaging', ERROR_MAP);
if (
@@ -49,7 +55,7 @@ export class ControllerInterface {
throw this.errorFactory_.create(ERROR_CODES.BAD_SENDER_ID);
}
this.messagingSenderId_ = app.options[SENDER_ID_OPTION_NAME];
this.messagingSenderId_ = app.options[SENDER_ID_OPTION_NAME]!;
this.tokenDetailsModel_ = new TokenDetailsModel();
this.vapidDetailsModel_ = new VapidDetailsModel();
@@ -66,8 +72,8 @@ export class ControllerInterface {
async getToken(): Promise<string | null> {
// Check with permissions
const currentPermission = this.getNotificationPermission_();
if (currentPermission !== NotificationPermission.GRANTED) {
if (currentPermission === NotificationPermission.DENIED) {
if (currentPermission !== 'granted') {
if (currentPermission === 'denied') {
return Promise.reject(
this.errorFactory_.create(ERROR_CODES.NOTIFICATIONS_BLOCKED)
);
@@ -113,7 +119,7 @@ export class ControllerInterface {
swReg: ServiceWorkerRegistration,
pushSubscription: PushSubscription,
publicVapidKey: Uint8Array,
tokenDetails: Object
tokenDetails: TokenDetails
): Promise<string> {
const isTokenValid = this.isTokenStillValid(
pushSubscription,
@@ -122,8 +128,8 @@ export class ControllerInterface {
);
if (isTokenValid) {
const now = Date.now();
if (now < tokenDetails['createTime'] + TOKEN_EXPIRATION_MILLIS) {
return tokenDetails['fcmToken'];
if (now < tokenDetails.createTime! + TOKEN_EXPIRATION_MILLIS) {
return tokenDetails.fcmToken;
} else {
return this.updateToken(
swReg,
@@ -138,7 +144,7 @@ export class ControllerInterface {
// have changed), delete the existing token from the FCM client and server
// database. No need to unsubscribe from the Service Worker as we have a
// good push subscription that we'd like to use in getNewToken.
await this.deleteTokenFromDB(tokenDetails['fcmToken']);
await this.deleteTokenFromDB(tokenDetails.fcmToken);
return this.getNewToken(swReg, pushSubscription, publicVapidKey);
}
@@ -148,20 +154,20 @@ export class ControllerInterface {
private isTokenStillValid(
pushSubscription: PushSubscription,
publicVapidKey: Uint8Array,
tokenDetails: Object
): Boolean {
if (arrayBufferToBase64(publicVapidKey) !== tokenDetails['vapidKey']) {
tokenDetails: TokenDetails
): boolean {
if (arrayBufferToBase64(publicVapidKey) !== tokenDetails.vapidKey) {
return false;
}
// getKey() isn't defined in the PushSubscription externs file, hence
// subscription['getKey']('<key name>').
// subscription.getKey('<key name>').
return (
pushSubscription.endpoint === tokenDetails['endpoint'] &&
arrayBufferToBase64(pushSubscription['getKey']('auth')) ===
tokenDetails['auth'] &&
arrayBufferToBase64(pushSubscription['getKey']('p256dh')) ===
tokenDetails['p256dh']
pushSubscription.endpoint === tokenDetails.endpoint &&
arrayBufferToBase64(pushSubscription.getKey('auth')!) ===
tokenDetails.auth &&
arrayBufferToBase64(pushSubscription.getKey('p256dh')!) ===
tokenDetails.p256dh
);
}
@@ -169,24 +175,24 @@ export class ControllerInterface {
swReg: ServiceWorkerRegistration,
pushSubscription: PushSubscription,
publicVapidKey: Uint8Array,
tokenDetails: Object
tokenDetails: TokenDetails
): Promise<string> {
try {
const updatedToken = await this.iidModel_.updateToken(
this.messagingSenderId_,
tokenDetails['fcmToken'],
tokenDetails['fcmPushSet'],
tokenDetails.fcmToken,
tokenDetails.fcmPushSet,
pushSubscription,
publicVapidKey
);
const allDetails = {
const allDetails: TokenDetails = {
swScope: swReg.scope,
vapidKey: publicVapidKey,
subscription: pushSubscription,
fcmSenderId: this.messagingSenderId_,
fcmToken: updatedToken,
fcmPushSet: tokenDetails['fcmPushSet']
fcmPushSet: tokenDetails.fcmPushSet
};
await this.tokenDetailsModel_.saveTokenDetails(allDetails);
@@ -196,7 +202,7 @@ export class ControllerInterface {
);
return updatedToken;
} catch (e) {
await this.deleteToken(tokenDetails['fcmToken']);
await this.deleteToken(tokenDetails.fcmToken);
throw e;
}
}
@@ -211,17 +217,17 @@ export class ControllerInterface {
pushSubscription,
publicVapidKey
);
const allDetails = {
const allDetails: TokenDetails = {
swScope: swReg.scope,
vapidKey: publicVapidKey,
subscription: pushSubscription,
fcmSenderId: this.messagingSenderId_,
fcmToken: tokenDetails['token'],
fcmPushSet: tokenDetails['pushSet']
fcmToken: tokenDetails.token,
fcmPushSet: tokenDetails.pushSet
};
await this.tokenDetailsModel_.saveTokenDetails(allDetails);
await this.vapidDetailsModel_.saveVapidDetails(swReg.scope, publicVapidKey);
return tokenDetails['token'];
return tokenDetails.token;
}
/**
@@ -231,7 +237,7 @@ export class ControllerInterface {
* whether or not the unsubscribe request was processed successfully.
* @export
*/
async deleteToken(token: string): Promise<Boolean> {
async deleteToken(token: string): Promise<boolean> {
// Delete the token details from the database.
await this.deleteTokenFromDB(token);
// Unsubscribe from the SW.
@@ -254,19 +260,19 @@ export class ControllerInterface {
private async deleteTokenFromDB(token: string): Promise<void> {
const details = await this.tokenDetailsModel_.deleteToken(token);
await this.iidModel_.deleteToken(
details['fcmSenderId'],
details['fcmToken'],
details['fcmPushSet']
details.fcmSenderId,
details.fcmToken,
details.fcmPushSet
);
}
getSWRegistration_(): Promise<ServiceWorkerRegistration> {
throw this.errorFactory_.create(ERROR_CODES.SHOULD_BE_INHERITED);
}
// Visible for testing
// TODO: Make protected
abstract getSWRegistration_(): Promise<ServiceWorkerRegistration>;
getPublicVapidKey_(): Promise<Uint8Array> {
throw this.errorFactory_.create(ERROR_CODES.SHOULD_BE_INHERITED);
}
// Visible for testing
// TODO: Make protected
abstract getPublicVapidKey_(): Promise<Uint8Array>;
/**
* Gets a PushSubscription for the current user.
@@ -295,44 +301,27 @@ export class ControllerInterface {
throw this.errorFactory_.create(ERROR_CODES.AVAILABLE_IN_WINDOW);
}
/**
* @export
* @param {!ServiceWorkerRegistration} registration
*/
useServiceWorker(registration) {
useServiceWorker(registration: ServiceWorkerRegistration) {
throw this.errorFactory_.create(ERROR_CODES.AVAILABLE_IN_WINDOW);
}
/**
* @export
* @param {!string} b64PublicKey
*/
usePublicVapidKey(b64PublicKey) {
usePublicVapidKey(b64PublicKey: string) {
throw this.errorFactory_.create(ERROR_CODES.AVAILABLE_IN_WINDOW);
}
/**
* @export
* @param {!firebase.Observer|function(*)} nextOrObserver
* @param {function(!Error)=} optError
* @param {function()=} optCompleted
* @return {!function()}
*/
onMessage(nextOrObserver, optError, optCompleted) {
onMessage(
nextOrObserver: NextFn<{}> | PartialObserver<{}>,
error?: (e: Error) => void,
completed?: () => void
) {
throw this.errorFactory_.create(ERROR_CODES.AVAILABLE_IN_WINDOW);
}
/**
* @export
* @param {!firebase.Observer|function()} nextOrObserver An observer object
* or a function triggered on token refresh.
* @param {function(!Error)=} optError Optional A function
* triggered on token refresh error.
* @param {function()=} optCompleted Optional function triggered when the
* observer is removed.
* @return {!function()} The unsubscribe function for the observer.
*/
onTokenRefresh(nextOrObserver, optError, optCompleted) {
onTokenRefresh(
nextOrObserver: NextFn<{}> | PartialObserver<{}>,
error?: (e: Error) => void,
completed?: () => void
) {
throw this.errorFactory_.create(ERROR_CODES.AVAILABLE_IN_WINDOW);
}
@@ -340,11 +329,7 @@ export class ControllerInterface {
// The following methods are used by the service worker only.
//
/**
* @export
* @param {function(Object)} callback
*/
setBackgroundMessageHandler(callback) {
setBackgroundMessageHandler(callback: (a: any) => any) {
throw this.errorFactory_.create(ERROR_CODES.AVAILABLE_IN_SW);
}
@@ -369,7 +354,7 @@ export class ControllerInterface {
* @private
* @return {string} The currenct permission state.
*/
getNotificationPermission_() {
getNotificationPermission_(): NotificationPermission {
return (Notification as any).permission;
}
@@ -18,24 +18,37 @@ import { ControllerInterface } from './controller-interface';
import { ERROR_CODES } from '../models/errors';
import { DEFAULT_PUBLIC_VAPID_KEY } from '../models/fcm-details';
import WorkerPageMessage from '../models/worker-page-message';
import { FirebaseApp } from '@firebase/app-types';
const FCM_MSG = 'FCM_MSG';
export class SWController extends ControllerInterface {
private bgMessageHandler_: ((input: Object) => Promise<any>) | null = null;
export type BgMessageHandler = (input: any) => Promise<any>;
constructor(app) {
export class SWController extends ControllerInterface {
private bgMessageHandler_: BgMessageHandler | null = null;
constructor(app: FirebaseApp) {
super(app);
self.addEventListener('push', e => this.onPush_(e), false);
self.addEventListener(
'push',
(e: any) => {
this.onPush_(e);
},
false
);
self.addEventListener(
'pushsubscriptionchange',
e => this.onSubChange_(e),
(e: any) => {
this.onSubChange_(e);
},
false
);
self.addEventListener(
'notificationclick',
e => this.onNotificationClick_(e),
(e: any) => {
this.onNotificationClick_(e);
},
false
);
}
@@ -51,12 +64,13 @@ export class SWController extends ControllerInterface {
*
* If there is no notification data in the payload then no notification will be
* shown.
* @private
*/
onPush_(event) {
let msgPayload;
// Visible for testing
// TODO: Make private
onPush_(event: any) {
let msgPayload: any;
try {
msgPayload = event.data.json();
msgPayload = event.data!.json();
} catch (err) {
// Not JSON so not an FCM message
return;
@@ -75,8 +89,8 @@ export class SWController extends ControllerInterface {
const notificationDetails = this.getNotificationData_(msgPayload);
if (notificationDetails) {
const notificationTitle = notificationDetails.title || '';
return (this.getSWRegistration_() as any).then(reg => {
const notificationTitle = (notificationDetails as any).title || '';
return this.getSWRegistration_().then(reg => {
return reg.showNotification(notificationTitle, notificationDetails);
});
} else if (this.bgMessageHandler_) {
@@ -88,10 +102,9 @@ export class SWController extends ControllerInterface {
event.waitUntil(handleMsgPromise);
}
/**
* @private
*/
onSubChange_(event) {
// Visible for testing
// TODO: Make private
onSubChange_(event: any) {
const promiseChain = this.getSWRegistration_()
.then(registration => {
return registration.pushManager
@@ -129,10 +142,9 @@ export class SWController extends ControllerInterface {
event.waitUntil(promiseChain);
}
/**
* @private
*/
onNotificationClick_(event) {
// Visible for testing
// TODO: Make private
onNotificationClick_(event: any) {
if (
!(
event.notification &&
@@ -192,12 +204,9 @@ export class SWController extends ControllerInterface {
event.waitUntil(promiseChain);
}
/**
* @private
* @param {Object} msgPayload
* @return {NotificationOptions|undefined}
*/
getNotificationData_(msgPayload) {
// Visible for testing
// TODO: Make private
getNotificationData_(msgPayload: any): NotificationOptions | undefined {
if (!msgPayload) {
return;
}
@@ -228,12 +237,12 @@ export class SWController extends ControllerInterface {
* and the promise it returns will be passed to event.waitUntil.
* If you do not set this callback then all push messages will let and the
* developer can handle them in a their own 'push' event callback
* @export
* @param {function(Object)} callback The callback to be called when a push
* message is received and a notification must be shown. The callback will
* be given the data from the push message.
*
* @param callback The callback to be called when a push message is received
* and a notification must be shown. The callback will be given the data from
* the push message.
*/
setBackgroundMessageHandler(callback) {
setBackgroundMessageHandler(callback: BgMessageHandler) {
if (!callback || typeof callback !== 'function') {
throw this.errorFactory_.create(ERROR_CODES.BG_HANDLER_FUNCTION_EXPECTED);
}
@@ -242,12 +251,12 @@ export class SWController extends ControllerInterface {
}
/**
* @private
* @param {string} url The URL to look for when focusing a client.
* @return {Object} Returns an existing window client or a newly opened
* WindowClient.
* @param url The URL to look for when focusing a client.
* @return Returns an existing window client or a newly opened WindowClient.
*/
getWindowClient_(url) {
// Visible for testing
// TODO: Make private
getWindowClient_(url: string): Promise<any> {
// 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;
@@ -257,7 +266,7 @@ export class SWController extends ControllerInterface {
type: 'window',
includeUncontrolled: true
})
.then(clientList => {
.then((clientList: any) => {
let suitableClient = null;
for (let i = 0; i < clientList.length; i++) {
const parsedClientUrl = new URL(
@@ -280,14 +289,14 @@ export class SWController extends ControllerInterface {
/**
* This message will attempt to send the message to a window client.
* @private
* @param {Object} client The WindowClient to send the message to.
* @param {Object} message The message to send to the client.
* @returns {Promise} Returns a promise that resolves after sending the
* message. This does not guarantee that the message was successfully
* received.
* @param client The WindowClient to send the message to.
* @param message The message to send to the client.
* @returns Returns a promise that resolves after sending the message. This
* does not guarantee that the message was successfully received.
*/
async attemptToMessageClient_(client, message) {
// Visible for testing
// TODO: Make private
async attemptToMessageClient_(client: any, message: any): Promise<void> {
// NOTE: This returns a promise in case this API is abstracted later on to
// do additional work
if (!client) {
@@ -300,42 +309,46 @@ export class SWController extends ControllerInterface {
}
/**
* @private
* @returns {Promise<boolean>} If there is currently a visible WindowClient,
* this method will resolve to true, otherwise false.
* @returns If there is currently a visible WindowClient, this method will
* resolve to true, otherwise false.
*/
hasVisibleClients_() {
// Visible for testing
// TODO: Make private
hasVisibleClients_(): Promise<boolean> {
return (self as any).clients
.matchAll({
type: 'window',
includeUncontrolled: true
})
.then(clientList => {
return clientList.some(client => client.visibilityState === 'visible');
.then((clientList: any) => {
return clientList.some(
(client: any) => client.visibilityState === 'visible'
);
});
}
/**
* @private
* @param {Object} msgPayload The data from the push event that should be sent
* to all available pages.
* @returns {Promise} Returns a promise that resolves once the message
* has been sent to all WindowClients.
* @param msgPayload The data from the push event that should be sent to all
* available pages.
* @returns Returns a promise that resolves once the message has been sent to
* all WindowClients.
*/
sendMessageToWindowClients_(msgPayload) {
// Visible for testing
// TODO: Make private
sendMessageToWindowClients_(msgPayload: any): Promise<void> {
return (self as any).clients
.matchAll({
type: 'window',
includeUncontrolled: true
})
.then(clientList => {
.then((clientList: any) => {
const internalMsg = WorkerPageMessage.createNewMsg(
WorkerPageMessage.TYPES_OF_MSG.PUSH_MSG_RECEIVED,
msgPayload
);
return Promise.all(
clientList.map(client => {
clientList.map((client: any) => {
return this.attemptToMessageClient_(client, internalMsg);
})
);
@@ -344,11 +357,9 @@ export class SWController extends ControllerInterface {
/**
* This will register the default service worker and return the registration.
* @private
* @return {Promise<!ServiceWorkerRegistration>} The service worker
* registration to be used for the push service.
* @return he service worker registration to be used for the push service.
*/
getSWRegistration_() {
getSWRegistration_(): Promise<ServiceWorkerRegistration> {
return Promise.resolve((self as any).registration);
}
@@ -15,73 +15,42 @@
*/
import { FirebaseMessaging } from '@firebase/messaging-types';
import { FirebaseApp } from '@firebase/app-types';
import { ControllerInterface } from './controller-interface';
import { ERROR_CODES } from '../models/errors';
import WorkerPageMessage from '../models/worker-page-message';
import { DEFAULT_SW_PATH, DEFAULT_SW_SCOPE } from '../models/default-sw';
import { NotificationPermission } from '../models/notification-permission';
import { DEFAULT_PUBLIC_VAPID_KEY } from '../models/fcm-details';
import { base64ToArrayBuffer } from '../helpers/base64-to-array-buffer';
import { createSubscribe } from '@firebase/util';
import {
createSubscribe,
Observer,
NextFn,
PartialObserver
} from '@firebase/util';
declare const firebase: any;
export class WindowController extends ControllerInterface
implements FirebaseMessaging {
private registrationToUse_;
private publicVapidKeyToUse_;
private manifestCheckPromise_;
private messageObserver_: any = null;
private registrationToUse_: ServiceWorkerRegistration | null = null;
private publicVapidKeyToUse_: Uint8Array | null = null;
private manifestCheckPromise_: Promise<any> | null = null;
private messageObserver_: Observer<{}, Error> | null = null;
private onMessage_ = createSubscribe(observer => {
this.messageObserver_ = observer;
});
private tokenRefreshObserver_: any = null;
private tokenRefreshObserver_: Observer<{}, Error> | null = null;
private onTokenRefresh_ = createSubscribe(observer => {
this.tokenRefreshObserver_ = observer;
});
/**
* A service that provides a MessagingService instance.
* @param {!firebase.app.App} app
*/
constructor(app) {
constructor(app: FirebaseApp) {
super(app);
/**
* @private
* @type {ServiceWorkerRegistration}
*/
this.registrationToUse_;
/**
* @private
* @type {Promise}
*/
this.manifestCheckPromise_;
/**
* @private
* @type {firebase.Observer}
*/
this.messageObserver_ = null;
/**
* @private {!firebase.Subscribe} The subscribe function to the onMessage
* observer.
*/
this.onMessage_ = createSubscribe(observer => {
this.messageObserver_ = observer;
});
/**
* @private
* @type {firebase.Observer}
*/
this.tokenRefreshObserver_ = null;
this.onTokenRefresh_ = createSubscribe(observer => {
this.tokenRefreshObserver_ = observer;
});
this.setupSWMessageListener_();
}
@@ -160,15 +129,17 @@ export class WindowController extends ControllerInterface
* rejects
*/
async requestPermission() {
if ((Notification as any).permission === NotificationPermission.GRANTED) {
if (
((Notification as any).permission as NotificationPermission) === 'granted'
) {
return;
}
return new Promise((resolve, reject) => {
const managePermissionResult = result => {
if (result === NotificationPermission.GRANTED) {
const managePermissionResult = (result: NotificationPermission) => {
if (result === 'granted') {
return resolve();
} else if (result === NotificationPermission.DENIED) {
} else if (result === 'denied') {
return reject(
this.errorFactory_.create(ERROR_CODES.PERMISSION_BLOCKED)
);
@@ -196,15 +167,15 @@ export class WindowController extends ControllerInterface
* This method allows a developer to override the default service worker and
* instead use a custom service worker.
* @export
* @param {!ServiceWorkerRegistration} registration The service worker
* registration that should be used to receive the push messages.
* @param registration The service worker registration that should be used to
* receive the push messages.
*/
useServiceWorker(registration) {
useServiceWorker(registration: ServiceWorkerRegistration) {
if (!(registration instanceof ServiceWorkerRegistration)) {
throw this.errorFactory_.create(ERROR_CODES.SW_REGISTRATION_EXPECTED);
}
if (typeof this.registrationToUse_ !== 'undefined') {
if (this.registrationToUse_ != null) {
throw this.errorFactory_.create(ERROR_CODES.USE_SW_BEFORE_GET_TOKEN);
}
@@ -215,14 +186,14 @@ export class WindowController extends ControllerInterface
* This method allows a developer to override the default vapid key
* and instead use a custom VAPID public key.
* @export
* @param {!string} publicKey A URL safe base64 encoded string.
* @param publicKey A URL safe base64 encoded string.
*/
usePublicVapidKey(publicKey) {
usePublicVapidKey(publicKey: string) {
if (typeof publicKey !== 'string') {
throw this.errorFactory_.create(ERROR_CODES.INVALID_PUBLIC_VAPID_KEY);
}
if (typeof this.publicVapidKeyToUse_ !== 'undefined') {
if (this.publicVapidKeyToUse_ != null) {
throw this.errorFactory_.create(
ERROR_CODES.USE_PUBLIC_KEY_BEFORE_GET_TOKEN
);
@@ -239,42 +210,55 @@ export class WindowController extends ControllerInterface
/**
* @export
* @param {!firebase.Observer|function(*)} nextOrObserver An observer object
* or a function triggered on message.
* @param {function(!Error)=} optError Optional A function triggered on
* message error.
* @param {function()=} optCompleted Optional function triggered when the
* observer is removed.
* @return {!function()} The unsubscribe function for the observer.
* @param nextOrObserver An observer object or a function triggered on
* message.
* @param error A function triggered on message error.
* @param completed function triggered when the observer is removed.
* @return The unsubscribe function for the observer.
*/
onMessage(nextOrObserver, optError?, optCompleted?) {
return this.onMessage_(nextOrObserver, optError, optCompleted);
onMessage(
nextOrObserver: NextFn<{}> | PartialObserver<{}>,
error?: (e: Error) => void,
completed?: () => void
) {
if (typeof nextOrObserver === 'function') {
return this.onMessage_(nextOrObserver, error, completed);
} else {
return this.onMessage_(nextOrObserver);
}
}
/**
* @export
* @param {!firebase.Observer|function()} nextOrObserver An observer object
* or a function triggered on token refresh.
* @param {function(!Error)=} optError Optional A function
* triggered on token refresh error.
* @param {function()=} optCompleted Optional function triggered when the
* observer is removed.
* @return {!function()} The unsubscribe function for the observer.
* @param nextOrObserver An observer object or a function triggered on token
* refresh.
* @param error A function triggered on token refresh error.
* @param completed function triggered when the observer is removed.
* @return The unsubscribe function for the observer.
*/
onTokenRefresh(nextOrObserver, optError, optCompleted) {
return this.onTokenRefresh_(nextOrObserver, optError, optCompleted);
onTokenRefresh(
nextOrObserver: NextFn<{}> | PartialObserver<{}>,
error?: (e: Error) => void,
completed?: () => void
) {
if (typeof nextOrObserver === 'function') {
return this.onTokenRefresh_(nextOrObserver, error, completed);
} else {
return this.onTokenRefresh_(nextOrObserver);
}
}
/**
* Given a registration, wait for the service worker it relates to
* become activer
* @private
* @param {ServiceWorkerRegistration} registration Registration to wait
* for service worker to become active
* @return {Promise<!ServiceWorkerRegistration>} Wait for service worker
* registration to become active
* @param registration Registration to wait for service worker to become active
* @return Wait for service worker registration to become active
*/
waitForRegistrationToActivate_(registration) {
// Visible for testing
// TODO: Make private
waitForRegistrationToActivate_(
registration: ServiceWorkerRegistration
): Promise<ServiceWorkerRegistration> {
const serviceWorker =
registration.installing || registration.waiting || registration.active;
@@ -312,12 +296,10 @@ export class WindowController extends ControllerInterface
}
/**
* This will regiater the default service worker and return the registration
* @private
* @return {Promise<!ServiceWorkerRegistration>} The service worker
* registration to be used for the push service.
* This will register the default service worker and return the registration
* @return The service worker registration to be used for the push service.
*/
getSWRegistration_() {
getSWRegistration_(): Promise<ServiceWorkerRegistration> {
if (this.registrationToUse_) {
return this.waitForRegistrationToActivate_(this.registrationToUse_);
}
@@ -326,11 +308,11 @@ export class WindowController extends ControllerInterface
// use a new service worker as registrationToUse_ is no longer undefined
this.registrationToUse_ = null;
return navigator.serviceWorker
return (navigator as any).serviceWorker
.register(DEFAULT_SW_PATH, {
scope: DEFAULT_SW_SCOPE
})
.catch(err => {
.catch((err: Error) => {
throw this.errorFactory_.create(
ERROR_CODES.FAILED_DEFAULT_REGISTRATION,
{
@@ -338,7 +320,7 @@ export class WindowController extends ControllerInterface
}
);
})
.then(registration => {
.then((registration: ServiceWorkerRegistration) => {
return this.waitForRegistrationToActivate_(registration).then(() => {
this.registrationToUse_ = registration;
@@ -355,7 +337,6 @@ export class WindowController extends ControllerInterface
/**
* This will return the default VAPID key or the uint8array version of the public VAPID key
* provided by the developer.
* @private
*/
getPublicVapidKey_(): Promise<Uint8Array> {
if (this.publicVapidKeyToUse_) {
@@ -369,17 +350,17 @@ export class WindowController extends ControllerInterface
* This method will set up a message listener to handle
* events from the service worker that should trigger
* events in the page.
*
* @private
*/
// Visible for testing
// TODO: Make private
setupSWMessageListener_() {
if (!('serviceWorker' in navigator)) {
return;
}
navigator.serviceWorker.addEventListener(
(navigator as any).serviceWorker.addEventListener(
'message',
event => {
(event: MessageEvent) => {
if (!event.data || !event.data[WorkerPageMessage.PARAMS.TYPE_OF_MSG]) {
// Not a message from FCM
return;
@@ -406,10 +387,11 @@ export class WindowController extends ControllerInterface
/**
* Checks to see if the required API's are valid or not.
* @private
* @return {boolean} Returns true if the desired APIs are available.
* @return Returns true if the desired APIs are available.
*/
isSupported_() {
// Visible for testing
// TODO: Make private
isSupported_(): boolean {
return (
'serviceWorker' in navigator &&
'PushManager' in window &&
@@ -14,12 +14,12 @@
* limitations under the License.
*/
function toBase64(arrayBuffer) {
function toBase64(arrayBuffer: ArrayBuffer | Uint8Array) {
const uint8Version = new Uint8Array(arrayBuffer);
return btoa(String.fromCharCode.apply(null, uint8Version));
}
export function arrayBufferToBase64(arrayBuffer) {
export function arrayBufferToBase64(arrayBuffer: ArrayBuffer | Uint8Array) {
const base64String = toBase64(arrayBuffer);
return base64String
.replace(/=/g, '')
@@ -14,7 +14,7 @@
* limitations under the License.
*/
export function base64ToArrayBuffer(base64String) {
export function base64ToArrayBuffer(base64String: string) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
@@ -1,5 +1,5 @@
/**
* Copyright 2017 Google Inc.
* 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.
@@ -14,8 +14,16 @@
* limitations under the License.
*/
export enum NotificationPermission {
DEFAULT = 'default',
GRANTED = 'granted',
DENIED = 'denied'
export interface TokenDetails {
fcmToken: string;
swScope: string;
vapidKey: string | Uint8Array;
subscription: PushSubscription;
fcmSenderId: string;
fcmPushSet: string;
createTime?: number;
endpoint?: string;
auth?: string;
p256dh?: string;
}
@@ -25,11 +25,7 @@ export class DBInterface {
protected errorFactory_: ErrorFactory<string>;
protected TRANSACTION_READ_WRITE: IDBTransactionMode;
/**
* @param {string} dbName
* @param {number} dbVersion
*/
constructor(dbName, dbVersion) {
constructor(dbName: string, dbVersion: number) {
this.errorFactory_ = new ErrorFactory('messaging', 'Messaging', ERROR_MAP);
this.DB_NAME_ = dbName;
this.dbVersion_ = dbVersion;
@@ -38,11 +34,11 @@ export class DBInterface {
}
/**
* Get the indexedDB as a promsie.
* @protected
* @return {!Promise<!IDBDatabase>} The IndexedDB database
* Get the indexedDB as a promise.
*/
openDatabase() {
// Visible for testing
// TODO: Make protected
openDatabase(): Promise<IDBDatabase> {
if (this.openDbPromise_) {
return this.openDbPromise_;
}
+7 -10
View File
@@ -19,6 +19,7 @@ import { ErrorFactory, base64 } from '@firebase/util';
import { ERROR_CODES, ERROR_MAP } from './errors';
import { arrayBufferToBase64 } from '../helpers/array-buffer-to-base64';
import { DEFAULT_PUBLIC_VAPID_KEY, ENDPOINT } from './fcm-details';
import { TokenDetails } from '../interfaces/token-details';
export interface IIDDetails {
token: string;
@@ -37,8 +38,8 @@ export class IIDModel {
subscription: PushSubscription,
publicVapidKey: Uint8Array
): Promise<IIDDetails> {
const p256dh = arrayBufferToBase64(subscription['getKey']('p256dh'));
const auth = arrayBufferToBase64(subscription['getKey']('auth'));
const p256dh = arrayBufferToBase64(subscription.getKey('p256dh')!);
const auth = arrayBufferToBase64(subscription.getKey('auth')!);
let fcmSubscribeBody =
`authorized_entity=${senderId}&` +
@@ -60,7 +61,7 @@ export class IIDModel {
body: fcmSubscribeBody
};
let responseData: Object | null = null;
let responseData: any = null;
try {
const response = await fetch(
ENDPOINT + '/fcm/connect/subscribe',
@@ -72,8 +73,6 @@ export class IIDModel {
throw this.errorFactory_.create(ERROR_CODES.TOKEN_SUBSCRIBE_FAILED);
}
responseData = responseData as Object;
if (responseData['error']) {
const message = responseData['error']['message'];
throw this.errorFactory_.create(ERROR_CODES.TOKEN_SUBSCRIBE_FAILED, {
@@ -105,8 +104,8 @@ export class IIDModel {
subscription: PushSubscription,
publicVapidKey: Uint8Array
): Promise<string> {
const p256dh = arrayBufferToBase64(subscription['getKey']('p256dh'));
const auth = arrayBufferToBase64(subscription['getKey']('auth'));
const p256dh = arrayBufferToBase64(subscription.getKey('p256dh')!);
const auth = arrayBufferToBase64(subscription.getKey('auth')!);
let fcmUpdateBody =
`push_set=${fcmPushSet}&` +
@@ -130,7 +129,7 @@ export class IIDModel {
body: fcmUpdateBody
};
let responseData: Object | null = null;
let responseData: any = null;
try {
const response = await fetch(
ENDPOINT + '/fcm/connect/subscribe',
@@ -141,8 +140,6 @@ export class IIDModel {
throw this.errorFactory_.create(ERROR_CODES.TOKEN_UPDATE_FAILED);
}
responseData = responseData as Object;
if (responseData['error']) {
const message = responseData['error']['message'];
throw this.errorFactory_.create(ERROR_CODES.TOKEN_UPDATE_FAILED, {
@@ -18,26 +18,12 @@ import { DBInterface } from './db-interface';
import { ERROR_CODES } from './errors';
import { arrayBufferToBase64 } from '../helpers/array-buffer-to-base64';
import { cleanV1 } from './clean-v1-undefined';
import { TokenDetails } from '../interfaces/token-details';
const FCM_TOKEN_OBJ_STORE = 'fcm_token_object_Store';
const DB_NAME = 'fcm_token_details_db';
const DB_VERSION = 2;
/** @record */
function ValidateInput() {}
/** @type {string|undefined} */
ValidateInput.prototype.fcmToken;
/** @type {string|undefined} */
ValidateInput.prototype.swScope;
/** @type {string|undefined} */
ValidateInput.prototype.vapidKey;
/** @type {PushSubscription|undefined} */
ValidateInput.prototype.subscription;
/** @type {string|undefined} */
ValidateInput.prototype.fcmSenderId;
/** @type {string|undefined} */
ValidateInput.prototype.fcmPushSet;
export class TokenDetailsModel extends DBInterface {
constructor() {
super(DB_NAME, DB_VERSION);
@@ -71,12 +57,9 @@ export class TokenDetailsModel extends DBInterface {
/**
* This method takes an object and will check for known arguments and
* validate the input.
* @private
* @param {!ValidateInput} input
* @return {!Promise} Returns promise that resolves if input is valid,
* rejects otherwise.
* @return Promise that resolves if input is valid, rejects otherwise.
*/
async validateInputs_(input) {
private async validateInputs_(input: Partial<TokenDetails>): Promise<void> {
if (input.fcmToken) {
if (typeof input.fcmToken !== 'string' || input.fcmToken.length === 0) {
return Promise.reject(this.errorFactory_.create(ERROR_CODES.BAD_TOKEN));
@@ -134,19 +117,17 @@ export class TokenDetailsModel extends DBInterface {
/**
* Given a token, this method will look up the details in indexedDB.
* @param {string} fcmToken
* @return {Promise<Object>} The details associated with that token.
* @return {Promise<TokenDetails>} The details associated with that token.
*/
getTokenDetailsFromToken(fcmToken) {
getTokenDetailsFromToken(fcmToken: string): Promise<TokenDetails> {
if (!fcmToken) {
return Promise.reject(this.errorFactory_.create(ERROR_CODES.BAD_TOKEN));
}
return this.validateInputs_({ fcmToken })
.then(() => {
return this.openDatabase();
})
.then(() => this.openDatabase())
.then(db => {
return new Promise((resolve, reject) => {
return new Promise<TokenDetails>((resolve, reject) => {
const transaction = db.transaction([FCM_TOKEN_OBJ_STORE]);
const objectStore = transaction.objectStore(FCM_TOKEN_OBJ_STORE);
const index = objectStore.index('fcmToken');
@@ -155,10 +136,9 @@ export class TokenDetailsModel extends DBInterface {
reject((<IDBRequest>event.target).error);
};
request.onsuccess = function(event) {
const result = (<IDBRequest>event.target).result
? (<IDBRequest>event.target).result
: null;
resolve(result);
const result: TokenDetails | null = (<IDBRequest>event.target)
.result;
resolve(result!);
};
});
});
@@ -167,11 +147,9 @@ export class TokenDetailsModel extends DBInterface {
/**
* Given a service worker scope, this method will look up the details in
* indexedDB.
* @public
* @param {string} swScope
* @return {Promise<Object>} The details associated with that token.
* @return The details associated with that token.
*/
getTokenDetailsFromSWScope(swScope) {
getTokenDetailsFromSWScope(swScope: string): Promise<TokenDetails> {
if (!swScope) {
return Promise.reject(this.errorFactory_.create(ERROR_CODES.BAD_SCOPE));
}
@@ -181,7 +159,7 @@ export class TokenDetailsModel extends DBInterface {
return this.openDatabase();
})
.then(db => {
return new Promise((resolve, reject) => {
return new Promise<TokenDetails>((resolve, reject) => {
const transaction = db.transaction([FCM_TOKEN_OBJ_STORE]);
const objectStore = transaction.objectStore(FCM_TOKEN_OBJ_STORE);
const scopeRequest = objectStore.get(swScope);
@@ -190,10 +168,9 @@ export class TokenDetailsModel extends DBInterface {
};
scopeRequest.onsuccess = event => {
const result = (<IDBRequest>event.target).result
? (<IDBRequest>event.target).result
: null;
resolve(result);
const result: TokenDetails | null = (<IDBRequest>event.target)
.result;
resolve(result!);
};
});
});
@@ -201,10 +178,7 @@ export class TokenDetailsModel extends DBInterface {
/**
* Save the details for the fcm token for re-use at a later date.
* @param {{swScope: !string, vapidKey: !string,
* subscription: !PushSubscription, fcmSenderId: !string, fcmToken: !string,
* fcmPushSet: !string}} input A plain js object containing args to save.
* @return {Promise<void>}
* @param input A plain js object containing args to save.
*/
saveTokenDetails({
swScope,
@@ -213,7 +187,7 @@ export class TokenDetailsModel extends DBInterface {
fcmSenderId,
fcmToken,
fcmPushSet
}) {
}: TokenDetails): Promise<void> {
if (!swScope) {
return Promise.reject(this.errorFactory_.create(ERROR_CODES.BAD_SCOPE));
}
@@ -258,22 +232,19 @@ export class TokenDetailsModel extends DBInterface {
return this.openDatabase();
})
.then(db => {
/**
* @dict
*/
const details = {
swScope: swScope,
vapidKey: arrayBufferToBase64(vapidKey),
vapidKey: arrayBufferToBase64(vapidKey as Uint8Array),
endpoint: subscription.endpoint,
auth: arrayBufferToBase64(subscription['getKey']('auth')),
p256dh: arrayBufferToBase64(subscription['getKey']('p256dh')),
auth: arrayBufferToBase64(subscription.getKey('auth')!),
p256dh: arrayBufferToBase64(subscription.getKey('p256dh')!),
fcmSenderId: fcmSenderId,
fcmToken: fcmToken,
fcmPushSet: fcmPushSet,
createTime: Date.now()
};
return new Promise((resolve, reject) => {
return new Promise<void>((resolve, reject) => {
const transaction = db.transaction(
[FCM_TOKEN_OBJ_STORE],
this.TRANSACTION_READ_WRITE
@@ -297,7 +268,7 @@ export class TokenDetailsModel extends DBInterface {
* @return {Promise<Object>} Resolves once the FCM token details have been
* deleted and returns the deleted details.
*/
deleteToken(token: string) {
deleteToken(token: string): Promise<TokenDetails> {
if (typeof token !== 'string' || token.length === 0) {
return Promise.reject(
this.errorFactory_.create(ERROR_CODES.INVALID_DELETE_TOKEN)
@@ -310,13 +281,13 @@ export class TokenDetailsModel extends DBInterface {
}
return this.openDatabase().then(db => {
return new Promise((resolve, reject) => {
return new Promise<TokenDetails>((resolve, reject) => {
const transaction = db.transaction(
[FCM_TOKEN_OBJ_STORE],
this.TRANSACTION_READ_WRITE
);
const objectStore = transaction.objectStore(FCM_TOKEN_OBJ_STORE);
const request = objectStore.delete(details['swScope']);
const request = objectStore.delete(details.swScope);
request.onerror = event => {
reject((<IDBRequest>event.target).error);
};
@@ -27,11 +27,7 @@ export class VapidDetailsModel extends DBInterface {
super(DB_NAME, DB_VERSION);
}
/**
* @override
* @param {IDBDatabase} db
*/
onDBUpgrade(db) {
onDBUpgrade(db: IDBDatabase) {
db.createObjectStore(FCM_VAPID_OBJ_STORE, {
keyPath: 'swScope'
});
@@ -28,13 +28,13 @@ const msgType = {
NOTIFICATION_CLICKED: 'notification-clicked'
};
const createNewMsg = (msgType, msgData) => {
function createNewMsg(msgType: any, msgData: any) {
const message = {
[PARAMS.TYPE_OF_MSG]: msgType,
[PARAMS.DATA]: msgData
};
return message;
};
}
export default {
PARAMS,
@@ -26,7 +26,6 @@ import { DEFAULT_PUBLIC_VAPID_KEY } from '../src/models/fcm-details';
import { TokenDetailsModel } from '../src/models/token-details-model';
import { VapidDetailsModel } from '../src/models/vapid-details-model';
import { IIDModel } from '../src/models/iid-model';
import { NotificationPermission } from '../src/models/notification-permission';
import { arrayBufferToBase64 } from '../src/helpers/array-buffer-to-base64';
import { base64ToArrayBuffer } from '../src/helpers/base64-to-array-buffer';
import { TOKEN_EXPIRATION_MILLIS } from '../src/controllers/controller-interface';
@@ -151,7 +150,7 @@ describe('Firebase Messaging > *Controller.getToken()', function() {
it('should handle a failure to get registration', function() {
sandbox
.stub(ControllerInterface.prototype, 'getNotificationPermission_')
.callsFake(() => NotificationPermission.GRANTED);
.callsFake(() => 'granted');
sandbox
.stub(navigator.serviceWorker, 'register')
@@ -181,10 +180,10 @@ describe('Firebase Messaging > *Controller.getToken()', function() {
ControllerInterface.prototype,
'getNotificationPermission_'
);
notificationStub.onCall(0).returns(NotificationPermission.DENIED);
notificationStub.onCall(1).returns(NotificationPermission.DEFAULT);
notificationStub.onCall(2).returns(NotificationPermission.DENIED);
notificationStub.onCall(3).returns(NotificationPermission.DEFAULT);
notificationStub.onCall(0).returns('denied');
notificationStub.onCall(1).returns('default');
notificationStub.onCall(2).returns('denied');
notificationStub.onCall(3).returns('default');
return servicesToTest.reduce((chain, ServiceClass) => {
const serviceInstance = new ServiceClass(app);
@@ -238,7 +237,7 @@ describe('Firebase Messaging > *Controller.getToken()', function() {
sandbox
.stub(ControllerInterface.prototype, 'getNotificationPermission_')
.callsFake(() => NotificationPermission.GRANTED);
.callsFake(() => 'granted');
sandbox
.stub(TokenDetailsModel.prototype, 'getTokenDetailsFromSWScope')
@@ -269,7 +268,7 @@ describe('Firebase Messaging > *Controller.getToken()', function() {
sandbox
.stub(ControllerInterface.prototype, 'getNotificationPermission_')
.callsFake(() => NotificationPermission.GRANTED);
.callsFake(() => 'granted');
sandbox
.stub(TokenDetailsModel.prototype, 'getTokenDetailsFromSWScope')
@@ -290,7 +289,7 @@ describe('Firebase Messaging > *Controller.getToken()', function() {
sandbox
.stub(ControllerInterface.prototype, 'getNotificationPermission_')
.callsFake(() => NotificationPermission.GRANTED);
.callsFake(() => 'granted');
sandbox
.stub(TokenDetailsModel.prototype, 'getTokenDetailsFromSWScope')
@@ -339,7 +338,7 @@ describe('Firebase Messaging > *Controller.getToken()', function() {
sandbox
.stub(ControllerInterface.prototype, 'getNotificationPermission_')
.callsFake(() => NotificationPermission.GRANTED);
.callsFake(() => 'granted');
sandbox
.stub(ServiceClass.prototype, 'getPushSubscription')
@@ -422,7 +421,7 @@ describe('Firebase Messaging > *Controller.getToken()', function() {
sandbox
.stub(ControllerInterface.prototype, 'getNotificationPermission_')
.callsFake(() => NotificationPermission.GRANTED);
.callsFake(() => 'granted');
const existingTokenDetails = VapidSetup['details'];
let vapidKeyToUse = DEFAULT_PUBLIC_VAPID_KEY;
@@ -497,7 +496,7 @@ describe('Firebase Messaging > *Controller.getToken()', function() {
sandbox
.stub(ControllerInterface.prototype, 'getNotificationPermission_')
.callsFake(() => NotificationPermission.GRANTED);
.callsFake(() => 'granted');
// start with using the deault key.
const getPublicVapidKeyStub = sandbox.stub(
@@ -584,7 +583,7 @@ describe('Firebase Messaging > *Controller.getToken()', function() {
sandbox
.stub(ControllerInterface.prototype, 'getNotificationPermission_')
.callsFake(() => NotificationPermission.GRANTED);
.callsFake(() => 'granted');
sandbox
.stub(TokenDetailsModel.prototype, 'getTokenDetailsFromSWScope')
@@ -28,6 +28,20 @@ import { IIDModel } from '../src/models/iid-model';
const controllersToTest = [WindowController, SWController];
/**
* As ControllerInterface is an abstract class, we need a concrete
* implementation that can be initialized for testing
*/
class MockControllerInterface extends ControllerInterface {
async getSWRegistration_() {
return await new ServiceWorkerRegistration();
}
async getPublicVapidKey_() {
return await new Uint8Array(0);
}
}
describe('Firebase Messaging > *ControllerInterface', function() {
const sandbox = sinon.sandbox.create();
const app = makeFakeApp({
@@ -48,44 +62,16 @@ describe('Firebase Messaging > *ControllerInterface', function() {
describe('INTERNAL.delete()', function() {
it('should call delete()', function() {
const controller = new ControllerInterface(app);
const controller = new MockControllerInterface(app);
sandbox.spy(controller, 'delete');
controller.INTERNAL.delete();
expect(controller.delete['callCount']).to.equal(1);
});
});
describe('getSWRegistration_()', function() {
it(`should throw`, function() {
const controller = new ControllerInterface(app);
let thrownError;
try {
controller.getSWRegistration_();
} catch (err) {
thrownError = err;
}
expect(thrownError).to.exist;
expect(thrownError.code).to.equal('messaging/should-be-overriden');
});
});
describe('getPublicVapidKey_()', function() {
it(`should throw`, function() {
const controller = new ControllerInterface(app);
let thrownError;
try {
controller.getPublicVapidKey_();
} catch (err) {
thrownError = err;
}
expect(thrownError).to.exist;
expect(thrownError.code).to.equal('messaging/should-be-overriden');
});
});
describe('requestPermission()', function() {
it(`should throw`, function() {
const controller = new ControllerInterface(app);
const controller = new MockControllerInterface(app);
let thrownError;
try {
controller.requestPermission();
@@ -163,10 +149,10 @@ describe('Firebase Messaging > *ControllerInterface', function() {
describe('useServiceWorker()', function() {
it(`should throw`, function() {
const controller = new ControllerInterface(app);
const controller = new MockControllerInterface(app);
let thrownError;
try {
controller.useServiceWorker(null);
controller.useServiceWorker(null as any);
} catch (err) {
thrownError = err;
}
@@ -177,10 +163,10 @@ describe('Firebase Messaging > *ControllerInterface', function() {
describe('usePublicVapidKey()', function() {
it(`should throw`, function() {
const controller = new ControllerInterface(app);
const controller = new MockControllerInterface(app);
let thrownError;
try {
controller.usePublicVapidKey(null);
controller.usePublicVapidKey(null as any);
} catch (err) {
thrownError = err;
}
@@ -191,10 +177,10 @@ describe('Firebase Messaging > *ControllerInterface', function() {
describe('onMessage()', function() {
it(`should throw`, function() {
const controller = new ControllerInterface(app);
const controller = new MockControllerInterface(app);
let thrownError;
try {
controller.onMessage(null, null, null);
controller.onMessage(null as any, null as any, null as any);
} catch (err) {
thrownError = err;
}
@@ -205,10 +191,10 @@ describe('Firebase Messaging > *ControllerInterface', function() {
describe('onTokenRefresh()', function() {
it(`should throw`, function() {
const controller = new ControllerInterface(app);
const controller = new MockControllerInterface(app);
let thrownError;
try {
controller.onTokenRefresh(null, null, null);
controller.onTokenRefresh(null as any, null as any, null as any);
} catch (err) {
thrownError = err;
}
@@ -219,10 +205,10 @@ describe('Firebase Messaging > *ControllerInterface', function() {
describe('setBackgroundMessageHandler()', function() {
it(`should throw`, function() {
const controller = new ControllerInterface(app);
const controller = new MockControllerInterface(app);
let thrownError;
try {
controller.setBackgroundMessageHandler(null);
controller.setBackgroundMessageHandler(null as any);
} catch (err) {
thrownError = err;
}
@@ -234,7 +220,7 @@ describe('Firebase Messaging > *ControllerInterface', function() {
describe('getNotificationPermission_', function() {
it('should return current permission', function() {
sandbox.stub(Notification as any, 'permission').value('test');
const controller = new ControllerInterface(app);
const controller = new MockControllerInterface(app);
const result = controller.getNotificationPermission_();
expect(result).to.equal('test');
});
@@ -242,7 +228,7 @@ describe('Firebase Messaging > *ControllerInterface', function() {
describe('getTokenDetailsModel', function() {
it('should return an instance of TokenDetailsModel', function() {
const controller = new ControllerInterface(app);
const controller = new MockControllerInterface(app);
const result = controller.getTokenDetailsModel();
expect(result).to.be.instanceof(TokenDetailsModel);
});
@@ -250,7 +236,7 @@ describe('Firebase Messaging > *ControllerInterface', function() {
describe('getVapidDetailsModel', function() {
it('should return an instance of VapidDetailsModel', function() {
const controller = new ControllerInterface(app);
const controller = new MockControllerInterface(app);
const result = controller.getVapidDetailsModel();
expect(result).to.be.instanceof(VapidDetailsModel);
});
@@ -258,7 +244,7 @@ describe('Firebase Messaging > *ControllerInterface', function() {
describe('getIIDModel', function() {
it('should return an instance of IIDModel', function() {
const controller = new ControllerInterface(app);
const controller = new MockControllerInterface(app);
const result = controller.getIIDModel();
expect(result).to.be.instanceof(IIDModel);
});
+7 -5
View File
@@ -14,10 +14,12 @@
* limitations under the License.
*/
export function makeFakeApp(options = {}) {
import { FirebaseApp } from '@firebase/app-types';
export function makeFakeApp(options = {}): FirebaseApp {
window['firebase'] = window['firebase'] || {};
let app /** @type {!firebase.app.App} */ = {};
(app as any).INTERNAL = window['firebase'].INTERNAL;
(app as any).options = options;
return app;
const app: any = {};
app.INTERNAL = window['firebase'].INTERNAL;
app.options = options;
return app as FirebaseApp;
}
@@ -128,7 +128,7 @@ describe('Firebase Messaging > *SWController', function() {
.callsFake(() => Promise.resolve(true));
const swController = new SWController(app);
swController.setBackgroundMessageHandler(() => {});
swController.setBackgroundMessageHandler((() => {}) as any);
swController.onPush_({
waitUntil: waitUntilSpy,
data: {
@@ -281,7 +281,7 @@ describe('Firebase Messaging > *SWController', function() {
const swController = new SWController(app);
let thrownError;
try {
swController.setBackgroundMessageHandler('');
swController.setBackgroundMessageHandler('' as any);
} catch (err) {
thrownError = err;
}
@@ -36,8 +36,8 @@ export function compareDetails(
const subscriptionKeys = ['endpoint', 'auth', 'p256dh'];
const subscriptionValues = {
endpoint: input.subscription.endpoint,
auth: arrayBufferToBase64(input.subscription.getKey('auth')),
p256dh: arrayBufferToBase64(input.subscription.getKey('p256dh'))
auth: arrayBufferToBase64(input.subscription.getKey('auth')!),
p256dh: arrayBufferToBase64(input.subscription.getKey('p256dh')!)
};
subscriptionKeys.forEach(keyName => {
@@ -15,8 +15,8 @@
*/
export const fetchMock = {
jsonOk: function(body: string): Promise<Response> {
var mockResponse = new (window as any).Response(body, {
jsonOk(body: string): Promise<Response> {
const mockResponse = new (window as any).Response(body, {
status: 200,
headers: {
'Content-type': 'application/json'
@@ -24,18 +24,21 @@ export const fetchMock = {
});
return Promise.resolve(mockResponse);
},
jsonError: function(status: number, msg: string): Promise<Response> {
jsonError(status: number, msg: string): Promise<Response> {
const errorMsg = { error: { message: msg } };
var mockResponse = new (window as any).Response(JSON.stringify(errorMsg), {
status: status,
headers: {
'Content-type': 'application/json'
const mockResponse = new (window as any).Response(
JSON.stringify(errorMsg),
{
status: status,
headers: {
'Content-type': 'application/json'
}
}
});
);
return Promise.resolve(mockResponse);
},
htmlError: function(status: number, msg: string): Promise<Response> {
var mockResponse = new (window as any).Response(msg, {
htmlError(status: number, msg: string): Promise<Response> {
const mockResponse = new (window as any).Response(msg, {
status: status,
headers: {
'Content-type': 'text/html'
@@ -220,14 +220,12 @@ describe('Firebase Messaging > *WindowController', function() {
const controller = new WindowController(app);
let thrownError;
try {
controller.useServiceWorker(null);
controller.useServiceWorker(null as any);
} catch (err) {
thrownError = err;
}
expect(thrownError).to.exist;
expect(thrownError.code).to.deep.equal(
'messaging/sw-registration-expected'
);
expect(thrownError.code).to.equal('messaging/sw-registration-expected');
});
it(`should only be callable once`, function() {
@@ -287,7 +285,7 @@ describe('Firebase Messaging > *WindowController', function() {
let thrownError;
try {
controller.usePublicVapidKey({});
controller.usePublicVapidKey({} as any);
} catch (err) {
thrownError = err;
}
@@ -375,7 +373,7 @@ describe('Firebase Messaging > *WindowController', function() {
});
const controller = new WindowController(app);
controller.onMessage(onMessageSpy, null, null);
controller.onMessage(onMessageSpy, null as any, null as any);
controller.setupSWMessageListener_();
const callback = spy.args[0][1];