import firebase from '@firebase/app';
import '@firebase/installations';
import { Component } from '@firebase/component';
import { ErrorFactory } from '@firebase/util';
import { openDb, deleteDb } from 'idb';

/**
 * @license
 * Copyright 2017 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 ERROR_MAP = {
    ["missing-app-config-values" /* MISSING_APP_CONFIG_VALUES */]: 'Missing App configuration value: "{$valueName}"',
    ["only-available-in-window" /* AVAILABLE_IN_WINDOW */]: 'This method is available in a Window context.',
    ["only-available-in-sw" /* AVAILABLE_IN_SW */]: 'This method is available in a service worker context.',
    ["permission-default" /* PERMISSION_DEFAULT */]: 'The notification permission was not granted and dismissed instead.',
    ["permission-blocked" /* PERMISSION_BLOCKED */]: 'The notification permission was not granted and blocked instead.',
    ["unsupported-browser" /* UNSUPPORTED_BROWSER */]: "This browser doesn't support the API's required to use the firebase SDK.",
    ["failed-service-worker-registration" /* FAILED_DEFAULT_REGISTRATION */]: 'We are unable to register the default service worker. {$browserErrorMessage}',
    ["token-subscribe-failed" /* TOKEN_SUBSCRIBE_FAILED */]: 'A problem occured while subscribing the user to FCM: {$errorInfo}',
    ["token-subscribe-no-token" /* TOKEN_SUBSCRIBE_NO_TOKEN */]: 'FCM returned no token when subscribing the user to push.',
    ["token-unsubscribe-failed" /* TOKEN_UNSUBSCRIBE_FAILED */]: 'A problem occured while unsubscribing the ' +
        'user from FCM: {$errorInfo}',
    ["token-update-failed" /* TOKEN_UPDATE_FAILED */]: 'A problem occured while updating the user from FCM: {$errorInfo}',
    ["token-update-no-token" /* TOKEN_UPDATE_NO_TOKEN */]: 'FCM returned no token when updating the user to push.',
    ["use-sw-after-get-token" /* USE_SW_AFTER_GET_TOKEN */]: 'The useServiceWorker() method may only be called once and must be ' +
        'called before calling getToken() to ensure your service worker is used.',
    ["invalid-sw-registration" /* INVALID_SW_REGISTRATION */]: 'The input to useServiceWorker() must be a ServiceWorkerRegistration.',
    ["invalid-bg-handler" /* INVALID_BG_HANDLER */]: 'The input to setBackgroundMessageHandler() must be a function.',
    ["invalid-vapid-key" /* INVALID_VAPID_KEY */]: 'The public VAPID key must be a string.',
    ["use-vapid-key-after-get-token" /* USE_VAPID_KEY_AFTER_GET_TOKEN */]: 'The usePublicVapidKey() method may only be called once and must be ' +
        'called before calling getToken() to ensure your VAPID key is used.'
};
const ERROR_FACTORY = new ErrorFactory('messaging', 'Messaging', ERROR_MAP);

/**
 * @license
 * Copyright 2019 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
function extractAppConfig(app) {
    if (!app || !app.options) {
        throw getMissingValueError('App Configuration Object');
    }
    if (!app.name) {
        throw getMissingValueError('App Name');
    }
    // Required app config keys
    const configKeys = [
        'projectId',
        'apiKey',
        'appId',
        'messagingSenderId'
    ];
    const { options } = app;
    for (const keyName of configKeys) {
        if (!options[keyName]) {
            throw getMissingValueError(keyName);
        }
    }
    return {
        appName: app.name,
        projectId: options.projectId,
        apiKey: options.apiKey,
        appId: options.appId,
        senderId: options.messagingSenderId
    };
}
function getMissingValueError(valueName) {
    return ERROR_FACTORY.create("missing-app-config-values" /* MISSING_APP_CONFIG_VALUES */, {
        valueName
    });
}

/**
 * @license
 * Copyright 2017 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 arrayToBase64(array) {
    const uint8Array = new Uint8Array(array);
    const base64String = btoa(String.fromCharCode(...uint8Array));
    return base64String
        .replace(/=/g, '')
        .replace(/\+/g, '-')
        .replace(/\//g, '_');
}
function base64ToArray(base64String) {
    const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
    const base64 = (base64String + padding)
        .replace(/\-/g, '+')
        .replace(/_/g, '/');
    const rawData = atob(base64);
    const outputArray = new Uint8Array(rawData.length);
    for (let i = 0; i < rawData.length; ++i) {
        outputArray[i] = rawData.charCodeAt(i);
    }
    return outputArray;
}

/**
 * @license
 * Copyright 2019 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
const OLD_DB_NAME = 'fcm_token_details_db';
/**
 * The last DB version of 'fcm_token_details_db' was 4. This is one higher,
 * so that the upgrade callback is called for all versions of the old DB.
 */
