diff options
Diffstat (limited to 'frontend-old/node_modules/@firebase/app-check/dist/index.cjs.js')
| -rw-r--r-- | frontend-old/node_modules/@firebase/app-check/dist/index.cjs.js | 1704 |
1 files changed, 1704 insertions, 0 deletions
diff --git a/frontend-old/node_modules/@firebase/app-check/dist/index.cjs.js b/frontend-old/node_modules/@firebase/app-check/dist/index.cjs.js new file mode 100644 index 0000000..340b411 --- /dev/null +++ b/frontend-old/node_modules/@firebase/app-check/dist/index.cjs.js @@ -0,0 +1,1704 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { value: true }); + +var app = require('@firebase/app'); +var component = require('@firebase/component'); +var util = require('@firebase/util'); +var logger$1 = require('@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. + */ +const APP_CHECK_STATES = new Map(); +const DEFAULT_STATE = { + activated: false, + tokenObservers: [] +}; +const DEBUG_STATE = { + initialized: false, + enabled: false +}; +/** + * Gets a reference to the state object. + */ +function getStateReference(app) { + return APP_CHECK_STATES.get(app) || { ...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. + */ +const BASE_ENDPOINT = 'https://content-firebaseappcheck.googleapis.com/v1'; +const EXCHANGE_RECAPTCHA_TOKEN_METHOD = 'exchangeRecaptchaV3Token'; +const EXCHANGE_RECAPTCHA_ENTERPRISE_TOKEN_METHOD = 'exchangeRecaptchaEnterpriseToken'; +const EXCHANGE_DEBUG_TOKEN_METHOD = 'exchangeDebugToken'; +const 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. + */ +const 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 +class Refresher { + constructor(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!'); + } + } + start() { + this.nextErrorWaitInterval = this.lowerBound; + this.process(true).catch(() => { + /* we don't care about the result */ + }); + } + stop() { + if (this.pending) { + this.pending.reject('cancelled'); + this.pending = null; + } + } + isRunning() { + return !!this.pending; + } + async process(hasSucceeded) { + this.stop(); + try { + this.pending = new util.Deferred(); + this.pending.promise.catch(_e => { + /* ignore */ + }); + await sleep(this.getNextRun(hasSucceeded)); + // 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(); + await this.pending.promise; + this.pending = new util.Deferred(); + this.pending.promise.catch(_e => { + /* ignore */ + }); + await this.operation(); + this.pending.resolve(); + await this.pending.promise; + this.process(true).catch(() => { + /* we don't care about the result */ + }); + } + catch (error) { + if (this.retryPolicy(error)) { + this.process(false).catch(() => { + /* we don't care about the result */ + }); + } + else { + this.stop(); + } + } + } + getNextRun(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. + const 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; + } + } +} +function sleep(ms) { + return new Promise(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. + */ +const ERRORS = { + ["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.', + ["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.', + ["fetch-network-error" /* AppCheckError.FETCH_NETWORK_ERROR */]: 'Fetch failed to connect to a network. Check Internet connection. ' + + 'Original error: {$originalErrorMessage}.', + ["fetch-parse-error" /* AppCheckError.FETCH_PARSE_ERROR */]: 'Fetch client could not parse response.' + + ' Original error: {$originalErrorMessage}.', + ["fetch-status-error" /* AppCheckError.FETCH_STATUS_ERROR */]: 'Fetch server returned an HTTP error status. HTTP status: {$httpStatus}.', + ["storage-open" /* AppCheckError.STORAGE_OPEN */]: 'Error thrown when opening storage. Original error: {$originalErrorMessage}.', + ["storage-get" /* AppCheckError.STORAGE_GET */]: 'Error thrown when reading from storage. Original error: {$originalErrorMessage}.', + ["storage-set" /* AppCheckError.STORAGE_WRITE */]: 'Error thrown when writing to storage. Original error: {$originalErrorMessage}.', + ["recaptcha-error" /* AppCheckError.RECAPTCHA_ERROR */]: 'ReCAPTCHA error.', + ["initial-throttle" /* AppCheckError.INITIAL_THROTTLE */]: `{$httpStatus} error. Attempts allowed again after {$time}`, + ["throttled" /* AppCheckError.THROTTLED */]: `Requests throttled due to previous {$httpStatus} error. Attempts allowed again after {$time}` +}; +const ERROR_FACTORY = new util.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 = false) { + if (isEnterprise) { + return self.grecaptcha?.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) { + const totalSeconds = Math.round(durationInMillis / 1000); + const days = Math.floor(totalSeconds / (3600 * 24)); + const hours = Math.floor((totalSeconds - days * 3600 * 24) / 3600); + const minutes = Math.floor((totalSeconds - days * 3600 * 24 - hours * 3600) / 60); + const seconds = totalSeconds - days * 3600 * 24 - hours * 3600 - minutes * 60; + let 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. + */ +async function exchangeToken({ url, body }, heartbeatServiceProvider) { + const headers = { + 'Content-Type': 'application/json' + }; + // If heartbeat service exists, add heartbeat header string to the header. + const heartbeatService = heartbeatServiceProvider.getImmediate({ + optional: true + }); + if (heartbeatService) { + const heartbeatsHeader = await heartbeatService.getHeartbeatsHeader(); + if (heartbeatsHeader) { + headers['X-Firebase-Client'] = heartbeatsHeader; + } + } + const options = { + method: 'POST', + body: JSON.stringify(body), + headers + }; + let response; + try { + response = await fetch(url, options); + } + catch (originalError) { + throw ERROR_FACTORY.create("fetch-network-error" /* AppCheckError.FETCH_NETWORK_ERROR */, { + originalErrorMessage: originalError?.message + }); + } + if (response.status !== 200) { + throw ERROR_FACTORY.create("fetch-status-error" /* AppCheckError.FETCH_STATUS_ERROR */, { + httpStatus: response.status + }); + } + let responseBody; + try { + // JSON parsing throws SyntaxError if the response body isn't a JSON string. + responseBody = await response.json(); + } + catch (originalError) { + throw ERROR_FACTORY.create("fetch-parse-error" /* AppCheckError.FETCH_PARSE_ERROR */, { + originalErrorMessage: originalError?.message + }); + } + // Protobuf duration format. + // https://developers.google.com/protocol-buffers/docs/reference/java/com/google/protobuf/Duration + const 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: ${responseBody.ttl}` + }); + } + const timeToLiveAsNumber = Number(match[1]) * 1000; + const now = Date.now(); + return { + token: responseBody.token, + expireTimeMillis: now + timeToLiveAsNumber, + issuedAtTimeMillis: now + }; +} +function getExchangeRecaptchaV3TokenRequest(app, reCAPTCHAToken) { + const { projectId, appId, apiKey } = app.options; + return { + url: `${BASE_ENDPOINT}/projects/${projectId}/apps/${appId}:${EXCHANGE_RECAPTCHA_TOKEN_METHOD}?key=${apiKey}`, + body: { + 'recaptcha_v3_token': reCAPTCHAToken + } + }; +} +function getExchangeRecaptchaEnterpriseTokenRequest(app, reCAPTCHAToken) { + const { projectId, appId, apiKey } = app.options; + return { + url: `${BASE_ENDPOINT}/projects/${projectId}/apps/${appId}:${EXCHANGE_RECAPTCHA_ENTERPRISE_TOKEN_METHOD}?key=${apiKey}`, + body: { + 'recaptcha_enterprise_token': reCAPTCHAToken + } + }; +} +function getExchangeDebugTokenRequest(app, debugToken) { + const { projectId, appId, apiKey } = app.options; + return { + url: `${BASE_ENDPOINT}/projects/${projectId}/apps/${appId}:${EXCHANGE_DEBUG_TOKEN_METHOD}?key=${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. + */ +const DB_NAME = 'firebase-app-check-database'; +const DB_VERSION = 1; +const STORE_NAME = 'firebase-app-check-store'; +const DEBUG_TOKEN_KEY = 'debug-token'; +let dbPromise = null; +function getDBPromise() { + if (dbPromise) { + return dbPromise; + } + dbPromise = new Promise((resolve, reject) => { + try { + const request = indexedDB.open(DB_NAME, DB_VERSION); + request.onsuccess = event => { + resolve(event.target.result); + }; + request.onerror = event => { + reject(ERROR_FACTORY.create("storage-open" /* AppCheckError.STORAGE_OPEN */, { + originalErrorMessage: event.target.error?.message + })); + }; + request.onupgradeneeded = event => { + const 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?.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); +} +async function write(key, value) { + const db = await getDBPromise(); + const transaction = db.transaction(STORE_NAME, 'readwrite'); + const store = transaction.objectStore(STORE_NAME); + const request = store.put({ + compositeKey: key, + value + }); + return new Promise((resolve, reject) => { + request.onsuccess = _event => { + resolve(); + }; + transaction.onerror = event => { + reject(ERROR_FACTORY.create("storage-set" /* AppCheckError.STORAGE_WRITE */, { + originalErrorMessage: event.target.error?.message + })); + }; + }); +} +async function read(key) { + const db = await getDBPromise(); + const transaction = db.transaction(STORE_NAME, 'readonly'); + const store = transaction.objectStore(STORE_NAME); + const request = store.get(key); + return new Promise((resolve, reject) => { + request.onsuccess = event => { + const result = event.target.result; + if (result) { + resolve(result.value); + } + else { + resolve(undefined); + } + }; + transaction.onerror = event => { + reject(ERROR_FACTORY.create("storage-get" /* AppCheckError.STORAGE_GET */, { + originalErrorMessage: event.target.error?.message + })); + }; + }); +} +function computeKey(app) { + return `${app.options.appId}-${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. + */ +const logger = new logger$1.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 + */ +async function readTokenFromStorage(app) { + if (util.isIndexedDBAvailable()) { + let token = undefined; + try { + token = await readTokenFromIndexedDB(app); + } + catch (e) { + // swallow the error and return undefined + logger.warn(`Failed to read token from IndexedDB. Error: ${e}`); + } + return token; + } + return undefined; +} +/** + * Always resolves. In case of an error writing to indexeddb, print a warning and resolve the promise + */ +function writeTokenToStorage(app, token) { + if (util.isIndexedDBAvailable()) { + return writeTokenToIndexedDB(app, token).catch(e => { + // swallow the error and resolve the promise + logger.warn(`Failed to write token to IndexedDB. Error: ${e}`); + }); + } + return Promise.resolve(); +} +async function readOrCreateDebugTokenFromStorage() { + /** + * Theoretically race condition can happen if we read, then write in 2 separate transactions. + * But it won't happen here, because this function will be called exactly once. + */ + let existingDebugToken = undefined; + try { + existingDebugToken = await readDebugTokenFromIndexedDB(); + } + catch (_e) { + // failed to read from indexeddb. We assume there is no existing debug token, and generate a new one. + } + if (!existingDebugToken) { + // create a new debug token + // This function is only available in secure contexts. See https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts + const newToken = crypto.randomUUID(); + // We don't need to block on writing to indexeddb + // In case persistence failed, a new debug token will be generated every time 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(e => logger.warn(`Failed to persist debug token to IndexedDB. Error: ${e}`)); + return newToken; + } + else { + 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() { + const debugState = getDebugState(); + return debugState.enabled; +} +async function getDebugToken() { + const state = getDebugState(); + if (state.enabled && state.token) { + return state.token.promise; + } + else { + // should not happen! + throw Error(` + Can't get debug token in production mode. + `); + } +} +function initializeDebugMode() { + const globals = util.getGlobal(); + const 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; + const deferredToken = new util.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. +const defaultTokenErrorData = { error: 'UNKNOWN_ERROR' }; +/** + * Stringify and base64 encode token error data. + * + * @param tokenError Error data, currently hardcoded. + */ +function formatDummyToken(tokenErrorData) { + return util.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 + */ +async function getToken$2(appCheck, forceRefresh = false, shouldLogErrors = false) { + const app = appCheck.app; + ensureActivated(app); + const state = getStateReference(app); + /** + * First check if there is a token in memory from a previous `getToken()` call. + */ + let token = state.token; + let 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 there is no valid token in memory, try to load token from indexedDB. + */ + if (!token) { + // cachedTokenPromise contains the token found in IndexedDB or undefined if not found. + const cachedToken = await state.cachedTokenPromise; + if (cachedToken) { + if (isValid(cachedToken)) { + token = cachedToken; + } + else { + // If there was an invalid token in the indexedDB cache, clear it. + await writeTokenToStorage(app, undefined); + } + } + } + // Return the cached token (from either memory or indexedDB) if it's valid + if (!forceRefresh && token && isValid(token)) { + return { + token: token.token + }; + } + // Only set to true if this `getToken()` call is making the actual + // REST call to the exchange endpoint, versus waiting for an already + // in-flight call (see debug and regular exchange endpoint paths below) + let shouldCallListeners = false; + /** + * DEBUG MODE + * If debug mode is set, and there is no cached token, fetch a new App + * Check token using the debug token, and return it directly. + */ + if (isDebugMode()) { + try { + // Avoid making another call to the exchange endpoint if one is in flight. + if (!state.exchangeTokenPromise) { + state.exchangeTokenPromise = exchangeToken(getExchangeDebugTokenRequest(app, await getDebugToken()), appCheck.heartbeatServiceProvider).finally(() => { + // Clear promise when settled - either resolved or rejected. + state.exchangeTokenPromise = undefined; + }); + shouldCallListeners = true; + } + const tokenFromDebugExchange = await state.exchangeTokenPromise; + // Write debug token to indexedDB. + await writeTokenToStorage(app, tokenFromDebugExchange); + // Write debug token to state. + state.token = tokenFromDebugExchange; + return { token: tokenFromDebugExchange.token }; + } + catch (e) { + if (e.code === `appCheck/${"throttled" /* AppCheckError.THROTTLED */}` || + e.code === + `appCheck/${"initial-throttle" /* AppCheckError.INITIAL_THROTTLE */}`) { + // Warn if throttled, but do not treat it as an error. + logger.warn(e.message); + } + else if (shouldLogErrors) { + logger.error(e); + } + // Return dummy token and error + return makeDummyTokenResult(e); + } + } + /** + * There are no valid tokens in memory or indexedDB and we are not in + * debug mode. + * Request a new token from the exchange endpoint. + */ + try { + // 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(() => { + // Clear promise when settled - either resolved or rejected. + state.exchangeTokenPromise = undefined; + }); + shouldCallListeners = true; + } + token = await getStateReference(app).exchangeTokenPromise; + } + catch (e) { + if (e.code === `appCheck/${"throttled" /* AppCheckError.THROTTLED */}` || + e.code === `appCheck/${"initial-throttle" /* AppCheckError.INITIAL_THROTTLE */}`) { + // Warn if throttled, but do not treat it as an error. + logger.warn(e.message); + } + else if (shouldLogErrors) { + logger.error(e); + } + // Always save error to be added to dummy token. + error = e; + } + let interopTokenResult; + if (!token) { + // If token is undefined, there must be an error. + // Return a dummy token along with the error. + interopTokenResult = makeDummyTokenResult(error); + } + else if (error) { + 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); + } + } + else { + 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; + await writeTokenToStorage(app, token); + } + if (shouldCallListeners) { + notifyTokenListeners(app, interopTokenResult); + } + return interopTokenResult; +} +/** + * Internal API for limited use tokens. Skips all FAC state and simply calls + * the underlying provider. + */ +async function getLimitedUseToken$1(appCheck) { + const app = appCheck.app; + ensureActivated(app); + const { provider } = getStateReference(app); + if (isDebugMode()) { + const debugToken = await getDebugToken(); + const { token } = await exchangeToken(getExchangeDebugTokenRequest(app, debugToken), appCheck.heartbeatServiceProvider); + return { token }; + } + else { + // provider is definitely valid since we ensure AppCheck was activated + const { token } = await provider.getToken(); + return { token }; + } +} +function addTokenListener(appCheck, type, listener, onError) { + const { app } = appCheck; + const state = getStateReference(app); + const tokenObserver = { + next: listener, + error: onError, + type + }; + state.tokenObservers = [...state.tokenObservers, tokenObserver]; + // Invoke the listener async immediately if there is a valid token + // in memory. + if (state.token && isValid(state.token)) { + const validToken = state.token; + Promise.resolve() + .then(() => { + listener({ token: validToken.token }); + initTokenRefresher(appCheck); + }) + .catch(() => { + /* 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(() => initTokenRefresher(appCheck)); +} +function removeTokenListener(app, listener) { + const state = getStateReference(app); + const newObservers = state.tokenObservers.filter(tokenObserver => 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) { + const { app } = appCheck; + const state = getStateReference(app); + // Create the refresher but don't start it if `isTokenAutoRefreshEnabled` + // is not true. + let refresher = state.tokenRefresher; + if (!refresher) { + refresher = createTokenRefresher(appCheck); + state.tokenRefresher = refresher; + } + if (!refresher.isRunning() && state.isTokenAutoRefreshEnabled) { + refresher.start(); + } +} +function createTokenRefresher(appCheck) { + const { app } = appCheck; + 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. + async () => { + const state = getStateReference(app); + // If there is no token, we will try to load it from storage and use it + // If there is a token, we force refresh it because we know it's going to expire soon + let result; + if (!state.token) { + result = await getToken$2(appCheck); + } + else { + result = await getToken$2(appCheck, true); + } + /** + * 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 true; + }, () => { + const state = getStateReference(app); + if (state.token) { + // issuedAtTime + (50% * total TTL) + 5 minutes + let 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) + const 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) { + const observers = getStateReference(app).tokenObservers; + for (const observer of observers) { + 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 + }; +} + +/** + * @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. + */ +class AppCheckService { + constructor(app, heartbeatServiceProvider) { + this.app = app; + this.heartbeatServiceProvider = heartbeatServiceProvider; + } + _delete() { + const { tokenObservers } = getStateReference(this.app); + for (const tokenObserver of tokenObservers) { + removeTokenListener(this.app, tokenObserver.next); + } + return Promise.resolve(); + } +} +function factory(app, heartbeatServiceProvider) { + return new AppCheckService(app, heartbeatServiceProvider); +} +function internalFactory(appCheck) { + return { + getToken: forceRefresh => getToken$2(appCheck, forceRefresh), + getLimitedUseToken: () => getLimitedUseToken$1(appCheck), + addTokenListener: listener => addTokenListener(appCheck, "INTERNAL" /* ListenerType.INTERNAL */, listener), + removeTokenListener: listener => removeTokenListener(appCheck.app, listener) + }; +} + +const name = "@firebase/app-check"; +const version = "0.11.0"; + +/** + * @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. + */ +const RECAPTCHA_URL = 'https://www.google.com/recaptcha/api.js'; +const RECAPTCHA_ENTERPRISE_URL = 'https://www.google.com/recaptcha/enterprise.js'; +function initializeV3(app, siteKey) { + const initialized = new util.Deferred(); + const state = getStateReference(app); + state.reCAPTCHAState = { initialized }; + const divId = makeDiv(app); + const grecaptcha = getRecaptcha(false); + if (!grecaptcha) { + loadReCAPTCHAV3Script(() => { + const 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) { + const initialized = new util.Deferred(); + const state = getStateReference(app); + state.reCAPTCHAState = { initialized }; + const divId = makeDiv(app); + const grecaptcha = getRecaptcha(true); + if (!grecaptcha) { + loadReCAPTCHAEnterpriseScript(() => { + const 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(() => { + // 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) { + const divId = `fire_app_check_${app.name}`; + const invisibleDiv = document.createElement('div'); + invisibleDiv.id = divId; + invisibleDiv.style.display = 'none'; + document.body.appendChild(invisibleDiv); + return divId; +} +async function getToken$1(app) { + ensureActivated(app); + // ensureActivated() guarantees that reCAPTCHAState is set + const reCAPTCHAState = getStateReference(app).reCAPTCHAState; + const recaptcha = await reCAPTCHAState.initialized.promise; + return new Promise((resolve, _reject) => { + // Updated after initialization is complete. + const reCAPTCHAState = getStateReference(app).reCAPTCHAState; + recaptcha.ready(() => { + 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) { + const widgetId = grecaptcha.render(container, { + sitekey: siteKey, + size: 'invisible', + // Success callback - set state + callback: () => { + getStateReference(app).reCAPTCHAState.succeeded = true; + }, + // Failure callback - set state + 'error-callback': () => { + getStateReference(app).reCAPTCHAState.succeeded = false; + } + }); + const state = getStateReference(app); + state.reCAPTCHAState = { + ...state.reCAPTCHAState, // state.reCAPTCHAState is set in the initialize() + widgetId + }; +} +function loadReCAPTCHAV3Script(onload) { + const script = document.createElement('script'); + script.src = RECAPTCHA_URL; + script.onload = onload; + document.head.appendChild(script); +} +function loadReCAPTCHAEnterpriseScript(onload) { + const 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 + */ +class ReCaptchaV3Provider { + /** + * Create a ReCaptchaV3Provider instance. + * @param siteKey - ReCAPTCHA V3 siteKey. + */ + constructor(_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 + */ + async getToken() { + throwIfThrottled(this._throttleData); + // Top-level `getToken()` has already checked that App Check is initialized + // and therefore this._app and this._heartbeatServiceProvider are available. + const attestedClaimsToken = await getToken$1(this._app).catch(_e => { + // reCaptcha.execute() throws null which is not very descriptive. + throw ERROR_FACTORY.create("recaptcha-error" /* AppCheckError.RECAPTCHA_ERROR */); + }); + // Check if a failure state was set by the recaptcha "error-callback". + if (!getStateReference(this._app).reCAPTCHAState?.succeeded) { + throw ERROR_FACTORY.create("recaptcha-error" /* AppCheckError.RECAPTCHA_ERROR */); + } + let result; + try { + result = await exchangeToken(getExchangeRecaptchaV3TokenRequest(this._app, attestedClaimsToken), this._heartbeatServiceProvider); + } + catch (e) { + if (e.code?.includes("fetch-status-error" /* AppCheckError.FETCH_STATUS_ERROR */)) { + this._throttleData = setBackoff(Number(e.customData?.httpStatus), this._throttleData); + throw ERROR_FACTORY.create("initial-throttle" /* AppCheckError.INITIAL_THROTTLE */, { + time: getDurationString(this._throttleData.allowRequestsAfter - Date.now()), + httpStatus: this._throttleData.httpStatus + }); + } + else { + throw e; + } + } + // If successful, clear throttle data. + this._throttleData = null; + return result; + } + /** + * @internal + */ + initialize(app$1) { + this._app = app$1; + this._heartbeatServiceProvider = app._getProvider(app$1, 'heartbeat'); + initializeV3(app$1, this._siteKey).catch(() => { + /* we don't care about the initialization result */ + }); + } + /** + * @internal + */ + isEqual(otherProvider) { + if (otherProvider instanceof ReCaptchaV3Provider) { + return this._siteKey === otherProvider._siteKey; + } + else { + return false; + } + } +} +/** + * App Check provider that can obtain a reCAPTCHA Enterprise token and exchange it + * for an App Check token. + * + * @public + */ +class ReCaptchaEnterpriseProvider { + /** + * Create a ReCaptchaEnterpriseProvider instance. + * @param siteKey - reCAPTCHA Enterprise score-based site key. + */ + constructor(_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 + */ + async getToken() { + throwIfThrottled(this._throttleData); + // Top-level `getToken()` has already checked that App Check is initialized + // and therefore this._app and this._heartbeatServiceProvider are available. + const attestedClaimsToken = await getToken$1(this._app).catch(_e => { + // reCaptcha.execute() throws null which is not very descriptive. + throw ERROR_FACTORY.create("recaptcha-error" /* AppCheckError.RECAPTCHA_ERROR */); + }); + // Check if a failure state was set by the recaptcha "error-callback". + if (!getStateReference(this._app).reCAPTCHAState?.succeeded) { + throw ERROR_FACTORY.create("recaptcha-error" /* AppCheckError.RECAPTCHA_ERROR */); + } + let result; + try { + result = await exchangeToken(getExchangeRecaptchaEnterpriseTokenRequest(this._app, attestedClaimsToken), this._heartbeatServiceProvider); + } + catch (e) { + if (e.code?.includes("fetch-status-error" /* AppCheckError.FETCH_STATUS_ERROR */)) { + this._throttleData = setBackoff(Number(e.customData?.httpStatus), this._throttleData); + throw ERROR_FACTORY.create("initial-throttle" /* AppCheckError.INITIAL_THROTTLE */, { + time: getDurationString(this._throttleData.allowRequestsAfter - Date.now()), + httpStatus: this._throttleData.httpStatus + }); + } + else { + throw e; + } + } + // If successful, clear throttle data. + this._throttleData = null; + return result; + } + /** + * @internal + */ + initialize(app$1) { + this._app = app$1; + this._heartbeatServiceProvider = app._getProvider(app$1, 'heartbeat'); + initializeEnterprise(app$1, this._siteKey).catch(() => { + /* we don't care about the initialization result */ + }); + } + /** + * @internal + */ + isEqual(otherProvider) { + if (otherProvider instanceof ReCaptchaEnterpriseProvider) { + return this._siteKey === otherProvider._siteKey; + } + else { + return false; + } + } +} +/** + * Custom provider class. + * @public + */ +class CustomProvider { + constructor(_customProviderOptions) { + this._customProviderOptions = _customProviderOptions; + } + /** + * @internal + */ + async getToken() { + // custom provider + const customToken = await this._customProviderOptions.getToken(); + // Try to extract IAT from custom token, in case this token is not + // being newly issued. JWT timestamps are in seconds since epoch. + const issuedAtTimeSeconds = util.issuedAtTime(customToken.token); + // Very basic validation, use current timestamp as IAT if JWT + // has no `iat` field or value is out of bounds. + const issuedAtTimeMillis = issuedAtTimeSeconds !== null && + issuedAtTimeSeconds < Date.now() && + issuedAtTimeSeconds > 0 + ? issuedAtTimeSeconds * 1000 + : Date.now(); + return { ...customToken, issuedAtTimeMillis }; + } + /** + * @internal + */ + initialize(app) { + this._app = app; + } + /** + * @internal + */ + isEqual(otherProvider) { + if (otherProvider instanceof CustomProvider) { + return (this._customProviderOptions.getToken.toString() === + otherProvider._customProviderOptions.getToken.toString()); + } + else { + return false; + } + } +} +/** + * 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 + }; + } + else { + /** + * For all other error codes, the time when it is ok to retry again + * is based on exponential backoff. + */ + const backoffCount = throttleData ? throttleData.backoffCount : 0; + const backoffMillis = util.calculateBackoffMillis(backoffCount, 1000, 2); + return { + backoffCount: backoffCount + 1, + allowRequestsAfter: Date.now() + backoffMillis, + 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$1 = app.getApp(), options) { + app$1 = util.getModularInstance(app$1); + const provider = app._getProvider(app$1, '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(token => + // Not using logger because I don't think we ever want this accidentally hidden. + console.log(`App Check debug token: ${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()) { + const existingInstance = provider.getImmediate(); + const 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$1.name + }); + } + } + const appCheck = provider.initialize({ options }); + _activate(app$1, 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$1).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 */, () => { }); + } + 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 = false) { + // Create an entry in the APP_CHECK_STATES map. Further changes should + // directly mutate this object. + const state = setInitialState(app, { ...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(cachedToken => { + if (cachedToken && isValid(cachedToken)) { + state.token = cachedToken; + // notify all listeners with the cached token + notifyTokenListeners(app, { token: cachedToken.token }); + } + return cachedToken; + }); + // Global `automaticDataCollectionEnabled` (defaults to true) and + // `isTokenAutoRefreshEnabled` must both be true. + state.isTokenAutoRefreshEnabled = + isTokenAutoRefreshEnabled && app.automaticDataCollectionEnabled; + if (!app.automaticDataCollectionEnabled && isTokenAutoRefreshEnabled) { + logger.warn('`isTokenAutoRefreshEnabled` is true but ' + + '`automaticDataCollectionEnabled` was set to false during' + + ' `initializeApp()`. This blocks automatic token refresh.'); + } + 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) { + const app = appCheckInstance.app; + const 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. If `forceRefresh` is false, this function first + * checks for a valid token in memory, then local persistence (IndexedDB). + * If not found, or if `forceRefresh` is true, it makes a request to the + * App Check endpoint for a fresh token. That request attaches + * to the most recent in-flight request if one is present. + * + * @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 + */ +async function getToken(appCheckInstance, forceRefresh) { + const result = await getToken$2(appCheckInstance, forceRefresh); + if (result.error) { + throw result.error; + } + if (result.internalError) { + throw result.internalError; + } + 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 + * <a href="https://firebase.google.com/docs/app-check/custom-resource-backend#replay-protection"> + * Replay Protection</a>. 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) { + let nextFn = () => { }; + let errorFn = () => { }; + 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 () => 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 + */ +const APP_CHECK_NAME = 'app-check'; +const APP_CHECK_NAME_INTERNAL = 'app-check-internal'; +function registerAppCheck() { + // The public interface + app._registerComponent(new component.Component(APP_CHECK_NAME, container => { + // getImmediate for FirebaseApp will always succeed + const app = container.getProvider('app').getImmediate(); + const 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((container, _identifier, _appcheckService) => { + container.getProvider(APP_CHECK_NAME_INTERNAL).initialize(); + })); + // The internal interface used by other Firebase products + app._registerComponent(new component.Component(APP_CHECK_NAME_INTERNAL, container => { + const appCheck = container.getProvider('app-check').getImmediate(); + return internalFactory(appCheck); + }, "PUBLIC" /* ComponentType.PUBLIC */).setInstantiationMode("EXPLICIT" /* InstantiationMode.EXPLICIT */)); + app.registerVersion(name, version); +} +registerAppCheck(); + +exports.CustomProvider = CustomProvider; +exports.ReCaptchaEnterpriseProvider = ReCaptchaEnterpriseProvider; +exports.ReCaptchaV3Provider = ReCaptchaV3Provider; +exports.getLimitedUseToken = getLimitedUseToken; +exports.getToken = getToken; +exports.initializeAppCheck = initializeAppCheck; +exports.onTokenChanged = onTokenChanged; +exports.setTokenAutoRefreshEnabled = setTokenAutoRefreshEnabled; +//# sourceMappingURL=index.cjs.js.map |
