import { getApp, _getProvider, _registerComponent, registerVersion, SDK_VERSION } from '@firebase/app'; import { ErrorFactory, FirebaseError, getModularInstance, calculateBackoffMillis, isIndexedDBAvailable, validateIndexedDBOpenable } from '@firebase/util'; import { Component } from '@firebase/component'; import { LogLevel, Logger } from '@firebase/logger'; import { __awaiter, __generator, __assign } from 'tslib'; import '@firebase/installations'; var name = "@firebase/remote-config"; var version = "0.4.5"; /** * @license * Copyright 2019 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. */ /** * Shims a minimal AbortSignal. * *

AbortController's AbortSignal conveniently decouples fetch timeout logic from other aspects * of networking, such as retries. Firebase doesn't use AbortController enough to justify a * polyfill recommendation, like we do with the Fetch API, but this minimal shim can easily be * swapped out if/when we do. */ var RemoteConfigAbortSignal = /** @class */ (function () { function RemoteConfigAbortSignal() { this.listeners = []; } RemoteConfigAbortSignal.prototype.addEventListener = function (listener) { this.listeners.push(listener); }; RemoteConfigAbortSignal.prototype.abort = function () { this.listeners.forEach(function (listener) { return listener(); }); }; return RemoteConfigAbortSignal; }()); /** * @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. */ var RC_COMPONENT_NAME = 'remote-config'; /** * @license * Copyright 2019 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. */ var _a; var ERROR_DESCRIPTION_MAP = (_a = {}, _a["registration-window" /* ErrorCode.REGISTRATION_WINDOW */] = 'Undefined window object. This SDK only supports usage in a browser environment.', _a["registration-project-id" /* ErrorCode.REGISTRATION_PROJECT_ID */] = 'Undefined project identifier. Check Firebase app initialization.', _a["registration-api-key" /* ErrorCode.REGISTRATION_API_KEY */] = 'Undefined API key. Check Firebase app initialization.', _a["registration-app-id" /* ErrorCode.REGISTRATION_APP_ID */] = 'Undefined app identifier. Check Firebase app initialization.', _a["storage-open" /* ErrorCode.STORAGE_OPEN */] = 'Error thrown when opening storage. Original error: {$originalErrorMessage}.', _a["storage-get" /* ErrorCode.STORAGE_GET */] = 'Error thrown when reading from storage. Original error: {$originalErrorMessage}.', _a["storage-set" /* ErrorCode.STORAGE_SET */] = 'Error thrown when writing to storage. Original error: {$originalErrorMessage}.', _a["storage-delete" /* ErrorCode.STORAGE_DELETE */] = 'Error thrown when deleting from storage. Original error: {$originalErrorMessage}.', _a["fetch-client-network" /* ErrorCode.FETCH_NETWORK */] = 'Fetch client failed to connect to a network. Check Internet connection.' + ' Original error: {$originalErrorMessage}.', _a["fetch-timeout" /* ErrorCode.FETCH_TIMEOUT */] = 'The config fetch request timed out. ' + ' Configure timeout using "fetchTimeoutMillis" SDK setting.', _a["fetch-throttle" /* ErrorCode.FETCH_THROTTLE */] = 'The config fetch request timed out while in an exponential backoff state.' + ' Configure timeout using "fetchTimeoutMillis" SDK setting.' + ' Unix timestamp in milliseconds when fetch request throttling ends: {$throttleEndTimeMillis}.', _a["fetch-client-parse" /* ErrorCode.FETCH_PARSE */] = 'Fetch client could not parse response.' + ' Original error: {$originalErrorMessage}.', _a["fetch-status" /* ErrorCode.FETCH_STATUS */] = 'Fetch server returned an HTTP error status. HTTP status: {$httpStatus}.', _a["indexed-db-unavailable" /* ErrorCode.INDEXED_DB_UNAVAILABLE */] = 'Indexed DB is not supported by current browser', _a); var ERROR_FACTORY = new ErrorFactory('remoteconfig' /* service */, 'Remote Config' /* service name */, ERROR_DESCRIPTION_MAP); // Note how this is like typeof/instanceof, but for ErrorCode. function hasErrorCode(e, errorCode) { return e instanceof FirebaseError && e.code.indexOf(errorCode) !== -1; } /** * @license * Copyright 2019 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. */ var DEFAULT_VALUE_FOR_BOOLEAN = false; var DEFAULT_VALUE_FOR_STRING = ''; var DEFAULT_VALUE_FOR_NUMBER = 0; var BOOLEAN_TRUTHY_VALUES = ['1', 'true', 't', 'yes', 'y', 'on']; var Value = /** @class */ (function () { function Value(_source, _value) { if (_value === void 0) { _value = DEFAULT_VALUE_FOR_STRING; } this._source = _source; this._value = _value; } Value.prototype.asString = function () { return this._value; }; Value.prototype.asBoolean = function () { if (this._source === 'static') { return DEFAULT_VALUE_FOR_BOOLEAN; } return BOOLEAN_TRUTHY_VALUES.indexOf(this._value.toLowerCase()) >= 0; }; Value.prototype.asNumber = function () { if (this._source === 'static') { return DEFAULT_VALUE_FOR_NUMBER; } var num = Number(this._value); if (isNaN(num)) { num = DEFAULT_VALUE_FOR_NUMBER; } return num; }; Value.prototype.getSource = function () { return this._source; }; return Value; }()); /** * @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. */ /** * * @param app - The {@link @firebase/app#FirebaseApp} instance. * @returns A {@link RemoteConfig} instance. * * @public */ function getRemoteConfig(app) { if (app === void 0) { app = getApp(); } app = getModularInstance(app); var rcProvider = _getProvider(app, RC_COMPONENT_NAME); return rcProvider.getImmediate(); } /** * Makes the last fetched config available to the getters. * @param remoteConfig - The {@link 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 */ function activate(remoteConfig) { return __awaiter(this, void 0, void 0, function () { var rc, _a, lastSuccessfulFetchResponse, activeConfigEtag; return __generator(this, function (_b) { switch (_b.label) { case 0: rc = getModularInstance(remoteConfig); return [4 /*yield*/, Promise.all([ rc._storage.getLastSuccessfulFetchResponse(), rc._storage.getActiveConfigEtag() ])]; case 1: _a = _b.sent(), lastSuccessfulFetchResponse = _a[0], activeConfigEtag = _a[1]; 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 [2 /*return*/, false]; } return [4 /*yield*/, Promise.all([ rc._storageCache.setActiveConfig(lastSuccessfulFetchResponse.config), rc._storage.setActiveConfigEtag(lastSuccessfulFetchResponse.eTag) ])]; case 2: _b.sent(); return [2 /*return*/, true]; } }); }); } /** * Ensures the last activated config are available to the getters. * @param remoteConfig - The {@link RemoteConfig} instance. * * @returns A `Promise` that resolves when the last activated config is available to the getters. * @public */ function ensureInitialized(remoteConfig) { var rc = getModularInstance(remoteConfig); if (!rc._initializePromise) { rc._initializePromise = rc._storageCache.loadFromStorage().then(function () { rc._isInitializationComplete = true; }); } return rc._initializePromise; } /** * Fetches and caches configuration from the Remote Config service. * @param remoteConfig - The {@link RemoteConfig} instance. * @public */ function fetchConfig(remoteConfig) { return __awaiter(this, void 0, void 0, function () { var rc, abortSignal, e_1, lastFetchStatus; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: rc = getModularInstance(remoteConfig); abortSignal = new RemoteConfigAbortSignal(); setTimeout(function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { // Note a very low delay, eg < 10ms, can elapse before listeners are initialized. abortSignal.abort(); return [2 /*return*/]; }); }); }, rc.settings.fetchTimeoutMillis); _a.label = 1; case 1: _a.trys.push([1, 4, , 6]); return [4 /*yield*/, rc._client.fetch({ cacheMaxAgeMillis: rc.settings.minimumFetchIntervalMillis, signal: abortSignal })]; case 2: _a.sent(); return [4 /*yield*/, rc._storageCache.setLastFetchStatus('success')]; case 3: _a.sent(); return [3 /*break*/, 6]; case 4: e_1 = _a.sent(); lastFetchStatus = hasErrorCode(e_1, "fetch-throttle" /* ErrorCode.FETCH_THROTTLE */) ? 'throttle' : 'failure'; return [4 /*yield*/, rc._storageCache.setLastFetchStatus(lastFetchStatus)]; case 5: _a.sent(); throw e_1; case 6: return [2 /*return*/]; } }); }); } /** * Gets all config. * * @param remoteConfig - The {@link RemoteConfig} instance. * @returns All config. * * @public */ function getAll(remoteConfig) { var rc = getModularInstance(remoteConfig); return getAllKeys(rc._storageCache.getActiveConfig(), rc.defaultConfig).reduce(function (allConfigs, key) { allConfigs[key] = getValue(remoteConfig, key); return allConfigs; }, {}); } /** * Gets the value for the given key as a boolean. * * Convenience method for calling remoteConfig.getValue(key).asBoolean(). * * @param remoteConfig - The {@link RemoteConfig} instance. * @param key - The name of the parameter. * * @returns The value for the given key as a boolean. * @public */ function getBoolean(remoteConfig, key) { return getValue(getModularInstance(remoteConfig), key).asBoolean(); } /** * Gets the value for the given key as a number. * * Convenience method for calling remoteConfig.getValue(key).asNumber(). * * @param remoteConfig - The {@link RemoteConfig} instance. * @param key - The name of the parameter. * * @returns The value for the given key as a number. * * @public */ function getNumber(remoteConfig, key) { return getValue(getModularInstance(remoteConfig), key).asNumber(); } /** * Gets the value for the given key as a string. * Convenience method for calling remoteConfig.getValue(key).asString(). * * @param remoteConfig - The {@link RemoteConfig} instance. * @param key - The name of the parameter. * * @returns The value for the given key as a string. * * @public */ function getString(remoteConfig, key) { return getValue(getModularInstance(remoteConfig), key).asString(); } /** * Gets the {@link Value} for the given key. * * @param remoteConfig - The {@link RemoteConfig} instance. * @param key - The name of the parameter. * * @returns The value for the given key. * * @public */ function getValue(remoteConfig, key) { var rc = getModularInstance(remoteConfig); if (!rc._isInitializationComplete) { rc._logger.debug("A value was requested for key \"".concat(key, "\" before SDK initialization completed.") + ' Await on ensureInitialized if the intent was to get a previously activated value.'); } var activeConfig = rc._storageCache.getActiveConfig(); if (activeConfig && activeConfig[key] !== undefined) { return new Value('remote', activeConfig[key]); } else if (rc.defaultConfig && rc.defaultConfig[key] !== undefined) { return new Value('default', String(rc.defaultConfig[key])); } rc._logger.debug("Returning static value for key \"".concat(key, "\".") + ' Define a default or remote value if this is unintentional.'); return new Value('static'); } /** * Defines the log level to use. * * @param remoteConfig - The {@link RemoteConfig} instance. * @param logLevel - The log level to set. * * @public */ function setLogLevel(remoteConfig, logLevel) { var rc = getModularInstance(remoteConfig); switch (logLevel) { case 'debug': rc._logger.logLevel = LogLevel.DEBUG; break; case 'silent': rc._logger.logLevel = LogLevel.SILENT; break; default: rc._logger.logLevel = LogLevel.ERROR; } } /** * Dedupes and returns an array of all the keys of the received objects. */ function getAllKeys(obj1, obj2) { if (obj1 === void 0) { obj1 = {}; } if (obj2 === void 0) { obj2 = {}; } return Object.keys(__assign(__assign({}, obj1), obj2)); } /** * @license * Copyright 2019 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. */ /** * Implements the {@link RemoteConfigClient} abstraction with success response caching. * *

