import firebase from '@firebase/app'; import { __spread, __awaiter, __generator, __extends, __assign } from 'tslib'; import { ErrorFactory, createSubscribe } from '@firebase/util'; /** * @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 _a; var ERROR_MAP = (_a = {}, _a["only-available-in-window" /* AVAILABLE_IN_WINDOW */] = 'This method is available in a Window context.', _a["only-available-in-sw" /* AVAILABLE_IN_SW */] = 'This method is available in a service worker context.', _a["should-be-overriden" /* SHOULD_BE_INHERITED */] = 'This method should be overriden by extended classes.', _a["bad-sender-id" /* BAD_SENDER_ID */] = "Please ensure that 'messagingSenderId' is set " + 'correctly in the options passed into firebase.initializeApp().', _a["permission-default" /* PERMISSION_DEFAULT */] = 'The required permissions were not granted and dismissed instead.', _a["permission-blocked" /* PERMISSION_BLOCKED */] = 'The required permissions were not granted and blocked instead.', _a["unsupported-browser" /* UNSUPPORTED_BROWSER */] = "This browser doesn't support the API's " + 'required to use the firebase SDK.', _a["notifications-blocked" /* NOTIFICATIONS_BLOCKED */] = 'Notifications have been blocked.', _a["failed-serviceworker-registration" /* FAILED_DEFAULT_REGISTRATION */] = 'We are unable to register the ' + 'default service worker. {$browserErrorMessage}', _a["sw-registration-expected" /* SW_REGISTRATION_EXPECTED */] = 'A service worker registration was the expected input.', _a["get-subscription-failed" /* GET_SUBSCRIPTION_FAILED */] = 'There was an error when trying to get ' + 'any existing Push Subscriptions.', _a["invalid-saved-token" /* INVALID_SAVED_TOKEN */] = 'Unable to access details of the saved token.', _a["sw-reg-redundant" /* SW_REG_REDUNDANT */] = 'The service worker being used for push was made redundant.', _a["token-subscribe-failed" /* TOKEN_SUBSCRIBE_FAILED */] = 'A problem occured while subscribing the user to FCM: {$errorInfo}', _a["token-subscribe-no-token" /* TOKEN_SUBSCRIBE_NO_TOKEN */] = 'FCM returned no token when subscribing the user to push.', _a["token-subscribe-no-push-set" /* TOKEN_SUBSCRIBE_NO_PUSH_SET */] = 'FCM returned an invalid response when getting an FCM token.', _a["token-unsubscribe-failed" /* TOKEN_UNSUBSCRIBE_FAILED */] = 'A problem occured while unsubscribing the ' + 'user from FCM: {$errorInfo}', _a["token-update-failed" /* TOKEN_UPDATE_FAILED */] = 'A problem occured while updating the user from FCM: {$errorInfo}', _a["token-update-no-token" /* TOKEN_UPDATE_NO_TOKEN */] = 'FCM returned no token when updating the user to push.', _a["use-sw-before-get-token" /* USE_SW_BEFORE_GET_TOKEN */] = 'The useServiceWorker() method may only be called once and must be ' + 'called before calling getToken() to ensure your service worker is used.', _a["invalid-delete-token" /* INVALID_DELETE_TOKEN */] = 'You must pass a valid token into ' + 'deleteToken(), i.e. the token from getToken().', _a["delete-token-not-found" /* DELETE_TOKEN_NOT_FOUND */] = 'The deletion attempt for token could not ' + 'be performed as the token was not found.', _a["delete-scope-not-found" /* DELETE_SCOPE_NOT_FOUND */] = 'The deletion attempt for service worker ' + 'scope could not be performed as the scope was not found.', _a["bg-handler-function-expected" /* BG_HANDLER_FUNCTION_EXPECTED */] = 'The input to setBackgroundMessageHandler() must be a function.', _a["no-window-client-to-msg" /* NO_WINDOW_CLIENT_TO_MSG */] = 'An attempt was made to message a non-existant window client.', _a["unable-to-resubscribe" /* UNABLE_TO_RESUBSCRIBE */] = 'There was an error while re-subscribing ' + 'the FCM token for push messaging. Will have to resubscribe the ' + 'user on next visit. {$errorInfo}', _a["no-fcm-token-for-resubscribe" /* NO_FCM_TOKEN_FOR_RESUBSCRIBE */] = 'Could not find an FCM token ' + 'and as a result, unable to resubscribe. Will have to resubscribe the ' + 'user on next visit.', _a["failed-to-delete-token" /* FAILED_TO_DELETE_TOKEN */] = 'Unable to delete the currently saved token.', _a["no-sw-in-reg" /* NO_SW_IN_REG */] = 'Even though the service worker registration was ' + 'successful, there was a problem accessing the service worker itself.', _a["bad-scope" /* BAD_SCOPE */] = 'The service worker scope must be a string with at ' + 'least one character.', _a["bad-vapid-key" /* BAD_VAPID_KEY */] = 'The public VAPID key is not a Uint8Array with 65 bytes.', _a["bad-subscription" /* BAD_SUBSCRIPTION */] = 'The subscription must be a valid PushSubscription.', _a["bad-token" /* BAD_TOKEN */] = 'The FCM Token used for storage / lookup was not ' + 'a valid token string.', _a["bad-push-set" /* BAD_PUSH_SET */] = 'The FCM push set used for storage / lookup was not ' + 'not a valid push set string.', _a["failed-delete-vapid-key" /* FAILED_DELETE_VAPID_KEY */] = 'The VAPID key could not be deleted.', _a["invalid-public-vapid-key" /* INVALID_PUBLIC_VAPID_KEY */] = 'The public VAPID key must be a string.', _a["use-public-key-before-get-token" /* USE_PUBLIC_KEY_BEFORE_GET_TOKEN */] = 'The usePublicVapidKey() method may only be called once and must be ' + 'called before calling getToken() to ensure your VAPID key is used.', _a["public-vapid-key-decryption-failed" /* PUBLIC_KEY_DECRYPTION_FAILED */] = 'The public VAPID key did not equal 65 bytes when decrypted.', _a); var errorFactory = new ErrorFactory('messaging', 'Messaging', ERROR_MAP); /** * @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 DEFAULT_PUBLIC_VAPID_KEY = new Uint8Array([ 0x04, 0x33, 0x94, 0xf7, 0xdf, 0xa1, 0xeb, 0xb1, 0xdc, 0x03, 0xa2, 0x5e, 0x15, 0x71, 0xdb, 0x48, 0xd3, 0x2e, 0xed, 0xed, 0xb2, 0x34, 0xdb, 0xb7, 0x47, 0x3a, 0x0c, 0x8f, 0xc4, 0xcc, 0xe1, 0x6f, 0x3c, 0x8c, 0x84, 0xdf, 0xab, 0xb6, 0x66, 0x3e, 0xf2, 0x0c, 0xd4, 0x8b, 0xfe, 0xe3, 0xf9, 0x76, 0x2f, 0x14, 0x1c, 0x63, 0x08, 0x6a, 0x6f, 0x2d, 0xb1, 0x1a, 0x95, 0xb0, 0xce, 0x37, 0xc0, 0x9c, 0x6e ]); var ENDPOINT = 'https://fcm.googleapis.com'; /** * @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 MessageParameter; (function (MessageParameter) { MessageParameter["TYPE_OF_MSG"] = "firebase-messaging-msg-type"; MessageParameter["DATA"] = "firebase-messaging-msg-data"; })(MessageParameter || (MessageParameter = {})); var MessageType; (function (MessageType) { MessageType["PUSH_MSG_RECEIVED"] = "push-msg-received"; MessageType["NOTIFICATION_CLICKED"] = "notification-clicked"; })(MessageType || (MessageType = {})); /** * @license * Copyright 2018 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 isArrayBufferEqual(a, b) { if (a == null || b == null) { return false; } if (a === b) { return true; } if (a.byteLength !== b.byteLength) { return false; } var viewA = new DataView(a); var viewB = new DataView(b); for (var i = 0; i < a.byteLength; i++) { if (viewA.getUint8(i) !== viewB.getUint8(i)) { return false; } } return true; } /** * @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 toBase64(arrayBuffer) { var uint8Version = new Uint8Array(arrayBuffer); return btoa(String.fromCharCode.apply(String, __spread(uint8Version))); } function arrayBufferToBase64(arrayBuffer) { var base64String = toBase64(arrayBuffer); return base64String .replace(/=/g, '') .replace(/\+/g, '-') .replace(/\//g, '_'); } /** * @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 IidModel = /** @class */ (function () { function IidModel() { } IidModel.prototype.getToken = function (senderId, subscription, publicVapidKey) { return __awaiter(this, void 0, void 0, function () { var p256dh, auth, fcmSubscribeBody, applicationPubKey, headers, subscribeOptions, responseData, response, err_1, message; return __generator(this, function (_a) { switch (_a.label) { case 0: p256dh = arrayBufferToBase64(subscription.getKey('p256dh')); auth = arrayBufferToBase64(subscription.getKey('auth')); fcmSubscribeBody = "authorized_entity=" + senderId + "&" + ("endpoint=" + subscription.endpoint + "&") + ("encryption_key=" + p256dh + "&") + ("encryption_auth=" + auth); if (!isArrayBufferEqual(publicVapidKey.buffer, DEFAULT_PUBLIC_VAPID_KEY.buffer)) { applicationPubKey = arrayBufferToBase64(publicVapidKey); fcmSubscribeBody += "&application_pub_key=" + applicationPubKey; } headers = new Headers(); headers.append('Content-Type', 'application/x-www-form-urlencoded'); subscribeOptions = { method: 'POST', headers: headers, body: fcmSubscribeBody }; _a.label = 1; case 1: _a.trys.push([1, 4, , 5]); return [4 /*yield*/, fetch(ENDPOINT + '/fcm/connect/subscribe', subscribeOptions)]; case 2: response = _a.sent(); return [4 /*yield*/, response.json()]; case 3: responseData = _a.sent(); return [3 /*break*/, 5]; case 4: err_1 = _a.sent(); throw errorFactory.create("token-subscribe-failed" /* TOKEN_SUBSCRIBE_FAILED */, { errorInfo: err_1 }); case 5: if (responseData.error) { message = responseData.error.message; throw errorFactory.create("token-subscribe-failed" /* TOKEN_SUBSCRIBE_FAILED */, { errorInfo: message }); } if (!responseData.token) { throw errorFactory.create("token-subscribe-no-token" /* TOKEN_SUBSCRIBE_NO_TOKEN */); } if (!responseData.pushSet) { throw errorFactory.create("token-subscribe-no-push-set" /* TOKEN_SUBSCRIBE_NO_PUSH_SET */); } return [2 /*return*/, { token: responseData.token, pushSet: responseData.pushSet }]; } }); }); }; /** * Update the underlying token details for fcmToken. */ IidModel.prototype.updateToken = function (senderId, fcmToken, fcmPushSet, subscription, publicVapidKey) { return __awaiter(this, void 0, void 0, function () { var p256dh, auth, fcmUpdateBody, applicationPubKey, headers, updateOptions, responseData, response, err_2, message; return __generator(this, function (_a) { switch (_a.label) { case 0: p256dh = arrayBufferToBase64(subscription.getKey('p256dh')); auth = arrayBufferToBase64(subscription.getKey('auth')); fcmUpdateBody = "push_set=" + fcmPushSet + "&" + ("token=" + fcmToken + "&") + ("authorized_entity=" + senderId + "&") + ("endpoint=" + subscription.endpoint + "&") + ("encryption_key=" + p256dh + "&") + ("encryption_auth=" + auth); if (!isArrayBufferEqual(publicVapidKey.buffer, DEFAULT_PUBLIC_VAPID_KEY.buffer)) { applicationPubKey = arrayBufferToBase64(publicVapidKey); fcmUpdateBody += "&application_pub_key=" + applicationPubKey; } headers = new Headers(); headers.append('Content-Type', 'application/x-www-form-urlencoded'); updateOptions = { method: 'POST', headers: headers, body: fcmUpdateBody }; _a.label = 1; case 1: _a.trys.push([1, 4, , 5]); return [4 /*yield*/, fetch(ENDPOINT + '/fcm/connect/subscribe', updateOptions)]; case 2: response = _a.sent(); return [4 /*yield*/, response.json()]; case 3: responseData = _a.sent(); return [3 /*break*/, 5]; case 4: err_2 = _a.sent(); throw errorFactory.create("token-update-failed" /* TOKEN_UPDATE_FAILED */, { errorInfo: err_2 }); case 5: if (responseData.error) { message = responseData.error.message; throw errorFactory.create("token-update-failed" /* TOKEN_UPDATE_FAILED */, { errorInfo: message }); } if (!responseData.token) { throw errorFactory.create("token-update-no-token" /* TOKEN_UPDATE_NO_TOKEN */); } return [2 /*return*/, responseData.token]; } }); }); }; /** * Given a fcmToken, pushSet and messagingSenderId, delete an FCM token. */ IidModel.prototype.deleteToken = function (senderId, fcmToken, fcmPushSet) { return __awaiter(this, void 0, void 0, function () { var fcmUnsubscribeBody, headers, unsubscribeOptions, response, responseData, message, err_3; return __generator(this, function (_a) { switch (_a.label) { case 0: fcmUnsubscribeBody = "authorized_entity=" + senderId + "&" + ("token=" + fcmToken + "&") + ("pushSet=" + fcmPushSet); headers = new Headers(); headers.append('Content-Type', 'application/x-www-form-urlencoded'); unsubscribeOptions = { method: 'POST', headers: headers, body: fcmUnsubscribeBody }; _a.label = 1; case 1: _a.trys.push([1, 4, , 5]); return [4 /*yield*/, fetch(ENDPOINT + '/fcm/connect/unsubscribe', unsubscribeOptions)]; case 2: response = _a.sent(); return [4 /*yield*/, response.json()]; case 3: responseData = _a.sent(); if (responseData.error) { message = responseData.error.message; throw errorFactory.create("token-unsubscribe-failed" /* TOKEN_UNSUBSCRIBE_FAILED */, { errorInfo: message }); } return [3 /*break*/, 5]; case 4: err_3 = _a.sent(); throw errorFactory.create("token-unsubscribe-failed" /* TOKEN_UNSUBSCRIBE_FAILED */, { errorInfo: err_3 }); case 5: return [2 /*return*/]; } }); }); }; return IidModel; }()); /** * @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 base64ToArrayBuffer(base64String) { var padding = '='.repeat((4 - (base64String.length % 4)) % 4); var base64 = (base64String + padding) .replace(/\-/g, '+') .replace(/_/g, '/'); var rawData = atob(base64); var outputArray = new Uint8Array(rawData.length); for (var i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray; } /** * @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 OLD_DB_NAME = 'undefined'; var OLD_OBJECT_STORE_NAME = 'fcm_token_object_Store'; function handleDb(db) { if (!db.objectStoreNames.contains(OLD_OBJECT_STORE_NAME)) { // We found a database with the name 'undefined', but our expected object // store isn't defined. return; } var transaction = db.transaction(OLD_OBJECT_STORE_NAME); var objectStore = transaction.objectStore(OLD_OBJECT_STORE_NAME); var iidModel = new IidModel(); var openCursorRequest = objectStore.openCursor(); openCursorRequest.onerror = function (event) { // NOOP - Nothing we can do. console.warn('Unable to cleanup old IDB.', event); }; openCursorRequest.onsuccess = function () { var cursor = openCursorRequest.result; if (cursor) { // cursor.value contains the current record being iterated through // this is where you'd do something with the result var tokenDetails = cursor.value; // tslint:disable-next-line:no-floating-promises iidModel.deleteToken(tokenDetails.fcmSenderId, tokenDetails.fcmToken, tokenDetails.fcmPushSet); cursor.continue(); } else { db.close(); indexedDB.deleteDatabase(OLD_DB_NAME); } }; } function cleanV1() { var request = indexedDB.open(OLD_DB_NAME); request.onerror = function (_event) { // NOOP - Nothing we can do. }; request.onsuccess = function (_event) { var db = request.result; handleDb(db); }; } /** * @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 DbInterface = /** @class */ (function () { function DbInterface() { this.dbPromise = null; } /** Gets record(s) from the objectStore that match the given key. */ DbInterface.prototype.get = function (key) { return this.createTransaction(function (objectStore) { return objectStore.get(key); }); }; /** Gets record(s) from the objectStore that match the given index. */ DbInterface.prototype.getIndex = function (index, key) { function runRequest(objectStore) { var idbIndex = objectStore.index(index); return idbIndex.get(key); } return this.createTransaction(runRequest); }; /** Assigns or overwrites the record for the given value. */ // IndexedDB values are of type "any" DbInterface.prototype.put = function (value) { return this.createTransaction(function (objectStore) { return objectStore.put(value); }, 'readwrite'); }; /** Deletes record(s) from the objectStore that match the given key. */ DbInterface.prototype.delete = function (key) { return this.createTransaction(function (objectStore) { return objectStore.delete(key); }, 'readwrite'); }; /** * Close the currently open database. */ DbInterface.prototype.closeDatabase = function () { return __awaiter(this, void 0, void 0, function () { var db; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!this.dbPromise) return [3 /*break*/, 2]; return [4 /*yield*/, this.dbPromise]; case 1: db = _a.sent(); db.close(); this.dbPromise = null; _a.label = 2; case 2: return [2 /*return*/]; } }); }); }; /** * Creates an IndexedDB Transaction and passes its objectStore to the * runRequest function, which runs the database request. * * @return Promise that resolves with the result of the runRequest function */ DbInterface.prototype.createTransaction = function (runRequest, mode) { if (mode === void 0) { mode = 'readonly'; } return __awaiter(this, void 0, void 0, function () { var db, transaction, request, result; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.getDb()]; case 1: db = _a.sent(); transaction = db.transaction(this.objectStoreName, mode); request = transaction.objectStore(this.objectStoreName); return [4 /*yield*/, promisify(runRequest(request))]; case 2: result = _a.sent(); return [2 /*return*/, new Promise(function (resolve, reject) { transaction.oncomplete = function () { resolve(result); }; transaction.onerror = function () { reject(transaction.error); }; })]; } }); }); }; /** Gets the cached db connection or opens a new one. */ DbInterface.prototype.getDb = function () { var _this = this; if (!this.dbPromise) { this.dbPromise = new Promise(function (resolve, reject) { var request = indexedDB.open(_this.dbName, _this.dbVersion); request.onsuccess = function () { resolve(request.result); }; request.onerror = function () { _this.dbPromise = null; reject(request.error); }; request.onupgradeneeded = function (event) { return _this.onDbUpgrade(request, event); }; }); } return this.dbPromise; }; return DbInterface; }()); /** Promisifies an IDBRequest. Resolves with the IDBRequest's result. */ function promisify(request) { return new Promise(function (resolve, reject) { request.onsuccess = function () { resolve(request.result); }; request.onerror = function () { reject(request.error); }; }); } /** * @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 TokenDetailsModel = /** @class */ (function (_super) { __extends(TokenDetailsModel, _super); function TokenDetailsModel() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.dbName = 'fcm_token_details_db'; _this.dbVersion = 3; _this.objectStoreName = 'fcm_token_object_Store'; return _this; } TokenDetailsModel.prototype.onDbUpgrade = function (request, event) { var db = request.result; // Lack of 'break' statements is intentional. switch (event.oldVersion) { case 0: { // New IDB instance var objectStore = db.createObjectStore(this.objectStoreName, { keyPath: 'swScope' }); // Make sure the sender ID can be searched objectStore.createIndex('fcmSenderId', 'fcmSenderId', { unique: false }); objectStore.createIndex('fcmToken', 'fcmToken', { unique: true }); } case 1: { // Prior to version 2, we were using either 'fcm_token_details_db' // or 'undefined' as the database name due to bug in the SDK // So remove the old tokens and databases. cleanV1(); } case 2: { var objectStore = request.transaction.objectStore(this.objectStoreName); var cursorRequest_1 = objectStore.openCursor(); cursorRequest_1.onsuccess = function () { var cursor = cursorRequest_1.result; if (cursor) { var value = cursor.value; var newValue = __assign({}, value); if (!value.createTime) { newValue.createTime = Date.now(); } if (typeof value.vapidKey === 'string') { newValue.vapidKey = base64ToArrayBuffer(value.vapidKey); } if (typeof value.auth === 'string') { newValue.auth = base64ToArrayBuffer(value.auth).buffer; } if (typeof value.auth === 'string') { newValue.p256dh = base64ToArrayBuffer(value.p256dh).buffer; } cursor.update(newValue); cursor.continue(); } }; } default: // ignore } }; /** * Given a token, this method will look up the details in indexedDB. */ TokenDetailsModel.prototype.getTokenDetailsFromToken = function (fcmToken) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { if (!fcmToken) { throw errorFactory.create("bad-token" /* BAD_TOKEN */); } validateInputs({ fcmToken: fcmToken }); return [2 /*return*/, this.getIndex('fcmToken', fcmToken)]; }); }); }; /** * Given a service worker scope, this method will look up the details in * indexedDB. * @return The details associated with that token. */ TokenDetailsModel.prototype.getTokenDetailsFromSWScope = function (swScope) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { if (!swScope) { throw errorFactory.create("bad-scope" /* BAD_SCOPE */); } validateInputs({ swScope: swScope }); return [2 /*return*/, this.get(swScope)]; }); }); }; /** * Save the details for the fcm token for re-use at a later date. * @param input A plain js object containing args to save. */ TokenDetailsModel.prototype.saveTokenDetails = function (tokenDetails) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { if (!tokenDetails.swScope) { throw errorFactory.create("bad-scope" /* BAD_SCOPE */); } if (!tokenDetails.vapidKey) { throw errorFactory.create("bad-vapid-key" /* BAD_VAPID_KEY */); } if (!tokenDetails.endpoint || !tokenDetails.auth || !tokenDetails.p256dh) { throw errorFactory.create("bad-subscription" /* BAD_SUBSCRIPTION */); } if (!tokenDetails.fcmSenderId) { throw errorFactory.create("bad-sender-id" /* BAD_SENDER_ID */); } if (!tokenDetails.fcmToken) { throw errorFactory.create("bad-token" /* BAD_TOKEN */); } if (!tokenDetails.fcmPushSet) { throw errorFactory.create("bad-push-set" /* BAD_PUSH_SET */); } validateInputs(tokenDetails); return [2 /*return*/, this.put(tokenDetails)]; }); }); }; /** * This method deletes details of the current FCM token. * It's returning a promise in case we need to move to an async * method for deleting at a later date. * * @return Resolves once the FCM token details have been deleted and returns * the deleted details. */ TokenDetailsModel.prototype.deleteToken = function (token) { return __awaiter(this, void 0, void 0, function () { var details; return __generator(this, function (_a) { switch (_a.label) { case 0: if (typeof token !== 'string' || token.length === 0) { return [2 /*return*/, Promise.reject(errorFactory.create("invalid-delete-token" /* INVALID_DELETE_TOKEN */))]; } return [4 /*yield*/, this.getTokenDetailsFromToken(token)]; case 1: details = _a.sent(); if (!details) { throw errorFactory.create("delete-token-not-found" /* DELETE_TOKEN_NOT_FOUND */); } return [4 /*yield*/, this.delete(details.swScope)]; case 2: _a.sent(); return [2 /*return*/, details]; } }); }); }; return TokenDetailsModel; }(DbInterface)); /** * This method takes an object and will check for known arguments and * validate the input. * @return Promise that resolves if input is valid, rejects otherwise. */ function validateInputs(input) { if (input.fcmToken) { if (typeof input.fcmToken !== 'string' || input.fcmToken.length === 0) { throw errorFactory.create("bad-token" /* BAD_TOKEN */); } } if (input.swScope) { if (typeof input.swScope !== 'string' || input.swScope.length === 0) { throw errorFactory.create("bad-scope" /* BAD_SCOPE */); } } if (input.vapidKey) { if (!(input.vapidKey instanceof Uint8Array) || input.vapidKey.length !== 65) { throw errorFactory.create("bad-vapid-key" /* BAD_VAPID_KEY */); } } if (input.endpoint) { if (typeof input.endpoint !== 'string' || input.endpoint.length === 0) { throw errorFactory.create("bad-subscription" /* BAD_SUBSCRIPTION */); } } if (input.auth) { if (!(input.auth instanceof ArrayBuffer)) { throw errorFactory.create("bad-subscription" /* BAD_SUBSCRIPTION */); } } if (input.p256dh) { if (!(input.p256dh instanceof ArrayBuffer)) { throw errorFactory.create("bad-subscription" /* BAD_SUBSCRIPTION */); } } if (input.fcmSenderId) { if (typeof input.fcmSenderId !== 'string' || input.fcmSenderId.length === 0) { throw errorFactory.create("bad-sender-id" /* BAD_SENDER_ID */); } } if (input.fcmPushSet) { if (typeof input.fcmPushSet !== 'string' || input.fcmPushSet.length === 0) { throw errorFactory.create("bad-push-set" /* BAD_PUSH_SET */); } } } /** * @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 UNCOMPRESSED_PUBLIC_KEY_SIZE = 65; var VapidDetailsModel = /** @class */ (function (_super) { __extends(VapidDetailsModel, _super); function VapidDetailsModel() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.dbName = 'fcm_vapid_details_db'; _this.dbVersion = 1; _this.objectStoreName = 'fcm_vapid_object_Store'; return _this; } VapidDetailsModel.prototype.onDbUpgrade = function (request) { var db = request.result; db.createObjectStore(this.objectStoreName, { keyPath: 'swScope' }); }; /** * Given a service worker scope, this method will look up the vapid key * in indexedDB. */ VapidDetailsModel.prototype.getVapidFromSWScope = function (swScope) { return __awaiter(this, void 0, void 0, function () { var result; return __generator(this, function (_a) { switch (_a.label) { case 0: if (typeof swScope !== 'string' || swScope.length === 0) { throw errorFactory.create("bad-scope" /* BAD_SCOPE */); } return [4 /*yield*/, this.get(swScope)]; case 1: result = _a.sent(); return [2 /*return*/, result ? result.vapidKey : undefined]; } }); }); }; /** * Save a vapid key against a swScope for later date. */ VapidDetailsModel.prototype.saveVapidDetails = function (swScope, vapidKey) { return __awaiter(this, void 0, void 0, function () { var details; return __generator(this, function (_a) { if (typeof swScope !== 'string' || swScope.length === 0) { throw errorFactory.create("bad-scope" /* BAD_SCOPE */); } if (vapidKey === null || vapidKey.length !== UNCOMPRESSED_PUBLIC_KEY_SIZE) { throw errorFactory.create("bad-vapid-key" /* BAD_VAPID_KEY */); } details = { swScope: swScope, vapidKey: vapidKey }; return [2 /*return*/, this.put(details)]; }); }); }; /** * This method deletes details of the current FCM VAPID key for a SW scope. * Resolves once the scope/vapid details have been deleted and returns the * deleted vapid key. */ VapidDetailsModel.prototype.deleteVapidDetails = function (swScope) { return __awaiter(this, void 0, void 0, function () { var vapidKey; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.getVapidFromSWScope(swScope)]; case 1: vapidKey = _a.sent(); if (!vapidKey) { throw errorFactory.create("delete-scope-not-found" /* DELETE_SCOPE_NOT_FOUND */); } return [4 /*yield*/, this.delete(swScope)]; case 2: _a.sent(); return [2 /*return*/, vapidKey]; } }); }); }; return VapidDetailsModel; }(DbInterface)); /** * @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 SENDER_ID_OPTION_NAME = 'messagingSenderId'; // Database cache should be invalidated once a week. var TOKEN_EXPIRATION_MILLIS = 7 * 24 * 60 * 60 * 1000; // 7 days var BaseController = /** @class */ (function () { /** * An interface of the Messaging Service API */ function BaseController(app) { var _this = this; if (!app.options[SENDER_ID_OPTION_NAME] || typeof app.options[SENDER_ID_OPTION_NAME] !== 'string') { throw errorFactory.create("bad-sender-id" /* BAD_SENDER_ID */); } this.messagingSenderId = app.options[SENDER_ID_OPTION_NAME]; this.tokenDetailsModel = new TokenDetailsModel(); this.vapidDetailsModel = new VapidDetailsModel(); this.iidModel = new IidModel(); this.app = app; this.INTERNAL = { delete: function () { return _this.delete(); } }; } /** * @export */ BaseController.prototype.getToken = function () { return __awaiter(this, void 0, void 0, function () { var currentPermission, swReg, publicVapidKey, pushSubscription, tokenDetails; return __generator(this, function (_a) { switch (_a.label) { case 0: currentPermission = this.getNotificationPermission_(); if (currentPermission === 'denied') { throw errorFactory.create("notifications-blocked" /* NOTIFICATIONS_BLOCKED */); } else if (currentPermission !== 'granted') { // We must wait for permission to be granted return [2 /*return*/, null]; } return [4 /*yield*/, this.getSWRegistration_()]; case 1: swReg = _a.sent(); return [4 /*yield*/, this.getPublicVapidKey_()]; case 2: publicVapidKey = _a.sent(); return [4 /*yield*/, this.getPushSubscription(swReg, publicVapidKey)]; case 3: pushSubscription = _a.sent(); return [4 /*yield*/, this.tokenDetailsModel.getTokenDetailsFromSWScope(swReg.scope)]; case 4: tokenDetails = _a.sent(); if (tokenDetails) { return [2 /*return*/, this.manageExistingToken(swReg, pushSubscription, publicVapidKey, tokenDetails)]; } return [2 /*return*/, this.getNewToken(swReg, pushSubscription, publicVapidKey)]; } }); }); }; /** * manageExistingToken is triggered if there's an existing FCM token in the * database and it can take 3 different actions: * 1) Retrieve the existing FCM token from the database. * 2) If VAPID details have changed: Delete the existing token and create a * new one with the new VAPID key. * 3) If the database cache is invalidated: Send a request to FCM to update * the token, and to check if the token is still valid on FCM-side. */ BaseController.prototype.manageExistingToken = function (swReg, pushSubscription, publicVapidKey, tokenDetails) { return __awaiter(this, void 0, void 0, function () { var isTokenValid, now; return __generator(this, function (_a) { switch (_a.label) { case 0: isTokenValid = isTokenStillValid(pushSubscription, publicVapidKey, tokenDetails); if (isTokenValid) { now = Date.now(); if (now < tokenDetails.createTime + TOKEN_EXPIRATION_MILLIS) { return [2 /*return*/, tokenDetails.fcmToken]; } else { return [2 /*return*/, this.updateToken(swReg, pushSubscription, publicVapidKey, tokenDetails)]; } } // If the token is no longer valid (for example if the VAPID details // have changed), delete the existing token from the FCM client and server // database. No need to unsubscribe from the Service Worker as we have a // good push subscription that we'd like to use in getNewToken. return [4 /*yield*/, this.deleteTokenFromDB(tokenDetails.fcmToken)]; case 1: // If the token is no longer valid (for example if the VAPID details // have changed), delete the existing token from the FCM client and server // database. No need to unsubscribe from the Service Worker as we have a // good push subscription that we'd like to use in getNewToken. _a.sent(); return [2 /*return*/, this.getNewToken(swReg, pushSubscription, publicVapidKey)]; } }); }); }; BaseController.prototype.updateToken = function (swReg, pushSubscription, publicVapidKey, tokenDetails) { return __awaiter(this, void 0, void 0, function () { var updatedToken, allDetails, e_1; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 4, , 6]); return [4 /*yield*/, this.iidModel.updateToken(this.messagingSenderId, tokenDetails.fcmToken, tokenDetails.fcmPushSet, pushSubscription, publicVapidKey)]; case 1: updatedToken = _a.sent(); allDetails = { swScope: swReg.scope, vapidKey: publicVapidKey, fcmSenderId: this.messagingSenderId, fcmToken: updatedToken, fcmPushSet: tokenDetails.fcmPushSet, createTime: Date.now(), endpoint: pushSubscription.endpoint, auth: pushSubscription.getKey('auth'), p256dh: pushSubscription.getKey('p256dh') }; return [4 /*yield*/, this.tokenDetailsModel.saveTokenDetails(allDetails)]; case 2: _a.sent(); return [4 /*yield*/, this.vapidDetailsModel.saveVapidDetails(swReg.scope, publicVapidKey)]; case 3: _a.sent(); return [2 /*return*/, updatedToken]; case 4: e_1 = _a.sent(); return [4 /*yield*/, this.deleteToken(tokenDetails.fcmToken)]; case 5: _a.sent(); throw e_1; case 6: return [2 /*return*/]; } }); }); }; BaseController.prototype.getNewToken = function (swReg, pushSubscription, publicVapidKey) { return __awaiter(this, void 0, void 0, function () { var tokenDetails, allDetails; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.iidModel.getToken(this.messagingSenderId, pushSubscription, publicVapidKey)]; case 1: tokenDetails = _a.sent(); allDetails = { swScope: swReg.scope, vapidKey: publicVapidKey, fcmSenderId: this.messagingSenderId, fcmToken: tokenDetails.token, fcmPushSet: tokenDetails.pushSet, createTime: Date.now(), endpoint: pushSubscription.endpoint, auth: pushSubscription.getKey('auth'), p256dh: pushSubscription.getKey('p256dh') }; return [4 /*yield*/, this.tokenDetailsModel.saveTokenDetails(allDetails)]; case 2: _a.sent(); return [4 /*yield*/, this.vapidDetailsModel.saveVapidDetails(swReg.scope, publicVapidKey)]; case 3: _a.sent(); return [2 /*return*/, tokenDetails.token]; } }); }); }; /** * This method deletes tokens that the token manager looks after, * unsubscribes the token from FCM and then unregisters the push * subscription if it exists. It returns a promise that indicates * whether or not the unsubscribe request was processed successfully. */ BaseController.prototype.deleteToken = function (token) { return __awaiter(this, void 0, void 0, function () { var registration, pushSubscription; return __generator(this, function (_a) { switch (_a.label) { case 0: // Delete the token details from the database. return [4 /*yield*/, this.deleteTokenFromDB(token)]; case 1: // Delete the token details from the database. _a.sent(); return [4 /*yield*/, this.getSWRegistration_()]; case 2: registration = _a.sent(); if (!registration) return [3 /*break*/, 4]; return [4 /*yield*/, registration.pushManager.getSubscription()]; case 3: pushSubscription = _a.sent(); if (pushSubscription) { return [2 /*return*/, pushSubscription.unsubscribe()]; } _a.label = 4; case 4: // If there's no SW, consider it a success. return [2 /*return*/, true]; } }); }); }; /** * This method will delete the token from the client database, and make a * call to FCM to remove it from the server DB. Does not temper with the * push subscription. */ BaseController.prototype.deleteTokenFromDB = function (token) { return __awaiter(this, void 0, void 0, function () { var details; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.tokenDetailsModel.deleteToken(token)]; case 1: details = _a.sent(); return [4 /*yield*/, this.iidModel.deleteToken(details.fcmSenderId, details.fcmToken, details.fcmPushSet)]; case 2: _a.sent(); return [2 /*return*/]; } }); }); }; /** * Gets a PushSubscription for the current user. */ BaseController.prototype.getPushSubscription = function (swRegistration, publicVapidKey) { return swRegistration.pushManager.getSubscription().then(function (subscription) { if (subscription) { return subscription; } return swRegistration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: publicVapidKey }); }); }; // // The following methods should only be available in the window. // /** * @deprecated Use Notification.requestPermission() instead. * https://developer.mozilla.org/en-US/docs/Web/API/Notification/requestPermission */ BaseController.prototype.requestPermission = function () { throw errorFactory.create("only-available-in-window" /* AVAILABLE_IN_WINDOW */); }; BaseController.prototype.useServiceWorker = function (_registration) { throw errorFactory.create("only-available-in-window" /* AVAILABLE_IN_WINDOW */); }; BaseController.prototype.usePublicVapidKey = function (_b64PublicKey) { throw errorFactory.create("only-available-in-window" /* AVAILABLE_IN_WINDOW */); }; BaseController.prototype.onMessage = function (_nextOrObserver, _error, _completed) { throw errorFactory.create("only-available-in-window" /* AVAILABLE_IN_WINDOW */); }; BaseController.prototype.onTokenRefresh = function (_nextOrObserver, _error, _completed) { throw errorFactory.create("only-available-in-window" /* AVAILABLE_IN_WINDOW */); }; // // The following methods are used by the service worker only. // BaseController.prototype.setBackgroundMessageHandler = function (_callback) { throw errorFactory.create("only-available-in-sw" /* AVAILABLE_IN_SW */); }; // // The following methods are used by the service themselves and not exposed // publicly or not expected to be used by developers. // /** * This method is required to adhere to the Firebase interface. * It closes any currently open indexdb database connections. */ BaseController.prototype.delete = function () { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, Promise.all([ this.tokenDetailsModel.closeDatabase(), this.vapidDetailsModel.closeDatabase() ])]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }; /** * Returns the current Notification Permission state. */ BaseController.prototype.getNotificationPermission_ = function () { return Notification.permission; }; BaseController.prototype.getTokenDetailsModel = function () { return this.tokenDetailsModel; }; BaseController.prototype.getVapidDetailsModel = function () { return this.vapidDetailsModel; }; // Visible for testing // TODO: make protected BaseController.prototype.getIidModel = function () { return this.iidModel; }; return BaseController; }()); /** * Checks if the tokenDetails match the details provided in the clients. */ function isTokenStillValid(pushSubscription, publicVapidKey, tokenDetails) { if (!tokenDetails.vapidKey || !isArrayBufferEqual(publicVapidKey.buffer, tokenDetails.vapidKey.buffer)) { return false; } var isEndpointEqual = pushSubscription.endpoint === tokenDetails.endpoint; var isAuthEqual = isArrayBufferEqual(pushSubscription.getKey('auth'), tokenDetails.auth); var isP256dhEqual = isArrayBufferEqual(pushSubscription.getKey('p256dh'), tokenDetails.p256dh); return 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 FCM_MSG = 'FCM_MSG'; var SwController = /** @class */ (function (_super) { __extends(SwController, _super); function SwController(app) { var _this = _super.call(this, app) || this; _this.bgMessageHandler = null; self.addEventListener('push', function (e) { _this.onPush(e); }); self.addEventListener('pushsubscriptionchange', function (e) { _this.onSubChange(e); }); self.addEventListener('notificationclick', function (e) { _this.onNotificationClick(e); }); return _this; } // Visible for testing // TODO: Make private SwController.prototype.onPush = function (event) { event.waitUntil(this.onPush_(event)); }; // Visible for testing // TODO: Make private SwController.prototype.onSubChange = function (event) { event.waitUntil(this.onSubChange_(event)); }; // Visible for testing // TODO: Make private SwController.prototype.onNotificationClick = function (event) { event.waitUntil(this.onNotificationClick_(event)); }; /** * 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. */ SwController.prototype.onPush_ = function (event) { return __awaiter(this, void 0, void 0, function () { var msgPayload, hasVisibleClients, notificationDetails, notificationTitle, reg, actions, maxActions; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!event.data) { return [2 /*return*/]; } try { msgPayload = event.data.json(); } catch (err) { // Not JSON so not an FCM message return [2 /*return*/]; } return [4 /*yield*/, this.hasVisibleClients_()]; case 1: hasVisibleClients = _a.sent(); if (hasVisibleClients) { // App in foreground. Send to page. return [2 /*return*/, this.sendMessageToWindowClients_(msgPayload)]; } notificationDetails = this.getNotificationData_(msgPayload); if (!notificationDetails) return [3 /*break*/, 3]; notificationTitle = notificationDetails.title || ''; return [4 /*yield*/, this.getSWRegistration_()]; case 2: reg = _a.sent(); actions = notificationDetails.actions; maxActions = Notification.maxActions; // tslint:enable no-any if (actions && maxActions && actions.length > maxActions) { console.warn("This browser only supports " + maxActions + " actions." + "The remaining actions will not be displayed."); } return [2 /*return*/, reg.showNotification(notificationTitle, notificationDetails)]; case 3: if (!this.bgMessageHandler) return [3 /*break*/, 5]; return [4 /*yield*/, this.bgMessageHandler(msgPayload)]; case 4: _a.sent(); return [2 /*return*/]; case 5: return [2 /*return*/]; } }); }); }; SwController.prototype.onSubChange_ = function (_event) { return __awaiter(this, void 0, void 0, function () { var registration, err_1, err_2, tokenDetailsModel, tokenDetails; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 3]); return [4 /*yield*/, this.getSWRegistration_()]; case 1: registration = _a.sent(); return [3 /*break*/, 3]; case 2: err_1 = _a.sent(); throw errorFactory.create("unable-to-resubscribe" /* UNABLE_TO_RESUBSCRIBE */, { errorInfo: err_1 }); case 3: _a.trys.push([3, 5, , 8]); return [4 /*yield*/, registration.pushManager.getSubscription()]; case 4: _a.sent(); return [3 /*break*/, 8]; case 5: err_2 = _a.sent(); tokenDetailsModel = this.getTokenDetailsModel(); return [4 /*yield*/, tokenDetailsModel.getTokenDetailsFromSWScope(registration.scope)]; case 6: tokenDetails = _a.sent(); if (!tokenDetails) { // This should rarely occure, but could if indexedDB // is corrupted or wiped throw err_2; } // Attempt to delete the token if we know it's bad return [4 /*yield*/, this.deleteToken(tokenDetails.fcmToken)]; case 7: // Attempt to delete the token if we know it's bad _a.sent(); throw err_2; case 8: return [2 /*return*/]; } }); }); }; SwController.prototype.onNotificationClick_ = function (event) { return __awaiter(this, void 0, void 0, function () { var msgPayload, link, windowClient, internalMsg; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!event.notification || !event.notification.data || !event.notification.data[FCM_MSG]) { // Not an FCM notification, do nothing. return [2 /*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 [2 /*return*/]; } // Prevent other listeners from receiving the event event.stopImmediatePropagation(); event.notification.close(); msgPayload = event.notification.data[FCM_MSG]; if (!msgPayload.notification) { // Nothing to do. return [2 /*return*/]; } link = (msgPayload.fcmOptions && msgPayload.fcmOptions.link) || msgPayload.notification.click_action; if (!link) { // Nothing to do. return [2 /*return*/]; } return [4 /*yield*/, this.getWindowClient_(link)]; case 1: windowClient = _a.sent(); if (!!windowClient) return [3 /*break*/, 3]; return [4 /*yield*/, self.clients.openWindow(link)]; case 2: // Unable to find window client so need to open one. windowClient = _a.sent(); return [3 /*break*/, 5]; case 3: return [4 /*yield*/, windowClient.focus()]; case 4: windowClient = _a.sent(); _a.label = 5; case 5: if (!windowClient) { // Window Client will not be returned if it's for a third party origin. return [2 /*return*/]; } // Delete notification and fcmOptions data from payload before sending to // the page. delete msgPayload.notification; delete msgPayload.fcmOptions; internalMsg = createNewMsg(MessageType.NOTIFICATION_CLICKED, msgPayload); // Attempt to send a message to the client to handle the data // Is affected by: https://github.com/slightlyoff/ServiceWorker/issues/728 return [2 /*return*/, this.attemptToMessageClient_(windowClient, internalMsg)]; } }); }); }; // Visible for testing // TODO: Make private SwController.prototype.getNotificationData_ = function (msgPayload) { var _a; if (!msgPayload) { return; } if (typeof msgPayload.notification !== 'object') { return; } var notificationInformation = __assign({}, msgPayload.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 = __assign({}, msgPayload.notification.data, (_a = {}, _a[FCM_MSG] = msgPayload, _a)); return notificationInformation; }; /** * 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. */ SwController.prototype.setBackgroundMessageHandler = function (callback) { if (!callback || typeof callback !== 'function') { throw errorFactory.create("bg-handler-function-expected" /* BG_HANDLER_FUNCTION_EXPECTED */); } this.bgMessageHandler = callback; }; /** * @param url The URL to look for when focusing a client. * @return Returns an existing window client or a newly opened WindowClient. */ // Visible for testing // TODO: Make private SwController.prototype.getWindowClient_ = function (url) { return __awaiter(this, void 0, void 0, function () { var parsedURL, clientList, suitableClient, i, parsedClientUrl; return __generator(this, function (_a) { switch (_a.label) { case 0: parsedURL = new URL(url, self.location.href).href; return [4 /*yield*/, getClientList()]; case 1: clientList = _a.sent(); suitableClient = null; for (i = 0; i < clientList.length; i++) { parsedClientUrl = new URL(clientList[i].url, self.location.href) .href; if (parsedClientUrl === parsedURL) { suitableClient = clientList[i]; break; } } return [2 /*return*/, suitableClient]; } }); }); }; /** * This message will attempt to send the message to a window client. * @param client The WindowClient to send the message to. * @param message The message to send to the client. * @returns Returns a promise that resolves after sending the message. This * does not guarantee that the message was successfully received. */ // Visible for testing // TODO: Make private SwController.prototype.attemptToMessageClient_ = function (client, message) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { // NOTE: This returns a promise in case this API is abstracted later on to // do additional work if (!client) { throw errorFactory.create("no-window-client-to-msg" /* NO_WINDOW_CLIENT_TO_MSG */); } client.postMessage(message); return [2 /*return*/]; }); }); }; /** * @returns If there is currently a visible WindowClient, this method will * resolve to true, otherwise false. */ // Visible for testing // TODO: Make private SwController.prototype.hasVisibleClients_ = function () { return __awaiter(this, void 0, void 0, function () { var clientList; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, getClientList()]; case 1: clientList = _a.sent(); return [2 /*return*/, clientList.some(function (client) { return client.visibilityState === 'visible' && // Ignore chrome-extension clients as that matches the background pages // of extensions, which are always considered visible. !client.url.startsWith('chrome-extension://'); })]; } }); }); }; /** * @param msgPayload 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. */ // Visible for testing // TODO: Make private SwController.prototype.sendMessageToWindowClients_ = function (msgPayload) { return __awaiter(this, void 0, void 0, function () { var clientList, internalMsg; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, getClientList()]; case 1: clientList = _a.sent(); internalMsg = createNewMsg(MessageType.PUSH_MSG_RECEIVED, msgPayload); return [4 /*yield*/, Promise.all(clientList.map(function (client) { return _this.attemptToMessageClient_(client, internalMsg); }))]; case 2: _a.sent(); return [2 /*return*/]; } }); }); }; /** * This will register the default service worker and return the registration. * @return he service worker registration to be used for the push service. */ SwController.prototype.getSWRegistration_ = function () { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, self.registration]; }); }); }; /** * This will return the default VAPID key or the uint8array version of the * public VAPID key provided by the developer. */ SwController.prototype.getPublicVapidKey_ = function () { return __awaiter(this, void 0, void 0, function () { var swReg, vapidKeyFromDatabase; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.getSWRegistration_()]; case 1: swReg = _a.sent(); if (!swReg) { throw errorFactory.create("sw-registration-expected" /* SW_REGISTRATION_EXPECTED */); } return [4 /*yield*/, this.getVapidDetailsModel().getVapidFromSWScope(swReg.scope)]; case 2: vapidKeyFromDatabase = _a.sent(); if (vapidKeyFromDatabase == null) { return [2 /*return*/, DEFAULT_PUBLIC_VAPID_KEY]; } return [2 /*return*/, vapidKeyFromDatabase]; } }); }); }; return SwController; }(BaseController)); function getClientList() { return self.clients.matchAll({ type: 'window', includeUncontrolled: true // TS doesn't know that "type: 'window'" means it'll return WindowClient[] }); } function createNewMsg(msgType, msgData) { var _a; return _a = {}, _a[MessageParameter.TYPE_OF_MSG] = msgType, _a[MessageParameter.DATA] = msgData, _a; } /** * @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 DEFAULT_SW_PATH = '/firebase-messaging-sw.js'; var DEFAULT_SW_SCOPE = '/firebase-cloud-messaging-push-scope'; /** * @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 WindowController = /** @class */ (function (_super) { __extends(WindowController, _super); /** * A service that provides a MessagingService instance. */ function WindowController(app) { var _this = _super.call(this, app) || this; _this.registrationToUse = null; _this.publicVapidKeyToUse = null; _this.messageObserver = null; // @ts-ignore: Unused variable error, this is not implemented yet. _this.tokenRefreshObserver = null; _this.onMessageInternal = createSubscribe(function (observer) { _this.messageObserver = observer; }); _this.onTokenRefreshInternal = createSubscribe(function (observer) { _this.tokenRefreshObserver = observer; }); _this.setupSWMessageListener_(); return _this; } /** * Request permission if it is not currently granted * * @return Resolves if the permission was granted, otherwise rejects * * @deprecated Use Notification.requestPermission() instead. * https://developer.mozilla.org/en-US/docs/Web/API/Notification/requestPermission */ WindowController.prototype.requestPermission = function () { return __awaiter(this, void 0, void 0, function () { var permissionResult; return __generator(this, function (_a) { switch (_a.label) { case 0: if (this.getNotificationPermission_() === 'granted') { return [2 /*return*/]; } return [4 /*yield*/, Notification.requestPermission()]; case 1: permissionResult = _a.sent(); if (permissionResult === 'granted') { return [2 /*return*/]; } else if (permissionResult === 'denied') { throw errorFactory.create("permission-blocked" /* PERMISSION_BLOCKED */); } else { throw errorFactory.create("permission-default" /* PERMISSION_DEFAULT */); } return [2 /*return*/]; } }); }); }; /** * This method allows a developer to override the default service worker and * instead use a custom service worker. * * @param registration The service worker registration that should be used to * receive the push messages. */ WindowController.prototype.useServiceWorker = function (registration) { if (!(registration instanceof ServiceWorkerRegistration)) { throw errorFactory.create("sw-registration-expected" /* SW_REGISTRATION_EXPECTED */); } if (this.registrationToUse != null) { throw errorFactory.create("use-sw-before-get-token" /* USE_SW_BEFORE_GET_TOKEN */); } this.registrationToUse = registration; }; /** * This method allows a developer to override the default vapid key * and instead use a custom VAPID public key. * * @param publicKey A URL safe base64 encoded string. */ WindowController.prototype.usePublicVapidKey = function (publicKey) { if (typeof publicKey !== 'string') { throw errorFactory.create("invalid-public-vapid-key" /* INVALID_PUBLIC_VAPID_KEY */); } if (this.publicVapidKeyToUse != null) { throw errorFactory.create("use-public-key-before-get-token" /* USE_PUBLIC_KEY_BEFORE_GET_TOKEN */); } var parsedKey = base64ToArrayBuffer(publicKey); if (parsedKey.length !== 65) { throw errorFactory.create("public-vapid-key-decryption-failed" /* PUBLIC_KEY_DECRYPTION_FAILED */); } this.publicVapidKeyToUse = parsedKey; }; /** * @export * @param nextOrObserver An observer object or a function triggered on * message. * @param error A function triggered on message error. * @param completed function triggered when the observer is removed. * @return The unsubscribe function for the observer. */ WindowController.prototype.onMessage = function (nextOrObserver, error, completed) { if (typeof nextOrObserver === 'function') { return this.onMessageInternal(nextOrObserver, error, completed); } else { return this.onMessageInternal(nextOrObserver); } }; /** * @param nextOrObserver An observer object or a function triggered on token * refresh. * @param error A function triggered on token refresh error. * @param completed function triggered when the observer is removed. * @return The unsubscribe function for the observer. */ WindowController.prototype.onTokenRefresh = function (nextOrObserver, error, completed) { if (typeof nextOrObserver === 'function') { return this.onTokenRefreshInternal(nextOrObserver, error, completed); } else { return this.onTokenRefreshInternal(nextOrObserver); } }; /** * Given a registration, wait for the service worker it relates to * become activer * @param registration Registration to wait for service worker to become active * @return Wait for service worker registration to become active */ // Visible for testing // TODO: Make private WindowController.prototype.waitForRegistrationToActivate_ = function (registration) { var serviceWorker = registration.installing || registration.waiting || registration.active; return new Promise(function (resolve, reject) { if (!serviceWorker) { // This is a rare scenario but has occured in firefox reject(errorFactory.create("no-sw-in-reg" /* NO_SW_IN_REG */)); return; } // Because the Promise function is called on next tick there is a // small chance that the worker became active or redundant already. if (serviceWorker.state === 'activated') { resolve(registration); return; } if (serviceWorker.state === 'redundant') { reject(errorFactory.create("sw-reg-redundant" /* SW_REG_REDUNDANT */)); return; } var stateChangeListener = function () { if (serviceWorker.state === 'activated') { resolve(registration); } else if (serviceWorker.state === 'redundant') { reject(errorFactory.create("sw-reg-redundant" /* SW_REG_REDUNDANT */)); } else { // Return early and wait to next state change return; } serviceWorker.removeEventListener('statechange', stateChangeListener); }; serviceWorker.addEventListener('statechange', stateChangeListener); }); }; /** * This will register the default service worker and return the registration * @return The service worker registration to be used for the push service. */ WindowController.prototype.getSWRegistration_ = function () { var _this = this; if (this.registrationToUse) { return this.waitForRegistrationToActivate_(this.registrationToUse); } // Make the registration null so we know useServiceWorker will not // use a new service worker as registrationToUse is no longer undefined this.registrationToUse = null; return navigator.serviceWorker .register(DEFAULT_SW_PATH, { scope: DEFAULT_SW_SCOPE }) .catch(function (err) { throw errorFactory.create("failed-serviceworker-registration" /* FAILED_DEFAULT_REGISTRATION */, { browserErrorMessage: err.message }); }) .then(function (registration) { return _this.waitForRegistrationToActivate_(registration).then(function () { _this.registrationToUse = registration; // We update after activation due to an issue with Firefox v49 where // a race condition occassionally causes the service worker to not // install // tslint:disable-next-line:no-floating-promises registration.update(); return registration; }); }); }; /** * This will return the default VAPID key or the uint8array version of the public VAPID key * provided by the developer. */ WindowController.prototype.getPublicVapidKey_ = function () { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { if (this.publicVapidKeyToUse) { return [2 /*return*/, this.publicVapidKeyToUse]; } return [2 /*return*/, DEFAULT_PUBLIC_VAPID_KEY]; }); }); }; /** * This method will set up a message listener to handle * events from the service worker that should trigger * events in the page. */ // Visible for testing // TODO: Make private WindowController.prototype.setupSWMessageListener_ = function () { var _this = this; navigator.serviceWorker.addEventListener('message', function (event) { if (!event.data || !event.data[MessageParameter.TYPE_OF_MSG]) { // Not a message from FCM return; } var workerPageMessage = event.data; switch (workerPageMessage[MessageParameter.TYPE_OF_MSG]) { case MessageType.PUSH_MSG_RECEIVED: case MessageType.NOTIFICATION_CLICKED: var pushMessage = workerPageMessage[MessageParameter.DATA]; if (_this.messageObserver) { _this.messageObserver.next(pushMessage); } break; default: // Noop. break; } }, false); }; return WindowController; }(BaseController)); /** * @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 registerMessaging(instance) { var messagingName = 'messaging'; var factoryMethod = function (app) { if (!isSupported()) { throw errorFactory.create("unsupported-browser" /* UNSUPPORTED_BROWSER */); } if (self && 'ServiceWorkerGlobalScope' in self) { // Running in ServiceWorker context return new SwController(app); } else { // Assume we are in the window context. return new WindowController(app); } }; var namespaceExports = { isSupported: isSupported }; instance.INTERNAL.registerService(messagingName, factoryMethod, namespaceExports); } registerMessaging(firebase); 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 (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 ('PushManager' in self && 'Notification' in self && ServiceWorkerRegistration.prototype.hasOwnProperty('showNotification') && PushSubscription.prototype.hasOwnProperty('getKey')); } export { isSupported, registerMessaging }; //# sourceMappingURL=index.esm.js.map