Skip to content

Commit 82a6add

Browse files
authored
Warn instead of throw for idb errors in core app (#6480)
1 parent 6a17eb6 commit 82a6add

File tree

4 files changed

+117
-23
lines changed

4 files changed

+117
-23
lines changed

.changeset/calm-pugs-leave.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@firebase/app': patch
3+
---
4+
5+
Prevent core app from throwing if IndexedDB heartbeat functions throw.

packages/app/src/errors.ts

+16-16
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ export const enum AppError {
2424
APP_DELETED = 'app-deleted',
2525
INVALID_APP_ARGUMENT = 'invalid-app-argument',
2626
INVALID_LOG_ARGUMENT = 'invalid-log-argument',
27-
STORAGE_OPEN = 'storage-open',
28-
STORAGE_GET = 'storage-get',
29-
STORAGE_WRITE = 'storage-set',
30-
STORAGE_DELETE = 'storage-delete'
27+
IDB_OPEN = 'idb-open',
28+
IDB_GET = 'idb-get',
29+
IDB_WRITE = 'idb-set',
30+
IDB_DELETE = 'idb-delete'
3131
}
3232

3333
const ERRORS: ErrorMap<AppError> = {
@@ -43,14 +43,14 @@ const ERRORS: ErrorMap<AppError> = {
4343
'Firebase App instance.',
4444
[AppError.INVALID_LOG_ARGUMENT]:
4545
'First argument to `onLog` must be null or a function.',
46-
[AppError.STORAGE_OPEN]:
47-
'Error thrown when opening storage. Original error: {$originalErrorMessage}.',
48-
[AppError.STORAGE_GET]:
49-
'Error thrown when reading from storage. Original error: {$originalErrorMessage}.',
50-
[AppError.STORAGE_WRITE]:
51-
'Error thrown when writing to storage. Original error: {$originalErrorMessage}.',
52-
[AppError.STORAGE_DELETE]:
53-
'Error thrown when deleting from storage. Original error: {$originalErrorMessage}.'
46+
[AppError.IDB_OPEN]:
47+
'Error thrown when opening IndexedDB. Original error: {$originalErrorMessage}.',
48+
[AppError.IDB_GET]:
49+
'Error thrown when reading from IndexedDB. Original error: {$originalErrorMessage}.',
50+
[AppError.IDB_WRITE]:
51+
'Error thrown when writing to IndexedDB. Original error: {$originalErrorMessage}.',
52+
[AppError.IDB_DELETE]:
53+
'Error thrown when deleting from IndexedDB. Original error: {$originalErrorMessage}.'
5454
};
5555

5656
interface ErrorParams {
@@ -59,10 +59,10 @@ interface ErrorParams {
5959
[AppError.DUPLICATE_APP]: { appName: string };
6060
[AppError.APP_DELETED]: { appName: string };
6161
[AppError.INVALID_APP_ARGUMENT]: { appName: string };
62-
[AppError.STORAGE_OPEN]: { originalErrorMessage?: string };
63-
[AppError.STORAGE_GET]: { originalErrorMessage?: string };
64-
[AppError.STORAGE_WRITE]: { originalErrorMessage?: string };
65-
[AppError.STORAGE_DELETE]: { originalErrorMessage?: string };
62+
[AppError.IDB_OPEN]: { originalErrorMessage?: string };
63+
[AppError.IDB_GET]: { originalErrorMessage?: string };
64+
[AppError.IDB_WRITE]: { originalErrorMessage?: string };
65+
[AppError.IDB_DELETE]: { originalErrorMessage?: string };
6666
}
6767

6868
export const ERROR_FACTORY = new ErrorFactory<AppError, ErrorParams>(

packages/app/src/indexeddb.test.ts

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* @license
3+
* Copyright 2022 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { expect } from 'chai';
19+
import '../test/setup';
20+
import { match, stub } from 'sinon';
21+
import {
22+
readHeartbeatsFromIndexedDB,
23+
writeHeartbeatsToIndexedDB
24+
} from './indexeddb';
25+
import { FirebaseApp } from './public-types';
26+
import { AppError } from './errors';
27+
import { HeartbeatsInIndexedDB } from './types';
28+
29+
/**
30+
* Mostly testing failure cases. heartbeatService.test.ts tests read-write
31+
* more extensively.
32+
*/
33+
34+
describe('IndexedDB functions', () => {
35+
it('readHeartbeatsFromIndexedDB warns if IndexedDB.open() throws', async () => {
36+
const warnStub = stub(console, 'warn');
37+
if (typeof window !== 'undefined') {
38+
// Ensure that indexedDB.open() fails in browser. It will always fail in Node.
39+
stub(window.indexedDB, 'open').throws(new Error('abcd'));
40+
await readHeartbeatsFromIndexedDB({
41+
name: 'testname',
42+
options: { appId: 'test-app-id' }
43+
} as FirebaseApp);
44+
expect(warnStub).to.be.calledWith(match.any, match(AppError.IDB_GET));
45+
} else {
46+
await readHeartbeatsFromIndexedDB({
47+
name: 'testname',
48+
options: { appId: 'test-app-id' }
49+
} as FirebaseApp);
50+
expect(warnStub).to.be.calledWith(match.any, match(AppError.IDB_GET));
51+
}
52+
});
53+
it('writeHeartbeatsToIndexedDB warns if IndexedDB.open() throws', async () => {
54+
const warnStub = stub(console, 'warn');
55+
if (typeof window !== 'undefined') {
56+
// Ensure that indexedDB.open() fails in browser. It will always fail in Node.
57+
stub(window.indexedDB, 'open').throws(new Error('abcd'));
58+
await writeHeartbeatsToIndexedDB(
59+
{
60+
name: 'testname',
61+
options: { appId: 'test-app-id' }
62+
} as FirebaseApp,
63+
{} as HeartbeatsInIndexedDB
64+
);
65+
expect(warnStub).to.be.calledWith(match.any, match(AppError.IDB_WRITE));
66+
} else {
67+
await writeHeartbeatsToIndexedDB(
68+
{
69+
name: 'testname',
70+
options: { appId: 'test-app-id' }
71+
} as FirebaseApp,
72+
{} as HeartbeatsInIndexedDB
73+
);
74+
expect(warnStub).to.be.calledWith(match.any, match(AppError.IDB_WRITE));
75+
}
76+
});
77+
});

packages/app/src/indexeddb.ts

+19-7
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515
* limitations under the License.
1616
*/
1717

18+
import { FirebaseError } from '@firebase/util';
1819
import { DBSchema, openDB, IDBPDatabase } from 'idb';
1920
import { AppError, ERROR_FACTORY } from './errors';
2021
import { FirebaseApp } from './public-types';
2122
import { HeartbeatsInIndexedDB } from './types';
23+
import { logger } from './logger';
2224

2325
const DB_NAME = 'firebase-heartbeat-database';
2426
const DB_VERSION = 1;
@@ -47,7 +49,7 @@ function getDbPromise(): Promise<IDBPDatabase<AppDB>> {
4749
}
4850
}
4951
}).catch(e => {
50-
throw ERROR_FACTORY.create(AppError.STORAGE_OPEN, {
52+
throw ERROR_FACTORY.create(AppError.IDB_OPEN, {
5153
originalErrorMessage: e.message
5254
});
5355
});
@@ -65,9 +67,14 @@ export async function readHeartbeatsFromIndexedDB(
6567
.objectStore(STORE_NAME)
6668
.get(computeKey(app)) as Promise<HeartbeatsInIndexedDB | undefined>;
6769
} catch (e) {
68-
throw ERROR_FACTORY.create(AppError.STORAGE_GET, {
69-
originalErrorMessage: (e as Error)?.message
70-
});
70+
if (e instanceof FirebaseError) {
71+
logger.warn(e.message);
72+
} else {
73+
const idbGetError = ERROR_FACTORY.create(AppError.IDB_GET, {
74+
originalErrorMessage: (e as Error)?.message
75+
});
76+
logger.warn(idbGetError.message);
77+
}
7178
}
7279
}
7380

@@ -82,9 +89,14 @@ export async function writeHeartbeatsToIndexedDB(
8289
await objectStore.put(heartbeatObject, computeKey(app));
8390
return tx.done;
8491
} catch (e) {
85-
throw ERROR_FACTORY.create(AppError.STORAGE_WRITE, {
86-
originalErrorMessage: (e as Error)?.message
87-
});
92+
if (e instanceof FirebaseError) {
93+
logger.warn(e.message);
94+
} else {
95+
const idbGetError = ERROR_FACTORY.create(AppError.IDB_WRITE, {
96+
originalErrorMessage: (e as Error)?.message
97+
});
98+
logger.warn(idbGetError.message);
99+
}
88100
}
89101
}
90102

0 commit comments

Comments
 (0)