const OLD_DB_VERSION = 5;
const OLD_OBJECT_STORE_NAME = 'fcm_token_object_Store';
async function migrateOldDatabase(senderId) {
    if ('databases' in indexedDB) {
        // indexedDb.databases() is an IndexedDB v3 API
        // and does not exist in all browsers.
        // TODO: Remove typecast when it lands in TS types.
        const databases = await indexedDB.databases();
        const dbNames = databases.map(db => db.name);
        if (!dbNames.includes(OLD_DB_NAME)) {
            // old DB didn't exist, no need to open.
            return null;
        }
    }
    let tokenDetails = null;
    const db = await openDb(OLD_DB_NAME, OLD_DB_VERSION, async (db) => {
        var _a;
        if (db.oldVersion < 2) {
            // Database too old, skip migration.
            return;
        }
        if (!db.objectStoreNames.contains(OLD_OBJECT_STORE_NAME)) {
            // Database did not exist. Nothing to do.
            return;
        }
        const objectStore = db.transaction.objectStore(OLD_OBJECT_STORE_NAME);
        const value = await objectStore.index('fcmSenderId').get(senderId);
        await objectStore.clear();
        if (!value) {
            // No entry in the database, nothing to migrate.
            return;
        }
        if (db.oldVersion === 2) {
            const oldDetails = value;
            if (!oldDetails.auth || !oldDetails.p256dh || !oldDetails.endpoint) {
                return;
            }
            tokenDetails = {
                token: oldDetails.fcmToken,
                createTime: (_a = oldDetails.createTime) !== null && _a !== void 0 ? _a : Date.now(),
                subscriptionOptions: {
                    auth: oldDetails.auth,
                    p256dh: oldDetails.p256dh,
                    endpoint: oldDetails.endpoint,
                    swScope: oldDetails.swScope,
                    vapidKey: typeof oldDetails.vapidKey === 'string'
                        ? oldDetails.vapidKey
                        : arrayToBase64(oldDetails.vapidKey)
                }
            };
        }
        else if (db.oldVersion === 3) {
            const oldDetails = value;
            tokenDetails = {
                token: oldDetails.fcmToken,
                createTime: oldDetails.createTime,
                subscriptionOptions: {
                    auth: arrayToBase64(oldDetails.auth),
                    p256dh: arrayToBase64(oldDetails.p256dh),
                    endpoint: oldDetails.endpoint,
                    swScope: oldDetails.swScope,
                    vapidKey: arrayToBase64(oldDetails.vapidKey)
                }
            };
        }
        else if (db.oldVersion === 4) {
            const oldDetails = value;
            tokenDetails = {
                token: oldDetails.fcmToken,
                createTime: oldDetails.createTime,
                subscriptionOptions: {
                    auth: arrayToBase64(oldDetails.auth),
                    p256dh: arrayToBase64(oldDetails.p256dh),
                    endpoint: oldDetails.endpoint,
                    swScope: oldDetails.swScope,
                    vapidKey: arrayToBase64(oldDetails.vapidKey)
                }
            };
        }
    });
    db.close();
    // Delete all old databases.
    await deleteDb(OLD_DB_NAME);
    await deleteDb('fcm_vapid_details_db');
    await deleteDb('undefined');
    return checkTokenDetails(tokenDetails) ? tokenDetails : null;
}
function checkTokenDetails(tokenDetails) {
    if (!tokenDetails || !tokenDetails.subscriptionOptions) {
        return false;
    }
    const { subscriptionOptions } = tokenDetails;
    return (typeof tokenDetails.createTime === 'number' &&
        tokenDetails.createTime > 0 &&
        typeof tokenDetails.token === 'string' &&
        tokenDetails.token.length > 0 &&
        typeof subscriptionOptions.auth === 'string' &&
        subscriptionOptions.auth.length > 0 &&
        typeof subscriptionOptions.p256dh === 'string' &&
        subscriptionOptions.p256dh.length > 0 &&
        typeof subscriptionOptions.endpoint === 'string' &&
        subscriptionOptions.endpoint.length > 0 &&
        typeof subscriptionOptions.swScope === 'string' &&
        subscriptionOptions.swScope.length > 0 &&
        typeof subscriptionOptions.vapidKey === 'string' &&
        subscriptionOptions.vapidKey.length > 0);
}

