1236 lines
55 KiB
JavaScript
1236 lines
55 KiB
JavaScript
'use strict';
|
|
|
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
|
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
|
|
|
|
var firebase = _interopDefault(require('@firebase/app'));
|
|
require('@firebase/installations');
|
|
var tslib = require('tslib');
|
|
var util = require('@firebase/util');
|
|
var logger = require('@firebase/logger');
|
|
var component = require('@firebase/component');
|
|
|
|
/**
|
|
* @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.
|
|
*
|
|
* <p>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}.
|
|
*
|
|
* <p>This is comparable to passing `headers = { 'Cache-Control': max-age <maxAge> }` to the
|
|
* native Fetch API.
|
|
*
|
|
* <p>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: " + cacheAgeMillis + ".") +
|
|
(" Cache max age millis (minimumFetchIntervalMillis setting): " + cacheMaxAgeMillis + ".") +
|
|
(" Is cache hit: " + isCachedDataFresh + "."));
|
|
return isCachedDataFresh;
|
|
};
|
|
CachingClient.prototype.fetch = function (request) {
|
|
return tslib.__awaiter(this, void 0, void 0, function () {
|
|
var _a, lastSuccessfulFetchTimestampMillis, lastSuccessfulFetchResponse, response, storageOperations;
|
|
return tslib.__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.
|
|
*/
|
|
var _a;
|
|
var ERROR_DESCRIPTION_MAP = (_a = {},
|
|
_a["registration-window" /* REGISTRATION_WINDOW */] = 'Undefined window object. This SDK only supports usage in a browser environment.',
|
|
_a["registration-project-id" /* REGISTRATION_PROJECT_ID */] = 'Undefined project identifier. Check Firebase app initialization.',
|
|
_a["registration-api-key" /* REGISTRATION_API_KEY */] = 'Undefined API key. Check Firebase app initialization.',
|
|
_a["registration-app-id" /* REGISTRATION_APP_ID */] = 'Undefined app identifier. Check Firebase app initialization.',
|
|
_a["storage-open" /* STORAGE_OPEN */] = 'Error thrown when opening storage. Original error: {$originalErrorMessage}.',
|
|
_a["storage-get" /* STORAGE_GET */] = 'Error thrown when reading from storage. Original error: {$originalErrorMessage}.',
|
|
_a["storage-set" /* STORAGE_SET */] = 'Error thrown when writing to storage. Original error: {$originalErrorMessage}.',
|
|
_a["storage-delete" /* STORAGE_DELETE */] = 'Error thrown when deleting from storage. Original error: {$originalErrorMessage}.',
|
|
_a["fetch-client-network" /* FETCH_NETWORK */] = 'Fetch client failed to connect to a network. Check Internet connection.' +
|
|
' Original error: {$originalErrorMessage}.',
|
|
_a["fetch-timeout" /* FETCH_TIMEOUT */] = 'The config fetch request timed out. ' +
|
|
' Configure timeout using "fetchTimeoutMillis" SDK setting.',
|
|
_a["fetch-throttle" /* 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" /* FETCH_PARSE */] = 'Fetch client could not parse response.' +
|
|
' Original error: {$originalErrorMessage}.',
|
|
_a["fetch-status" /* FETCH_STATUS */] = 'Fetch server returned an HTTP error status. HTTP status: {$httpStatus}.',
|
|
_a);
|
|
var ERROR_FACTORY = new util.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 util.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.
|
|
*/
|
|
/**
|
|
* Attempts to get the most accurate browser language setting.
|
|
*
|
|
* <p>Adapted from getUserLanguage in packages/auth/src/utils.js for TypeScript.
|
|
*
|
|
* <p>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 tslib.__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 tslib.__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 = urlBase + "/v1/projects/" + this.projectId + "/namespaces/" + this.namespace + ":fetch?key=" + 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" /* FETCH_NETWORK */;
|
|
if (originalError_1.name === 'AbortError') {
|
|
errorCode = "fetch-timeout" /* FETCH_TIMEOUT */;
|
|
}
|
|
throw ERROR_FACTORY.create(errorCode, {
|
|
originalErrorMessage: 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" /* FETCH_PARSE */, {
|
|
originalErrorMessage: 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" /* 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.
|
|
*/
|
|
/**
|
|
* Shims a minimal AbortSignal.
|
|
*
|
|
* <p>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 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 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.
|
|
_client, _storageCache, _storage, _logger) {
|
|
this.app = app;
|
|
this._client = _client;
|
|
this._storageCache = _storageCache;
|
|
this._storage = _storage;
|
|
this._logger = _logger;
|
|
// Tracks completion of initialization promise.
|
|
this._isInitializationComplete = false;
|
|
this.settings = {
|
|
fetchTimeoutMillis: DEFAULT_FETCH_TIMEOUT_MILLIS,
|
|
minimumFetchIntervalMillis: DEFAULT_CACHE_MAX_AGE_MILLIS
|
|
};
|
|
this.defaultConfig = {};
|
|
}
|
|
// Based on packages/firestore/src/util/log.ts but not static because we need per-instance levels
|
|
// to differentiate 2p and 3p use-cases.
|
|
RemoteConfig.prototype.setLogLevel = function (logLevel) {
|
|
switch (logLevel) {
|
|
case 'debug':
|
|
this._logger.logLevel = logger.LogLevel.DEBUG;
|
|
break;
|
|
case 'silent':
|
|
this._logger.logLevel = logger.LogLevel.SILENT;
|
|
break;
|
|
default:
|
|
this._logger.logLevel = logger.LogLevel.ERROR;
|
|
}
|
|
};
|
|
Object.defineProperty(RemoteConfig.prototype, "fetchTimeMillis", {
|
|
get: function () {
|
|
return this._storageCache.getLastSuccessfulFetchTimestampMillis() || -1;
|
|
},
|
|
enumerable: true,
|
|
configurable: true
|
|
});
|
|
Object.defineProperty(RemoteConfig.prototype, "lastFetchStatus", {
|
|
get: function () {
|
|
return this._storageCache.getLastFetchStatus() || 'no-fetch-yet';
|
|
},
|
|
enumerable: true,
|
|
configurable: true
|
|
});
|
|
RemoteConfig.prototype.activate = function () {
|
|
return tslib.__awaiter(this, void 0, void 0, function () {
|
|
var _a, lastSuccessfulFetchResponse, activeConfigEtag;
|
|
return tslib.__generator(this, function (_b) {
|
|
switch (_b.label) {
|
|
case 0: return [4 /*yield*/, Promise.all([
|
|
this._storage.getLastSuccessfulFetchResponse(),
|
|
this._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([
|
|
this._storageCache.setActiveConfig(lastSuccessfulFetchResponse.config),
|
|
this._storage.setActiveConfigEtag(lastSuccessfulFetchResponse.eTag)
|
|
])];
|
|
case 2:
|
|
_b.sent();
|
|
return [2 /*return*/, true];
|
|
}
|
|
});
|
|
});
|
|
};
|
|
RemoteConfig.prototype.ensureInitialized = function () {
|
|
var _this = this;
|
|
if (!this._initializePromise) {
|
|
this._initializePromise = this._storageCache
|
|
.loadFromStorage()
|
|
.then(function () {
|
|
_this._isInitializationComplete = true;
|
|
});
|
|
}
|
|
return this._initializePromise;
|
|
};
|
|
/**
|
|
* @throws a {@link ErrorCode.FETCH_CLIENT_TIMEOUT} if the request takes longer than
|
|
* {@link Settings.fetchTimeoutInSeconds} or
|
|
* {@link DEFAULT_FETCH_TIMEOUT_SECONDS}.
|
|
*/
|
|
RemoteConfig.prototype.fetch = function () {
|
|
return tslib.__awaiter(this, void 0, void 0, function () {
|
|
var abortSignal, e_1, lastFetchStatus;
|
|
var _this = this;
|
|
return tslib.__generator(this, function (_a) {
|
|
switch (_a.label) {
|
|
case 0:
|
|
abortSignal = new RemoteConfigAbortSignal();
|
|
setTimeout(function () { return tslib.__awaiter(_this, void 0, void 0, function () {
|
|
return tslib.__generator(this, function (_a) {
|
|
// Note a very low delay, eg < 10ms, can elapse before listeners are initialized.
|
|
abortSignal.abort();
|
|
return [2 /*return*/];
|
|
});
|
|
}); }, this.settings.fetchTimeoutMillis);
|
|
_a.label = 1;
|
|
case 1:
|
|
_a.trys.push([1, 4, , 6]);
|
|
return [4 /*yield*/, this._client.fetch({
|
|
cacheMaxAgeMillis: this.settings.minimumFetchIntervalMillis,
|
|
signal: abortSignal
|
|
})];
|
|
case 2:
|
|
_a.sent();
|
|
return [4 /*yield*/, this._storageCache.setLastFetchStatus('success')];
|
|
case 3:
|
|
_a.sent();
|
|
return [3 /*break*/, 6];
|
|
case 4:
|
|
e_1 = _a.sent();
|
|
lastFetchStatus = hasErrorCode(e_1, "fetch-throttle" /* FETCH_THROTTLE */)
|
|
? 'throttle'
|
|
: 'failure';
|
|
return [4 /*yield*/, this._storageCache.setLastFetchStatus(lastFetchStatus)];
|
|
case 5:
|
|
_a.sent();
|
|
throw e_1;
|
|
case 6: return [2 /*return*/];
|
|
}
|
|
});
|
|
});
|
|
};
|
|
RemoteConfig.prototype.fetchAndActivate = function () {
|
|
return tslib.__awaiter(this, void 0, void 0, function () {
|
|
return tslib.__generator(this, function (_a) {
|
|
switch (_a.label) {
|
|
case 0: return [4 /*yield*/, this.fetch()];
|
|
case 1:
|
|
_a.sent();
|
|
return [2 /*return*/, this.activate()];
|
|
}
|
|
});
|
|
});
|
|
};
|
|
RemoteConfig.prototype.getAll = function () {
|
|
var _this = this;
|
|
return getAllKeys(this._storageCache.getActiveConfig(), this.defaultConfig).reduce(function (allConfigs, key) {
|
|
allConfigs[key] = _this.getValue(key);
|
|
return allConfigs;
|
|
}, {});
|
|
};
|
|
RemoteConfig.prototype.getBoolean = function (key) {
|
|
return this.getValue(key).asBoolean();
|
|
};
|
|
RemoteConfig.prototype.getNumber = function (key) {
|
|
return this.getValue(key).asNumber();
|
|
};
|
|
RemoteConfig.prototype.getString = function (key) {
|
|
return this.getValue(key).asString();
|
|
};
|
|
RemoteConfig.prototype.getValue = function (key) {
|
|
if (!this._isInitializationComplete) {
|
|
this._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.');
|
|
}
|
|
var activeConfig = this._storageCache.getActiveConfig();
|
|
if (activeConfig && activeConfig[key] !== undefined) {
|
|
return new Value('remote', activeConfig[key]);
|
|
}
|
|
else if (this.defaultConfig && this.defaultConfig[key] !== undefined) {
|
|
return new Value('default', String(this.defaultConfig[key]));
|
|
}
|
|
this._logger.debug("Returning static value for key \"" + key + "\"." +
|
|
' Define a default or remote value if this is unintentional.');
|
|
return new Value('static');
|
|
};
|
|
return RemoteConfig;
|
|
}());
|
|
/**
|
|
* 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(tslib.__assign(tslib.__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.
|
|
*/
|
|
/**
|
|
* 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.message
|
|
});
|
|
}
|
|
/**
|
|
* A general-purpose store keyed by app + namespace + {@link
|
|
* ProjectNamespaceKeyFieldValue}.
|
|
*
|
|
* <p>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}.
|
|
*
|
|
* <p>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) {
|
|
var request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
request.onerror = function (event) {
|
|
reject(toFirebaseError(event, "storage-open" /* 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'
|
|
});
|
|
}
|
|
};
|
|
});
|
|
}
|
|
/**
|
|
* 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 tslib.__awaiter(this, void 0, void 0, function () {
|
|
var db;
|
|
var _this = this;
|
|
return tslib.__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" /* 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" /* STORAGE_GET */, {
|
|
originalErrorMessage: e && e.message
|
|
}));
|
|
}
|
|
})];
|
|
}
|
|
});
|
|
});
|
|
};
|
|
Storage.prototype.set = function (key, value) {
|
|
return tslib.__awaiter(this, void 0, void 0, function () {
|
|
var db;
|
|
var _this = this;
|
|
return tslib.__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" /* STORAGE_SET */));
|
|
};
|
|
request.onsuccess = function () {
|
|
resolve();
|
|
};
|
|
}
|
|
catch (e) {
|
|
reject(ERROR_FACTORY.create("storage-set" /* STORAGE_SET */, {
|
|
originalErrorMessage: e && e.message
|
|
}));
|
|
}
|
|
})];
|
|
}
|
|
});
|
|
});
|
|
};
|
|
Storage.prototype.delete = function (key) {
|
|
return tslib.__awaiter(this, void 0, void 0, function () {
|
|
var db;
|
|
var _this = this;
|
|
return tslib.__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" /* STORAGE_DELETE */));
|
|
};
|
|
request.onsuccess = function () {
|
|
resolve();
|
|
};
|
|
}
|
|
catch (e) {
|
|
reject(ERROR_FACTORY.create("storage-delete" /* STORAGE_DELETE */, {
|
|
originalErrorMessage: e && 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 tslib.__awaiter(this, void 0, void 0, function () {
|
|
var lastFetchStatusPromise, lastSuccessfulFetchTimestampMillisPromise, activeConfigPromise, lastFetchStatus, lastSuccessfulFetchTimestampMillis, activeConfig;
|
|
return tslib.__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 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.
|
|
*/
|
|
/**
|
|
* The amount of milliseconds to exponentially increase.
|
|
*/
|
|
var INTERVAL_MILLIS = 1000;
|
|
/**
|
|
* The factor to backoff by.
|
|
* Should be a number greater than 1.
|
|
*/
|
|
var BACKOFF_FACTOR = 2;
|
|
/**
|
|
* The maximum milliseconds to increase to.
|
|
*
|
|
* <p>Visible for testing
|
|
*/
|
|
var MAX_VALUE_MILLIS = 4 * 60 * 60 * 1000; // Four hours, like iOS and Android.
|
|
/**
|
|
* The percentage of backoff time to randomize by.
|
|
* See
|
|
* http://go/safe-client-behavior#step-1-determine-the-appropriate-retry-interval-to-handle-spike-traffic
|
|
* for context.
|
|
*
|
|
* <p>Visible for testing
|
|
*/
|
|
var RANDOM_FACTOR = 0.5;
|
|
/**
|
|
* Based on the backoff method from
|
|
* https://github.com/google/closure-library/blob/master/closure/goog/math/exponentialbackoff.js.
|
|
* Extracted here so we don't need to pass metadata and a stateful ExponentialBackoff object around.
|
|
*/
|
|
function calculateBackoffMillis(backoffCount) {
|
|
// Calculates an exponentially increasing value.
|
|
// Deviation: calculates value from count and a constant interval, so we only need to save value
|
|
// and count to restore state.
|
|
var currBaseValue = INTERVAL_MILLIS * Math.pow(BACKOFF_FACTOR, backoffCount);
|
|
// A random "fuzz" to avoid waves of retries.
|
|
// Deviation: randomFactor is required.
|
|
var randomWait = Math.round(
|
|
// A fraction of the backoff value to add/subtract.
|
|
// Deviation: changes multiplication order to improve readability.
|
|
RANDOM_FACTOR *
|
|
currBaseValue *
|
|
// A random float (rounded to int by Math.round above) in the range [-1, 1]. Determines
|
|
// if we add or subtract.
|
|
(Math.random() - 0.5) *
|
|
2);
|
|
// Limits backoff to max to avoid effectively permanent backoff.
|
|
return Math.min(MAX_VALUE_MILLIS, currBaseValue + randomWait);
|
|
}
|
|
|
|
/**
|
|
* @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:
|
|
*
|
|
* <ul>
|
|
* <li>Promisifying setTimeout, so we can set a timeout in our Promise chain</li>
|
|
* <li>Listening on a signal bus for abort events, just like the Fetch API</li>
|
|
* <li>Failing in the same way the Fetch API fails, so timing out a live request and a throttled
|
|
* request appear the same.</li>
|
|
* </ul>
|
|
*
|
|
* <p>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" /* FETCH_THROTTLE */, {
|
|
throttleEndTimeMillis: throttleEndTimeMillis
|
|
}));
|
|
});
|
|
});
|
|
}
|
|
/**
|
|
* Returns true if the {@link Error} indicates a fetch request may succeed later.
|
|
*/
|
|
function isRetriableError(e) {
|
|
if (!(e instanceof util.FirebaseError)) {
|
|
return false;
|
|
}
|
|
// Uses string index defined by ErrorData, which FirebaseError implements.
|
|
var httpStatus = Number(e['httpStatus']);
|
|
return (httpStatus === 429 ||
|
|
httpStatus === 500 ||
|
|
httpStatus === 503 ||
|
|
httpStatus === 504);
|
|
}
|
|
/**
|
|
* Decorates a Client with retry logic.
|
|
*
|
|
* <p>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 tslib.__awaiter(this, void 0, void 0, function () {
|
|
var throttleMetadata;
|
|
return tslib.__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 tslib.__awaiter(this, void 0, void 0, function () {
|
|
var response, e_1, throttleMetadata;
|
|
return tslib.__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;
|
|
}());
|
|
|
|
var name = "@firebase/remote-config";
|
|
var version = "0.1.21";
|
|
|
|
/**
|
|
* @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.
|
|
*/
|
|
function registerRemoteConfig(firebaseInstance) {
|
|
firebaseInstance.INTERNAL.registerComponent(new component.Component('remoteConfig', remoteConfigFactory, "PUBLIC" /* PUBLIC */).setMultipleInstances(true));
|
|
firebaseInstance.registerVersion(name, version);
|
|
function remoteConfigFactory(container, namespace) {
|
|
/* 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').getImmediate();
|
|
// Guards against the SDK being used in non-browser environments.
|
|
if (typeof window === 'undefined') {
|
|
throw ERROR_FACTORY.create("registration-window" /* REGISTRATION_WINDOW */);
|
|
}
|
|
// Normalizes optional inputs.
|
|
var _a = app.options, projectId = _a.projectId, apiKey = _a.apiKey, appId = _a.appId;
|
|
if (!projectId) {
|
|
throw ERROR_FACTORY.create("registration-project-id" /* REGISTRATION_PROJECT_ID */);
|
|
}
|
|
if (!apiKey) {
|
|
throw ERROR_FACTORY.create("registration-api-key" /* REGISTRATION_API_KEY */);
|
|
}
|
|
if (!appId) {
|
|
throw ERROR_FACTORY.create("registration-app-id" /* REGISTRATION_APP_ID */);
|
|
}
|
|
namespace = namespace || 'firebase';
|
|
var storage = new Storage(appId, app.name, namespace);
|
|
var storageCache = new StorageCache(storage);
|
|
var logger$1 = new logger.Logger(name);
|
|
// Sets ERROR as the default log level.
|
|
// See RemoteConfig#setLogLevel for corresponding normalization to ERROR log level.
|
|
logger$1.logLevel = logger.LogLevel.ERROR;
|
|
var restClient = new RestClient(installations,
|
|
// Uses the JS SDK version, by which the RC package version can be deduced, if necessary.
|
|
firebaseInstance.SDK_VERSION, namespace, projectId, apiKey, appId);
|
|
var retryingClient = new RetryingClient(restClient, storage);
|
|
var cachingClient = new CachingClient(retryingClient, storage, storageCache, logger$1);
|
|
var remoteConfigInstance = new RemoteConfig(app, cachingClient, storageCache, storage, logger$1);
|
|
// Starts warming cache.
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
remoteConfigInstance.ensureInitialized();
|
|
return remoteConfigInstance;
|
|
}
|
|
}
|
|
registerRemoteConfig(firebase);
|
|
|
|
exports.registerRemoteConfig = registerRemoteConfig;
|
|
//# sourceMappingURL=index.cjs.js.map
|