1
0
mirror of https://github.com/musix-org/musix-oss synced 2024-11-14 16:00:17 +00:00
musix-oss/node_modules/@firebase/messaging/dist/index.esm.js
2019-10-10 16:43:04 +03:00

2070 lines
88 KiB
JavaScript

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