voluptasmollitia
Version:
Monorepo for the Firebase JavaScript SDK
261 lines (245 loc) • 8.41 kB
text/typescript
/**
* @license
* Copyright 2020 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 { _getProvider, FirebaseApp, getApp } from '@firebase/app-exp';
import {
LogLevel as RemoteConfigLogLevel,
RemoteConfig,
Value
} from './public_types';
import { RemoteConfigAbortSignal } from './client/remote_config_fetch_client';
import { RC_COMPONENT_NAME } from './constants';
import { ErrorCode, hasErrorCode } from './errors';
import { RemoteConfig as RemoteConfigImpl } from './remote_config';
import { Value as ValueImpl } from './value';
import { LogLevel as FirebaseLogLevel } from '@firebase/logger';
import { getModularInstance } from '@firebase/util';
/**
*
* @param app - The `FirebaseApp` instance.
* @returns A `RemoteConfig` instance.
*
* @public
*/
export function getRemoteConfig(app: FirebaseApp = getApp()): RemoteConfig {
app = getModularInstance(app);
const rcProvider = _getProvider(app, RC_COMPONENT_NAME);
return rcProvider.getImmediate();
}
/**
* Makes the last fetched config available to the getters.
* @param remoteConfig - The `RemoteConfig` instance.
* @returns A promise which resolves to true if the current call activated the fetched configs.
* If the fetched configs were already activated, the promise will resolve to false.
*
* @public
*/
export async function activate(remoteConfig: RemoteConfig): Promise<boolean> {
const rc = getModularInstance(remoteConfig) as RemoteConfigImpl;
const [lastSuccessfulFetchResponse, activeConfigEtag] = await Promise.all([
rc._storage.getLastSuccessfulFetchResponse(),
rc._storage.getActiveConfigEtag()
]);
if (
!lastSuccessfulFetchResponse ||
!lastSuccessfulFetchResponse.config ||
!lastSuccessfulFetchResponse.eTag ||
lastSuccessfulFetchResponse.eTag === activeConfigEtag
) {
// Either there is no successful fetched config, or is the same as current active
// config.
return false;
}
await Promise.all([
rc._storageCache.setActiveConfig(lastSuccessfulFetchResponse.config),
rc._storage.setActiveConfigEtag(lastSuccessfulFetchResponse.eTag)
]);
return true;
}
/**
* Ensures the last activated config are available to the getters.
* @param remoteConfig - The `RemoteConfig` instance.
*
* @returns A promise that resolves when the last activated config is available to the getters.
* @public
*/
export function ensureInitialized(remoteConfig: RemoteConfig): Promise<void> {
const rc = getModularInstance(remoteConfig) as RemoteConfigImpl;
if (!rc._initializePromise) {
rc._initializePromise = rc._storageCache.loadFromStorage().then(() => {
rc._isInitializationComplete = true;
});
}
return rc._initializePromise;
}
/**
* Fetches and caches configuration from the Remote Config service.
* @param remoteConfig - The `RemoteConfig` instance.
* @public
*/
export async function fetchConfig(remoteConfig: RemoteConfig): Promise<void> {
const rc = getModularInstance(remoteConfig) as RemoteConfigImpl;
// Aborts the request after the given timeout, causing the fetch call to
// reject with an AbortError.
//
// <p>Aborting after the request completes is a no-op, so we don't need a
// corresponding clearTimeout.
//
// Locating abort logic here because:
// * it uses a developer setting (timeout)
// * it applies to all retries (like curl's max-time arg)
// * it is consistent with the Fetch API's signal input
const abortSignal = new RemoteConfigAbortSignal();
setTimeout(async () => {
// Note a very low delay, eg < 10ms, can elapse before listeners are initialized.
abortSignal.abort();
}, rc.settings.fetchTimeoutMillis);
// Catches *all* errors thrown by client so status can be set consistently.
try {
await rc._client.fetch({
cacheMaxAgeMillis: rc.settings.minimumFetchIntervalMillis,
signal: abortSignal
});
await rc._storageCache.setLastFetchStatus('success');
} catch (e) {
const lastFetchStatus = hasErrorCode(e, ErrorCode.FETCH_THROTTLE)
? 'throttle'
: 'failure';
await rc._storageCache.setLastFetchStatus(lastFetchStatus);
throw e;
}
}
/**
* Gets all config.
*
* @param remoteConfig - The `RemoteConfig` instance.
* @returns All config.
*
* @public
*/
export function getAll(remoteConfig: RemoteConfig): Record<string, Value> {
const rc = getModularInstance(remoteConfig) as RemoteConfigImpl;
return getAllKeys(
rc._storageCache.getActiveConfig(),
rc.defaultConfig
).reduce((allConfigs, key) => {
allConfigs[key] = getValue(remoteConfig, key);
return allConfigs;
}, {} as Record<string, Value>);
}
/**
* Gets the value for the given key as a boolean.
*
* Convenience method for calling <code>remoteConfig.getValue(key).asBoolean()</code>.
*
* @param remoteConfig - The `RemoteConfig` instance.
* @param key - The name of the parameter.
*
* @returns The value for the given key as a boolean.
* @public
*/
export function getBoolean(remoteConfig: RemoteConfig, key: string): boolean {
return getValue(getModularInstance(remoteConfig), key).asBoolean();
}
/**
* Gets the value for the given key as a number.
*
* Convenience method for calling <code>remoteConfig.getValue(key).asNumber()</code>.
*
* @param remoteConfig - The `RemoteConfig` instance.
* @param key - The name of the parameter.
*
* @returns The value for the given key as a number.
*
* @public
*/
export function getNumber(remoteConfig: RemoteConfig, key: string): number {
return getValue(getModularInstance(remoteConfig), key).asNumber();
}
/**
* Gets the value for the given key as a string.
* Convenience method for calling <code>remoteConfig.getValue(key).asString()</code>.
*
* @param remoteConfig - The `RemoteConfig` instance.
* @param key - The name of the parameter.
*
* @returns The value for the given key as a string.
*
* @public
*/
export function getString(remoteConfig: RemoteConfig, key: string): string {
return getValue(getModularInstance(remoteConfig), key).asString();
}
/**
* Gets the {@link Value} for the given key.
*
* @param remoteConfig - The `RemoteConfig` instance.
* @param key - The name of the parameter.
*
* @returns The value for the given key.
*
* @public
*/
export function getValue(remoteConfig: RemoteConfig, key: string): Value {
const rc = getModularInstance(remoteConfig) as RemoteConfigImpl;
if (!rc._isInitializationComplete) {
rc._logger.debug(
`A value was requested for key "${key}" before SDK initialization completed.` +
' Await on ensureInitialized if the intent was to get a previously activated value.'
);
}
const activeConfig = rc._storageCache.getActiveConfig();
if (activeConfig && activeConfig[key] !== undefined) {
return new ValueImpl('remote', activeConfig[key]);
} else if (rc.defaultConfig && rc.defaultConfig[key] !== undefined) {
return new ValueImpl('default', String(rc.defaultConfig[key]));
}
rc._logger.debug(
`Returning static value for key "${key}".` +
' Define a default or remote value if this is unintentional.'
);
return new ValueImpl('static');
}
/**
* Defines the log level to use.
*
* @param remoteConfig - The `RemoteConfig` instance.
* @param logLevel - The log level to set.
*
* @public
*/
export function setLogLevel(
remoteConfig: RemoteConfig,
logLevel: RemoteConfigLogLevel
): void {
const rc = getModularInstance(remoteConfig) as RemoteConfigImpl;
switch (logLevel) {
case 'debug':
rc._logger.logLevel = FirebaseLogLevel.DEBUG;
break;
case 'silent':
rc._logger.logLevel = FirebaseLogLevel.SILENT;
break;
default:
rc._logger.logLevel = FirebaseLogLevel.ERROR;
}
}
/**
* Dedupes and returns an array of all the keys of the received objects.
*/
function getAllKeys(obj1: {} = {}, obj2: {} = {}): string[] {
return Object.keys({ ...obj1, ...obj2 });
}