Skip to content

Commit f90c1d0

Browse files
authored
Add environment check to Remote-Config Module (#3623)
1 parent f78ceca commit f90c1d0

File tree

9 files changed

+104
-34
lines changed

9 files changed

+104
-34
lines changed

.changeset/blue-rice-ring.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"firebase": minor
3+
"@firebase/remote-config": minor
4+
---
5+
6+
Issue 2393 - Add environment check to Remote-Config Module

common/api-review/remote-config.api.md

+3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ export function getString(remoteConfig: RemoteConfig, key: string): string;
3939
// @public
4040
export function getValue(remoteConfig: RemoteConfig, key: string): Value;
4141

42+
// @public
43+
export function isSupported(): Promise<boolean>;
44+
4245
// @public
4346
export type LogLevel = 'debug' | 'error' | 'silent';
4447

packages/firebase/compat/index.d.ts

+18-5
Original file line numberDiff line numberDiff line change
@@ -2031,6 +2031,16 @@ declare namespace firebase.remoteConfig {
20312031
* Defines levels of Remote Config logging.
20322032
*/
20332033
export type LogLevel = 'debug' | 'error' | 'silent';
2034+
/**
2035+
* This method provides two different checks:
2036+
*
2037+
* 1. Check if IndexedDB exists in the browser environment.
2038+
* 2. Check if the current browser context allows IndexedDB `open()` calls.
2039+
*
2040+
* It returns a `Promise` which resolves to true if a {@link RemoteConfig} instance
2041+
* can be initialized in this environment, or false if it cannot.
2042+
*/
2043+
export function isSupported(): Promise<boolean>;
20342044
}
20352045

20362046
declare namespace firebase.functions {
@@ -4623,13 +4633,14 @@ declare namespace firebase.auth {
46234633
* instance must be initialized with an API key, otherwise an error will be
46244634
* thrown.
46254635
*/
4626-
class RecaptchaVerifier extends RecaptchaVerifier_Instance { }
4636+
class RecaptchaVerifier extends RecaptchaVerifier_Instance {}
46274637
/**
46284638
* @webonly
46294639
* @hidden
46304640
*/
46314641
class RecaptchaVerifier_Instance
4632-
implements firebase.auth.ApplicationVerifier {
4642+
implements firebase.auth.ApplicationVerifier
4643+
{
46334644
constructor(
46344645
container: any | string,
46354646
parameters?: Object | null,
@@ -7310,7 +7321,7 @@ declare namespace firebase.database {
73107321

73117322
interface ThenableReference
73127323
extends firebase.database.Reference,
7313-
Pick<Promise<Reference>, 'then' | 'catch'> { }
7324+
Pick<Promise<Reference>, 'then' | 'catch'> {}
73147325

73157326
/**
73167327
* Logs debugging information to the console.
@@ -7690,7 +7701,9 @@ declare namespace firebase.storage {
76907701
* resolves with the full updated metadata or rejects if the updated failed,
76917702
* including if the object did not exist.
76927703
*/
7693-
updateMetadata(metadata: firebase.storage.SettableMetadata): Promise<FullMetadata>;
7704+
updateMetadata(
7705+
metadata: firebase.storage.SettableMetadata
7706+
): Promise<FullMetadata>;
76947707
/**
76957708
* List all items (files) and prefixes (folders) under this storage reference.
76967709
*
@@ -9520,7 +9533,7 @@ declare namespace firebase.firestore {
95209533
*/
95219534
export class QueryDocumentSnapshot<
95229535
T = DocumentData
9523-
> extends DocumentSnapshot<T> {
9536+
> extends DocumentSnapshot<T> {
95249537
private constructor();
95259538

95269539
/**

packages/remote-config-compat/src/index.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
ComponentType,
2323
InstanceFactoryOptions
2424
} from '@firebase/component';
25-
import { RemoteConfigCompatImpl } from './remoteConfig';
25+
import { RemoteConfigCompatImpl, isSupported } from './remoteConfig';
2626
import { name as packageName, version } from '../package.json';
2727
import { RemoteConfig as RemoteConfigCompat } from '@firebase/remote-config-types';
2828

@@ -34,7 +34,9 @@ function registerRemoteConfigCompat(
3434
'remoteConfig-compat',
3535
remoteConfigFactory,
3636
ComponentType.PUBLIC
37-
).setMultipleInstances(true)
37+
)
38+
.setMultipleInstances(true)
39+
.setServiceProps({ isSupported })
3840
);
3941

4042
firebaseInstance.registerVersion(packageName, version);

packages/remote-config-compat/src/remoteConfig.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,15 @@ import {
3434
getBoolean,
3535
getNumber,
3636
getString,
37-
getValue
37+
getValue,
38+
isSupported
3839
} from '@firebase/remote-config';
3940

41+
export { isSupported };
42+
4043
export class RemoteConfigCompatImpl
41-
implements RemoteConfigCompat, _FirebaseService {
44+
implements RemoteConfigCompat, _FirebaseService
45+
{
4246
constructor(public app: FirebaseApp, readonly _delegate: RemoteConfig) {}
4347

4448
get defaultConfig(): { [key: string]: string | number | boolean } {

packages/remote-config/src/api2.ts

+28-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717

1818
import { RemoteConfig } from './public_types';
1919
import { activate, fetchConfig } from './api';
20-
import { getModularInstance } from '@firebase/util';
20+
import {
21+
getModularInstance,
22+
isIndexedDBAvailable,
23+
validateIndexedDBOpenable
24+
} from '@firebase/util';
2125

2226
// This API is put in a separate file, so we can stub fetchConfig and activate in tests.
2327
// It's not possible to stub standalone functions from the same module.
@@ -39,3 +43,26 @@ export async function fetchAndActivate(
3943
await fetchConfig(remoteConfig);
4044
return activate(remoteConfig);
4145
}
46+
47+
/**
48+
* This method provides two different checks:
49+
*
50+
* 1. Check if IndexedDB exists in the browser environment.
51+
* 2. Check if the current browser context allows IndexedDB `open()` calls.
52+
*
53+
* @returns A `Promise` which resolves to true if a {@link RemoteConfig} instance
54+
* can be initialized in this environment, or false if it cannot.
55+
* @public
56+
*/
57+
export async function isSupported(): Promise<boolean> {
58+
if (!isIndexedDBAvailable()) {
59+
return false;
60+
}
61+
62+
try {
63+
const isDBOpenable: boolean = await validateIndexedDBOpenable();
64+
return isDBOpenable;
65+
} catch (error) {
66+
return false;
67+
}
68+
}

packages/remote-config/src/errors.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ export const enum ErrorCode {
3030
FETCH_TIMEOUT = 'fetch-timeout',
3131
FETCH_THROTTLE = 'fetch-throttle',
3232
FETCH_PARSE = 'fetch-client-parse',
33-
FETCH_STATUS = 'fetch-status'
33+
FETCH_STATUS = 'fetch-status',
34+
INDEXED_DB_UNAVAILABLE = 'indexed-db-unavailable'
3435
}
3536

3637
const ERROR_DESCRIPTION_MAP: { readonly [key in ErrorCode]: string } = {
@@ -64,7 +65,9 @@ const ERROR_DESCRIPTION_MAP: { readonly [key in ErrorCode]: string } = {
6465
'Fetch client could not parse response.' +
6566
' Original error: {$originalErrorMessage}.',
6667
[ErrorCode.FETCH_STATUS]:
67-
'Fetch server returned an HTTP error status. HTTP status: {$httpStatus}.'
68+
'Fetch server returned an HTTP error status. HTTP status: {$httpStatus}.',
69+
[ErrorCode.INDEXED_DB_UNAVAILABLE]:
70+
'Indexed DB is not supported by current browser'
6871
};
6972

7073
// Note this is effectively a type system binding a code to params. This approach overlaps with the

packages/remote-config/src/register.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
registerVersion,
2020
SDK_VERSION
2121
} from '@firebase/app';
22+
import { isIndexedDBAvailable } from '@firebase/util';
2223
import {
2324
Component,
2425
ComponentType,
@@ -68,7 +69,10 @@ export function registerRemoteConfig(): void {
6869
if (typeof window === 'undefined') {
6970
throw ERROR_FACTORY.create(ErrorCode.REGISTRATION_WINDOW);
7071
}
71-
72+
// Guards against the SDK being used when indexedDB is not available.
73+
if (!isIndexedDBAvailable()) {
74+
throw ERROR_FACTORY.create(ErrorCode.INDEXED_DB_UNAVAILABLE);
75+
}
7276
// Normalizes optional inputs.
7377
const { projectId, apiKey, appId } = app.options;
7478
if (!projectId) {

packages/remote-config/src/storage/storage.ts

+29-21
Original file line numberDiff line numberDiff line change
@@ -75,28 +75,36 @@ type ProjectNamespaceKeyFieldValue =
7575
// Visible for testing.
7676
export function openDatabase(): Promise<IDBDatabase> {
7777
return new Promise((resolve, reject) => {
78-
const request = indexedDB.open(DB_NAME, DB_VERSION);
79-
request.onerror = event => {
80-
reject(toFirebaseError(event, ErrorCode.STORAGE_OPEN));
81-
};
82-
request.onsuccess = event => {
83-
resolve((event.target as IDBOpenDBRequest).result);
84-
};
85-
request.onupgradeneeded = event => {
86-
const db = (event.target as IDBOpenDBRequest).result;
78+
try {
79+
const request = indexedDB.open(DB_NAME, DB_VERSION);
80+
request.onerror = event => {
81+
reject(toFirebaseError(event, ErrorCode.STORAGE_OPEN));
82+
};
83+
request.onsuccess = event => {
84+
resolve((event.target as IDBOpenDBRequest).result);
85+
};
86+
request.onupgradeneeded = event => {
87+
const db = (event.target as IDBOpenDBRequest).result;
8788

88-
// We don't use 'break' in this switch statement, the fall-through
89-
// behavior is what we want, because if there are multiple versions between
90-
// the old version and the current version, we want ALL the migrations
91-
// that correspond to those versions to run, not only the last one.
92-
// eslint-disable-next-line default-case
93-
switch (event.oldVersion) {
94-
case 0:
95-
db.createObjectStore(APP_NAMESPACE_STORE, {
96-
keyPath: 'compositeKey'
97-
});
98-
}
99-
};
89+
// We don't use 'break' in this switch statement, the fall-through
90+
// behavior is what we want, because if there are multiple versions between
91+
// the old version and the current version, we want ALL the migrations
92+
// that correspond to those versions to run, not only the last one.
93+
// eslint-disable-next-line default-case
94+
switch (event.oldVersion) {
95+
case 0:
96+
db.createObjectStore(APP_NAMESPACE_STORE, {
97+
keyPath: 'compositeKey'
98+
});
99+
}
100+
};
101+
} catch (error) {
102+
reject(
103+
ERROR_FACTORY.create(ErrorCode.STORAGE_OPEN, {
104+
originalErrorMessage: error
105+
})
106+
);
107+
}
100108
});
101109
}
102110

0 commit comments

Comments
 (0)