/**
 * @license
 * Copyright 2019 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// Exported for tests.
const DATABASE_NAME = 'firebase-messaging-database';
const DATABASE_VERSION = 1;
const OBJECT_STORE_NAME = 'firebase-messaging-store';
let dbPromise = null;
function getDbPromise() {
    if (!dbPromise) {
        dbPromise = openDb(DATABASE_NAME, DATABASE_VERSION, upgradeDb => {
            // 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 (upgradeDb.oldVersion) {
                case 0:
                    upgradeDb.createObjectStore(OBJECT_STORE_NAME);
            }
        });
    }
    return dbPromise;
}
/** Gets record(s) from the objectStore that match the given key. */
async function dbGet(firebaseDependencies) {
    const key = getKey(firebaseDependencies);
    const db = await getDbPromise();
    const tokenDetails = await db
        .transaction(OBJECT_STORE_NAME)
        .objectStore(OBJECT_STORE_NAME)
        .get(key);
    if (tokenDetails) {
        return tokenDetails;
    }
    else {
        // Check if there is a tokenDetails object in the old DB.
        const oldTokenDetails = await migrateOldDatabase(firebaseDependencies.appConfig.senderId);
        if (oldTokenDetails) {
            await dbSet(firebaseDependencies, oldTokenDetails);
            return oldTokenDetails;
        }
    }
}
/** Assigns or overwrites the record for the given key with the given value. */
async function dbSet(firebaseDependencies, tokenDetails) {
    const key = getKey(firebaseDependencies);
    const db = await getDbPromise();
    const tx = db.transaction(OBJECT_STORE_NAME, 'readwrite');
    await tx.objectStore(OBJECT_STORE_NAME).put(tokenDetails, key);
    await tx.complete;
    return tokenDetails;
}
/** Removes record(s) from the objectStore that match the given key. */
async function dbRemove(firebaseDependencies) {
    const key = getKey(firebaseDependencies);
    const db = await getDbPromise();
    const tx = db.transaction(OBJECT_STORE_NAME, 'readwrite');
    await tx.objectStore(OBJECT_STORE_NAME).delete(key);
    await tx.complete;
}
function getKey({ appConfig }) {
    return appConfig.appId;
}

/**
 * @license
 * Copyright 2019 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
const DEFAULT_SW_PATH = '/firebase-messaging-sw.js';
const DEFAULT_SW_SCOPE = '/firebase-cloud-messaging-push-scope';
const DEFAULT_VAPID_KEY = 'BDOU99-h67HcA6JeFXHbSNMu7e2yNNu3RzoMj8TM4W88jITfq7ZmPvIM1Iv-4_l2LxQcYwhqby2xGpWwzjfAnG4';
const ENDPOINT = 'https://fcmregistrations.googleapis.com/v1';
/** Key of FCM Payload in Notification's data field. */
const FCM_MSG = 'FCM_MSG';
const CONSOLE_CAMPAIGN_ID = 'google.c.a.c_id';
const CONSOLE_CAMPAIGN_NAME = 'google.c.a.c_l';
const CONSOLE_CAMPAIGN_TIME = 'google.c.a.ts';
/** Set to '1' if Analytics is enabled for the campaign */
const CONSOLE_CAMPAIGN_ANALYTICS_ENABLED = 'google.c.a.e';

/**
 * @license
 * Copyright 2019 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
async function requestGetToken(firebaseDependencies, subscriptionOptions) {
    const headers = await getHeaders(firebaseDependencies);
    const body = getBody(subscriptionOptions);
    const subscribeOptions = {
        method: 'POST',
        headers,
        body: JSON.stringify(body)
    };
    let responseData;
    try {
        const response = await fetch(getEndpoint(firebaseDependencies.appConfig), subscribeOptions);
        responseData = await response.json();
    }
    catch (err) {
        throw ERROR_FACTORY.create("token-subscribe-failed" /* TOKEN_SUBSCRIBE_FAILED */, {
            errorInfo: err
        });
    }
    if (responseData.error) {
        const message = responseData.error.message;
        throw ERROR_FACTORY.create("token-subscribe-failed" /* TOKEN_SUBSCRIBE_FAILED */, {
            errorInfo: message
        });
    }
    if (!responseData.token) {
        throw ERROR_FACTORY.create("token-subscribe-no-token" /* TOKEN_SUBSCRIBE_NO_TOKEN */);
    }
    return responseData.token;
}
async function requestUpdateToken(firebaseDependencies, tokenDetails) {
    const headers = await getHeaders(firebaseDependencies);
    const body = getBody(tokenDetails.subscriptionOptions);
    const updateOptions = {
        method: 'PATCH',
        headers,
        body: JSON.stringify(body)
    };
    let responseData;
    try {
        const response = await fetch(`${getEndpoint(firebaseDependencies.appConfig)}/${tokenDetails.token}`, updateOptions);
        responseData = await response.json();
    }
    catch (err) {
        throw ERROR_FACTORY.create("token-update-failed" /* TOKEN_UPDATE_FAILED */, {
            errorInfo: err
        });
    }
    if (responseData.error) {
        const message = responseData.error.message;
        throw ERROR_FACTORY.create("token-update-failed" /* TOKEN_UPDATE_FAILED */, {
            errorInfo: message
        });
    }
    if (!responseData.token) {
        throw ERROR_FACTORY.create("token-update-no-token" /* TOKEN_UPDATE_NO_TOKEN */);
    }
    return responseData.token;
}
async function requestDeleteToken(firebaseDependencies, token) {
    const headers = await getHeaders(firebaseDependencies);
    const unsubscribeOptions = {
        method: 'DELETE',
        headers
    };
    try {
        const response = await fetch(`${getEndpoint(firebaseDependencies.appConfig)}/${token}`, unsubscribeOptions);
        const responseData = await response.json();
        if (responseData.error) {
            const message = responseData.error.message;
            throw ERROR_FACTORY.create("token-unsubscribe-failed" /* TOKEN_UNSUBSCRIBE_FAILED */, {
                errorInfo: message
            });
        }
    }
    catch (err) {
        throw ERROR_FACTORY.create("token-unsubscribe-failed" /* TOKEN_UNSUBSCRIBE_FAILED */, {
            errorInfo: err
        });
    }
}
function getEndpoint({ projectId }) {
    return `${ENDPOINT}/projects/${projectId}/registrations`;
}
async function getHeaders({ appConfig, installations }) {
    const authToken = await installations.getToken();
    return new Headers({
        'Content-Type': 'application/json',
        Accept: 'application/json',
        'x-goog-api-key': appConfig.apiKey,
        'x-goog-firebase-installations-auth': `FIS ${authToken}`
    });
}
function getBody({ p256dh, auth, endpoint, vapidKey }) {
    const body = {
        web: {
            endpoint,
            auth,
            p256dh
        }
    };
    if (vapidKey !== DEFAULT_VAPID_KEY) {
        body.web.applicationPubKey = vapidKey;
    }
    return body;
}

