mirror of
https://github.com/hanzoai/firebase-js-sdk.git
synced 2026-09-24 16:59:10 +00:00
Refactor IndexedDb operations (#645)
* Refactor IndexedDb operations Simplifies a lot of IndexedDb operations and removes most of the duplicated code. Also refactors tests. * [AUTOMATED]: Prettier Code Styling * [AUTOMATED]: License Headers * Fix failing tests * Address comments * Address comments
This commit is contained in:
committed by
GitHub
parent
25d32da6e6
commit
52aa813f55
@@ -367,16 +367,19 @@ export class SWController extends ControllerInterface {
|
||||
* This will return the default VAPID key or the uint8array version of the
|
||||
* public VAPID key provided by the developer.
|
||||
*/
|
||||
getPublicVapidKey_(): Promise<Uint8Array> {
|
||||
return this.getSWRegistration_()
|
||||
.then(swReg => {
|
||||
return this.getVapidDetailsModel().getVapidFromSWScope(swReg.scope);
|
||||
})
|
||||
.then(vapidKeyFromDatabase => {
|
||||
if (vapidKeyFromDatabase === null) {
|
||||
return DEFAULT_PUBLIC_VAPID_KEY;
|
||||
}
|
||||
return vapidKeyFromDatabase;
|
||||
});
|
||||
async getPublicVapidKey_(): Promise<Uint8Array> {
|
||||
const swReg = await this.getSWRegistration_();
|
||||
if (!swReg) {
|
||||
throw this.errorFactory_.create(ERROR_CODES.SW_REGISTRATION_EXPECTED);
|
||||
}
|
||||
|
||||
const vapidKeyFromDatabase = await this.getVapidDetailsModel().getVapidFromSWScope(
|
||||
swReg.scope
|
||||
);
|
||||
if (vapidKeyFromDatabase == null) {
|
||||
return DEFAULT_PUBLIC_VAPID_KEY;
|
||||
}
|
||||
|
||||
return vapidKeyFromDatabase;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export interface VapidDetails {
|
||||
swScope: string;
|
||||
vapidKey: Uint8Array;
|
||||
}
|
||||
@@ -18,73 +18,126 @@ import { ErrorFactory } from '@firebase/util';
|
||||
|
||||
import { ERROR_CODES, ERROR_MAP } from './errors';
|
||||
|
||||
export class DBInterface {
|
||||
private readonly DB_NAME_: string;
|
||||
private readonly dbVersion_: number;
|
||||
private openDbPromise_: Promise<IDBDatabase> | null;
|
||||
protected errorFactory_: ErrorFactory<string>;
|
||||
protected TRANSACTION_READ_WRITE: IDBTransactionMode;
|
||||
export abstract class DBInterface {
|
||||
private dbPromise: Promise<IDBDatabase> | null = null;
|
||||
|
||||
constructor(dbName: string, dbVersion: number) {
|
||||
this.errorFactory_ = new ErrorFactory('messaging', 'Messaging', ERROR_MAP);
|
||||
this.DB_NAME_ = dbName;
|
||||
this.dbVersion_ = dbVersion;
|
||||
this.openDbPromise_ = null;
|
||||
this.TRANSACTION_READ_WRITE = 'readwrite';
|
||||
}
|
||||
protected abstract readonly dbName: string;
|
||||
protected abstract readonly dbVersion: number;
|
||||
protected abstract readonly objectStoreName: string;
|
||||
|
||||
protected readonly errorFactory: ErrorFactory<string> = new ErrorFactory(
|
||||
'messaging',
|
||||
'Messaging',
|
||||
ERROR_MAP
|
||||
);
|
||||
|
||||
/**
|
||||
* Get the indexedDB as a promise.
|
||||
* Database initialization.
|
||||
*
|
||||
* This function should create and update object stores.
|
||||
*/
|
||||
// Visible for testing
|
||||
// TODO: Make protected
|
||||
openDatabase(): Promise<IDBDatabase> {
|
||||
if (this.openDbPromise_) {
|
||||
return this.openDbPromise_;
|
||||
protected abstract onDbUpgrade(
|
||||
db: IDBDatabase,
|
||||
event: IDBVersionChangeEvent
|
||||
): void;
|
||||
|
||||
/** Gets record(s) from the objectStore that match the given key. */
|
||||
get<T>(key: IDBValidKey): Promise<T | undefined> {
|
||||
return this.createTransaction(objectStore => objectStore.get(key));
|
||||
}
|
||||
|
||||
/** Gets record(s) from the objectStore that match the given index. */
|
||||
getIndex<T>(index: string, key: IDBValidKey): Promise<T | undefined> {
|
||||
function runRequest(objectStore: IDBObjectStore): IDBRequest {
|
||||
const idbIndex = objectStore.index(index);
|
||||
return idbIndex.get(key);
|
||||
}
|
||||
|
||||
this.openDbPromise_ = new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open(this.DB_NAME_, this.dbVersion_);
|
||||
request.onerror = event => {
|
||||
reject((event.target as IDBRequest).error);
|
||||
};
|
||||
request.onsuccess = event => {
|
||||
resolve((event.target as IDBRequest).result);
|
||||
};
|
||||
request.onupgradeneeded = event => {
|
||||
let db;
|
||||
try {
|
||||
db = (event.target as IDBRequest).result;
|
||||
this.onDBUpgrade(db, event);
|
||||
} catch (err) {
|
||||
// close the database as it can't be used.
|
||||
db.close();
|
||||
reject(err);
|
||||
}
|
||||
};
|
||||
});
|
||||
return this.createTransaction(runRequest);
|
||||
}
|
||||
|
||||
return this.openDbPromise_;
|
||||
/** Assigns or overwrites the record for the given value. */
|
||||
// tslint:disable-next-line:no-any IndexedDB values are of type "any"
|
||||
put(value: any): Promise<void> {
|
||||
return this.createTransaction(
|
||||
objectStore => objectStore.put(value),
|
||||
'readwrite'
|
||||
);
|
||||
}
|
||||
|
||||
/** Deletes record(s) from the objectStore that match the given key. */
|
||||
delete(key: IDBValidKey | IDBKeyRange): Promise<void> {
|
||||
return this.createTransaction(
|
||||
objectStore => objectStore.delete(key),
|
||||
'readwrite'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the currently open database.
|
||||
*/
|
||||
closeDatabase(): Promise<void> {
|
||||
return Promise.resolve().then(() => {
|
||||
if (this.openDbPromise_) {
|
||||
return this.openDbPromise_.then(db => {
|
||||
db.close();
|
||||
this.openDbPromise_ = null;
|
||||
});
|
||||
}
|
||||
});
|
||||
async closeDatabase(): Promise<void> {
|
||||
if (this.dbPromise) {
|
||||
const db = await this.dbPromise;
|
||||
db.close();
|
||||
this.dbPromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* Creates an IndexedDB Transaction and passes its objectStore to the
|
||||
* runRequest function, which runs the database request.
|
||||
*
|
||||
* @return Promise that resolves with the result of the runRequest function
|
||||
*/
|
||||
onDBUpgrade(db: IDBDatabase, event: IDBVersionChangeEvent): void {
|
||||
throw this.errorFactory_.create(ERROR_CODES.SHOULD_BE_INHERITED);
|
||||
private async createTransaction<T>(
|
||||
runRequest: (objectStore: IDBObjectStore) => IDBRequest,
|
||||
mode?: 'readonly' | 'readwrite'
|
||||
): Promise<T> {
|
||||
const db = await this.getDb();
|
||||
const transaction = db.transaction(this.objectStoreName, mode);
|
||||
const request = transaction.objectStore(this.objectStoreName);
|
||||
const result = await promisify<T>(runRequest(request));
|
||||
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
transaction.oncomplete = () => {
|
||||
resolve(result);
|
||||
};
|
||||
transaction.onerror = () => {
|
||||
reject(transaction.error);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** Gets the cached db connection or opens a new one. */
|
||||
private getDb(): Promise<IDBDatabase> {
|
||||
if (!this.dbPromise) {
|
||||
this.dbPromise = new Promise<IDBDatabase>((resolve, reject) => {
|
||||
const request = indexedDB.open(this.dbName, this.dbVersion);
|
||||
request.onsuccess = () => {
|
||||
resolve(request.result);
|
||||
};
|
||||
request.onerror = () => {
|
||||
this.dbPromise = null;
|
||||
reject(request.error);
|
||||
};
|
||||
request.onupgradeneeded = event =>
|
||||
this.onDbUpgrade(request.result, event);
|
||||
});
|
||||
}
|
||||
|
||||
return this.dbPromise;
|
||||
}
|
||||
}
|
||||
|
||||
/** Promisifies an IDBRequest. Resolves with the IDBRequest's result. */
|
||||
function promisify<T>(request: IDBRequest): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
request.onsuccess = () => {
|
||||
resolve(request.result);
|
||||
};
|
||||
request.onerror = () => {
|
||||
reject(request.error);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,19 +20,15 @@ import { cleanV1 } from './clean-v1-undefined';
|
||||
import { DBInterface } from './db-interface';
|
||||
import { ERROR_CODES } from './errors';
|
||||
|
||||
const FCM_TOKEN_OBJ_STORE = 'fcm_token_object_Store';
|
||||
const DB_NAME = 'fcm_token_details_db';
|
||||
const DB_VERSION = 2;
|
||||
|
||||
export class TokenDetailsModel extends DBInterface {
|
||||
constructor() {
|
||||
super(DB_NAME, DB_VERSION);
|
||||
}
|
||||
protected readonly dbName: string = 'fcm_token_details_db';
|
||||
protected readonly dbVersion: number = 2;
|
||||
protected readonly objectStoreName: string = 'fcm_token_object_Store';
|
||||
|
||||
onDBUpgrade(db: IDBDatabase, evt: IDBVersionChangeEvent): void {
|
||||
protected onDbUpgrade(db: IDBDatabase, evt: IDBVersionChangeEvent): void {
|
||||
if (evt.oldVersion < 1) {
|
||||
// New IDB instance
|
||||
const objectStore = db.createObjectStore(FCM_TOKEN_OBJ_STORE, {
|
||||
const objectStore = db.createObjectStore(this.objectStoreName, {
|
||||
keyPath: 'swScope'
|
||||
});
|
||||
|
||||
@@ -59,16 +55,16 @@ export class TokenDetailsModel extends DBInterface {
|
||||
* validate the input.
|
||||
* @return Promise that resolves if input is valid, rejects otherwise.
|
||||
*/
|
||||
private async validateInputs_(input: Partial<TokenDetails>): Promise<void> {
|
||||
private validateInputs(input: Partial<TokenDetails>): void {
|
||||
if (input.fcmToken) {
|
||||
if (typeof input.fcmToken !== 'string' || input.fcmToken.length === 0) {
|
||||
return Promise.reject(this.errorFactory_.create(ERROR_CODES.BAD_TOKEN));
|
||||
throw this.errorFactory.create(ERROR_CODES.BAD_TOKEN);
|
||||
}
|
||||
}
|
||||
|
||||
if (input.swScope) {
|
||||
if (typeof input.swScope !== 'string' || input.swScope.length === 0) {
|
||||
return Promise.reject(this.errorFactory_.create(ERROR_CODES.BAD_SCOPE));
|
||||
throw this.errorFactory.create(ERROR_CODES.BAD_SCOPE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,17 +73,13 @@ export class TokenDetailsModel extends DBInterface {
|
||||
!(input.vapidKey instanceof Uint8Array) ||
|
||||
input.vapidKey.length !== 65
|
||||
) {
|
||||
return Promise.reject(
|
||||
this.errorFactory_.create(ERROR_CODES.BAD_VAPID_KEY)
|
||||
);
|
||||
throw this.errorFactory.create(ERROR_CODES.BAD_VAPID_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
if (input.subscription) {
|
||||
if (!(input.subscription instanceof PushSubscription)) {
|
||||
return Promise.reject(
|
||||
this.errorFactory_.create(ERROR_CODES.BAD_SUBSCRIPTION)
|
||||
);
|
||||
throw this.errorFactory.create(ERROR_CODES.BAD_SUBSCRIPTION);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,9 +88,7 @@ export class TokenDetailsModel extends DBInterface {
|
||||
typeof input.fcmSenderId !== 'string' ||
|
||||
input.fcmSenderId.length === 0
|
||||
) {
|
||||
return Promise.reject(
|
||||
this.errorFactory_.create(ERROR_CODES.BAD_SENDER_ID)
|
||||
);
|
||||
throw this.errorFactory.create(ERROR_CODES.BAD_SENDER_ID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,9 +97,7 @@ export class TokenDetailsModel extends DBInterface {
|
||||
typeof input.fcmPushSet !== 'string' ||
|
||||
input.fcmPushSet.length === 0
|
||||
) {
|
||||
return Promise.reject(
|
||||
this.errorFactory_.create(ERROR_CODES.BAD_PUSH_SET)
|
||||
);
|
||||
throw this.errorFactory.create(ERROR_CODES.BAD_PUSH_SET);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,29 +107,16 @@ export class TokenDetailsModel extends DBInterface {
|
||||
* @param {string} fcmToken
|
||||
* @return {Promise<TokenDetails>} The details associated with that token.
|
||||
*/
|
||||
getTokenDetailsFromToken(fcmToken: string): Promise<TokenDetails> {
|
||||
async getTokenDetailsFromToken(
|
||||
fcmToken: string
|
||||
): Promise<TokenDetails | undefined> {
|
||||
if (!fcmToken) {
|
||||
return Promise.reject(this.errorFactory_.create(ERROR_CODES.BAD_TOKEN));
|
||||
throw this.errorFactory.create(ERROR_CODES.BAD_TOKEN);
|
||||
}
|
||||
|
||||
return this.validateInputs_({ fcmToken })
|
||||
.then(() => this.openDatabase())
|
||||
.then(db => {
|
||||
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');
|
||||
const request = index.get(fcmToken);
|
||||
request.onerror = event => {
|
||||
reject((event.target as IDBRequest).error);
|
||||
};
|
||||
request.onsuccess = event => {
|
||||
const result: TokenDetails | null = (event.target as IDBRequest)
|
||||
.result;
|
||||
resolve(result);
|
||||
};
|
||||
});
|
||||
});
|
||||
this.validateInputs({ fcmToken });
|
||||
|
||||
return this.getIndex<TokenDetails>('fcmToken', fcmToken);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,160 +124,97 @@ export class TokenDetailsModel extends DBInterface {
|
||||
* indexedDB.
|
||||
* @return The details associated with that token.
|
||||
*/
|
||||
getTokenDetailsFromSWScope(swScope: string): Promise<TokenDetails> {
|
||||
async getTokenDetailsFromSWScope(
|
||||
swScope: string
|
||||
): Promise<TokenDetails | undefined> {
|
||||
if (!swScope) {
|
||||
return Promise.reject(this.errorFactory_.create(ERROR_CODES.BAD_SCOPE));
|
||||
throw this.errorFactory.create(ERROR_CODES.BAD_SCOPE);
|
||||
}
|
||||
|
||||
return this.validateInputs_({ swScope })
|
||||
.then(() => {
|
||||
return this.openDatabase();
|
||||
})
|
||||
.then(db => {
|
||||
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);
|
||||
scopeRequest.onerror = event => {
|
||||
reject((event.target as IDBRequest).error);
|
||||
};
|
||||
this.validateInputs({ swScope });
|
||||
|
||||
scopeRequest.onsuccess = event => {
|
||||
const result: TokenDetails | null = (event.target as IDBRequest)
|
||||
.result;
|
||||
resolve(result);
|
||||
};
|
||||
});
|
||||
});
|
||||
return this.get<TokenDetails>(swScope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the details for the fcm token for re-use at a later date.
|
||||
* @param input A plain js object containing args to save.
|
||||
*/
|
||||
saveTokenDetails({
|
||||
swScope,
|
||||
vapidKey,
|
||||
subscription,
|
||||
fcmSenderId,
|
||||
fcmToken,
|
||||
fcmPushSet
|
||||
}: TokenDetails): Promise<void> {
|
||||
if (!swScope) {
|
||||
return Promise.reject(this.errorFactory_.create(ERROR_CODES.BAD_SCOPE));
|
||||
async saveTokenDetails(tokenDetails: TokenDetails): Promise<void> {
|
||||
if (!tokenDetails.swScope) {
|
||||
throw this.errorFactory.create(ERROR_CODES.BAD_SCOPE);
|
||||
}
|
||||
|
||||
if (!vapidKey) {
|
||||
return Promise.reject(
|
||||
this.errorFactory_.create(ERROR_CODES.BAD_VAPID_KEY)
|
||||
);
|
||||
if (!tokenDetails.vapidKey) {
|
||||
throw this.errorFactory.create(ERROR_CODES.BAD_VAPID_KEY);
|
||||
}
|
||||
|
||||
if (!subscription) {
|
||||
return Promise.reject(
|
||||
this.errorFactory_.create(ERROR_CODES.BAD_SUBSCRIPTION)
|
||||
);
|
||||
if (!tokenDetails.subscription) {
|
||||
throw this.errorFactory.create(ERROR_CODES.BAD_SUBSCRIPTION);
|
||||
}
|
||||
|
||||
if (!fcmSenderId) {
|
||||
return Promise.reject(
|
||||
this.errorFactory_.create(ERROR_CODES.BAD_SENDER_ID)
|
||||
);
|
||||
if (!tokenDetails.fcmSenderId) {
|
||||
throw this.errorFactory.create(ERROR_CODES.BAD_SENDER_ID);
|
||||
}
|
||||
|
||||
if (!fcmToken) {
|
||||
return Promise.reject(this.errorFactory_.create(ERROR_CODES.BAD_TOKEN));
|
||||
if (!tokenDetails.fcmToken) {
|
||||
throw this.errorFactory.create(ERROR_CODES.BAD_TOKEN);
|
||||
}
|
||||
|
||||
if (!fcmPushSet) {
|
||||
return Promise.reject(
|
||||
this.errorFactory_.create(ERROR_CODES.BAD_PUSH_SET)
|
||||
);
|
||||
if (!tokenDetails.fcmPushSet) {
|
||||
throw this.errorFactory.create(ERROR_CODES.BAD_PUSH_SET);
|
||||
}
|
||||
|
||||
return this.validateInputs_({
|
||||
swScope,
|
||||
vapidKey,
|
||||
subscription,
|
||||
fcmSenderId,
|
||||
fcmToken,
|
||||
fcmPushSet
|
||||
})
|
||||
.then(() => {
|
||||
return this.openDatabase();
|
||||
})
|
||||
.then(db => {
|
||||
const details = {
|
||||
swScope: swScope,
|
||||
vapidKey: arrayBufferToBase64(vapidKey as Uint8Array),
|
||||
endpoint: subscription.endpoint,
|
||||
auth: arrayBufferToBase64(subscription.getKey('auth')),
|
||||
p256dh: arrayBufferToBase64(subscription.getKey('p256dh')),
|
||||
fcmSenderId: fcmSenderId,
|
||||
fcmToken: fcmToken,
|
||||
fcmPushSet: fcmPushSet,
|
||||
createTime: Date.now()
|
||||
};
|
||||
this.validateInputs(tokenDetails);
|
||||
|
||||
return new Promise<void>((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.put(details);
|
||||
request.onerror = event => {
|
||||
reject((event.target as IDBRequest).error);
|
||||
};
|
||||
request.onsuccess = event => {
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
});
|
||||
const details = {
|
||||
swScope: tokenDetails.swScope,
|
||||
vapidKey: arrayBufferToBase64(tokenDetails.vapidKey as Uint8Array),
|
||||
endpoint: tokenDetails.subscription.endpoint,
|
||||
auth: arrayBufferToBase64(tokenDetails.subscription.getKey('auth')),
|
||||
p256dh: arrayBufferToBase64(tokenDetails.subscription.getKey('p256dh')),
|
||||
fcmSenderId: tokenDetails.fcmSenderId,
|
||||
fcmToken: tokenDetails.fcmToken,
|
||||
fcmPushSet: tokenDetails.fcmPushSet,
|
||||
createTime: Date.now()
|
||||
};
|
||||
|
||||
return this.put(details);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method deletes details of the current FCM token.
|
||||
* It's returning a promise in case we need to move to an async
|
||||
* method for deleting at a later date.
|
||||
* @return {Promise<Object>} Resolves once the FCM token details have been
|
||||
* deleted and returns the deleted details.
|
||||
*
|
||||
* @return Resolves once the FCM token details have been deleted and returns
|
||||
* the deleted details.
|
||||
*/
|
||||
deleteToken(token: string): Promise<TokenDetails> {
|
||||
async deleteToken(token: string): Promise<TokenDetails> {
|
||||
if (typeof token !== 'string' || token.length === 0) {
|
||||
return Promise.reject(
|
||||
this.errorFactory_.create(ERROR_CODES.INVALID_DELETE_TOKEN)
|
||||
this.errorFactory.create(ERROR_CODES.INVALID_DELETE_TOKEN)
|
||||
);
|
||||
}
|
||||
|
||||
return this.getTokenDetailsFromToken(token).then(details => {
|
||||
if (!details) {
|
||||
throw this.errorFactory_.create(ERROR_CODES.DELETE_TOKEN_NOT_FOUND);
|
||||
}
|
||||
const details = await this.getTokenDetailsFromToken(token);
|
||||
if (!details) {
|
||||
throw this.errorFactory.create(ERROR_CODES.DELETE_TOKEN_NOT_FOUND);
|
||||
}
|
||||
|
||||
return this.openDatabase().then(db => {
|
||||
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);
|
||||
request.onerror = event => {
|
||||
reject((event.target as IDBRequest).error);
|
||||
};
|
||||
request.onsuccess = event => {
|
||||
if ((event.target as IDBRequest).result === 0) {
|
||||
reject(
|
||||
this.errorFactory_.create(ERROR_CODES.FAILED_TO_DELETE_TOKEN)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(details);
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
await this.delete(details.swScope);
|
||||
return details;
|
||||
}
|
||||
}
|
||||
|
||||
/** Promisifies an IDBRequest. Resolves with the IDBRequest's result. */
|
||||
function promisify<ReturnType>(request: IDBRequest): Promise<ReturnType> {
|
||||
return new Promise<ReturnType>((resolve, reject) => {
|
||||
request.onsuccess = () => {
|
||||
resolve(request.result);
|
||||
};
|
||||
request.onerror = () => {
|
||||
reject(request.error);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,90 +14,52 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { VapidDetails } from '../interfaces/vapid-details';
|
||||
import { DBInterface } from './db-interface';
|
||||
import { ERROR_CODES } from './errors';
|
||||
|
||||
const FCM_VAPID_OBJ_STORE = 'fcm_vapid_object_Store';
|
||||
const DB_NAME = 'fcm_vapid_details_db';
|
||||
const DB_VERSION = 1;
|
||||
const UNCOMPRESSED_PUBLIC_KEY_SIZE = 65;
|
||||
|
||||
export class VapidDetailsModel extends DBInterface {
|
||||
constructor() {
|
||||
super(DB_NAME, DB_VERSION);
|
||||
}
|
||||
protected readonly dbName: string = 'fcm_vapid_details_db';
|
||||
protected readonly dbVersion: number = 1;
|
||||
protected readonly objectStoreName: string = 'fcm_vapid_object_Store';
|
||||
|
||||
onDBUpgrade(db: IDBDatabase): void {
|
||||
db.createObjectStore(FCM_VAPID_OBJ_STORE, {
|
||||
keyPath: 'swScope'
|
||||
});
|
||||
protected onDbUpgrade(db: IDBDatabase): void {
|
||||
db.createObjectStore(this.objectStoreName, { keyPath: 'swScope' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a service worker scope, this method will look up the vapid key
|
||||
* in indexedDB.
|
||||
*/
|
||||
getVapidFromSWScope(swScope: string): Promise<Uint8Array> {
|
||||
async getVapidFromSWScope(swScope: string): Promise<Uint8Array | undefined> {
|
||||
if (typeof swScope !== 'string' || swScope.length === 0) {
|
||||
return Promise.reject(this.errorFactory_.create(ERROR_CODES.BAD_SCOPE));
|
||||
throw this.errorFactory.create(ERROR_CODES.BAD_SCOPE);
|
||||
}
|
||||
|
||||
return this.openDatabase().then(db => {
|
||||
return new Promise<Uint8Array>((resolve, reject) => {
|
||||
const transaction = db.transaction([FCM_VAPID_OBJ_STORE]);
|
||||
const objectStore = transaction.objectStore(FCM_VAPID_OBJ_STORE);
|
||||
const scopeRequest = objectStore.get(swScope);
|
||||
scopeRequest.onerror = () => {
|
||||
reject(scopeRequest.error);
|
||||
};
|
||||
|
||||
scopeRequest.onsuccess = () => {
|
||||
const result = scopeRequest.result;
|
||||
let vapidKey: any = null;
|
||||
if (result) {
|
||||
vapidKey = result.vapidKey;
|
||||
}
|
||||
resolve(vapidKey);
|
||||
};
|
||||
});
|
||||
});
|
||||
const result = await this.get<VapidDetails>(swScope);
|
||||
return result ? result.vapidKey : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a vapid key against a swScope for later date.
|
||||
*/
|
||||
saveVapidDetails(swScope: string, vapidKey: Uint8Array): Promise<void> {
|
||||
async saveVapidDetails(swScope: string, vapidKey: Uint8Array): Promise<void> {
|
||||
if (typeof swScope !== 'string' || swScope.length === 0) {
|
||||
return Promise.reject(this.errorFactory_.create(ERROR_CODES.BAD_SCOPE));
|
||||
throw this.errorFactory.create(ERROR_CODES.BAD_SCOPE);
|
||||
}
|
||||
|
||||
if (vapidKey === null || vapidKey.length !== UNCOMPRESSED_PUBLIC_KEY_SIZE) {
|
||||
return Promise.reject(
|
||||
this.errorFactory_.create(ERROR_CODES.BAD_VAPID_KEY)
|
||||
);
|
||||
throw this.errorFactory.create(ERROR_CODES.BAD_VAPID_KEY);
|
||||
}
|
||||
|
||||
const details = {
|
||||
swScope: swScope,
|
||||
vapidKey: vapidKey
|
||||
const details: VapidDetails = {
|
||||
swScope,
|
||||
vapidKey
|
||||
};
|
||||
|
||||
return this.openDatabase().then(db => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const transaction = db.transaction(
|
||||
[FCM_VAPID_OBJ_STORE],
|
||||
this.TRANSACTION_READ_WRITE
|
||||
);
|
||||
const objectStore = transaction.objectStore(FCM_VAPID_OBJ_STORE);
|
||||
const request = objectStore.put(details);
|
||||
request.onerror = () => {
|
||||
reject(request.error);
|
||||
};
|
||||
request.onsuccess = () => {
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
});
|
||||
return this.put(details);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,35 +67,13 @@ export class VapidDetailsModel extends DBInterface {
|
||||
* Resolves once the scope/vapid details have been deleted and returns the
|
||||
* deleted vapid key.
|
||||
*/
|
||||
deleteVapidDetails(swScope: string): Promise<Uint8Array> {
|
||||
return this.getVapidFromSWScope(swScope).then(vapidKey => {
|
||||
if (!vapidKey) {
|
||||
throw this.errorFactory_.create(ERROR_CODES.DELETE_SCOPE_NOT_FOUND);
|
||||
}
|
||||
async deleteVapidDetails(swScope: string): Promise<Uint8Array> {
|
||||
const vapidKey = await this.getVapidFromSWScope(swScope);
|
||||
if (!vapidKey) {
|
||||
throw this.errorFactory.create(ERROR_CODES.DELETE_SCOPE_NOT_FOUND);
|
||||
}
|
||||
|
||||
return this.openDatabase().then(db => {
|
||||
return new Promise<Uint8Array>((resolve, reject) => {
|
||||
const transaction = db.transaction(
|
||||
[FCM_VAPID_OBJ_STORE],
|
||||
this.TRANSACTION_READ_WRITE
|
||||
);
|
||||
const objectStore = transaction.objectStore(FCM_VAPID_OBJ_STORE);
|
||||
const request = objectStore.delete(swScope);
|
||||
request.onerror = () => {
|
||||
reject(request.error);
|
||||
};
|
||||
request.onsuccess = () => {
|
||||
if (request.result === 0) {
|
||||
reject(
|
||||
this.errorFactory_.create(ERROR_CODES.FAILED_DELETE_VAPID_KEY)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(vapidKey);
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
await this.delete(swScope);
|
||||
return vapidKey;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { expect } from 'chai';
|
||||
import { DBInterface } from '../src/models/db-interface';
|
||||
import { deleteDatabase } from './testing-utils/db-helper';
|
||||
|
||||
const VALUE = {
|
||||
key: 'key-value',
|
||||
index: 'index-value',
|
||||
otherValue: 'other-value'
|
||||
};
|
||||
const DIFFERENT_VALUE = {
|
||||
key: 'different-key-value',
|
||||
index: 'different-index-value',
|
||||
otherValue: 'different-other-value'
|
||||
};
|
||||
|
||||
class MockDbInterface extends DBInterface {
|
||||
readonly dbName: string = 'test-db';
|
||||
readonly dbVersion: number = 1;
|
||||
readonly objectStoreName: string = 'test-obj-store';
|
||||
|
||||
protected onDbUpgrade(db: IDBDatabase, event: IDBVersionChangeEvent): void {
|
||||
const objectStore = db.createObjectStore(this.objectStoreName, {
|
||||
keyPath: 'key'
|
||||
});
|
||||
|
||||
objectStore.createIndex('indexName', 'index');
|
||||
}
|
||||
}
|
||||
|
||||
describe('DBInterface', () => {
|
||||
let dbInterface: MockDbInterface;
|
||||
|
||||
beforeEach(() => {
|
||||
dbInterface = new MockDbInterface();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
dbInterface.closeDatabase();
|
||||
await deleteDatabase(dbInterface.dbName);
|
||||
});
|
||||
|
||||
describe('get', () => {
|
||||
beforeEach(async () => {
|
||||
await dbInterface.put(VALUE);
|
||||
});
|
||||
|
||||
it('gets a value that exists', async () => {
|
||||
const value = await dbInterface.get<string>(VALUE.key);
|
||||
expect(value).to.deep.equal(VALUE);
|
||||
});
|
||||
|
||||
it('returns undefined for a key that does not exist', async () => {
|
||||
const value = await dbInterface.get<string>('randomKey');
|
||||
expect(value).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe('getIndex', () => {
|
||||
beforeEach(async () => {
|
||||
await dbInterface.put(VALUE);
|
||||
});
|
||||
|
||||
it('gets a value that exists', async () => {
|
||||
const value = await dbInterface.getIndex<string>(
|
||||
'indexName',
|
||||
VALUE.index
|
||||
);
|
||||
expect(value).to.deep.equal(VALUE);
|
||||
});
|
||||
|
||||
it('returns undefined for an index that does not exist', async () => {
|
||||
const value = await dbInterface.getIndex<string>(
|
||||
'indexName',
|
||||
'randomIndexValue'
|
||||
);
|
||||
expect(value).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe('put', () => {
|
||||
it('puts a value', async () => {
|
||||
await dbInterface.put(VALUE);
|
||||
expect(await dbInterface.get(VALUE.key)).to.deep.equal(VALUE);
|
||||
});
|
||||
|
||||
it('puts multiple values', async () => {
|
||||
await dbInterface.put(VALUE);
|
||||
await dbInterface.put(DIFFERENT_VALUE);
|
||||
expect(await dbInterface.get(VALUE.key)).to.deep.equal(VALUE);
|
||||
expect(await dbInterface.get(DIFFERENT_VALUE.key)).to.deep.equal(
|
||||
DIFFERENT_VALUE
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('deletes a value', async () => {
|
||||
await dbInterface.put(VALUE);
|
||||
await dbInterface.delete(VALUE.key);
|
||||
expect(await dbInterface.get(VALUE.key)).to.be.undefined;
|
||||
});
|
||||
|
||||
it('does not throw if key does not exist', async () => {
|
||||
await dbInterface.delete('randomKey');
|
||||
expect(await dbInterface.get('randomKey')).to.be.undefined;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -31,15 +31,4 @@ describe(`Firebase Messaging > db-interface.onDBUpgrade()`, () => {
|
||||
after(() => {
|
||||
return cleanup();
|
||||
});
|
||||
|
||||
it(`should throw error when not inherited`, () => {
|
||||
return new DBInterface(TEST_DB_NAME, 1).openDatabase().then(
|
||||
() => {
|
||||
throw new Error(`An error was expected but method was successful.`);
|
||||
},
|
||||
err => {
|
||||
expect(err.code).to.equal('messaging/should-be-overriden');
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,44 +23,33 @@ import { makeFakeSubscription } from './make-fake-subscription';
|
||||
import { deleteDatabase } from './testing-utils/db-helper';
|
||||
import { compareDetails } from './testing-utils/detail-comparator';
|
||||
|
||||
const EXAMPLE_INPUT = {
|
||||
swScope: '/example-scope',
|
||||
vapidKey: base64ToArrayBuffer(
|
||||
'BNJxw7sCGkGLOUP2cawBaBXRuWZ3lw_PmQMgreLVVvX_b' +
|
||||
'4emEWVURkCF8fUTHEFe2xrEgTt5ilh5xD94v0pFe_I'
|
||||
),
|
||||
subscription: makeFakeSubscription(),
|
||||
fcmSenderId: '1234567',
|
||||
fcmToken: 'qwerty',
|
||||
fcmPushSet: '7654321'
|
||||
};
|
||||
|
||||
describe('Firebase Messaging > TokenDetailsModel.deleteToken()', () => {
|
||||
const EXAMPLE_INPUT = {
|
||||
swScope: '/example-scope',
|
||||
vapidKey: base64ToArrayBuffer(
|
||||
'BNJxw7sCGkGLOUP2cawBaBXRuWZ3lw_PmQMgreLVVvX_b' +
|
||||
'4emEWVURkCF8fUTHEFe2xrEgTt5ilh5xD94v0pFe_I'
|
||||
),
|
||||
subscription: makeFakeSubscription(),
|
||||
fcmSenderId: '1234567',
|
||||
fcmToken: 'qwerty',
|
||||
fcmPushSet: '7654321'
|
||||
};
|
||||
|
||||
let globalTokenModel;
|
||||
|
||||
const cleanUp = () => {
|
||||
const promises: any = [];
|
||||
|
||||
if (globalTokenModel) {
|
||||
promises.push(globalTokenModel.closeDatabase());
|
||||
}
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(() => deleteDatabase('fcm_token_details_db'))
|
||||
.then(() => (globalTokenModel = null));
|
||||
};
|
||||
let globalTokenModel: TokenDetailsModel;
|
||||
|
||||
beforeEach(() => {
|
||||
return cleanUp();
|
||||
globalTokenModel = new TokenDetailsModel();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
return cleanUp();
|
||||
afterEach(async () => {
|
||||
await globalTokenModel.closeDatabase();
|
||||
await deleteDatabase('fcm_token_details_db');
|
||||
});
|
||||
|
||||
it('should handle no input', () => {
|
||||
globalTokenModel = new TokenDetailsModel();
|
||||
return globalTokenModel.deleteToken().then(
|
||||
return globalTokenModel.deleteToken(undefined as any).then(
|
||||
() => {
|
||||
throw new Error('Expected this to throw an error due to no token');
|
||||
},
|
||||
|
||||
@@ -23,44 +23,34 @@ import { makeFakeSubscription } from './make-fake-subscription';
|
||||
import { deleteDatabase } from './testing-utils/db-helper';
|
||||
import { compareDetails } from './testing-utils/detail-comparator';
|
||||
|
||||
const EXAMPLE_INPUT = {
|
||||
swScope: '/example-scope',
|
||||
vapidKey: base64ToArrayBuffer(
|
||||
'BNJxw7sCGkGLOUP2cawBaBXRuWZ3lw_PmQMgreLVVvX_b' +
|
||||
'4emEWVURkCF8fUTHEFe2xrEgTt5ilh5xD94v0pFe_I'
|
||||
),
|
||||
subscription: makeFakeSubscription(),
|
||||
fcmSenderId: '1234567',
|
||||
fcmToken: 'qwerty',
|
||||
fcmPushSet: '7654321'
|
||||
};
|
||||
|
||||
describe('Firebase Messaging > TokenDetailsModel.getTokenDetailsFromToken()', () => {
|
||||
const EXAMPLE_INPUT = {
|
||||
swScope: '/example-scope',
|
||||
vapidKey: base64ToArrayBuffer(
|
||||
'BNJxw7sCGkGLOUP2cawBaBXRuWZ3lw_PmQMgreLVVvX_b' +
|
||||
'4emEWVURkCF8fUTHEFe2xrEgTt5ilh5xD94v0pFe_I'
|
||||
),
|
||||
subscription: makeFakeSubscription(),
|
||||
fcmSenderId: '1234567',
|
||||
fcmToken: 'qwerty',
|
||||
fcmPushSet: '7654321'
|
||||
};
|
||||
|
||||
let globalTokenModel;
|
||||
|
||||
const cleanUp = () => {
|
||||
const promises: any[] = [];
|
||||
if (globalTokenModel) {
|
||||
promises.push(globalTokenModel.closeDatabase());
|
||||
}
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(() => deleteDatabase('fcm_token_details_db'))
|
||||
.then(() => (globalTokenModel = null));
|
||||
};
|
||||
let globalTokenModel: TokenDetailsModel;
|
||||
|
||||
beforeEach(() => {
|
||||
return cleanUp();
|
||||
globalTokenModel = new TokenDetailsModel();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
return cleanUp();
|
||||
afterEach(async () => {
|
||||
await globalTokenModel.closeDatabase();
|
||||
await deleteDatabase('fcm_token_details_db');
|
||||
});
|
||||
|
||||
['', [], {}, true, null, 123].forEach(badInput => {
|
||||
it(`should throw on bad scope input ${JSON.stringify(badInput)}`, () => {
|
||||
globalTokenModel = new TokenDetailsModel();
|
||||
return globalTokenModel.getTokenDetailsFromSWScope(badInput).then(
|
||||
return globalTokenModel.getTokenDetailsFromSWScope(badInput as any).then(
|
||||
() => {
|
||||
throw new Error('Expected promise to reject');
|
||||
},
|
||||
@@ -74,7 +64,7 @@ describe('Firebase Messaging > TokenDetailsModel.getTokenDetailsFromToken()', ()
|
||||
['', [], {}, true, null, 123].forEach(badInput => {
|
||||
it(`should throw on bad FCM Token input: '${badInput}'`, () => {
|
||||
globalTokenModel = new TokenDetailsModel();
|
||||
return globalTokenModel.getTokenDetailsFromToken(badInput).then(
|
||||
return globalTokenModel.getTokenDetailsFromToken(badInput as any).then(
|
||||
() => {
|
||||
throw new Error('Expected promise to reject');
|
||||
},
|
||||
@@ -92,7 +82,7 @@ describe('Firebase Messaging > TokenDetailsModel.getTokenDetailsFromToken()', ()
|
||||
return globalTokenModel
|
||||
.getTokenDetailsFromSWScope(EXAMPLE_INPUT.swScope)
|
||||
.then(details => {
|
||||
assert.equal(null, details);
|
||||
assert.notExists(details);
|
||||
return globalTokenModel.saveTokenDetails(EXAMPLE_INPUT);
|
||||
})
|
||||
.then(() => {
|
||||
@@ -101,6 +91,7 @@ describe('Firebase Messaging > TokenDetailsModel.getTokenDetailsFromToken()', ()
|
||||
);
|
||||
})
|
||||
.then(details => {
|
||||
assert.exists(details);
|
||||
compareDetails(EXAMPLE_INPUT, details, now);
|
||||
clock.restore();
|
||||
});
|
||||
@@ -113,7 +104,7 @@ describe('Firebase Messaging > TokenDetailsModel.getTokenDetailsFromToken()', ()
|
||||
return globalTokenModel
|
||||
.getTokenDetailsFromToken(EXAMPLE_INPUT.fcmToken)
|
||||
.then(details => {
|
||||
assert.equal(null, details);
|
||||
assert.notExists(details);
|
||||
return globalTokenModel.saveTokenDetails(EXAMPLE_INPUT);
|
||||
})
|
||||
.then(() => {
|
||||
@@ -122,6 +113,7 @@ describe('Firebase Messaging > TokenDetailsModel.getTokenDetailsFromToken()', ()
|
||||
);
|
||||
})
|
||||
.then(details => {
|
||||
assert.exists(details);
|
||||
compareDetails(EXAMPLE_INPUT, details, now);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
@@ -20,38 +20,28 @@ import { TokenDetailsModel } from '../src/models/token-details-model';
|
||||
import { makeFakeSubscription } from './make-fake-subscription';
|
||||
import { deleteDatabase } from './testing-utils/db-helper';
|
||||
|
||||
const EXAMPLE_INPUT = {
|
||||
swScope: '/example-scope',
|
||||
vapidKey: base64ToArrayBuffer(
|
||||
'BNJxw7sCGkGLOUP2cawBaBXRuWZ3lw_PmQMgreLVVvX_b' +
|
||||
'4emEWVURkCF8fUTHEFe2xrEgTt5ilh5xD94v0pFe_I'
|
||||
),
|
||||
subscription: makeFakeSubscription(),
|
||||
fcmSenderId: '1234567',
|
||||
fcmToken: 'qwerty',
|
||||
fcmPushSet: '7654321'
|
||||
};
|
||||
|
||||
describe('Firebase Messaging > TokenDetailsModel.saveToken()', () => {
|
||||
const EXAMPLE_INPUT = {
|
||||
swScope: '/example-scope',
|
||||
vapidKey: base64ToArrayBuffer(
|
||||
'BNJxw7sCGkGLOUP2cawBaBXRuWZ3lw_PmQMgreLVVvX_b' +
|
||||
'4emEWVURkCF8fUTHEFe2xrEgTt5ilh5xD94v0pFe_I'
|
||||
),
|
||||
subscription: makeFakeSubscription(),
|
||||
fcmSenderId: '1234567',
|
||||
fcmToken: 'qwerty',
|
||||
fcmPushSet: '7654321'
|
||||
};
|
||||
|
||||
let globalTokenModel;
|
||||
|
||||
const cleanUp = () => {
|
||||
const promises: any = [];
|
||||
if (globalTokenModel) {
|
||||
promises.push(globalTokenModel.closeDatabase());
|
||||
}
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(() => deleteDatabase('fcm_token_details_db'))
|
||||
.then(() => (globalTokenModel = null));
|
||||
};
|
||||
let globalTokenModel: TokenDetailsModel;
|
||||
|
||||
beforeEach(() => {
|
||||
return cleanUp();
|
||||
globalTokenModel = new TokenDetailsModel();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
return cleanUp;
|
||||
afterEach(async () => {
|
||||
await globalTokenModel.closeDatabase();
|
||||
await deleteDatabase('fcm_token_details_db');
|
||||
});
|
||||
|
||||
it('should throw on bad input', () => {
|
||||
|
||||
@@ -19,46 +19,38 @@ import { ERROR_CODES } from '../src/models/errors';
|
||||
import { VapidDetailsModel } from '../src/models/vapid-details-model';
|
||||
import { deleteDatabase } from './testing-utils/db-helper';
|
||||
|
||||
const EXAMPLE_SCOPE = '/example-scope';
|
||||
const EXAMPLE_VAPID_STRING =
|
||||
'BNJxw7sCGkGLOUP2cawBaBXRuWZ3lw_PmQMgreLVVvX_b' +
|
||||
'4emEWVURkCF8fUTHEFe2xrEgTt5ilh5xD94v0pFe_I';
|
||||
const EXAMPLE_VAPID_KEY = base64ToArrayBuffer(EXAMPLE_VAPID_STRING);
|
||||
|
||||
describe('Firebase Messaging > VapidDetailsModel.deleteToken()', () => {
|
||||
const EXAMPLE_SCOPE = '/example-scope';
|
||||
const EXAMPLE_VAPID_STRING =
|
||||
'BNJxw7sCGkGLOUP2cawBaBXRuWZ3lw_PmQMgreLVVvX_b' +
|
||||
'4emEWVURkCF8fUTHEFe2xrEgTt5ilh5xD94v0pFe_I';
|
||||
const EXAMPLE_VAPID_KEY = base64ToArrayBuffer(EXAMPLE_VAPID_STRING);
|
||||
|
||||
let vapidModel;
|
||||
|
||||
const cleanUp = () => {
|
||||
let promiseChain = Promise.resolve();
|
||||
if (vapidModel) {
|
||||
promiseChain = promiseChain.then(() => vapidModel.closeDatabase());
|
||||
}
|
||||
|
||||
return promiseChain
|
||||
.then(() => deleteDatabase('fcm_vapid_details_db'))
|
||||
.then(() => (vapidModel = null));
|
||||
};
|
||||
let vapidModel: VapidDetailsModel;
|
||||
|
||||
beforeEach(() => {
|
||||
return cleanUp();
|
||||
vapidModel = new VapidDetailsModel();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
return cleanUp();
|
||||
afterEach(async () => {
|
||||
await vapidModel.closeDatabase();
|
||||
await deleteDatabase('fcm_vapid_details_db');
|
||||
});
|
||||
|
||||
it('should throw on bad scope input', () => {
|
||||
const badInputs = ['', [], {}, true, null, 123];
|
||||
badInputs.forEach(badInput => {
|
||||
vapidModel = new VapidDetailsModel();
|
||||
return vapidModel.saveVapidDetails(badInput, EXAMPLE_VAPID_KEY).then(
|
||||
() => {
|
||||
throw new Error('Expected promise to reject');
|
||||
},
|
||||
err => {
|
||||
assert.equal('messaging/' + ERROR_CODES.BAD_SCOPE, err.code);
|
||||
}
|
||||
);
|
||||
return vapidModel
|
||||
.saveVapidDetails(badInput as any, EXAMPLE_VAPID_KEY)
|
||||
.then(
|
||||
() => {
|
||||
throw new Error('Expected promise to reject');
|
||||
},
|
||||
err => {
|
||||
assert.equal('messaging/' + ERROR_CODES.BAD_SCOPE, err.code);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -89,7 +81,7 @@ describe('Firebase Messaging > VapidDetailsModel.deleteToken()', () => {
|
||||
return vapidModel.getVapidFromSWScope(EXAMPLE_SCOPE);
|
||||
})
|
||||
.then(vapid => {
|
||||
assert.deepEqual(vapid, null);
|
||||
assert.equal(vapid, null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,39 +19,28 @@ import { ERROR_CODES } from '../src/models/errors';
|
||||
import { VapidDetailsModel } from '../src/models/vapid-details-model';
|
||||
import { deleteDatabase } from './testing-utils/db-helper';
|
||||
|
||||
const EXAMPLE_SCOPE = '/example-scope';
|
||||
const EXAMPLE_VAPID_STRING =
|
||||
'BNJxw7sCGkGLOUP2cawBaBXRuWZ3lw_PmQMgreLVVvX_b' +
|
||||
'4emEWVURkCF8fUTHEFe2xrEgTt5ilh5xD94v0pFe_I';
|
||||
const EXAMPLE_VAPID_KEY = base64ToArrayBuffer(EXAMPLE_VAPID_STRING);
|
||||
|
||||
describe('Firebase Messaging > VapidDetailsModel.getVapidFromSWScope()', () => {
|
||||
const EXAMPLE_SCOPE = '/example-scope';
|
||||
const EXAMPLE_VAPID_STRING =
|
||||
'BNJxw7sCGkGLOUP2cawBaBXRuWZ3lw_PmQMgreLVVvX_b' +
|
||||
'4emEWVURkCF8fUTHEFe2xrEgTt5ilh5xD94v0pFe_I';
|
||||
const EXAMPLE_VAPID_KEY = base64ToArrayBuffer(EXAMPLE_VAPID_STRING);
|
||||
|
||||
let vapidModel;
|
||||
|
||||
const cleanUp = () => {
|
||||
let promiseChain = Promise.resolve();
|
||||
if (vapidModel) {
|
||||
promiseChain = promiseChain.then(() => vapidModel.closeDatabase());
|
||||
}
|
||||
|
||||
return promiseChain
|
||||
.then(() => deleteDatabase('fcm_vapid_details_db'))
|
||||
.then(() => (vapidModel = null));
|
||||
};
|
||||
let vapidModel: VapidDetailsModel;
|
||||
|
||||
beforeEach(() => {
|
||||
return cleanUp();
|
||||
vapidModel = new VapidDetailsModel();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
return cleanUp();
|
||||
afterEach(async () => {
|
||||
await vapidModel.closeDatabase();
|
||||
await deleteDatabase('fcm_vapid_details_db');
|
||||
});
|
||||
|
||||
it('should throw on bad scope input', () => {
|
||||
const badInputs = ['', [], {}, true, null, 123];
|
||||
badInputs.forEach(badInput => {
|
||||
vapidModel = new VapidDetailsModel();
|
||||
return vapidModel.getVapidFromSWScope(badInput).then(
|
||||
const badInputs = ['', [], {}, true, null, 123];
|
||||
badInputs.forEach(badInput => {
|
||||
it(`should throw on bad scope input ${badInput}`, () => {
|
||||
return vapidModel.getVapidFromSWScope(badInput as any).then(
|
||||
() => {
|
||||
throw new Error('Expected promise to reject');
|
||||
},
|
||||
@@ -63,11 +52,10 @@ describe('Firebase Messaging > VapidDetailsModel.getVapidFromSWScope()', () => {
|
||||
});
|
||||
|
||||
it('should get vapid key', () => {
|
||||
vapidModel = new VapidDetailsModel();
|
||||
return vapidModel
|
||||
.getVapidFromSWScope(EXAMPLE_SCOPE)
|
||||
.then(vapidKey => {
|
||||
assert.deepEqual(null, vapidKey);
|
||||
assert.equal(null, vapidKey);
|
||||
return vapidModel.saveVapidDetails(EXAMPLE_SCOPE, EXAMPLE_VAPID_KEY);
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
@@ -19,46 +19,38 @@ import { ERROR_CODES } from '../src/models/errors';
|
||||
import { VapidDetailsModel } from '../src/models/vapid-details-model';
|
||||
import { deleteDatabase } from './testing-utils/db-helper';
|
||||
|
||||
const EXAMPLE_SCOPE = '/example-scope';
|
||||
const EXAMPLE_VAPID_STRING =
|
||||
'BNJxw7sCGkGLOUP2cawBaBXRuWZ3lw_PmQMgreLVVvX_b' +
|
||||
'4emEWVURkCF8fUTHEFe2xrEgTt5ilh5xD94v0pFe_I';
|
||||
const EXAMPLE_VAPID_KEY = base64ToArrayBuffer(EXAMPLE_VAPID_STRING);
|
||||
|
||||
describe('Firebase Messaging > VapidDetailsModel.saveVapidDetails()', () => {
|
||||
const EXAMPLE_SCOPE = '/example-scope';
|
||||
const EXAMPLE_VAPID_STRING =
|
||||
'BNJxw7sCGkGLOUP2cawBaBXRuWZ3lw_PmQMgreLVVvX_b' +
|
||||
'4emEWVURkCF8fUTHEFe2xrEgTt5ilh5xD94v0pFe_I';
|
||||
const EXAMPLE_VAPID_KEY = base64ToArrayBuffer(EXAMPLE_VAPID_STRING);
|
||||
|
||||
let vapidModel;
|
||||
|
||||
const cleanUp = () => {
|
||||
let promiseChain = Promise.resolve();
|
||||
if (vapidModel) {
|
||||
promiseChain = promiseChain.then(() => vapidModel.closeDatabase());
|
||||
}
|
||||
|
||||
return promiseChain
|
||||
.then(() => deleteDatabase('fcm_vapid_details_db'))
|
||||
.then(() => (vapidModel = null));
|
||||
};
|
||||
let vapidModel: VapidDetailsModel;
|
||||
|
||||
beforeEach(() => {
|
||||
return cleanUp();
|
||||
vapidModel = new VapidDetailsModel();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
return cleanUp();
|
||||
afterEach(async () => {
|
||||
await vapidModel.closeDatabase();
|
||||
await deleteDatabase('fcm_vapid_details_db');
|
||||
});
|
||||
|
||||
it('should throw on bad scope input', () => {
|
||||
const badInputs = ['', [], {}, true, null, 123];
|
||||
badInputs.forEach(badInput => {
|
||||
vapidModel = new VapidDetailsModel();
|
||||
return vapidModel.saveVapidDetails(badInput, EXAMPLE_VAPID_KEY).then(
|
||||
() => {
|
||||
throw new Error('Expected promise to reject');
|
||||
},
|
||||
err => {
|
||||
assert.equal('messaging/' + ERROR_CODES.BAD_SCOPE, err.code);
|
||||
}
|
||||
);
|
||||
return vapidModel
|
||||
.saveVapidDetails(badInput as any, EXAMPLE_VAPID_KEY)
|
||||
.then(
|
||||
() => {
|
||||
throw new Error('Expected promise to reject');
|
||||
},
|
||||
err => {
|
||||
assert.equal('messaging/' + ERROR_CODES.BAD_SCOPE, err.code);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -66,7 +58,7 @@ describe('Firebase Messaging > VapidDetailsModel.saveVapidDetails()', () => {
|
||||
const badInputs = ['', [], {}, true, null, 123];
|
||||
badInputs.forEach(badInput => {
|
||||
vapidModel = new VapidDetailsModel();
|
||||
return vapidModel.saveVapidDetails(EXAMPLE_SCOPE, badInput).then(
|
||||
return vapidModel.saveVapidDetails(EXAMPLE_SCOPE, badInput as any).then(
|
||||
() => {
|
||||
throw new Error('Expected promise to reject');
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user