'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 = {
    /**
     * 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 {
            const debugToken = await getDebugToken();
            // Avoid making another call to the exchange endpoint if one is in flight.
            if (!state.exchangeTokenPromise) {
                state.exchangeTokenPromise = exchangeToken(getExchangeDebugTokenRequest(app, debugToken), 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 request = getExchangeDebugTokenRequest(app, debugToken);
        request.body['limited_use'] = true;
        const { token } = await exchangeToken(request, appCheck.heartbeatServiceProvider);
        return { token };
    }
    else {
        // provider is definitely valid since we ensure AppCheck was activated
        const { token } = await provider.getToken(true /* isLimitedUse */);
        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.13.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');
    // This param is required when we plan to render a widget explicitly.
    script.src = RECAPTCHA_ENTERPRISE_URL + '?render=explicit';
    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(isLimitedUse = false) {
        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 {
            const request = getExchangeRecaptchaV3TokenRequest(this._app, attestedClaimsToken);
            if (isLimitedUse) {
                request.body['limited_use'] = true;
            }
            result = await exchangeToken(request, 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(isLimitedUse = false) {
        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 {
            const request = getExchangeRecaptchaEnterpriseTokenRequest(this._app, attestedClaimsToken);
            if (isLimitedUse) {
                request.body['limited_use'] = true;
            }
            result = await exchangeToken(request, 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 &&
            !!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
 */
/**
 * @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.
 */
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