/**
 * @license
 * Copyright 2019 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/** UpdateRegistration will be called once every week. */
const TOKEN_EXPIRATION_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
async function getToken(firebaseDependencies, swRegistration, vapidKey) {
    if (Notification.permission !== 'granted') {
        throw ERROR_FACTORY.create("permission-blocked" /* PERMISSION_BLOCKED */);
    }
    // If a PushSubscription exists it's returned, otherwise a new subscription
    // is generated and returned.
    const pushSubscription = await getPushSubscription(swRegistration, vapidKey);
    const tokenDetails = await dbGet(firebaseDependencies);
    const subscriptionOptions = {
        vapidKey,
        swScope: swRegistration.scope,
        endpoint: pushSubscription.endpoint,
        auth: arrayToBase64(pushSubscription.getKey('auth')),
        p256dh: arrayToBase64(pushSubscription.getKey('p256dh'))
    };
    if (!tokenDetails) {
        // No token, get a new one.
        return getNewToken(firebaseDependencies, subscriptionOptions);
    }
    else if (!isTokenValid(tokenDetails.subscriptionOptions, subscriptionOptions)) {
        // Invalid token, get a new one.
        try {
            await requestDeleteToken(firebaseDependencies, tokenDetails.token);
        }
        catch (e) {
            // Suppress errors because of #2364
            console.warn(e);
        }
        return getNewToken(firebaseDependencies, subscriptionOptions);
    }
    else if (Date.now() >= tokenDetails.createTime + TOKEN_EXPIRATION_MS) {
        // Weekly token refresh
        return updateToken({
            token: tokenDetails.token,
            createTime: Date.now(),
            subscriptionOptions
        }, firebaseDependencies, swRegistration);
    }
    else {
        // Valid token, nothing to do.
        return tokenDetails.token;
    }
}
/**
 * This method deletes the token from the database, unsubscribes the token from
 * FCM, and unregisters the push subscription if it exists.
 */
async function deleteToken(firebaseDependencies, swRegistration) {
    const tokenDetails = await dbGet(firebaseDependencies);
    if (tokenDetails) {
        await requestDeleteToken(firebaseDependencies, tokenDetails.token);
        await dbRemove(firebaseDependencies);
    }
    // Unsubscribe from the push subscription.
    const pushSubscription = await swRegistration.pushManager.getSubscription();
    if (pushSubscription) {
        return pushSubscription.unsubscribe();
    }
    // If there's no SW, consider it a success.
    return true;
}
async function updateToken(tokenDetails, firebaseDependencies, swRegistration) {
    try {
        const updatedToken = await requestUpdateToken(firebaseDependencies, tokenDetails);
        const updatedTokenDetails = Object.assign({ token: updatedToken, createTime: Date.now() }, tokenDetails);
        await dbSet(firebaseDependencies, updatedTokenDetails);
        return updatedToken;
    }
    catch (e) {
        await deleteToken(firebaseDependencies, swRegistration);
        throw e;
    }
}
async function getNewToken(firebaseDependencies, subscriptionOptions) {
    const token = await requestGetToken(firebaseDependencies, subscriptionOptions);
    const tokenDetails = {
        token,
        createTime: Date.now(),
        subscriptionOptions
    };
    await dbSet(firebaseDependencies, tokenDetails);
    return tokenDetails.token;
}
/**
 * Gets a PushSubscription for the current user.
 */
