From 82a6add1354fe7e4ac1d444157ac027cdd41da6e Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Tue, 2 Aug 2022 14:41:50 -0700 Subject: [PATCH] Warn instead of throw for idb errors in core app (#6480) --- .changeset/calm-pugs-leave.md | 5 ++ packages/app/src/errors.ts | 32 ++++++------- packages/app/src/indexeddb.test.ts | 77 ++++++++++++++++++++++++++++++ packages/app/src/indexeddb.ts | 26 +++++++--- 4 files changed, 117 insertions(+), 23 deletions(-) create mode 100644 .changeset/calm-pugs-leave.md create mode 100644 packages/app/src/indexeddb.test.ts diff --git a/.changeset/calm-pugs-leave.md b/.changeset/calm-pugs-leave.md new file mode 100644 index 00000000000..2610836d5bc --- /dev/null +++ b/.changeset/calm-pugs-leave.md @@ -0,0 +1,5 @@ +--- +'@firebase/app': patch +--- + +Prevent core app from throwing if IndexedDB heartbeat functions throw. diff --git a/packages/app/src/errors.ts b/packages/app/src/errors.ts index 8c9742e69de..ed307085b09 100644 --- a/packages/app/src/errors.ts +++ b/packages/app/src/errors.ts @@ -24,10 +24,10 @@ export const enum AppError { APP_DELETED = 'app-deleted', INVALID_APP_ARGUMENT = 'invalid-app-argument', INVALID_LOG_ARGUMENT = 'invalid-log-argument', - STORAGE_OPEN = 'storage-open', - STORAGE_GET = 'storage-get', - STORAGE_WRITE = 'storage-set', - STORAGE_DELETE = 'storage-delete' + IDB_OPEN = 'idb-open', + IDB_GET = 'idb-get', + IDB_WRITE = 'idb-set', + IDB_DELETE = 'idb-delete' } const ERRORS: ErrorMap = { @@ -43,14 +43,14 @@ const ERRORS: ErrorMap = { 'Firebase App instance.', [AppError.INVALID_LOG_ARGUMENT]: 'First argument to `onLog` must be null or a function.', - [AppError.STORAGE_OPEN]: - 'Error thrown when opening storage. Original error: {$originalErrorMessage}.', - [AppError.STORAGE_GET]: - 'Error thrown when reading from storage. Original error: {$originalErrorMessage}.', - [AppError.STORAGE_WRITE]: - 'Error thrown when writing to storage. Original error: {$originalErrorMessage}.', - [AppError.STORAGE_DELETE]: - 'Error thrown when deleting from storage. Original error: {$originalErrorMessage}.' + [AppError.IDB_OPEN]: + 'Error thrown when opening IndexedDB. Original error: {$originalErrorMessage}.', + [AppError.IDB_GET]: + 'Error thrown when reading from IndexedDB. Original error: {$originalErrorMessage}.', + [AppError.IDB_WRITE]: + 'Error thrown when writing to IndexedDB. Original error: {$originalErrorMessage}.', + [AppError.IDB_DELETE]: + 'Error thrown when deleting from IndexedDB. Original error: {$originalErrorMessage}.' }; interface ErrorParams { @@ -59,10 +59,10 @@ interface ErrorParams { [AppError.DUPLICATE_APP]: { appName: string }; [AppError.APP_DELETED]: { appName: string }; [AppError.INVALID_APP_ARGUMENT]: { appName: string }; - [AppError.STORAGE_OPEN]: { originalErrorMessage?: string }; - [AppError.STORAGE_GET]: { originalErrorMessage?: string }; - [AppError.STORAGE_WRITE]: { originalErrorMessage?: string }; - [AppError.STORAGE_DELETE]: { originalErrorMessage?: string }; + [AppError.IDB_OPEN]: { originalErrorMessage?: string }; + [AppError.IDB_GET]: { originalErrorMessage?: string }; + [AppError.IDB_WRITE]: { originalErrorMessage?: string }; + [AppError.IDB_DELETE]: { originalErrorMessage?: string }; } export const ERROR_FACTORY = new ErrorFactory( diff --git a/packages/app/src/indexeddb.test.ts b/packages/app/src/indexeddb.test.ts new file mode 100644 index 00000000000..a8b53a18844 --- /dev/null +++ b/packages/app/src/indexeddb.test.ts @@ -0,0 +1,77 @@ +/** + * @license + * Copyright 2022 Google LLC + * + * 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 '../test/setup'; +import { match, stub } from 'sinon'; +import { + readHeartbeatsFromIndexedDB, + writeHeartbeatsToIndexedDB +} from './indexeddb'; +import { FirebaseApp } from './public-types'; +import { AppError } from './errors'; +import { HeartbeatsInIndexedDB } from './types'; + +/** + * Mostly testing failure cases. heartbeatService.test.ts tests read-write + * more extensively. + */ + +describe('IndexedDB functions', () => { + it('readHeartbeatsFromIndexedDB warns if IndexedDB.open() throws', async () => { + const warnStub = stub(console, 'warn'); + if (typeof window !== 'undefined') { + // Ensure that indexedDB.open() fails in browser. It will always fail in Node. + stub(window.indexedDB, 'open').throws(new Error('abcd')); + await readHeartbeatsFromIndexedDB({ + name: 'testname', + options: { appId: 'test-app-id' } + } as FirebaseApp); + expect(warnStub).to.be.calledWith(match.any, match(AppError.IDB_GET)); + } else { + await readHeartbeatsFromIndexedDB({ + name: 'testname', + options: { appId: 'test-app-id' } + } as FirebaseApp); + expect(warnStub).to.be.calledWith(match.any, match(AppError.IDB_GET)); + } + }); + it('writeHeartbeatsToIndexedDB warns if IndexedDB.open() throws', async () => { + const warnStub = stub(console, 'warn'); + if (typeof window !== 'undefined') { + // Ensure that indexedDB.open() fails in browser. It will always fail in Node. + stub(window.indexedDB, 'open').throws(new Error('abcd')); + await writeHeartbeatsToIndexedDB( + { + name: 'testname', + options: { appId: 'test-app-id' } + } as FirebaseApp, + {} as HeartbeatsInIndexedDB + ); + expect(warnStub).to.be.calledWith(match.any, match(AppError.IDB_WRITE)); + } else { + await writeHeartbeatsToIndexedDB( + { + name: 'testname', + options: { appId: 'test-app-id' } + } as FirebaseApp, + {} as HeartbeatsInIndexedDB + ); + expect(warnStub).to.be.calledWith(match.any, match(AppError.IDB_WRITE)); + } + }); +}); diff --git a/packages/app/src/indexeddb.ts b/packages/app/src/indexeddb.ts index 6e7c991cc37..761a39bc15a 100644 --- a/packages/app/src/indexeddb.ts +++ b/packages/app/src/indexeddb.ts @@ -15,10 +15,12 @@ * limitations under the License. */ +import { FirebaseError } from '@firebase/util'; import { DBSchema, openDB, IDBPDatabase } from 'idb'; import { AppError, ERROR_FACTORY } from './errors'; import { FirebaseApp } from './public-types'; import { HeartbeatsInIndexedDB } from './types'; +import { logger } from './logger'; const DB_NAME = 'firebase-heartbeat-database'; const DB_VERSION = 1; @@ -47,7 +49,7 @@ function getDbPromise(): Promise> { } } }).catch(e => { - throw ERROR_FACTORY.create(AppError.STORAGE_OPEN, { + throw ERROR_FACTORY.create(AppError.IDB_OPEN, { originalErrorMessage: e.message }); }); @@ -65,9 +67,14 @@ export async function readHeartbeatsFromIndexedDB( .objectStore(STORE_NAME) .get(computeKey(app)) as Promise; } catch (e) { - throw ERROR_FACTORY.create(AppError.STORAGE_GET, { - originalErrorMessage: (e as Error)?.message - }); + if (e instanceof FirebaseError) { + logger.warn(e.message); + } else { + const idbGetError = ERROR_FACTORY.create(AppError.IDB_GET, { + originalErrorMessage: (e as Error)?.message + }); + logger.warn(idbGetError.message); + } } } @@ -82,9 +89,14 @@ export async function writeHeartbeatsToIndexedDB( await objectStore.put(heartbeatObject, computeKey(app)); return tx.done; } catch (e) { - throw ERROR_FACTORY.create(AppError.STORAGE_WRITE, { - originalErrorMessage: (e as Error)?.message - }); + if (e instanceof FirebaseError) { + logger.warn(e.message); + } else { + const idbGetError = ERROR_FACTORY.create(AppError.IDB_WRITE, { + originalErrorMessage: (e as Error)?.message + }); + logger.warn(idbGetError.message); + } } }