Comparable to the browser's Cache API for responses, but the Cache API requires a Service * Worker, which requires HTTPS, which would significantly complicate SDK installation. Also, the * Cache API doesn't support matching entries by time. */ var CachingClient = /** @class */ (function () { function CachingClient(client, storage, storageCache, logger) { this.client = client; this.storage = storage; this.storageCache = storageCache; this.logger = logger; } /** * Returns true if the age of the cached fetched configs is less than or equal to * {@link Settings#minimumFetchIntervalInSeconds}. * *

This is comparable to passing `headers = { 'Cache-Control': max-age }` to the * native Fetch API. * *

Visible for testing. */ CachingClient.prototype.isCachedDataFresh = function (cacheMaxAgeMillis, lastSuccessfulFetchTimestampMillis) { // Cache can only be fresh if it's populated. if (!lastSuccessfulFetchTimestampMillis) { this.logger.debug('Config fetch cache check. Cache unpopulated.'); return false; } // Calculates age of cache entry. var cacheAgeMillis = Date.now() - lastSuccessfulFetchTimestampMillis; var isCachedDataFresh = cacheAgeMillis <= cacheMaxAgeMillis; this.logger.debug('Config fetch cache check.' + " Cache age millis: ".concat(cacheAgeMillis, ".") + " Cache max age millis (minimumFetchIntervalMillis setting): ".concat(cacheMaxAgeMillis, ".") + " Is cache hit: ".concat(isCachedDataFresh, ".")); return isCachedDataFresh; }; CachingClient.prototype.fetch = function (request) { return __awaiter(this, void 0, void 0, function () { var _a, lastSuccessfulFetchTimestampMillis, lastSuccessfulFetchResponse, response, storageOperations; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, Promise.all([ this.storage.getLastSuccessfulFetchTimestampMillis(), this.storage.getLastSuccessfulFetchResponse() ])]; case 1: _a = _b.sent(), lastSuccessfulFetchTimestampMillis = _a[0], lastSuccessfulFetchResponse = _a[1]; // Exits early on cache hit. if (lastSuccessfulFetchResponse && this.isCachedDataFresh(request.cacheMaxAgeMillis, lastSuccessfulFetchTimestampMillis)) { return [2 /*return*/, lastSuccessfulFetchResponse]; } // Deviates from pure decorator by not honoring a passed ETag since we don't have a public API // that allows the caller to pass an ETag. request.eTag = lastSuccessfulFetchResponse && lastSuccessfulFetchResponse.eTag; return [4 /*yield*/, this.client.fetch(request)]; case 2: response = _b.sent(); storageOperations = [ // Uses write-through cache for consistency with synchronous public API. this.storageCache.setLastSuccessfulFetchTimestampMillis(Date.now()) ]; if (response.status === 200) { // Caches response only if it has changed, ie non-304 responses. storageOperations.push(this.storage.setLastSuccessfulFetchResponse(response)); } return [4 /*yield*/, Promise.all(storageOperations)]; case 3: _b.sent(); return [2 /*return*/, response]; } }); }); }; return CachingClient; }()); /** * @license * Copyright 2019 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. */ /** * Attempts to get the most accurate browser language setting. * *

Adapted from getUserLanguage in packages/auth/src/utils.js for TypeScript. * *

Defers default language specification to server logic for consistency. * * @param navigatorLanguage Enables tests to override read-only {@link NavigatorLanguage}. */ function getUserLanguage(navigatorLanguage) { if (navigatorLanguage === void 0) { navigatorLanguage = navigator; } return ( // Most reliable, but only supported in Chrome/Firefox. (navigatorLanguage.languages && navigatorLanguage.languages[0]) || // Supported in most browsers, but returns the language of the browser // UI, not the language set in browser settings. navigatorLanguage.language // Polyfill otherwise. ); } /** * @license * Copyright 2019 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. */ /** * Implements the Client abstraction for the Remote Config REST API. */ var RestClient = /** @class */ (function () { function RestClient(firebaseInstallations, sdkVersion, namespace, projectId, apiKey, appId) { this.firebaseInstallations = firebaseInstallations; this.sdkVersion = sdkVersion; this.namespace = namespace; this.projectId = projectId; this.apiKey = apiKey; this.appId = appId; } /** * Fetches from the Remote Config REST API. * * @throws a {@link ErrorCode.FETCH_NETWORK} error if {@link GlobalFetch#fetch} can't * connect to the network. * @throws a {@link ErrorCode.FETCH_PARSE} error if {@link Response#json} can't parse the * fetch response. * @throws a {@link ErrorCode.FETCH_STATUS} error if the service returns an HTTP error status. */ RestClient.prototype.fetch = function (request) { return __awaiter(this, void 0, void 0, function () { var _a, installationId, installationToken, urlBase, url, headers, requestBody, options, fetchPromise, timeoutPromise, response, originalError_1, errorCode, status, responseEtag, config, state, responseBody, originalError_2; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, Promise.all([ this.firebaseInstallations.getId(), this.firebaseInstallations.getToken() ])]; case 1: _a = _b.sent(), installationId = _a[0], installationToken = _a[1]; urlBase = window.FIREBASE_REMOTE_CONFIG_URL_BASE || 'https://firebaseremoteconfig.googleapis.com'; url = "".concat(urlBase, "/v1/projects/").concat(this.projectId, "/namespaces/").concat(this.namespace, ":fetch?key=").concat(this.apiKey); headers = { 'Content-Type': 'application/json', 'Content-Encoding': 'gzip', // Deviates from pure decorator by not passing max-age header since we don't currently have // service behavior using that header. 'If-None-Match': request.eTag || '*' }; requestBody = { /* eslint-disable camelcase */ sdk_version: this.sdkVersion, app_instance_id: installationId, app_instance_id_token: installationToken, app_id: this.appId, language_code: getUserLanguage() /* eslint-enable camelcase */ }; options = { method: 'POST', headers: headers, body: JSON.stringify(requestBody) }; fetchPromise = fetch(url, options); timeoutPromise = new Promise(function (_resolve, reject) { // Maps async event listener to Promise API. request.signal.addEventListener(function () { // Emulates https://heycam.github.io/webidl/#aborterror var error = new Error('The operation was aborted.'); error.name = 'AbortError'; reject(error); }); }); _b.label = 2; case 2: _b.trys.push([2, 5, , 6]); return [4 /*yield*/, Promise.race([fetchPromise, timeoutPromise])]; case 3: _b.sent(); return [4 /*yield*/, fetchPromise]; case 4: response = _b.sent(); return [3 /*break*/, 6]; case 5: originalError_1 = _b.sent(); errorCode = "fetch-client-network" /* ErrorCode.FETCH_NETWORK */; if ((originalError_1 === null || originalError_1 === void 0 ? void 0 : originalError_1.name) === 'AbortError') { errorCode = "fetch-timeout" /* ErrorCode.FETCH_TIMEOUT */; } throw ERROR_FACTORY.create(errorCode, { originalErrorMessage: originalError_1 === null || originalError_1 === void 0 ? void 0 : originalError_1.message }); case 6: status = response.status; responseEtag = response.headers.get('ETag') || undefined; if (!(response.status === 200)) return [3 /*break*/, 11]; responseBody = void 0; _b.label = 7; case 7: _b.trys.push([7, 9, , 10]); return [4 /*yield*/, response.json()]; case 8: responseBody = _b.sent(); return [3 /*break*/, 10]; case 9: originalError_2 = _b.sent(); throw ERROR_FACTORY.create("fetch-client-parse" /* ErrorCode.FETCH_PARSE */, { originalErrorMessage: originalError_2 === null || originalError_2 === void 0 ? void 0 : originalError_2.message }); case 10: config = responseBody['entries']; state = responseBody['state']; _b.label = 11; case 11: // Normalizes based on legacy state. if (state === 'INSTANCE_STATE_UNSPECIFIED') { status = 500; } else if (state === 'NO_CHANGE') { status = 304; } else if (state === 'NO_TEMPLATE' || state === 'EMPTY_CONFIG') { // These cases can be fixed remotely, so normalize to safe value. config = {}; } // Normalize to exception-based control flow for non-success cases. // Encapsulates HTTP specifics in this class as much as possible. Status is still the best for // differentiating success states (200 from 304; the state body param is undefined in a // standard 304). if (status !== 304 && status !== 200) { throw ERROR_FACTORY.create("fetch-status" /* ErrorCode.FETCH_STATUS */, { httpStatus: status }); } return [2 /*return*/, { status: status, eTag: responseEtag, config: config }]; } }); }); }; return RestClient; }()); /** * @license * Copyright 2019 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. */ /** * Supports waiting on a backoff by: * *

* *

Visible for testing. */ function setAbortableTimeout(signal, throttleEndTimeMillis) { return new Promise(function (resolve, reject) { // Derives backoff from given end time, normalizing negative numbers to zero. var backoffMillis = Math.max(throttleEndTimeMillis - Date.now(), 0); var timeout = setTimeout(resolve, backoffMillis); // Adds listener, rather than sets onabort, because signal is a shared object. signal.addEventListener(function () { clearTimeout(timeout); // If the request completes before this timeout, the rejection has no effect. reject(ERROR_FACTORY.create("fetch-throttle" /* ErrorCode.FETCH_THROTTLE */, { throttleEndTimeMillis: throttleEndTimeMillis })); }); }); } /** * Returns true if the {@link Error} indicates a fetch request may succeed later. */ function isRetriableError(e) { if (!(e instanceof FirebaseError) || !e.customData) { return false; } // Uses string index defined by ErrorData, which FirebaseError implements. var httpStatus = Number(e.customData['httpStatus']); return (httpStatus === 429 || httpStatus === 500 || httpStatus === 503 || httpStatus === 504); } /** * Decorates a Client with retry logic. * *

Comparable to CachingClient, but uses backoff logic instead of cache max age and doesn't cache * responses (because the SDK has no use for error responses). */ var RetryingClient = /** @class */ (function () { function RetryingClient(client, storage) { this.client = client; this.storage = storage; } RetryingClient.prototype.fetch = function (request) { return __awaiter(this, void 0, void 0, function () { var throttleMetadata; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.storage.getThrottleMetadata()]; case 1: throttleMetadata = (_a.sent()) || { backoffCount: 0, throttleEndTimeMillis: Date.now() }; return [2 /*return*/, this.attemptFetch(request, throttleMetadata)]; } }); }); }; /** * A recursive helper for attempting a fetch request repeatedly. * * @throws any non-retriable errors. */ RetryingClient.prototype.attemptFetch = function (request, _a) { var throttleEndTimeMillis = _a.throttleEndTimeMillis, backoffCount = _a.backoffCount; return __awaiter(this, void 0, void 0, function () { var response, e_1, throttleMetadata; return __generator(this, function (_b) { switch (_b.label) { case 0: // Starts with a (potentially zero) timeout to support resumption from stored state. // Ensures the throttle end time is honored if the last attempt timed out. // Note the SDK will never make a request if the fetch timeout expires at this point. return [4 /*yield*/, setAbortableTimeout(request.signal, throttleEndTimeMillis)]; case 1: // Starts with a (potentially zero) timeout to support resumption from stored state. // Ensures the throttle end time is honored if the last attempt timed out. // Note the SDK will never make a request if the fetch timeout expires at this point. _b.sent(); _b.label = 2; case 2: _b.trys.push([2, 5, , 7]); return [4 /*yield*/, this.client.fetch(request)]; case 3: response = _b.sent(); // Note the SDK only clears throttle state if response is success or non-retriable. return [4 /*yield*/, this.storage.deleteThrottleMetadata()]; case 4: // Note the SDK only clears throttle state if response is success or non-retriable. _b.sent(); return [2 /*return*/, response]; case 5: e_1 = _b.sent(); if (!isRetriableError(e_1)) { throw e_1; } throttleMetadata = { throttleEndTimeMillis: Date.now() + calculateBackoffMillis(backoffCount), backoffCount: backoffCount + 1 }; // Persists state. return [4 /*yield*/, this.storage.setThrottleMetadata(throttleMetadata)]; case 6: // Persists state. _b.sent(); return [2 /*return*/, this.attemptFetch(request, throttleMetadata)]; case 7: return [2 /*return*/]; } }); }); }; return RetryingClient; }()); /** * @license * Copyright 2019 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. */ var DEFAULT_FETCH_TIMEOUT_MILLIS = 60 * 1000; // One minute var DEFAULT_CACHE_MAX_AGE_MILLIS = 12 * 60 * 60 * 1000; // Twelve hours. /** * Encapsulates business logic mapping network and storage dependencies to the public SDK API. * * See {@link https://github.com/FirebasePrivate/firebase-js-sdk/blob/master/packages/firebase/index.d.ts|interface documentation} for method descriptions. */ var RemoteConfig = /** @class */ (function () { function RemoteConfig( // Required by FirebaseServiceFactory interface. app, // JS doesn't support private yet // (https://github.com/tc39/proposal-class-fields#private-fields), so we hint using an // underscore prefix. /** * @internal */ _client, /** * @internal */ _storageCache, /** * @internal */ _storage, /** * @internal */ _logger) { this.app = app; this._client = _client; this._storageCache = _storageCache; this._storage = _storage; this._logger = _logger; /** * Tracks completion of initialization promise. * @internal */ this._isInitializationComplete = false; this.settings = { fetchTimeoutMillis: DEFAULT_FETCH_TIMEOUT_MILLIS, minimumFetchIntervalMillis: DEFAULT_CACHE_MAX_AGE_MILLIS }; this.defaultConfig = {}; } Object.defineProperty(RemoteConfig.prototype, "fetchTimeMillis", { get: function () { return this._storageCache.getLastSuccessfulFetchTimestampMillis() || -1; }, enumerable: false, configurable: true }); Object.defineProperty(RemoteConfig.prototype, "lastFetchStatus", { get: function () { return this._storageCache.getLastFetchStatus() || 'no-fetch-yet'; }, enumerable: false, configurable: true }); return RemoteConfig; }()); /** * @license * Copyright 2019 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. */ /** * Converts an error event associated with a {@link IDBRequest} to a {@link FirebaseError}. */ function toFirebaseError(event, errorCode) { var originalError = event.target.error || undefined; return ERROR_FACTORY.create(errorCode, { originalErrorMessage: originalError && (originalError === null || originalError === void 0 ? void 0 : originalError.message) }); } /** * A general-purpose store keyed by app + namespace + {@link * ProjectNamespaceKeyFieldValue}. * *

The Remote Config SDK can be used with multiple app installations, and each app can interact * with multiple namespaces, so this store uses app (ID + name) and namespace as common parent keys * for a set of key-value pairs. See {@link Storage#createCompositeKey}. * *

Visible for testing. */ var APP_NAMESPACE_STORE = 'app_namespace_store'; var DB_NAME = 'firebase_remote_config'; var DB_VERSION = 1; // Visible for testing. function openDatabase() { return new Promise(function (resolve, reject) { try { var request = indexedDB.open(DB_NAME, DB_VERSION); request.onerror = function (event) { reject(toFirebaseError(event, "storage-open" /* ErrorCode.STORAGE_OPEN */)); }; request.onsuccess = function (event) { resolve(event.target.result); }; request.onupgradeneeded = function (event) { var db = event.target.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' }); } }; } catch (error) { reject(ERROR_FACTORY.create("storage-open" /* ErrorCode.STORAGE_OPEN */, { originalErrorMessage: error === null || error === void 0 ? void 0 : error.message })); } }); } /** * Abstracts data persistence. */ var Storage = /** @class */ (function () { /** * @param appId enables storage segmentation by app (ID + name). * @param appName enables storage segmentation by app (ID + name). * @param namespace enables storage segmentation by namespace. */ function Storage(appId, appName, namespace, openDbPromise) { if (openDbPromise === void 0) { openDbPromise = openDatabase(); } this.appId = appId; this.appName = appName; this.namespace = namespace; this.openDbPromise = openDbPromise; } Storage.prototype.getLastFetchStatus = function () { return this.get('last_fetch_status'); }; Storage.prototype.setLastFetchStatus = function (status) { return this.set('last_fetch_status', status); }; // This is comparable to a cache entry timestamp. If we need to expire other data, we could // consider adding timestamp to all storage records and an optional max age arg to getters. Storage.prototype.getLastSuccessfulFetchTimestampMillis = function () { return this.get('last_successful_fetch_timestamp_millis'); }; Storage.prototype.setLastSuccessfulFetchTimestampMillis = function (timestamp) { return this.set('last_successful_fetch_timestamp_millis', timestamp); }; Storage.prototype.getLastSuccessfulFetchResponse = function () { return this.get('last_successful_fetch_response'); }; Storage.prototype.setLastSuccessfulFetchResponse = function (response) { return this.set('last_successful_fetch_response', response); }; Storage.prototype.getActiveConfig = function () { return this.get('active_config'); }; Storage.prototype.setActiveConfig = function (config) { return this.set('active_config', config); }; Storage.prototype.getActiveConfigEtag = function () { return this.get('active_config_etag'); }; Storage.prototype.setActiveConfigEtag = function (etag) { return this.set('active_config_etag', etag); }; Storage.prototype.getThrottleMetadata = function () { return this.get('throttle_metadata'); }; Storage.prototype.setThrottleMetadata = function (metadata) { return this.set('throttle_metadata', metadata); }; Storage.prototype.deleteThrottleMetadata = function () { return this.delete('throttle_metadata'); }; Storage.prototype.get = function (key) { return __awaiter(this, void 0, void 0, function () { var db; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.openDbPromise]; case 1: db = _a.sent(); return [2 /*return*/, new Promise(function (resolve, reject) { var transaction = db.transaction([APP_NAMESPACE_STORE], 'readonly'); var objectStore = transaction.objectStore(APP_NAMESPACE_STORE); var compositeKey = _this.createCompositeKey(key); try { var request = objectStore.get(compositeKey); request.onerror = function (event) { reject(toFirebaseError(event, "storage-get" /* ErrorCode.STORAGE_GET */)); }; request.onsuccess = function (event) { var result = event.target.result; if (result) { resolve(result.value); } else { resolve(undefined); } }; } catch (e) { reject(ERROR_FACTORY.create("storage-get" /* ErrorCode.STORAGE_GET */, { originalErrorMessage: e === null || e === void 0 ? void 0 : e.message })); } })]; } }); }); }; Storage.prototype.set = function (key, value) { return __awaiter(this, void 0, void 0, function () { var db; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.openDbPromise]; case 1: db = _a.sent(); return [2 /*return*/, new Promise(function (resolve, reject) { var transaction = db.transaction([APP_NAMESPACE_STORE], 'readwrite'); var objectStore = transaction.objectStore(APP_NAMESPACE_STORE); var compositeKey = _this.createCompositeKey(key); try { var request = objectStore.put({ compositeKey: compositeKey, value: value }); request.onerror = function (event) { reject(toFirebaseError(event, "storage-set" /* ErrorCode.STORAGE_SET */)); }; request.onsuccess = function () { resolve(); }; } catch (e) { reject(ERROR_FACTORY.create("storage-set" /* ErrorCode.STORAGE_SET */, { originalErrorMessage: e === null || e === void 0 ? void 0 : e.message })); } })]; } }); }); }; Storage.prototype.delete = function (key) { return __awaiter(this, void 0, void 0, function () { var db; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.openDbPromise]; case 1: db = _a.sent(); return [2 /*return*/, new Promise(function (resolve, reject) { var transaction = db.transaction([APP_NAMESPACE_STORE], 'readwrite'); var objectStore = transaction.objectStore(APP_NAMESPACE_STORE); var compositeKey = _this.createCompositeKey(key); try { var request = objectStore.delete(compositeKey); request.onerror = function (event) { reject(toFirebaseError(event, "storage-delete" /* ErrorCode.STORAGE_DELETE */)); }; request.onsuccess = function () { resolve(); }; } catch (e) { reject(ERROR_FACTORY.create("storage-delete" /* ErrorCode.STORAGE_DELETE */, { originalErrorMessage: e === null || e === void 0 ? void 0 : e.message })); } })]; } }); }); }; // Facilitates composite key functionality (which is unsupported in IE). Storage.prototype.createCompositeKey = function (key) { return [this.appId, this.appName, this.namespace, key].join(); }; return Storage; }()); /** * @license * Copyright 2019 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. */ /** * A memory cache layer over storage to support the SDK's synchronous read requirements. */ var StorageCache = /** @class */ (function () { function StorageCache(storage) { this.storage = storage; } /** * Memory-only getters */ StorageCache.prototype.getLastFetchStatus = function () { return this.lastFetchStatus; }; StorageCache.prototype.getLastSuccessfulFetchTimestampMillis = function () { return this.lastSuccessfulFetchTimestampMillis; }; StorageCache.prototype.getActiveConfig = function () { return this.activeConfig; }; /** * Read-ahead getter */ StorageCache.prototype.loadFromStorage = function () { return __awaiter(this, void 0, void 0, function () { var lastFetchStatusPromise, lastSuccessfulFetchTimestampMillisPromise, activeConfigPromise, lastFetchStatus, lastSuccessfulFetchTimestampMillis, activeConfig; return __generator(this, function (_a) { switch (_a.label) { case 0: lastFetchStatusPromise = this.storage.getLastFetchStatus(); lastSuccessfulFetchTimestampMillisPromise = this.storage.getLastSuccessfulFetchTimestampMillis(); activeConfigPromise = this.storage.getActiveConfig(); return [4 /*yield*/, lastFetchStatusPromise]; case 1: lastFetchStatus = _a.sent(); if (lastFetchStatus) { this.lastFetchStatus = lastFetchStatus; } return [4 /*yield*/, lastSuccessfulFetchTimestampMillisPromise]; case 2: lastSuccessfulFetchTimestampMillis = _a.sent(); if (lastSuccessfulFetchTimestampMillis) { this.lastSuccessfulFetchTimestampMillis = lastSuccessfulFetchTimestampMillis; } return [4 /*yield*/, activeConfigPromise]; case 3: activeConfig = _a.sent(); if (activeConfig) { this.activeConfig = activeConfig; } return [2 /*return*/]; } }); }); }; /** * Write-through setters */ StorageCache.prototype.setLastFetchStatus = function (status) { this.lastFetchStatus = status; return this.storage.setLastFetchStatus(status); }; StorageCache.prototype.setLastSuccessfulFetchTimestampMillis = function (timestampMillis) { this.lastSuccessfulFetchTimestampMillis = timestampMillis; return this.storage.setLastSuccessfulFetchTimestampMillis(timestampMillis); }; StorageCache.prototype.setActiveConfig = function (activeConfig) { this.activeConfig = activeConfig; return this.storage.setActiveConfig(activeConfig); }; return StorageCache; }()); /** * @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. */ function registerRemoteConfig() { _registerComponent(new Component(RC_COMPONENT_NAME, remoteConfigFactory, "PUBLIC" /* ComponentType.PUBLIC */).setMultipleInstances(true)); registerVersion(name, version); // BUILD_TARGET will be replaced by values like esm5, esm2017, cjs5, etc during the compilation registerVersion(name, version, 'esm5'); function remoteConfigFactory(container, _a) { var namespace = _a.instanceIdentifier; /* Dependencies */ // getImmediate for FirebaseApp will always succeed var app = container.getProvider('app').getImmediate(); // The following call will always succeed because rc has `import '@firebase/installations'` var installations = container .getProvider('installations-internal') .getImmediate(); // Guards against the SDK being used in non-browser environments. if (typeof window === 'undefined') { throw ERROR_FACTORY.create("registration-window" /* ErrorCode.REGISTRATION_WINDOW */); } // Guards against the SDK being used when indexedDB is not available. if (!isIndexedDBAvailable()) { throw ERROR_FACTORY.create("indexed-db-unavailable" /* ErrorCode.INDEXED_DB_UNAVAILABLE */); } // Normalizes optional inputs. var _b = app.options, projectId = _b.projectId, apiKey = _b.apiKey, appId = _b.appId; if (!projectId) { throw ERROR_FACTORY.create("registration-project-id" /* ErrorCode.REGISTRATION_PROJECT_ID */); } if (!apiKey) { throw ERROR_FACTORY.create("registration-api-key" /* ErrorCode.REGISTRATION_API_KEY */); } if (!appId) { throw ERROR_FACTORY.create("registration-app-id" /* ErrorCode.REGISTRATION_APP_ID */); } namespace = namespace || 'firebase'; var storage = new Storage(appId, app.name, namespace); var storageCache = new StorageCache(storage); var logger = new Logger(name); // Sets ERROR as the default log level. // See RemoteConfig#setLogLevel for corresponding normalization to ERROR log level. logger.logLevel = LogLevel.ERROR; var restClient = new RestClient(installations, // Uses the JS SDK version, by which the RC package version can be deduced, if necessary. SDK_VERSION, namespace, projectId, apiKey, appId); var retryingClient = new RetryingClient(restClient, storage); var cachingClient = new CachingClient(retryingClient, storage, storageCache, logger); var remoteConfigInstance = new RemoteConfig(app, cachingClient, storageCache, storage, logger); // Starts warming cache. // eslint-disable-next-line @typescript-eslint/no-floating-promises ensureInitialized(remoteConfigInstance); return remoteConfigInstance; } } /** * @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. */ // 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. /** * * Performs fetch and activate operations, as a convenience. * * @param remoteConfig - The {@link 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 */ function fetchAndActivate(remoteConfig) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: remoteConfig = getModularInstance(remoteConfig); return [4 /*yield*/, fetchConfig(remoteConfig)]; case 1: _a.sent(); return [2 /*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 */ function isSupported() { return __awaiter(this, void 0, void 0, function () { var isDBOpenable; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!isIndexedDBAvailable()) { return [2 /*return*/, false]; } _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, validateIndexedDBOpenable()]; case 2: isDBOpenable = _a.sent(); return [2 /*return*/, isDBOpenable]; case 3: _a.sent(); return [2 /*return*/, false]; case 4: return [2 /*return*/]; } }); }); } /** * The Firebase Remote Config Web SDK. * This SDK does not work in a Node.js environment. * * @packageDocumentation */ /** register component and version */ registerRemoteConfig(); export { activate, ensureInitialized, fetchAndActivate, fetchConfig, getAll, getBoolean, getNumber, getRemoteConfig, getString, getValue, isSupported, setLogLevel }; //# sourceMappingURL=index.esm.js.map