async function getPushSubscription(swRegistration, vapidKey) {
    const subscription = await swRegistration.pushManager.getSubscription();
    if (subscription) {
        return subscription;
    }
    return swRegistration.pushManager.subscribe({
        userVisibleOnly: true,
        // Chrome <= 75 doesn't support base64-encoded VAPID key. For backward compatibility, VAPID key
        // submitted to pushManager#subscribe must be of type Uint8Array.
        applicationServerKey: base64ToArray(vapidKey)
    });
}
/**
 * Checks if the saved tokenDetails object matches the configuration provided.
 */
function isTokenValid(dbOptions, currentOptions) {
    const isVapidKeyEqual = currentOptions.vapidKey === dbOptions.vapidKey;
    const isEndpointEqual = currentOptions.endpoint === dbOptions.endpoint;
    const isAuthEqual = currentOptions.auth === dbOptions.auth;
    const isP256dhEqual = currentOptions.p256dh === dbOptions.p256dh;
    return isVapidKeyEqual && isEndpointEqual && isAuthEqual && isP256dhEqual;
}

/**
 * @license
 * Copyright 2017 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var MessageType;
(function (MessageType) {
    MessageType["PUSH_RECEIVED"] = "push-received";
    MessageType["NOTIFICATION_CLICKED"] = "notification-clicked";
})(MessageType || (MessageType = {}));

/**
 * @license
 * Copyright 2019 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
function isConsoleMessage(data) {
    // This message has a campaign ID, meaning it was sent using the
    // Firebase Console.
    return typeof data === 'object' && !!data && CONSOLE_CAMPAIGN_ID in data;
}

/**
 * @license
 * Copyright 2017 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.
 */
class WindowController {
    constructor(firebaseDependencies) {
        this.firebaseDependencies = firebaseDependencies;
        this.vapidKey = null;
        this.onMessageCallback = null;
        navigator.serviceWorker.addEventListener('message', e => this.messageEventListener(e));
    }
    get app() {
        return this.firebaseDependencies.app;
    }
    async getToken() {
        if (!this.vapidKey) {
            this.vapidKey = DEFAULT_VAPID_KEY;
        }
        const swRegistration = await this.getServiceWorkerRegistration();
        // Check notification permission.
        if (Notification.permission === 'default') {
            // The user hasn't allowed or denied notifications yet. Ask them.
            await Notification.requestPermission();
        }
        if (Notification.permission !== 'granted') {
            throw ERROR_FACTORY.create("permission-blocked" /* PERMISSION_BLOCKED */);
        }
        return getToken(this.firebaseDependencies, swRegistration, this.vapidKey);
    }
    async deleteToken() {
        const swRegistration = await this.getServiceWorkerRegistration();
        return deleteToken(this.firebaseDependencies, swRegistration);
    }
    /**
     * Request permission if it is not currently granted.
     *
     * @return Resolves if the permission was granted, rejects otherwise.
     *
     * @deprecated Use Notification.requestPermission() instead.
     * https://developer.mozilla.org/en-US/docs/Web/API/Notification/requestPermission
     */
    async requestPermission() {
        if (Notification.permission === 'granted') {
            return;
        }
        const permissionResult = await Notification.requestPermission();
        if (permissionResult === 'granted') {
            return;
        }
        else if (permissionResult === 'denied') {
            throw ERROR_FACTORY.create("permission-blocked" /* PERMISSION_BLOCKED */);
        }
        else {
            throw ERROR_FACTORY.create("permission-default" /* PERMISSION_DEFAULT */);
        }
    }
    // TODO: Deprecate this and make VAPID key a parameter in getToken.
    usePublicVapidKey(vapidKey) {
        if (this.vapidKey !== null) {
            throw ERROR_FACTORY.create("use-vapid-key-after-get-token" /* USE_VAPID_KEY_AFTER_GET_TOKEN */);
        }
        if (typeof vapidKey !== 'string' || vapidKey.length === 0) {
            throw ERROR_FACTORY.create("invalid-vapid-key" /* INVALID_VAPID_KEY */);
        }
        this.vapidKey = vapidKey;
    }
    useServiceWorker(swRegistration) {
        if (!(swRegistration instanceof ServiceWorkerRegistration)) {
            throw ERROR_FACTORY.create("invalid-sw-registration" /* INVALID_SW_REGISTRATION */);
        }
        if (this.swRegistration) {
            throw ERROR_FACTORY.create("use-sw-after-get-token" /* USE_SW_AFTER_GET_TOKEN */);
        }
        this.swRegistration = swRegistration;
    }
    /**
     * @param nextOrObserver An observer object or a function triggered on
     * message.
     * @return The unsubscribe function for the observer.
     */
    // TODO: Simplify this to only accept a function and not an Observer.
    onMessage(nextOrObserver) {
        this.onMessageCallback =
            typeof nextOrObserver === 'function'
                ? nextOrObserver
                : nextOrObserver.next;
        return () => {
            this.onMessageCallback = null;
        };
    }
    setBackgroundMessageHandler() {
        throw ERROR_FACTORY.create("only-available-in-sw" /* AVAILABLE_IN_SW */);
    }
    // Unimplemented
    onTokenRefresh() {
        return () => { };
    }
    /**
     * Creates or updates the default service worker registration.
     * @return The service worker registration to be used for the push service.
     */
    async getServiceWorkerRegistration() {
        if (!this.swRegistration) {
            try {
                this.swRegistration = await navigator.serviceWorker.register(DEFAULT_SW_PATH, {
                    scope: DEFAULT_SW_SCOPE
                });
                // The timing when browser updates sw when sw has an update is unreliable by my experiment.
                // It leads to version conflict when the SDK upgrades to a newer version in the main page, but
                // sw is stuck with the old version. For example, https://github.com/firebase/firebase-js-sdk/issues/2590
                // The following line reliably updates sw if there was an update.
                this.swRegistration.update().catch(() => {
                    /* it is non blocking and we don't care if it failed */
                });
            }
            catch (e) {
                throw ERROR_FACTORY.create("failed-service-worker-registration" /* FAILED_DEFAULT_REGISTRATION */, {
                    browserErrorMessage: e.message
                });
            }
        }
        return this.swRegistration;
    }
    async messageEventListener(event) {
        var _a;
        if (!((_a = event.data) === null || _a === void 0 ? void 0 : _a.firebaseMessaging)) {
            // Not a message from FCM
            return;
        }
        const { type, payload } = event.data.firebaseMessaging;
        if (this.onMessageCallback && type === MessageType.PUSH_RECEIVED) {
            this.onMessageCallback(payload);
        }
        const { data } = payload;
        if (isConsoleMessage(data) &&
            data[CONSOLE_CAMPAIGN_ANALYTICS_ENABLED] === '1') {
            // Analytics is enabled on this message, so we should log it.
            await this.logEvent(type, data);
        }
    }
    async logEvent(messageType, data) {
        const eventType = getEventType(messageType);
        const analytics = await this.firebaseDependencies.analyticsProvider.get();
        analytics.logEvent(eventType, {
            /* eslint-disable camelcase */
            message_id: data[CONSOLE_CAMPAIGN_ID],
            message_name: data[CONSOLE_CAMPAIGN_NAME],
            message_time: data[CONSOLE_CAMPAIGN_TIME],
            message_device_time: Math.floor(Date.now() / 1000)
            /* eslint-enable camelcase */
        });
    }
}
function getEventType(messageType) {
    switch (messageType) {
        case MessageType.NOTIFICATION_CLICKED:
            return 'notification_open';
        case MessageType.PUSH_RECEIVED:
            return 'notification_foreground';
        default:
            throw new Error();
    }
}

