Skip to content

Commit

Permalink
Add environment check to Remote-Config Module (#3623)
Browse files Browse the repository at this point in the history
  • Loading branch information
XuechunHou committed Sep 21, 2021
1 parent f78ceca commit f90c1d0
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 34 deletions.
6 changes: 6 additions & 0 deletions .changeset/blue-rice-ring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"firebase": minor
"@firebase/remote-config": minor
---

Issue 2393 - Add environment check to Remote-Config Module
3 changes: 3 additions & 0 deletions common/api-review/remote-config.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ export function getString(remoteConfig: RemoteConfig, key: string): string;
// @public
export function getValue(remoteConfig: RemoteConfig, key: string): Value;

// @public
export function isSupported(): Promise<boolean>;

// @public
export type LogLevel = 'debug' | 'error' | 'silent';

Expand Down
23 changes: 18 additions & 5 deletions packages/firebase/compat/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2031,6 +2031,16 @@ declare namespace firebase.remoteConfig {
* Defines levels of Remote Config logging.
*/
export type LogLevel = 'debug' | 'error' | 'silent';
/**
* This method provides two different checks:
*
* 1. Check if IndexedDB exists in the browser environment.
* 2. Check if the current browser context allows IndexedDB `open()` calls.
*
* It returns a `Promise` which resolves to true if a {@link RemoteConfig} instance
* can be initialized in this environment, or false if it cannot.
*/
export function isSupported(): Promise<boolean>;
}

declare namespace firebase.functions {
Expand Down Expand Up @@ -4623,13 +4633,14 @@ declare namespace firebase.auth {
* instance must be initialized with an API key, otherwise an error will be
* thrown.
*/
class RecaptchaVerifier extends RecaptchaVerifier_Instance { }
class RecaptchaVerifier extends RecaptchaVerifier_Instance {}
/**
* @webonly
* @hidden
*/
class RecaptchaVerifier_Instance
implements firebase.auth.ApplicationVerifier {
implements firebase.auth.ApplicationVerifier
{
constructor(
container: any | string,
parameters?: Object | null,
Expand Down Expand Up @@ -7310,7 +7321,7 @@ declare namespace firebase.database {

interface ThenableReference
extends firebase.database.Reference,
Pick<Promise<Reference>, 'then' | 'catch'> { }
Pick<Promise<Reference>, 'then' | 'catch'> {}

/**
* Logs debugging information to the console.
Expand Down Expand Up @@ -7690,7 +7701,9 @@ declare namespace firebase.storage {
* resolves with the full updated metadata or rejects if the updated failed,
* including if the object did not exist.
*/
updateMetadata(metadata: firebase.storage.SettableMetadata): Promise<FullMetadata>;
updateMetadata(
metadata: firebase.storage.SettableMetadata
): Promise<FullMetadata>;
/**
* List all items (files) and prefixes (folders) under this storage reference.
*
Expand Down Expand Up @@ -9520,7 +9533,7 @@ declare namespace firebase.firestore {
*/
export class QueryDocumentSnapshot<
T = DocumentData
> extends DocumentSnapshot<T> {
> extends DocumentSnapshot<T> {
private constructor();

/**
Expand Down
6 changes: 4 additions & 2 deletions packages/remote-config-compat/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
ComponentType,
InstanceFactoryOptions
} from '@firebase/component';
import { RemoteConfigCompatImpl } from './remoteConfig';
import { RemoteConfigCompatImpl, isSupported } from './remoteConfig';
import { name as packageName, version } from '../package.json';
import { RemoteConfig as RemoteConfigCompat } from '@firebase/remote-config-types';

Expand All @@ -34,7 +34,9 @@ function registerRemoteConfigCompat(
'remoteConfig-compat',
remoteConfigFactory,
ComponentType.PUBLIC
).setMultipleInstances(true)
)
.setMultipleInstances(true)
.setServiceProps({ isSupported })
);

firebaseInstance.registerVersion(packageName, version);
Expand Down
8 changes: 6 additions & 2 deletions packages/remote-config-compat/src/remoteConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,15 @@ import {
getBoolean,
getNumber,
getString,
getValue
getValue,
isSupported
} from '@firebase/remote-config';

export { isSupported };

export class RemoteConfigCompatImpl
implements RemoteConfigCompat, _FirebaseService {
implements RemoteConfigCompat, _FirebaseService
{
constructor(public app: FirebaseApp, readonly _delegate: RemoteConfig) {}

get defaultConfig(): { [key: string]: string | number | boolean } {
Expand Down
29 changes: 28 additions & 1 deletion packages/remote-config/src/api2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@

import { RemoteConfig } from './public_types';
import { activate, fetchConfig } from './api';
import { getModularInstance } from '@firebase/util';
import {
getModularInstance,
isIndexedDBAvailable,
validateIndexedDBOpenable
} from '@firebase/util';

// This API is put in a separate file, so we can stub fetchConfig and activate in tests.
// It's not possible to stub standalone functions from the same module.
Expand All @@ -39,3 +43,26 @@ export async function fetchAndActivate(
await fetchConfig(remoteConfig);
return activate(remoteConfig);
}

/**
* This method provides two different checks:
*
* 1. Check if IndexedDB exists in the browser environment.
* 2. Check if the current browser context allows IndexedDB `open()` calls.
*
* @returns A `Promise` which resolves to true if a {@link RemoteConfig} instance
* can be initialized in this environment, or false if it cannot.
* @public
*/
export async function isSupported(): Promise<boolean> {
if (!isIndexedDBAvailable()) {
return false;
}

try {
const isDBOpenable: boolean = await validateIndexedDBOpenable();
return isDBOpenable;
} catch (error) {
return false;
}
}
7 changes: 5 additions & 2 deletions packages/remote-config/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ export const enum ErrorCode {
FETCH_TIMEOUT = 'fetch-timeout',
FETCH_THROTTLE = 'fetch-throttle',
FETCH_PARSE = 'fetch-client-parse',
FETCH_STATUS = 'fetch-status'
FETCH_STATUS = 'fetch-status',
INDEXED_DB_UNAVAILABLE = 'indexed-db-unavailable'
}

const ERROR_DESCRIPTION_MAP: { readonly [key in ErrorCode]: string } = {
Expand Down Expand Up @@ -64,7 +65,9 @@ const ERROR_DESCRIPTION_MAP: { readonly [key in ErrorCode]: string } = {
'Fetch client could not parse response.' +
' Original error: {$originalErrorMessage}.',
[ErrorCode.FETCH_STATUS]:
'Fetch server returned an HTTP error status. HTTP status: {$httpStatus}.'
'Fetch server returned an HTTP error status. HTTP status: {$httpStatus}.',
[ErrorCode.INDEXED_DB_UNAVAILABLE]:
'Indexed DB is not supported by current browser'
};

// Note this is effectively a type system binding a code to params. This approach overlaps with the
Expand Down
6 changes: 5 additions & 1 deletion packages/remote-config/src/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
registerVersion,
SDK_VERSION
} from '@firebase/app';
import { isIndexedDBAvailable } from '@firebase/util';
import {
Component,
ComponentType,
Expand Down Expand Up @@ -68,7 +69,10 @@ export function registerRemoteConfig(): void {
if (typeof window === 'undefined') {
throw ERROR_FACTORY.create(ErrorCode.REGISTRATION_WINDOW);
}

// Guards against the SDK being used when indexedDB is not available.
if (!isIndexedDBAvailable()) {
throw ERROR_FACTORY.create(ErrorCode.INDEXED_DB_UNAVAILABLE);
}
// Normalizes optional inputs.
const { projectId, apiKey, appId } = app.options;
if (!projectId) {
Expand Down
50 changes: 29 additions & 21 deletions packages/remote-config/src/storage/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,28 +75,36 @@ type ProjectNamespaceKeyFieldValue =
// Visible for testing.
export function openDatabase(): Promise<IDBDatabase> {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, DB_VERSION);
request.onerror = event => {
reject(toFirebaseError(event, ErrorCode.STORAGE_OPEN));
};
request.onsuccess = event => {
resolve((event.target as IDBOpenDBRequest).result);
};
request.onupgradeneeded = event => {
const db = (event.target as IDBOpenDBRequest).result;
try {
const request = indexedDB.open(DB_NAME, DB_VERSION);
request.onerror = event => {
reject(toFirebaseError(event, ErrorCode.STORAGE_OPEN));
};
request.onsuccess = event => {
resolve((event.target as IDBOpenDBRequest).result);
};
request.onupgradeneeded = event => {
const db = (event.target as IDBOpenDBRequest).result;

// We don't use 'break' in this switch statement, the fall-through
// behavior is what we want, because if there are multiple versions between
// the old version and the current version, we want ALL the migrations
// that correspond to those versions to run, not only the last one.
// eslint-disable-next-line default-case
switch (event.oldVersion) {
case 0:
db.createObjectStore(APP_NAMESPACE_STORE, {
keyPath: 'compositeKey'
});
}
};
// We don't use 'break' in this switch statement, the fall-through
// behavior is what we want, because if there are multiple versions between
// the old version and the current version, we want ALL the migrations
// that correspond to those versions to run, not only the last one.
// eslint-disable-next-line default-case
switch (event.oldVersion) {
case 0:
db.createObjectStore(APP_NAMESPACE_STORE, {
keyPath: 'compositeKey'
});
}
};
} catch (error) {
reject(
ERROR_FACTORY.create(ErrorCode.STORAGE_OPEN, {
originalErrorMessage: error
})
);
}
});
}

Expand Down

0 comments on commit f90c1d0

Please sign in to comment.