1
0
mirror of https://github.com/musix-org/musix-oss synced 2024-11-14 03:30:18 +00:00
musix-oss/node_modules/@firebase/messaging/dist/index.esm2017.js

1201 lines
46 KiB
JavaScript
Raw Normal View History

2020-03-03 20:30:50 +00:00
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 Inc.
*
* 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 Inc.
*
* 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 Inc.
*
* 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, '_');
}
/**
* @license
* Copyright 2019 Google Inc.
*
* 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, (_a !== 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 Inc.
*
* 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 Inc.
*
* 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 Inc.
*
* 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 Inc.
*
* 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,
applicationServerKey: 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 Inc.
*
* 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 Inc.
*
* 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 Inc.
*
* 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
});
}
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 Inc.
*
* 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 Inc.
*
* 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, _c;
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 = (_c = (_b = (_a = tokenDetails) === null || _a === void 0 ? void 0 : _a.subscriptionOptions) === null || _b === void 0 ? void 0 : _b.vapidKey, (_c !== null && _c !== void 0 ? _c : 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, _c;
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, (_c = (_b = (_a = tokenDetails) === null || _a === void 0 ? void 0 : _a.subscriptionOptions) === null || _b === void 0 ? void 0 : _b.vapidKey, (_c !== null && _c !== void 0 ? _c : 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).href;
const clientList = await getClientList();
for (const client of clientList) {
const parsedClientUrl = new URL(client.url, self.location.href).href;
if (parsedClientUrl === parsedURL) {
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, (_a !== null && _a !== void 0 ? _a : ''));
const { actions } = details;
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, (_b !== 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 Inc.
*
* 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