/**
 * @license
 * Copyright 2019 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/** Returns a promise that resolves after given time passes. */
function sleep(ms) {
    return new Promise(resolve => {
        setTimeout(resolve, ms);
    });
}

/**
 * @license
 * Copyright 2017 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.
 */
class SwController {
    constructor(firebaseDependencies) {
        this.firebaseDependencies = firebaseDependencies;
        this.vapidKey = null;
        this.bgMessageHandler = null;
        self.addEventListener('push', e => {
            e.waitUntil(this.onPush(e));
        });
        self.addEventListener('pushsubscriptionchange', e => {
            e.waitUntil(this.onSubChange(e));
        });
        self.addEventListener('notificationclick', e => {
            e.waitUntil(this.onNotificationClick(e));
        });
    }
    get app() {
        return this.firebaseDependencies.app;
    }
    /**
     * Calling setBackgroundMessageHandler will opt in to some specific
     * behaviours.
     * 1.) If a notification doesn't need to be shown due to a window already
     * being visible, then push messages will be sent to the page.
     * 2.) If a notification needs to be shown, and the message contains no
     * notification data this method will be called
     * and the promise it returns will be passed to event.waitUntil.
     * If you do not set this callback then all push messages will let and the
     * developer can handle them in a their own 'push' event callback
     *
     * @param callback The callback to be called when a push message is received
     * and a notification must be shown. The callback will be given the data from
     * the push message.
     */
    setBackgroundMessageHandler(callback) {
        if (!callback || typeof callback !== 'function') {
            throw ERROR_FACTORY.create("invalid-bg-handler" /* INVALID_BG_HANDLER */);
        }
        this.bgMessageHandler = callback;
    }
    // TODO: Remove getToken from SW Controller.
    // Calling this from an old SW can cause all kinds of trouble.
    async getToken() {
        var _a, _b;
        if (!this.vapidKey) {
            // Call getToken using the current VAPID key if there already is a token.
            // This is needed because usePublicVapidKey was not available in SW.
            // It will be removed when vapidKey becomes a parameter of getToken, or
            // when getToken is removed from SW.
            const tokenDetails = await dbGet(this.firebaseDependencies);
            this.vapidKey = (_b = (_a = tokenDetails === null || tokenDetails === void 0 ? void 0 : tokenDetails.subscriptionOptions) === null || _a === void 0 ? void 0 : _a.vapidKey) !== null && _b !== void 0 ? _b : DEFAULT_VAPID_KEY;
        }
        return getToken(this.firebaseDependencies, self.registration, this.vapidKey);
    }
    // TODO: Remove deleteToken from SW Controller.
    // Calling this from an old SW can cause all kinds of trouble.
    deleteToken() {
        return deleteToken(this.firebaseDependencies, self.registration);
    }
    requestPermission() {
        throw ERROR_FACTORY.create("only-available-in-window" /* AVAILABLE_IN_WINDOW */);
    }
    // TODO: Deprecate this and make VAPID key a parameter in getToken.
    // TODO: Remove this together with getToken from SW Controller.
    usePublicVapidKey(vapidKey) {
        if (this.vapidKey !== null) {
            throw ERROR_FACTORY.create("use-vapid-key-after-get-token" /* USE_VAPID_KEY_AFTER_GET_TOKEN */);
        }
        if (typeof vapidKey !== 'string' || vapidKey.length === 0) {
            throw ERROR_FACTORY.create("invalid-vapid-key" /* INVALID_VAPID_KEY */);
        }
        this.vapidKey = vapidKey;
    }
    useServiceWorker() {
        throw ERROR_FACTORY.create("only-available-in-window" /* AVAILABLE_IN_WINDOW */);
    }
    onMessage() {
        throw ERROR_FACTORY.create("only-available-in-window" /* AVAILABLE_IN_WINDOW */);
    }
    onTokenRefresh() {
        throw ERROR_FACTORY.create("only-available-in-window" /* AVAILABLE_IN_WINDOW */);
    }
    /**
     * A handler for push events that shows notifications based on the content of
     * the payload.
     *
     * The payload must be a JSON-encoded Object with a `notification` key. The
     * value of the `notification` property will be used as the NotificationOptions
     * object passed to showNotification. Additionally, the `title` property of the
     * notification object will be used as the title.
     *
     * If there is no notification data in the payload then no notification will be
     * shown.
     */
    async onPush(event) {
        const payload = getMessagePayload(event);
        if (!payload) {
            return;
        }
        const clientList = await getClientList();
        if (hasVisibleClients(clientList)) {
            // App in foreground. Send to page.
            return sendMessageToWindowClients(clientList, payload);
        }
        const notificationDetails = getNotificationData(payload);
        if (notificationDetails) {
            await showNotification(notificationDetails);
        }
        else if (this.bgMessageHandler) {
            await this.bgMessageHandler(payload);
        }
    }
    async onSubChange(event) {
        var _a, _b;
        const { newSubscription } = event;
        if (!newSubscription) {
            // Subscription revoked, delete token
            await deleteToken(this.firebaseDependencies, self.registration);
            return;
        }
        const tokenDetails = await dbGet(this.firebaseDependencies);
        await deleteToken(this.firebaseDependencies, self.registration);
        await getToken(this.firebaseDependencies, self.registration, (_b = (_a = tokenDetails === null || tokenDetails === void 0 ? void 0 : tokenDetails.subscriptionOptions) === null || _a === void 0 ? void 0 : _a.vapidKey) !== null && _b !== void 0 ? _b : DEFAULT_VAPID_KEY);
    }
    async onNotificationClick(event) {
        var _a, _b;
        const payload = (_b = (_a = event.notification) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b[FCM_MSG];
        if (!payload) {
            // Not an FCM notification, do nothing.
            return;
        }
        else if (event.action) {
            // User clicked on an action button.
            // This will allow devs to act on action button clicks by using a custom
            // onNotificationClick listener that they define.
            return;
        }
        // Prevent other listeners from receiving the event
        event.stopImmediatePropagation();
        event.notification.close();
        const link = getLink(payload);
        if (!link) {
            return;
        }
        let client = await getWindowClient(link);
        if (!client) {
            // Unable to find window client so need to open one.
            // This also focuses the opened client.
            client = await self.clients.openWindow(link);
            // Wait three seconds for the client to initialize and set up the message
            // handler so that it can receive the message.
            await sleep(3000);
        }
        else {
            client = await client.focus();
        }
        if (!client) {
            // Window Client will not be returned if it's for a third party origin.
            return;
        }
        const message = createNewMessage(MessageType.NOTIFICATION_CLICKED, payload);
        return client.postMessage(message);
    }
}
function getMessagePayload({ data }) {
    if (!data) {
        return null;
    }
    try {
        return data.json();
    }
    catch (err) {
        // Not JSON so not an FCM message.
        return null;
    }
}
function getNotificationData(payload) {
    if (!payload || typeof payload.notification !== 'object') {
        return;
    }
    const notificationInformation = Object.assign({}, payload.notification);
    // Put the message payload under FCM_MSG name so we can identify the
    // notification as being an FCM notification vs a notification from
    // somewhere else (i.e. normal web push or developer generated
    // notification).
    notificationInformation.data = Object.assign(Object.assign({}, payload.notification.data), { [FCM_MSG]: payload });
    return notificationInformation;
}
/**
 * @param url The URL to look for when focusing a client.
 * @return Returns an existing window client or a newly opened WindowClient.
 */
async function getWindowClient(url) {
    // Use URL to normalize the URL when comparing to windowClients.
    // This at least handles whether to include trailing slashes or not
    const parsedURL = new URL(url, self.location.href);
    const clientList = await getClientList();
    for (const client of clientList) {
        const parsedClientUrl = new URL(client.url, self.location.href);
        if (parsedClientUrl.host === parsedURL.host) {
            return client;
        }
    }
    return null;
}
/**
 * @returns If there is currently a visible WindowClient, this method will
 * resolve to true, otherwise false.
 */
function hasVisibleClients(clientList) {
    return clientList.some(client => client.visibilityState === 'visible' &&
        // Ignore chrome-extension clients as that matches the background pages
        // of extensions, which are always considered visible for some reason.
        !client.url.startsWith('chrome-extension://'));
}
/**
 * @param payload The data from the push event that should be sent to all
 * available pages.
 * @returns Returns a promise that resolves once the message has been sent to
 * all WindowClients.
 */
function sendMessageToWindowClients(clientList, payload) {
    const message = createNewMessage(MessageType.PUSH_RECEIVED, payload);
    for (const client of clientList) {
        client.postMessage(message);
    }
}
function getClientList() {
    return self.clients.matchAll({
        type: 'window',
        includeUncontrolled: true
        // TS doesn't know that "type: 'window'" means it'll return WindowClient[]
    });
}
function createNewMessage(type, payload) {
    return {
        firebaseMessaging: { type, payload }
    };
}
function showNotification(details) {
    var _a;
    const title = (_a = details.title) !== null && _a !== void 0 ? _a : '';
    const { actions } = details;
    // Note: Firefox does not support the maxActions property.
    // https://developer.mozilla.org/en-US/docs/Web/API/notification/maxActions
    const { maxActions } = Notification;
    if (actions && maxActions && actions.length > maxActions) {
        console.warn(`This browser only supports ${maxActions} actions. The remaining actions will not be displayed.`);
    }
    return self.registration.showNotification(title, details);
}
function getLink(payload) {
    var _a, _b, _c;
    // eslint-disable-next-line camelcase
    const link = (_b = (_a = payload.fcmOptions) === null || _a === void 0 ? void 0 : _a.link) !== null && _b !== void 0 ? _b : (_c = payload.notification) === null || _c === void 0 ? void 0 : _c.click_action;
    if (link) {
        return link;
    }
    if (isConsoleMessage(payload.data)) {
        // Notification created in the Firebase Console. Redirect to origin.
        return self.location.origin;
    }
    else {
        return null;
    }
}

/**
 * @license
 * Copyright 2017 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 MESSAGING_NAME = 'messaging';
function factoryMethod(container) {
    // Dependencies.
    const app = container.getProvider('app').getImmediate();
    const appConfig = extractAppConfig(app);
    const installations = container.getProvider('installations').getImmediate();
    const analyticsProvider = container.getProvider('analytics-internal');
    const firebaseDependencies = {
        app,
        appConfig,
        installations,
        analyticsProvider
    };
    if (!isSupported()) {
        throw ERROR_FACTORY.create("unsupported-browser" /* UNSUPPORTED_BROWSER */);
    }
    if (self && 'ServiceWorkerGlobalScope' in self) {
        // Running in ServiceWorker context
        return new SwController(firebaseDependencies);
    }
    else {
        // Assume we are in the window context.
        return new WindowController(firebaseDependencies);
    }
}
const NAMESPACE_EXPORTS = {
    isSupported
};
firebase.INTERNAL.registerComponent(new Component(MESSAGING_NAME, factoryMethod, "PUBLIC" /* PUBLIC */).setServiceProps(NAMESPACE_EXPORTS));
function isSupported() {
    if (self && 'ServiceWorkerGlobalScope' in self) {
        // Running in ServiceWorker context
        return isSWControllerSupported();
    }
    else {
        // Assume we are in the window context.
        return isWindowControllerSupported();
    }
}
/**
 * Checks to see if the required APIs exist.
 */
function isWindowControllerSupported() {
    return ('indexedDB' in window &&
        indexedDB !== null &&
        navigator.cookieEnabled &&
        'serviceWorker' in navigator &&
        'PushManager' in window &&
        'Notification' in window &&
        'fetch' in window &&
        ServiceWorkerRegistration.prototype.hasOwnProperty('showNotification') &&
        PushSubscription.prototype.hasOwnProperty('getKey'));
}
/**
 * Checks to see if the required APIs exist within SW Context.
 */
function isSWControllerSupported() {
    return ('indexedDB' in self &&
        indexedDB !== null &&
        'PushManager' in self &&
        'Notification' in self &&
        ServiceWorkerRegistration.prototype.hasOwnProperty('showNotification') &&
        PushSubscription.prototype.hasOwnProperty('getKey'));
}
//# sourceMappingURL=index.esm2017.js.map
