import { _getProvider, getApp, _registerComponent, registerVersion } from '@firebase/app'; import { Component } from '@firebase/component'; import { __assign, __awaiter, __generator, __spreadArray } from 'tslib'; import { Deferred, ErrorFactory, isIndexedDBAvailable, uuidv4, getGlobal, base64, issuedAtTime, calculateBackoffMillis, getModularInstance } from '@firebase/util'; import { Logger } from '@firebase/logger'; /** * @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 APP_CHECK_STATES = new Map(); var DEFAULT_STATE = { activated: false, tokenObservers: [] }; var DEBUG_STATE = { initialized: false, enabled: false }; /** * Gets a reference to the state object. */ function getStateReference(app) { return APP_CHECK_STATES.get(app) || __assign({}, DEFAULT_STATE); } /** * Set once on initialization. The map should hold the same reference to the * same object until this entry is deleted. */ function setInitialState(app, state) { APP_CHECK_STATES.set(app, state); return APP_CHECK_STATES.get(app); } function getDebugState() { return DEBUG_STATE; } /** * @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 BASE_ENDPOINT = 'https://content-firebaseappcheck.googleapis.com/v1'; var EXCHANGE_RECAPTCHA_TOKEN_METHOD = 'exchangeRecaptchaV3Token'; var EXCHANGE_RECAPTCHA_ENTERPRISE_TOKEN_METHOD = 'exchangeRecaptchaEnterpriseToken'; var EXCHANGE_DEBUG_TOKEN_METHOD = 'exchangeDebugToken'; var TOKEN_REFRESH_TIME = { /** * The offset time before token natural expiration to run the refresh. * This is currently 5 minutes. */ OFFSET_DURATION: 5 * 60 * 1000, /** * This is the first retrial wait after an error. This is currently * 30 seconds. */ RETRIAL_MIN_WAIT: 30 * 1000, /** * This is the maximum retrial wait, currently 16 minutes. */ RETRIAL_MAX_WAIT: 16 * 60 * 1000 }; /** * One day in millis, for certain error code backoffs. */ var ONE_DAY = 24 * 60 * 60 * 1000; /** * @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. */ /** * Port from auth proactiverefresh.js * */ // TODO: move it to @firebase/util? // TODO: allow to config whether refresh should happen in the background var Refresher = /** @class */ (function () { function Refresher(operation, retryPolicy, getWaitDuration, lowerBound, upperBound) { this.operation = operation; this.retryPolicy = retryPolicy; this.getWaitDuration = getWaitDuration; this.lowerBound = lowerBound; this.upperBound = upperBound; this.pending = null; this.nextErrorWaitInterval = lowerBound; if (lowerBound > upperBound) { throw new Error('Proactive refresh lower bound greater than upper bound!'); } } Refresher.prototype.start = function () { this.nextErrorWaitInterval = this.lowerBound; this.process(true).catch(function () { /* we don't care about the result */ }); }; Refresher.prototype.stop = function () { if (this.pending) { this.pending.reject('cancelled'); this.pending = null; } }; Refresher.prototype.isRunning = function () { return !!this.pending; }; Refresher.prototype.process = function (hasSucceeded) { return __awaiter(this, void 0, void 0, function () { var error_1; return __generator(this, function (_a) { switch (_a.label) { case 0: this.stop(); _a.label = 1; case 1: _a.trys.push([1, 6, , 7]); this.pending = new Deferred(); this.pending.promise.catch(function (_e) { /* ignore */ }); return [4 /*yield*/, sleep(this.getNextRun(hasSucceeded))]; case 2: _a.sent(); // Why do we resolve a promise, then immediate wait for it? // We do it to make the promise chain cancellable. // We can call stop() which rejects the promise before the following line execute, which makes // the code jump to the catch block. // TODO: unit test this this.pending.resolve(); return [4 /*yield*/, this.pending.promise]; case 3: _a.sent(); this.pending = new Deferred(); this.pending.promise.catch(function (_e) { /* ignore */ }); return [4 /*yield*/, this.operation()]; case 4: _a.sent(); this.pending.resolve(); return [4 /*yield*/, this.pending.promise]; case 5: _a.sent(); this.process(true).catch(function () { /* we don't care about the result */ }); return [3 /*break*/, 7]; case 6: error_1 = _a.sent(); if (this.retryPolicy(error_1)) { this.process(false).catch(function () { /* we don't care about the result */ }); } else { this.stop(); } return [3 /*break*/, 7]; case 7: return [2 /*return*/]; } }); }); }; Refresher.prototype.getNextRun = function (hasSucceeded) { if (hasSucceeded) { // If last operation succeeded, reset next error wait interval and return // the default wait duration. this.nextErrorWaitInterval = this.lowerBound; // Return typical wait duration interval after a successful operation. return this.getWaitDuration(); } else { // Get next error wait interval. var currentErrorWaitInterval = this.nextErrorWaitInterval; // Double interval for next consecutive error. this.nextErrorWaitInterval *= 2; // Make sure next wait interval does not exceed the maximum upper bound. if (this.nextErrorWaitInterval > this.upperBound) { this.nextErrorWaitInterval = this.upperBound; } return currentErrorWaitInterval; } }; return Refresher; }()); function sleep(ms) { return new Promise(function (resolve) { setTimeout(resolve, ms); }); } /** * @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 _a; var ERRORS = (_a = {}, _a["already-initialized" /* AppCheckError.ALREADY_INITIALIZED */] = 'You have already called initializeAppCheck() for FirebaseApp {$appName} with ' + 'different options. To avoid this error, call initializeAppCheck() with the ' + 'same options as when it was originally called. This will return the ' + 'already initialized instance.', _a["use-before-activation" /* AppCheckError.USE_BEFORE_ACTIVATION */] = 'App Check is being used before initializeAppCheck() is called for FirebaseApp {$appName}. ' + 'Call initializeAppCheck() before instantiating other Firebase services.', _a["fetch-network-error" /* AppCheckError.FETCH_NETWORK_ERROR */] = 'Fetch failed to connect to a network. Check Internet connection. ' + 'Original error: {$originalErrorMessage}.', _a["fetch-parse-error" /* AppCheckError.FETCH_PARSE_ERROR */] = 'Fetch client could not parse response.' + ' Original error: {$originalErrorMessage}.', _a["fetch-status-error" /* AppCheckError.FETCH_STATUS_ERROR */] = 'Fetch server returned an HTTP error status. HTTP status: {$httpStatus}.', _a["storage-open" /* AppCheckError.STORAGE_OPEN */] = 'Error thrown when opening storage. Original error: {$originalErrorMessage}.', _a["storage-get" /* AppCheckError.STORAGE_GET */] = 'Error thrown when reading from storage. Original error: {$originalErrorMessage}.', _a["storage-set" /* AppCheckError.STORAGE_WRITE */] = 'Error thrown when writing to storage. Original error: {$originalErrorMessage}.', _a["recaptcha-error" /* AppCheckError.RECAPTCHA_ERROR */] = 'ReCAPTCHA error.', _a["throttled" /* AppCheckError.THROTTLED */] = "Requests throttled due to {$httpStatus} error. Attempts allowed again after {$time}", _a); var ERROR_FACTORY = new ErrorFactory('appCheck', 'AppCheck', ERRORS); /** * @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 getRecaptcha(isEnterprise) { var _a; if (isEnterprise === void 0) { isEnterprise = false; } if (isEnterprise) { return (_a = self.grecaptcha) === null || _a === void 0 ? void 0 : _a.enterprise; } return self.grecaptcha; } function ensureActivated(app) { if (!getStateReference(app).activated) { throw ERROR_FACTORY.create("use-before-activation" /* AppCheckError.USE_BEFORE_ACTIVATION */, { appName: app.name }); } } function getDurationString(durationInMillis) { var totalSeconds = Math.round(durationInMillis / 1000); var days = Math.floor(totalSeconds / (3600 * 24)); var hours = Math.floor((totalSeconds - days * 3600 * 24) / 3600); var minutes = Math.floor((totalSeconds - days * 3600 * 24 - hours * 3600) / 60); var seconds = totalSeconds - days * 3600 * 24 - hours * 3600 - minutes * 60; var result = ''; if (days) { result += pad(days) + 'd:'; } if (hours) { result += pad(hours) + 'h:'; } result += pad(minutes) + 'm:' + pad(seconds) + 's'; return result; } function pad(value) { if (value === 0) { return '00'; } return value >= 10 ? value.toString() : '0' + 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. */ function exchangeToken(_a, heartbeatServiceProvider) { var url = _a.url, body = _a.body; return __awaiter(this, void 0, void 0, function () { var headers, heartbeatService, heartbeatsHeader, options, response, originalError_1, responseBody, originalError_2, match, timeToLiveAsNumber, now; return __generator(this, function (_b) { switch (_b.label) { case 0: headers = { 'Content-Type': 'application/json' }; heartbeatService = heartbeatServiceProvider.getImmediate({ optional: true }); if (!heartbeatService) return [3 /*break*/, 2]; return [4 /*yield*/, heartbeatService.getHeartbeatsHeader()]; case 1: heartbeatsHeader = _b.sent(); if (heartbeatsHeader) { headers['X-Firebase-Client'] = heartbeatsHeader; } _b.label = 2; case 2: options = { method: 'POST', body: JSON.stringify(body), headers: headers }; _b.label = 3; case 3: _b.trys.push([3, 5, , 6]); return [4 /*yield*/, fetch(url, options)]; case 4: response = _b.sent(); return [3 /*break*/, 6]; case 5: originalError_1 = _b.sent(); throw ERROR_FACTORY.create("fetch-network-error" /* AppCheckError.FETCH_NETWORK_ERROR */, { originalErrorMessage: originalError_1 === null || originalError_1 === void 0 ? void 0 : originalError_1.message }); case 6: if (response.status !== 200) { throw ERROR_FACTORY.create("fetch-status-error" /* AppCheckError.FETCH_STATUS_ERROR */, { httpStatus: response.status }); } _b.label = 7; case 7: _b.trys.push([7, 9, , 10]); return [4 /*yield*/, response.json()]; case 8: // JSON parsing throws SyntaxError if the response body isn't a JSON string. responseBody = _b.sent(); return [3 /*break*/, 10]; case 9: originalError_2 = _b.sent(); throw ERROR_FACTORY.create("fetch-parse-error" /* AppCheckError.FETCH_PARSE_ERROR */, { originalErrorMessage: originalError_2 === null || originalError_2 === void 0 ? void 0 : originalError_2.message }); case 10: match = responseBody.ttl.match(/^([\d.]+)(s)$/); if (!match || !match[2] || isNaN(Number(match[1]))) { throw ERROR_FACTORY.create("fetch-parse-error" /* AppCheckError.FETCH_PARSE_ERROR */, { originalErrorMessage: "ttl field (timeToLive) is not in standard Protobuf Duration " + "format: ".concat(responseBody.ttl) }); } timeToLiveAsNumber = Number(match[1]) * 1000; now = Date.now(); return [2 /*return*/, { token: responseBody.token, expireTimeMillis: now + timeToLiveAsNumber, issuedAtTimeMillis: now }]; } }); }); } function getExchangeRecaptchaV3TokenRequest(app, reCAPTCHAToken) { var _a = app.options, projectId = _a.projectId, appId = _a.appId, apiKey = _a.apiKey; return { url: "".concat(BASE_ENDPOINT, "/projects/").concat(projectId, "/apps/").concat(appId, ":").concat(EXCHANGE_RECAPTCHA_TOKEN_METHOD, "?key=").concat(apiKey), body: { 'recaptcha_v3_token': reCAPTCHAToken } }; } function getExchangeRecaptchaEnterpriseTokenRequest(app, reCAPTCHAToken) { var _a = app.options, projectId = _a.projectId, appId = _a.appId, apiKey = _a.apiKey; return { url: "".concat(BASE_ENDPOINT, "/projects/").concat(projectId, "/apps/").concat(appId, ":").concat(EXCHANGE_RECAPTCHA_ENTERPRISE_TOKEN_METHOD, "?key=").concat(apiKey), body: { 'recaptcha_enterprise_token': reCAPTCHAToken } }; } function getExchangeDebugTokenRequest(app, debugToken) { var _a = app.options, projectId = _a.projectId, appId = _a.appId, apiKey = _a.apiKey; return { url: "".concat(BASE_ENDPOINT, "/projects/").concat(projectId, "/apps/").concat(appId, ":").concat(EXCHANGE_DEBUG_TOKEN_METHOD, "?key=").concat(apiKey), body: { // eslint-disable-next-line debug_token: debugToken } }; } /** * @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 DB_NAME = 'firebase-app-check-database'; var DB_VERSION = 1; var STORE_NAME = 'firebase-app-check-store'; var DEBUG_TOKEN_KEY = 'debug-token'; var dbPromise = null; function getDBPromise() { if (dbPromise) { return dbPromise; } dbPromise = new Promise(function (resolve, reject) { try { var request = indexedDB.open(DB_NAME, DB_VERSION); request.onsuccess = function (event) { resolve(event.target.result); }; request.onerror = function (event) { var _a; reject(ERROR_FACTORY.create("storage-open" /* AppCheckError.STORAGE_OPEN */, { originalErrorMessage: (_a = event.target.error) === null || _a === void 0 ? void 0 : _a.message })); }; 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(STORE_NAME, { keyPath: 'compositeKey' }); } }; } catch (e) { reject(ERROR_FACTORY.create("storage-open" /* AppCheckError.STORAGE_OPEN */, { originalErrorMessage: e === null || e === void 0 ? void 0 : e.message })); } }); return dbPromise; } function readTokenFromIndexedDB(app) { return read(computeKey(app)); } function writeTokenToIndexedDB(app, token) { return write(computeKey(app), token); } function writeDebugTokenToIndexedDB(token) { return write(DEBUG_TOKEN_KEY, token); } function readDebugTokenFromIndexedDB() { return read(DEBUG_TOKEN_KEY); } function write(key, value) { return __awaiter(this, void 0, void 0, function () { var db, transaction, store, request; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, getDBPromise()]; case 1: db = _a.sent(); transaction = db.transaction(STORE_NAME, 'readwrite'); store = transaction.objectStore(STORE_NAME); request = store.put({ compositeKey: key, value: value }); return [2 /*return*/, new Promise(function (resolve, reject) { request.onsuccess = function (_event) { resolve(); }; transaction.onerror = function (event) { var _a; reject(ERROR_FACTORY.create("storage-set" /* AppCheckError.STORAGE_WRITE */, { originalErrorMessage: (_a = event.target.error) === null || _a === void 0 ? void 0 : _a.message })); }; })]; } }); }); } function read(key) { return __awaiter(this, void 0, void 0, function () { var db, transaction, store, request; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, getDBPromise()]; case 1: db = _a.sent(); transaction = db.transaction(STORE_NAME, 'readonly'); store = transaction.objectStore(STORE_NAME); request = store.get(key); return [2 /*return*/, new Promise(function (resolve, reject) { request.onsuccess = function (event) { var result = event.target.result; if (result) { resolve(result.value); } else { resolve(undefined); } }; transaction.onerror = function (event) { var _a; reject(ERROR_FACTORY.create("storage-get" /* AppCheckError.STORAGE_GET */, { originalErrorMessage: (_a = event.target.error) === null || _a === void 0 ? void 0 : _a.message })); }; })]; } }); }); } function computeKey(app) { return "".concat(app.options.appId, "-").concat(app.name); } /** * @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 logger = new Logger('@firebase/app-check'); /** * @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. */ /** * Always resolves. In case of an error reading from indexeddb, resolve with undefined */ function readTokenFromStorage(app) { return __awaiter(this, void 0, void 0, function () { var token, e_1; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!isIndexedDBAvailable()) return [3 /*break*/, 5]; token = undefined; _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, readTokenFromIndexedDB(app)]; case 2: token = _a.sent(); return [3 /*break*/, 4]; case 3: e_1 = _a.sent(); // swallow the error and return undefined logger.warn("Failed to read token from IndexedDB. Error: ".concat(e_1)); return [3 /*break*/, 4]; case 4: return [2 /*return*/, token]; case 5: return [2 /*return*/, undefined]; } }); }); } /** * Always resolves. In case of an error writing to indexeddb, print a warning and resolve the promise */ function writeTokenToStorage(app, token) { if (isIndexedDBAvailable()) { return writeTokenToIndexedDB(app, token).catch(function (e) { // swallow the error and resolve the promise logger.warn("Failed to write token to IndexedDB. Error: ".concat(e)); }); } return Promise.resolve(); } function readOrCreateDebugTokenFromStorage() { return __awaiter(this, void 0, void 0, function () { var existingDebugToken, newToken; return __generator(this, function (_a) { switch (_a.label) { case 0: existingDebugToken = undefined; _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, readDebugTokenFromIndexedDB()]; case 2: existingDebugToken = _a.sent(); return [3 /*break*/, 4]; case 3: _a.sent(); return [3 /*break*/, 4]; case 4: if (!existingDebugToken) { newToken = uuidv4(); // We don't need to block on writing to indexeddb // In case persistence failed, a new debug token will be generated everytime the page is refreshed. // It renders the debug token useless because you have to manually register(whitelist) the new token in the firebase console again and again. // If you see this error trying to use debug token, it probably means you are using a browser that doesn't support indexeddb. // You should switch to a different browser that supports indexeddb writeDebugTokenToIndexedDB(newToken).catch(function (e) { return logger.warn("Failed to persist debug token to IndexedDB. Error: ".concat(e)); }); return [2 /*return*/, newToken]; } else { return [2 /*return*/, existingDebugToken]; } } }); }); } /** * @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 isDebugMode() { var debugState = getDebugState(); return debugState.enabled; } function getDebugToken() { return __awaiter(this, void 0, void 0, function () { var state; return __generator(this, function (_a) { state = getDebugState(); if (state.enabled && state.token) { return [2 /*return*/, state.token.promise]; } else { // should not happen! throw Error("\n Can't get debug token in production mode.\n "); } }); }); } function initializeDebugMode() { var globals = getGlobal(); var debugState = getDebugState(); // Set to true if this function has been called, whether or not // it enabled debug mode. debugState.initialized = true; if (typeof globals.FIREBASE_APPCHECK_DEBUG_TOKEN !== 'string' && globals.FIREBASE_APPCHECK_DEBUG_TOKEN !== true) { return; } debugState.enabled = true; var deferredToken = new Deferred(); debugState.token = deferredToken; if (typeof globals.FIREBASE_APPCHECK_DEBUG_TOKEN === 'string') { deferredToken.resolve(globals.FIREBASE_APPCHECK_DEBUG_TOKEN); } else { deferredToken.resolve(readOrCreateDebugTokenFromStorage()); } } /** * @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. */ // Initial hardcoded value agreed upon across platforms for initial launch. // Format left open for possible dynamic error values and other fields in the future. var defaultTokenErrorData = { error: 'UNKNOWN_ERROR' }; /** * Stringify and base64 encode token error data. * * @param tokenError Error data, currently hardcoded. */ function formatDummyToken(tokenErrorData) { return base64.encodeString(JSON.stringify(tokenErrorData), /* webSafe= */ false); } /** * This function always resolves. * The result will contain an error field if there is any error. * In case there is an error, the token field in the result will be populated with a dummy value */ function getToken$2(appCheck, forceRefresh) { if (forceRefresh === void 0) { forceRefresh = false; } return __awaiter(this, void 0, void 0, function () { var app, state, token, error, cachedToken, shouldCallListeners, _a, _b, _c, _d, tokenFromDebugExchange, e_1, interopTokenResult; return __generator(this, function (_e) { switch (_e.label) { case 0: app = appCheck.app; ensureActivated(app); state = getStateReference(app); token = state.token; error = undefined; /** * If an invalid token was found in memory, clear token from * memory and unset the local variable `token`. */ if (token && !isValid(token)) { state.token = undefined; token = undefined; } if (!!token) return [3 /*break*/, 4]; return [4 /*yield*/, state.cachedTokenPromise]; case 1: cachedToken = _e.sent(); if (!cachedToken) return [3 /*break*/, 4]; if (!isValid(cachedToken)) return [3 /*break*/, 2]; token = cachedToken; return [3 /*break*/, 4]; case 2: // If there was an invalid token in the indexedDB cache, clear it. return [4 /*yield*/, writeTokenToStorage(app, undefined)]; case 3: // If there was an invalid token in the indexedDB cache, clear it. _e.sent(); _e.label = 4; case 4: // Return the cached token (from either memory or indexedDB) if it's valid if (!forceRefresh && token && isValid(token)) { return [2 /*return*/, { token: token.token }]; } shouldCallListeners = false; if (!isDebugMode()) return [3 /*break*/, 9]; if (!!state.exchangeTokenPromise) return [3 /*break*/, 6]; _a = state; _b = exchangeToken; _c = getExchangeDebugTokenRequest; _d = [app]; return [4 /*yield*/, getDebugToken()]; case 5: _a.exchangeTokenPromise = _b.apply(void 0, [_c.apply(void 0, _d.concat([_e.sent()])), appCheck.heartbeatServiceProvider]).finally(function () { // Clear promise when settled - either resolved or rejected. state.exchangeTokenPromise = undefined; }); shouldCallListeners = true; _e.label = 6; case 6: return [4 /*yield*/, state.exchangeTokenPromise]; case 7: tokenFromDebugExchange = _e.sent(); // Write debug token to indexedDB. return [4 /*yield*/, writeTokenToStorage(app, tokenFromDebugExchange)]; case 8: // Write debug token to indexedDB. _e.sent(); // Write debug token to state. state.token = tokenFromDebugExchange; return [2 /*return*/, { token: tokenFromDebugExchange.token }]; case 9: _e.trys.push([9, 11, , 12]); // Avoid making another call to the exchange endpoint if one is in flight. if (!state.exchangeTokenPromise) { // state.provider is populated in initializeAppCheck() // ensureActivated() at the top of this function checks that // initializeAppCheck() has been called. state.exchangeTokenPromise = state.provider.getToken().finally(function () { // Clear promise when settled - either resolved or rejected. state.exchangeTokenPromise = undefined; }); shouldCallListeners = true; } return [4 /*yield*/, getStateReference(app).exchangeTokenPromise]; case 10: token = _e.sent(); return [3 /*break*/, 12]; case 11: e_1 = _e.sent(); if (e_1.code === "appCheck/".concat("throttled" /* AppCheckError.THROTTLED */)) { // Warn if throttled, but do not treat it as an error. logger.warn(e_1.message); } else { // `getToken()` should never throw, but logging error text to console will aid debugging. logger.error(e_1); } // Always save error to be added to dummy token. error = e_1; return [3 /*break*/, 12]; case 12: if (!!token) return [3 /*break*/, 13]; // If token is undefined, there must be an error. // Return a dummy token along with the error. interopTokenResult = makeDummyTokenResult(error); return [3 /*break*/, 16]; case 13: if (!error) return [3 /*break*/, 14]; if (isValid(token)) { // It's also possible a valid token exists, but there's also an error. // (Such as if the token is almost expired, tries to refresh, and // the exchange request fails.) // We add a special error property here so that the refresher will // count this as a failed attempt and use the backoff instead of // retrying repeatedly with no delay, but any 3P listeners will not // be hindered in getting the still-valid token. interopTokenResult = { token: token.token, internalError: error }; } else { // No invalid tokens should make it to this step. Memory and cached tokens // are checked. Other tokens are from fresh exchanges. But just in case. interopTokenResult = makeDummyTokenResult(error); } return [3 /*break*/, 16]; case 14: interopTokenResult = { token: token.token }; // write the new token to the memory state as well as the persistent storage. // Only do it if we got a valid new token state.token = token; return [4 /*yield*/, writeTokenToStorage(app, token)]; case 15: _e.sent(); _e.label = 16; case 16: if (shouldCallListeners) { notifyTokenListeners(app, interopTokenResult); } return [2 /*return*/, interopTokenResult]; } }); }); } /** * Internal API for limited use tokens. Skips all FAC state and simply calls * the underlying provider. */ function getLimitedUseToken$1(appCheck) { return __awaiter(this, void 0, void 0, function () { var app, provider, debugToken, token, token; return __generator(this, function (_a) { switch (_a.label) { case 0: app = appCheck.app; ensureActivated(app); provider = getStateReference(app).provider; if (!isDebugMode()) return [3 /*break*/, 3]; return [4 /*yield*/, getDebugToken()]; case 1: debugToken = _a.sent(); return [4 /*yield*/, exchangeToken(getExchangeDebugTokenRequest(app, debugToken), appCheck.heartbeatServiceProvider)]; case 2: token = (_a.sent()).token; return [2 /*return*/, { token: token }]; case 3: return [4 /*yield*/, provider.getToken()]; case 4: token = (_a.sent()).token; return [2 /*return*/, { token: token }]; } }); }); } function addTokenListener(appCheck, type, listener, onError) { var app = appCheck.app; var state = getStateReference(app); var tokenObserver = { next: listener, error: onError, type: type }; state.tokenObservers = __spreadArray(__spreadArray([], state.tokenObservers, true), [tokenObserver], false); // Invoke the listener async immediately if there is a valid token // in memory. if (state.token && isValid(state.token)) { var validToken_1 = state.token; Promise.resolve() .then(function () { listener({ token: validToken_1.token }); initTokenRefresher(appCheck); }) .catch(function () { /* we don't care about exceptions thrown in listeners */ }); } /** * Wait for any cached token promise to resolve before starting the token * refresher. The refresher checks to see if there is an existing token * in state and calls the exchange endpoint if not. We should first let the * IndexedDB check have a chance to populate state if it can. * * Listener call isn't needed here because cachedTokenPromise will call any * listeners that exist when it resolves. */ // state.cachedTokenPromise is always populated in `activate()`. void state.cachedTokenPromise.then(function () { return initTokenRefresher(appCheck); }); } function removeTokenListener(app, listener) { var state = getStateReference(app); var newObservers = state.tokenObservers.filter(function (tokenObserver) { return tokenObserver.next !== listener; }); if (newObservers.length === 0 && state.tokenRefresher && state.tokenRefresher.isRunning()) { state.tokenRefresher.stop(); } state.tokenObservers = newObservers; } /** * Logic to create and start refresher as needed. */ function initTokenRefresher(appCheck) { var app = appCheck.app; var state = getStateReference(app); // Create the refresher but don't start it if `isTokenAutoRefreshEnabled` // is not true. var refresher = state.tokenRefresher; if (!refresher) { refresher = createTokenRefresher(appCheck); state.tokenRefresher = refresher; } if (!refresher.isRunning() && state.isTokenAutoRefreshEnabled) { refresher.start(); } } function createTokenRefresher(appCheck) { var _this = this; var app = appCheck.app; return new Refresher( // Keep in mind when this fails for any reason other than the ones // for which we should retry, it will effectively stop the proactive refresh. function () { return __awaiter(_this, void 0, void 0, function () { var state, result; return __generator(this, function (_a) { switch (_a.label) { case 0: state = getStateReference(app); if (!!state.token) return [3 /*break*/, 2]; return [4 /*yield*/, getToken$2(appCheck)]; case 1: result = _a.sent(); return [3 /*break*/, 4]; case 2: return [4 /*yield*/, getToken$2(appCheck, true)]; case 3: result = _a.sent(); _a.label = 4; case 4: /** * getToken() always resolves. In case the result has an error field defined, it means * the operation failed, and we should retry. */ if (result.error) { throw result.error; } /** * A special `internalError` field reflects that there was an error * getting a new token from the exchange endpoint, but there's still a * previous token that's valid for now and this should be passed to 2P/3P * requests for a token. But we want this callback (`this.operation` in * `Refresher`) to throw in order to kick off the Refresher's retry * backoff. (Setting `hasSucceeded` to false.) */ if (result.internalError) { throw result.internalError; } return [2 /*return*/]; } }); }); }, function () { return true; }, function () { var state = getStateReference(app); if (state.token) { // issuedAtTime + (50% * total TTL) + 5 minutes var nextRefreshTimeMillis = state.token.issuedAtTimeMillis + (state.token.expireTimeMillis - state.token.issuedAtTimeMillis) * 0.5 + 5 * 60 * 1000; // Do not allow refresh time to be past (expireTime - 5 minutes) var latestAllowableRefresh = state.token.expireTimeMillis - 5 * 60 * 1000; nextRefreshTimeMillis = Math.min(nextRefreshTimeMillis, latestAllowableRefresh); return Math.max(0, nextRefreshTimeMillis - Date.now()); } else { return 0; } }, TOKEN_REFRESH_TIME.RETRIAL_MIN_WAIT, TOKEN_REFRESH_TIME.RETRIAL_MAX_WAIT); } function notifyTokenListeners(app, token) { var observers = getStateReference(app).tokenObservers; for (var _i = 0, observers_1 = observers; _i < observers_1.length; _i++) { var observer = observers_1[_i]; try { if (observer.type === "EXTERNAL" /* ListenerType.EXTERNAL */ && token.error != null) { // If this listener was added by a 3P call, send any token error to // the supplied error handler. A 3P observer always has an error // handler. observer.error(token.error); } else { // If the token has no error field, always return the token. // If this is a 2P listener, return the token, whether or not it // has an error field. observer.next(token); } } catch (e) { // Errors in the listener function itself are always ignored. } } } function isValid(token) { return token.expireTimeMillis - Date.now() > 0; } function makeDummyTokenResult(error) { return { token: formatDummyToken(defaultTokenErrorData), error: error }; } /** * @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. */ /** * AppCheck Service class. */ var AppCheckService = /** @class */ (function () { function AppCheckService(app, heartbeatServiceProvider) { this.app = app; this.heartbeatServiceProvider = heartbeatServiceProvider; } AppCheckService.prototype._delete = function () { var tokenObservers = getStateReference(this.app).tokenObservers; for (var _i = 0, tokenObservers_1 = tokenObservers; _i < tokenObservers_1.length; _i++) { var tokenObserver = tokenObservers_1[_i]; removeTokenListener(this.app, tokenObserver.next); } return Promise.resolve(); }; return AppCheckService; }()); function factory(app, heartbeatServiceProvider) { return new AppCheckService(app, heartbeatServiceProvider); } function internalFactory(appCheck) { return { getToken: function (forceRefresh) { return getToken$2(appCheck, forceRefresh); }, getLimitedUseToken: function () { return getLimitedUseToken$1(appCheck); }, addTokenListener: function (listener) { return addTokenListener(appCheck, "INTERNAL" /* ListenerType.INTERNAL */, listener); }, removeTokenListener: function (listener) { return removeTokenListener(appCheck.app, listener); } }; } var name = "@firebase/app-check"; var version = "0.8.2"; /** * @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 RECAPTCHA_URL = 'https://www.google.com/recaptcha/api.js'; var RECAPTCHA_ENTERPRISE_URL = 'https://www.google.com/recaptcha/enterprise.js'; function initializeV3(app, siteKey) { var initialized = new Deferred(); var state = getStateReference(app); state.reCAPTCHAState = { initialized: initialized }; var divId = makeDiv(app); var grecaptcha = getRecaptcha(false); if (!grecaptcha) { loadReCAPTCHAV3Script(function () { var grecaptcha = getRecaptcha(false); if (!grecaptcha) { // it shouldn't happen. throw new Error('no recaptcha'); } queueWidgetRender(app, siteKey, grecaptcha, divId, initialized); }); } else { queueWidgetRender(app, siteKey, grecaptcha, divId, initialized); } return initialized.promise; } function initializeEnterprise(app, siteKey) { var initialized = new Deferred(); var state = getStateReference(app); state.reCAPTCHAState = { initialized: initialized }; var divId = makeDiv(app); var grecaptcha = getRecaptcha(true); if (!grecaptcha) { loadReCAPTCHAEnterpriseScript(function () { var grecaptcha = getRecaptcha(true); if (!grecaptcha) { // it shouldn't happen. throw new Error('no recaptcha'); } queueWidgetRender(app, siteKey, grecaptcha, divId, initialized); }); } else { queueWidgetRender(app, siteKey, grecaptcha, divId, initialized); } return initialized.promise; } /** * Add listener to render the widget and resolve the promise when * the grecaptcha.ready() event fires. */ function queueWidgetRender(app, siteKey, grecaptcha, container, initialized) { grecaptcha.ready(function () { // Invisible widgets allow us to set a different siteKey for each widget, // so we use them to support multiple apps renderInvisibleWidget(app, siteKey, grecaptcha, container); initialized.resolve(grecaptcha); }); } /** * Add invisible div to page. */ function makeDiv(app) { var divId = "fire_app_check_".concat(app.name); var invisibleDiv = document.createElement('div'); invisibleDiv.id = divId; invisibleDiv.style.display = 'none'; document.body.appendChild(invisibleDiv); return divId; } function getToken$1(app) { return __awaiter(this, void 0, void 0, function () { var reCAPTCHAState, recaptcha; return __generator(this, function (_a) { switch (_a.label) { case 0: ensureActivated(app); reCAPTCHAState = getStateReference(app).reCAPTCHAState; return [4 /*yield*/, reCAPTCHAState.initialized.promise]; case 1: recaptcha = _a.sent(); return [2 /*return*/, new Promise(function (resolve, _reject) { // Updated after initialization is complete. var reCAPTCHAState = getStateReference(app).reCAPTCHAState; recaptcha.ready(function () { resolve( // widgetId is guaranteed to be available if reCAPTCHAState.initialized.promise resolved. recaptcha.execute(reCAPTCHAState.widgetId, { action: 'fire_app_check' })); }); })]; } }); }); } /** * * @param app * @param container - Id of a HTML element. */ function renderInvisibleWidget(app, siteKey, grecaptcha, container) { var widgetId = grecaptcha.render(container, { sitekey: siteKey, size: 'invisible', // Success callback - set state callback: function () { getStateReference(app).reCAPTCHAState.succeeded = true; }, // Failure callback - set state 'error-callback': function () { getStateReference(app).reCAPTCHAState.succeeded = false; } }); var state = getStateReference(app); state.reCAPTCHAState = __assign(__assign({}, state.reCAPTCHAState), { // state.reCAPTCHAState is set in the initialize() widgetId: widgetId }); } function loadReCAPTCHAV3Script(onload) { var script = document.createElement('script'); script.src = RECAPTCHA_URL; script.onload = onload; document.head.appendChild(script); } function loadReCAPTCHAEnterpriseScript(onload) { var script = document.createElement('script'); script.src = RECAPTCHA_ENTERPRISE_URL; script.onload = onload; document.head.appendChild(script); } /** * @license * Copyright 2021 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. */ /** * App Check provider that can obtain a reCAPTCHA V3 token and exchange it * for an App Check token. * * @public */ var ReCaptchaV3Provider = /** @class */ (function () { /** * Create a ReCaptchaV3Provider instance. * @param siteKey - ReCAPTCHA V3 siteKey. */ function ReCaptchaV3Provider(_siteKey) { this._siteKey = _siteKey; /** * Throttle requests on certain error codes to prevent too many retries * in a short time. */ this._throttleData = null; } /** * Returns an App Check token. * @internal */ ReCaptchaV3Provider.prototype.getToken = function () { var _a, _b, _c; return __awaiter(this, void 0, void 0, function () { var attestedClaimsToken, result, e_1; return __generator(this, function (_d) { switch (_d.label) { case 0: throwIfThrottled(this._throttleData); return [4 /*yield*/, getToken$1(this._app).catch(function (_e) { // reCaptcha.execute() throws null which is not very descriptive. throw ERROR_FACTORY.create("recaptcha-error" /* AppCheckError.RECAPTCHA_ERROR */); })]; case 1: attestedClaimsToken = _d.sent(); // Check if a failure state was set by the recaptcha "error-callback". if (!((_a = getStateReference(this._app).reCAPTCHAState) === null || _a === void 0 ? void 0 : _a.succeeded)) { throw ERROR_FACTORY.create("recaptcha-error" /* AppCheckError.RECAPTCHA_ERROR */); } _d.label = 2; case 2: _d.trys.push([2, 4, , 5]); return [4 /*yield*/, exchangeToken(getExchangeRecaptchaV3TokenRequest(this._app, attestedClaimsToken), this._heartbeatServiceProvider)]; case 3: result = _d.sent(); return [3 /*break*/, 5]; case 4: e_1 = _d.sent(); if ((_b = e_1.code) === null || _b === void 0 ? void 0 : _b.includes("fetch-status-error" /* AppCheckError.FETCH_STATUS_ERROR */)) { this._throttleData = setBackoff(Number((_c = e_1.customData) === null || _c === void 0 ? void 0 : _c.httpStatus), this._throttleData); throw ERROR_FACTORY.create("throttled" /* AppCheckError.THROTTLED */, { time: getDurationString(this._throttleData.allowRequestsAfter - Date.now()), httpStatus: this._throttleData.httpStatus }); } else { throw e_1; } case 5: // If successful, clear throttle data. this._throttleData = null; return [2 /*return*/, result]; } }); }); }; /** * @internal */ ReCaptchaV3Provider.prototype.initialize = function (app) { this._app = app; this._heartbeatServiceProvider = _getProvider(app, 'heartbeat'); initializeV3(app, this._siteKey).catch(function () { /* we don't care about the initialization result */ }); }; /** * @internal */ ReCaptchaV3Provider.prototype.isEqual = function (otherProvider) { if (otherProvider instanceof ReCaptchaV3Provider) { return this._siteKey === otherProvider._siteKey; } else { return false; } }; return ReCaptchaV3Provider; }()); /** * App Check provider that can obtain a reCAPTCHA Enterprise token and exchange it * for an App Check token. * * @public */ var ReCaptchaEnterpriseProvider = /** @class */ (function () { /** * Create a ReCaptchaEnterpriseProvider instance. * @param siteKey - reCAPTCHA Enterprise score-based site key. */ function ReCaptchaEnterpriseProvider(_siteKey) { this._siteKey = _siteKey; /** * Throttle requests on certain error codes to prevent too many retries * in a short time. */ this._throttleData = null; } /** * Returns an App Check token. * @internal */ ReCaptchaEnterpriseProvider.prototype.getToken = function () { var _a, _b, _c; return __awaiter(this, void 0, void 0, function () { var attestedClaimsToken, result, e_2; return __generator(this, function (_d) { switch (_d.label) { case 0: throwIfThrottled(this._throttleData); return [4 /*yield*/, getToken$1(this._app).catch(function (_e) { // reCaptcha.execute() throws null which is not very descriptive. throw ERROR_FACTORY.create("recaptcha-error" /* AppCheckError.RECAPTCHA_ERROR */); })]; case 1: attestedClaimsToken = _d.sent(); // Check if a failure state was set by the recaptcha "error-callback". if (!((_a = getStateReference(this._app).reCAPTCHAState) === null || _a === void 0 ? void 0 : _a.succeeded)) { throw ERROR_FACTORY.create("recaptcha-error" /* AppCheckError.RECAPTCHA_ERROR */); } _d.label = 2; case 2: _d.trys.push([2, 4, , 5]); return [4 /*yield*/, exchangeToken(getExchangeRecaptchaEnterpriseTokenRequest(this._app, attestedClaimsToken), this._heartbeatServiceProvider)]; case 3: result = _d.sent(); return [3 /*break*/, 5]; case 4: e_2 = _d.sent(); if ((_b = e_2.code) === null || _b === void 0 ? void 0 : _b.includes("fetch-status-error" /* AppCheckError.FETCH_STATUS_ERROR */)) { this._throttleData = setBackoff(Number((_c = e_2.customData) === null || _c === void 0 ? void 0 : _c.httpStatus), this._throttleData); throw ERROR_FACTORY.create("throttled" /* AppCheckError.THROTTLED */, { time: getDurationString(this._throttleData.allowRequestsAfter - Date.now()), httpStatus: this._throttleData.httpStatus }); } else { throw e_2; } case 5: // If successful, clear throttle data. this._throttleData = null; return [2 /*return*/, result]; } }); }); }; /** * @internal */ ReCaptchaEnterpriseProvider.prototype.initialize = function (app) { this._app = app; this._heartbeatServiceProvider = _getProvider(app, 'heartbeat'); initializeEnterprise(app, this._siteKey).catch(function () { /* we don't care about the initialization result */ }); }; /** * @internal */ ReCaptchaEnterpriseProvider.prototype.isEqual = function (otherProvider) { if (otherProvider instanceof ReCaptchaEnterpriseProvider) { return this._siteKey === otherProvider._siteKey; } else { return false; } }; return ReCaptchaEnterpriseProvider; }()); /** * Custom provider class. * @public */ var CustomProvider = /** @class */ (function () { function CustomProvider(_customProviderOptions) { this._customProviderOptions = _customProviderOptions; } /** * @internal */ CustomProvider.prototype.getToken = function () { return __awaiter(this, void 0, void 0, function () { var customToken, issuedAtTimeSeconds, issuedAtTimeMillis; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this._customProviderOptions.getToken()]; case 1: customToken = _a.sent(); issuedAtTimeSeconds = issuedAtTime(customToken.token); issuedAtTimeMillis = issuedAtTimeSeconds !== null && issuedAtTimeSeconds < Date.now() && issuedAtTimeSeconds > 0 ? issuedAtTimeSeconds * 1000 : Date.now(); return [2 /*return*/, __assign(__assign({}, customToken), { issuedAtTimeMillis: issuedAtTimeMillis })]; } }); }); }; /** * @internal */ CustomProvider.prototype.initialize = function (app) { this._app = app; }; /** * @internal */ CustomProvider.prototype.isEqual = function (otherProvider) { if (otherProvider instanceof CustomProvider) { return (this._customProviderOptions.getToken.toString() === otherProvider._customProviderOptions.getToken.toString()); } else { return false; } }; return CustomProvider; }()); /** * Set throttle data to block requests until after a certain time * depending on the failed request's status code. * @param httpStatus - Status code of failed request. * @param throttleData - `ThrottleData` object containing previous throttle * data state. * @returns Data about current throttle state and expiration time. */ function setBackoff(httpStatus, throttleData) { /** * Block retries for 1 day for the following error codes: * * 404: Likely malformed URL. * * 403: * - Attestation failed * - Wrong API key * - Project deleted */ if (httpStatus === 404 || httpStatus === 403) { return { backoffCount: 1, allowRequestsAfter: Date.now() + ONE_DAY, httpStatus: httpStatus }; } else { /** * For all other error codes, the time when it is ok to retry again * is based on exponential backoff. */ var backoffCount = throttleData ? throttleData.backoffCount : 0; var backoffMillis = calculateBackoffMillis(backoffCount, 1000, 2); return { backoffCount: backoffCount + 1, allowRequestsAfter: Date.now() + backoffMillis, httpStatus: httpStatus }; } } function throwIfThrottled(throttleData) { if (throttleData) { if (Date.now() - throttleData.allowRequestsAfter <= 0) { // If before, throw. throw ERROR_FACTORY.create("throttled" /* AppCheckError.THROTTLED */, { time: getDurationString(throttleData.allowRequestsAfter - Date.now()), httpStatus: throttleData.httpStatus }); } } } /** * @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. */ /** * Activate App Check for the given app. Can be called only once per app. * @param app - the {@link @firebase/app#FirebaseApp} to activate App Check for * @param options - App Check initialization options * @public */ function initializeAppCheck(app, options) { if (app === void 0) { app = getApp(); } app = getModularInstance(app); var provider = _getProvider(app, 'app-check'); // Ensure initializeDebugMode() is only called once. if (!getDebugState().initialized) { initializeDebugMode(); } // Log a message containing the debug token when `initializeAppCheck()` // is called in debug mode. if (isDebugMode()) { // Do not block initialization to get the token for the message. void getDebugToken().then(function (token) { // Not using logger because I don't think we ever want this accidentally hidden. return console.log("App Check debug token: ".concat(token, ". You will need to add it to your app's App Check settings in the Firebase console for it to work.")); }); } if (provider.isInitialized()) { var existingInstance = provider.getImmediate(); var initialOptions = provider.getOptions(); if (initialOptions.isTokenAutoRefreshEnabled === options.isTokenAutoRefreshEnabled && initialOptions.provider.isEqual(options.provider)) { return existingInstance; } else { throw ERROR_FACTORY.create("already-initialized" /* AppCheckError.ALREADY_INITIALIZED */, { appName: app.name }); } } var appCheck = provider.initialize({ options: options }); _activate(app, options.provider, options.isTokenAutoRefreshEnabled); // If isTokenAutoRefreshEnabled is false, do not send any requests to the // exchange endpoint without an explicit call from the user either directly // or through another Firebase library (storage, functions, etc.) if (getStateReference(app).isTokenAutoRefreshEnabled) { // Adding a listener will start the refresher and fetch a token if needed. // This gets a token ready and prevents a delay when an internal library // requests the token. // Listener function does not need to do anything, its base functionality // of calling getToken() already fetches token and writes it to memory/storage. addTokenListener(appCheck, "INTERNAL" /* ListenerType.INTERNAL */, function () { }); } return appCheck; } /** * Activate App Check * @param app - Firebase app to activate App Check for. * @param provider - reCAPTCHA v3 provider or * custom token provider. * @param isTokenAutoRefreshEnabled - If true, the SDK automatically * refreshes App Check tokens as needed. If undefined, defaults to the * value of `app.automaticDataCollectionEnabled`, which defaults to * false and can be set in the app config. */ function _activate(app, provider, isTokenAutoRefreshEnabled) { // Create an entry in the APP_CHECK_STATES map. Further changes should // directly mutate this object. var state = setInitialState(app, __assign({}, DEFAULT_STATE)); state.activated = true; state.provider = provider; // Read cached token from storage if it exists and store it in memory. state.cachedTokenPromise = readTokenFromStorage(app).then(function (cachedToken) { if (cachedToken && isValid(cachedToken)) { state.token = cachedToken; // notify all listeners with the cached token notifyTokenListeners(app, { token: cachedToken.token }); } return cachedToken; }); // Use value of global `automaticDataCollectionEnabled` (which // itself defaults to false if not specified in config) if // `isTokenAutoRefreshEnabled` param was not provided by user. state.isTokenAutoRefreshEnabled = isTokenAutoRefreshEnabled === undefined ? app.automaticDataCollectionEnabled : isTokenAutoRefreshEnabled; state.provider.initialize(app); } /** * Set whether App Check will automatically refresh tokens as needed. * * @param appCheckInstance - The App Check service instance. * @param isTokenAutoRefreshEnabled - If true, the SDK automatically * refreshes App Check tokens as needed. This overrides any value set * during `initializeAppCheck()`. * @public */ function setTokenAutoRefreshEnabled(appCheckInstance, isTokenAutoRefreshEnabled) { var app = appCheckInstance.app; var state = getStateReference(app); // This will exist if any product libraries have called // `addTokenListener()` if (state.tokenRefresher) { if (isTokenAutoRefreshEnabled === true) { state.tokenRefresher.start(); } else { state.tokenRefresher.stop(); } } state.isTokenAutoRefreshEnabled = isTokenAutoRefreshEnabled; } /** * Get the current App Check token. Attaches to the most recent * in-flight request if one is present. Returns null if no token * is present and no token requests are in-flight. * * @param appCheckInstance - The App Check service instance. * @param forceRefresh - If true, will always try to fetch a fresh token. * If false, will use a cached token if found in storage. * @public */ function getToken(appCheckInstance, forceRefresh) { return __awaiter(this, void 0, void 0, function () { var result; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, getToken$2(appCheckInstance, forceRefresh)]; case 1: result = _a.sent(); if (result.error) { throw result.error; } return [2 /*return*/, { token: result.token }]; } }); }); } /** * Requests a Firebase App Check token. This method should be used * only if you need to authorize requests to a non-Firebase backend. * * Returns limited-use tokens that are intended for use with your * non-Firebase backend endpoints that are protected with * * Replay Protection. This method * does not affect the token generation behavior of the * #getAppCheckToken() method. * * @param appCheckInstance - The App Check service instance. * @returns The limited use token. * @public */ function getLimitedUseToken(appCheckInstance) { return getLimitedUseToken$1(appCheckInstance); } /** * Wraps `addTokenListener`/`removeTokenListener` methods in an `Observer` * pattern for public use. */ function onTokenChanged(appCheckInstance, onNextOrObserver, onError, /** * NOTE: Although an `onCompletion` callback can be provided, it will * never be called because the token stream is never-ending. * It is added only for API consistency with the observer pattern, which * we follow in JS APIs. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars onCompletion) { var nextFn = function () { }; var errorFn = function () { }; if (onNextOrObserver.next != null) { nextFn = onNextOrObserver.next.bind(onNextOrObserver); } else { nextFn = onNextOrObserver; } if (onNextOrObserver.error != null) { errorFn = onNextOrObserver.error.bind(onNextOrObserver); } else if (onError) { errorFn = onError; } addTokenListener(appCheckInstance, "EXTERNAL" /* ListenerType.EXTERNAL */, nextFn, errorFn); return function () { return removeTokenListener(appCheckInstance.app, nextFn); }; } /** * The Firebase App Check Web SDK. * * @remarks * Firebase App Check does not work in a Node.js environment using `ReCaptchaV3Provider` or * `ReCaptchaEnterpriseProvider`, but can be used in Node.js if you use * `CustomProvider` and write your own attestation method. * * @packageDocumentation */ var APP_CHECK_NAME = 'app-check'; var APP_CHECK_NAME_INTERNAL = 'app-check-internal'; function registerAppCheck() { // The public interface _registerComponent(new Component(APP_CHECK_NAME, function (container) { // getImmediate for FirebaseApp will always succeed var app = container.getProvider('app').getImmediate(); var heartbeatServiceProvider = container.getProvider('heartbeat'); return factory(app, heartbeatServiceProvider); }, "PUBLIC" /* ComponentType.PUBLIC */) .setInstantiationMode("EXPLICIT" /* InstantiationMode.EXPLICIT */) /** * Initialize app-check-internal after app-check is initialized to make AppCheck available to * other Firebase SDKs */ .setInstanceCreatedCallback(function (container, _identifier, _appcheckService) { container.getProvider(APP_CHECK_NAME_INTERNAL).initialize(); })); // The internal interface used by other Firebase products _registerComponent(new Component(APP_CHECK_NAME_INTERNAL, function (container) { var appCheck = container.getProvider('app-check').getImmediate(); return internalFactory(appCheck); }, "PUBLIC" /* ComponentType.PUBLIC */).setInstantiationMode("EXPLICIT" /* InstantiationMode.EXPLICIT */)); registerVersion(name, version); } registerAppCheck(); export { CustomProvider, ReCaptchaEnterpriseProvider, ReCaptchaV3Provider, getLimitedUseToken, getToken, initializeAppCheck, onTokenChanged, setTokenAutoRefreshEnabled }; //# sourceMappingURL=index.esm.js.map