UNPKG

@firebase/auth

Version:

The Firebase Authenticaton component of the Firebase JS SDK.

1,156 lines (1,145 loc) • 161 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var phone = require('./phone-cf00e1ee.js'); var tslib = require('tslib'); var util = require('@firebase/util'); var app = require('@firebase/app'); require('@firebase/component'); require('@firebase/logger'); /** * @license * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // There are two different browser persistence types: local and session. // Both have the same implementation but use a different underlying storage // object. var BrowserPersistenceClass = /** @class */ (function () { function BrowserPersistenceClass(storageRetriever, type) { this.storageRetriever = storageRetriever; this.type = type; } BrowserPersistenceClass.prototype._isAvailable = function () { try { if (!this.storage) { return Promise.resolve(false); } this.storage.setItem(phone.STORAGE_AVAILABLE_KEY, '1'); this.storage.removeItem(phone.STORAGE_AVAILABLE_KEY); return Promise.resolve(true); } catch (_a) { return Promise.resolve(false); } }; BrowserPersistenceClass.prototype._set = function (key, value) { this.storage.setItem(key, JSON.stringify(value)); return Promise.resolve(); }; BrowserPersistenceClass.prototype._get = function (key) { var json = this.storage.getItem(key); return Promise.resolve(json ? JSON.parse(json) : null); }; BrowserPersistenceClass.prototype._remove = function (key) { this.storage.removeItem(key); return Promise.resolve(); }; Object.defineProperty(BrowserPersistenceClass.prototype, "storage", { get: function () { return this.storageRetriever(); }, enumerable: false, configurable: true }); return BrowserPersistenceClass; }()); /** * @license * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ function _iframeCannotSyncWebStorage() { var ua = util.getUA(); return phone._isSafari(ua) || phone._isIOS(ua); } // The polling period in case events are not supported var _POLLING_INTERVAL_MS$1 = 1000; // The IE 10 localStorage cross tab synchronization delay in milliseconds var IE10_LOCAL_STORAGE_SYNC_DELAY = 10; var BrowserLocalPersistence = /** @class */ (function (_super) { tslib.__extends(BrowserLocalPersistence, _super); function BrowserLocalPersistence() { var _this = _super.call(this, function () { return window.localStorage; }, "LOCAL" /* LOCAL */) || this; _this.boundEventHandler = function (event, poll) { return _this.onStorageEvent(event, poll); }; _this.listeners = {}; _this.localCache = {}; // setTimeout return value is platform specific // eslint-disable-next-line @typescript-eslint/no-explicit-any _this.pollTimer = null; // Safari or iOS browser and embedded in an iframe. _this.safariLocalStorageNotSynced = _iframeCannotSyncWebStorage() && phone._isIframe(); // Whether to use polling instead of depending on window events _this.fallbackToPolling = phone._isMobileBrowser(); _this._shouldAllowMigration = true; return _this; } BrowserLocalPersistence.prototype.forAllChangedKeys = function (cb) { // Check all keys with listeners on them. for (var _i = 0, _a = Object.keys(this.listeners); _i < _a.length; _i++) { var key = _a[_i]; // Get value from localStorage. var newValue = this.storage.getItem(key); var oldValue = this.localCache[key]; // If local map value does not match, trigger listener with storage event. // Differentiate this simulated event from the real storage event. if (newValue !== oldValue) { cb(key, oldValue, newValue); } } }; BrowserLocalPersistence.prototype.onStorageEvent = function (event, poll) { var _this = this; if (poll === void 0) { poll = false; } // Key would be null in some situations, like when localStorage is cleared if (!event.key) { this.forAllChangedKeys(function (key, _oldValue, newValue) { _this.notifyListeners(key, newValue); }); return; } var key = event.key; // Check the mechanism how this event was detected. // The first event will dictate the mechanism to be used. if (poll) { // Environment detects storage changes via polling. // Remove storage event listener to prevent possible event duplication. this.detachListener(); } else { // Environment detects storage changes via storage event listener. // Remove polling listener to prevent possible event duplication. this.stopPolling(); } // Safari embedded iframe. Storage event will trigger with the delta // changes but no changes will be applied to the iframe localStorage. if (this.safariLocalStorageNotSynced) { // Get current iframe page value. var storedValue_1 = this.storage.getItem(key); // Value not synchronized, synchronize manually. if (event.newValue !== storedValue_1) { if (event.newValue !== null) { // Value changed from current value. this.storage.setItem(key, event.newValue); } else { // Current value deleted. this.storage.removeItem(key); } } else if (this.localCache[key] === event.newValue && !poll) { // Already detected and processed, do not trigger listeners again. return; } } var triggerListeners = function () { // Keep local map up to date in case storage event is triggered before // poll. var storedValue = _this.storage.getItem(key); if (!poll && _this.localCache[key] === storedValue) { // Real storage event which has already been detected, do nothing. // This seems to trigger in some IE browsers for some reason. return; } _this.notifyListeners(key, storedValue); }; var storedValue = this.storage.getItem(key); if (phone._isIE10() && storedValue !== event.newValue && event.newValue !== event.oldValue) { // IE 10 has this weird bug where a storage event would trigger with the // correct key, oldValue and newValue but localStorage.getItem(key) does // not yield the updated value until a few milliseconds. This ensures // this recovers from that situation. setTimeout(triggerListeners, IE10_LOCAL_STORAGE_SYNC_DELAY); } else { triggerListeners(); } }; BrowserLocalPersistence.prototype.notifyListeners = function (key, value) { this.localCache[key] = value; var listeners = this.listeners[key]; if (listeners) { for (var _i = 0, _a = Array.from(listeners); _i < _a.length; _i++) { var listener = _a[_i]; listener(value ? JSON.parse(value) : value); } } }; BrowserLocalPersistence.prototype.startPolling = function () { var _this = this; this.stopPolling(); this.pollTimer = setInterval(function () { _this.forAllChangedKeys(function (key, oldValue, newValue) { _this.onStorageEvent(new StorageEvent('storage', { key: key, oldValue: oldValue, newValue: newValue }), /* poll */ true); }); }, _POLLING_INTERVAL_MS$1); }; BrowserLocalPersistence.prototype.stopPolling = function () { if (this.pollTimer) { clearInterval(this.pollTimer); this.pollTimer = null; } }; BrowserLocalPersistence.prototype.attachListener = function () { window.addEventListener('storage', this.boundEventHandler); }; BrowserLocalPersistence.prototype.detachListener = function () { window.removeEventListener('storage', this.boundEventHandler); }; BrowserLocalPersistence.prototype._addListener = function (key, listener) { if (Object.keys(this.listeners).length === 0) { // Whether browser can detect storage event when it had already been pushed to the background. // This may happen in some mobile browsers. A localStorage change in the foreground window // will not be detected in the background window via the storage event. // This was detected in iOS 7.x mobile browsers if (this.fallbackToPolling) { this.startPolling(); } else { this.attachListener(); } } if (!this.listeners[key]) { this.listeners[key] = new Set(); // Populate the cache to avoid spuriously triggering on first poll. this.localCache[key] = this.storage.getItem(key); } this.listeners[key].add(listener); }; BrowserLocalPersistence.prototype._removeListener = function (key, listener) { if (this.listeners[key]) { this.listeners[key].delete(listener); if (this.listeners[key].size === 0) { delete this.listeners[key]; } } if (Object.keys(this.listeners).length === 0) { this.detachListener(); this.stopPolling(); } }; // Update local cache on base operations: BrowserLocalPersistence.prototype._set = function (key, value) { return tslib.__awaiter(this, void 0, void 0, function () { return tslib.__generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, _super.prototype._set.call(this, key, value)]; case 1: _a.sent(); this.localCache[key] = JSON.stringify(value); return [2 /*return*/]; } }); }); }; BrowserLocalPersistence.prototype._get = function (key) { return tslib.__awaiter(this, void 0, void 0, function () { var value; return tslib.__generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, _super.prototype._get.call(this, key)]; case 1: value = _a.sent(); this.localCache[key] = JSON.stringify(value); return [2 /*return*/, value]; } }); }); }; BrowserLocalPersistence.prototype._remove = function (key) { return tslib.__awaiter(this, void 0, void 0, function () { return tslib.__generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, _super.prototype._remove.call(this, key)]; case 1: _a.sent(); delete this.localCache[key]; return [2 /*return*/]; } }); }); }; BrowserLocalPersistence.type = 'LOCAL'; return BrowserLocalPersistence; }(BrowserPersistenceClass)); /** * An implementation of {@link Persistence} of type `LOCAL` using `localStorage` * for the underlying storage. * * @public */ var browserLocalPersistence = BrowserLocalPersistence; /** * @license * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var BrowserSessionPersistence = /** @class */ (function (_super) { tslib.__extends(BrowserSessionPersistence, _super); function BrowserSessionPersistence() { return _super.call(this, function () { return window.sessionStorage; }, "SESSION" /* SESSION */) || this; } BrowserSessionPersistence.prototype._addListener = function (_key, _listener) { // Listeners are not supported for session storage since it cannot be shared across windows return; }; BrowserSessionPersistence.prototype._removeListener = function (_key, _listener) { // Listeners are not supported for session storage since it cannot be shared across windows return; }; BrowserSessionPersistence.type = 'SESSION'; return BrowserSessionPersistence; }(BrowserPersistenceClass)); /** * An implementation of {@link Persistence} of `SESSION` using `sessionStorage` * for the underlying storage. * * @public */ var browserSessionPersistence = BrowserSessionPersistence; /** * @license * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Shim for Promise.allSettled, note the slightly different format of `fulfilled` vs `status`. * * @param promises - Array of promises to wait on. */ function _allSettled(promises) { var _this = this; return Promise.all(promises.map(function (promise) { return tslib.__awaiter(_this, void 0, void 0, function () { var value, reason_1; return tslib.__generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 3]); return [4 /*yield*/, promise]; case 1: value = _a.sent(); return [2 /*return*/, { fulfilled: true, value: value }]; case 2: reason_1 = _a.sent(); return [2 /*return*/, { fulfilled: false, reason: reason_1 }]; case 3: return [2 /*return*/]; } }); }); })); } /** * @license * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Interface class for receiving messages. * */ var Receiver = /** @class */ (function () { function Receiver(eventTarget) { this.eventTarget = eventTarget; this.handlersMap = {}; this.boundEventHandler = this.handleEvent.bind(this); } /** * Obtain an instance of a Receiver for a given event target, if none exists it will be created. * * @param eventTarget - An event target (such as window or self) through which the underlying * messages will be received. */ Receiver._getInstance = function (eventTarget) { // The results are stored in an array since objects can't be keys for other // objects. In addition, setting a unique property on an event target as a // hash map key may not be allowed due to CORS restrictions. var existingInstance = this.receivers.find(function (receiver) { return receiver.isListeningto(eventTarget); }); if (existingInstance) { return existingInstance; } var newInstance = new Receiver(eventTarget); this.receivers.push(newInstance); return newInstance; }; Receiver.prototype.isListeningto = function (eventTarget) { return this.eventTarget === eventTarget; }; /** * Fans out a MessageEvent to the appropriate listeners. * * @remarks * Sends an {@link Status.ACK} upon receipt and a {@link Status.DONE} once all handlers have * finished processing. * * @param event - The MessageEvent. * */ Receiver.prototype.handleEvent = function (event) { return tslib.__awaiter(this, void 0, void 0, function () { var messageEvent, _a, eventId, eventType, data, handlers, promises, response; var _this = this; return tslib.__generator(this, function (_b) { switch (_b.label) { case 0: messageEvent = event; _a = messageEvent.data, eventId = _a.eventId, eventType = _a.eventType, data = _a.data; handlers = this.handlersMap[eventType]; if (!(handlers === null || handlers === void 0 ? void 0 : handlers.size)) { return [2 /*return*/]; } messageEvent.ports[0].postMessage({ status: "ack" /* ACK */, eventId: eventId, eventType: eventType }); promises = Array.from(handlers).map(function (handler) { return tslib.__awaiter(_this, void 0, void 0, function () { return tslib.__generator(this, function (_a) { return [2 /*return*/, handler(messageEvent.origin, data)]; }); }); }); return [4 /*yield*/, _allSettled(promises)]; case 1: response = _b.sent(); messageEvent.ports[0].postMessage({ status: "done" /* DONE */, eventId: eventId, eventType: eventType, response: response }); return [2 /*return*/]; } }); }); }; /** * Subscribe an event handler for a particular event. * * @param eventType - Event name to subscribe to. * @param eventHandler - The event handler which should receive the events. * */ Receiver.prototype._subscribe = function (eventType, eventHandler) { if (Object.keys(this.handlersMap).length === 0) { this.eventTarget.addEventListener('message', this.boundEventHandler); } if (!this.handlersMap[eventType]) { this.handlersMap[eventType] = new Set(); } this.handlersMap[eventType].add(eventHandler); }; /** * Unsubscribe an event handler from a particular event. * * @param eventType - Event name to unsubscribe from. * @param eventHandler - Optinoal event handler, if none provided, unsubscribe all handlers on this event. * */ Receiver.prototype._unsubscribe = function (eventType, eventHandler) { if (this.handlersMap[eventType] && eventHandler) { this.handlersMap[eventType].delete(eventHandler); } if (!eventHandler || this.handlersMap[eventType].size === 0) { delete this.handlersMap[eventType]; } if (Object.keys(this.handlersMap).length === 0) { this.eventTarget.removeEventListener('message', this.boundEventHandler); } }; Receiver.receivers = []; return Receiver; }()); /** * @license * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ function _generateEventId(prefix, digits) { if (prefix === void 0) { prefix = ''; } if (digits === void 0) { digits = 10; } var random = ''; for (var i = 0; i < digits; i++) { random += Math.floor(Math.random() * 10); } return prefix + random; } /** * @license * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Interface for sending messages and waiting for a completion response. * */ var Sender = /** @class */ (function () { function Sender(target) { this.target = target; this.handlers = new Set(); } /** * Unsubscribe the handler and remove it from our tracking Set. * * @param handler - The handler to unsubscribe. */ Sender.prototype.removeMessageHandler = function (handler) { if (handler.messageChannel) { handler.messageChannel.port1.removeEventListener('message', handler.onMessage); handler.messageChannel.port1.close(); } this.handlers.delete(handler); }; /** * Send a message to the Receiver located at {@link target}. * * @remarks * We'll first wait a bit for an ACK , if we get one we will wait significantly longer until the * receiver has had a chance to fully process the event. * * @param eventType - Type of event to send. * @param data - The payload of the event. * @param timeout - Timeout for waiting on an ACK from the receiver. * * @returns An array of settled promises from all the handlers that were listening on the receiver. */ Sender.prototype._send = function (eventType, data, timeout) { if (timeout === void 0) { timeout = 50 /* ACK */; } return tslib.__awaiter(this, void 0, void 0, function () { var messageChannel, completionTimer, handler; var _this = this; return tslib.__generator(this, function (_a) { messageChannel = typeof MessageChannel !== 'undefined' ? new MessageChannel() : null; if (!messageChannel) { throw new Error("connection_unavailable" /* CONNECTION_UNAVAILABLE */); } return [2 /*return*/, new Promise(function (resolve, reject) { var eventId = _generateEventId('', 20); messageChannel.port1.start(); var ackTimer = setTimeout(function () { reject(new Error("unsupported_event" /* UNSUPPORTED_EVENT */)); }, timeout); handler = { messageChannel: messageChannel, onMessage: function (event) { var messageEvent = event; if (messageEvent.data.eventId !== eventId) { return; } switch (messageEvent.data.status) { case "ack" /* ACK */: // The receiver should ACK first. clearTimeout(ackTimer); completionTimer = setTimeout(function () { reject(new Error("timeout" /* TIMEOUT */)); }, 3000 /* COMPLETION */); break; case "done" /* DONE */: // Once the receiver's handlers are finished we will get the results. clearTimeout(completionTimer); resolve(messageEvent.data.response); break; default: clearTimeout(ackTimer); clearTimeout(completionTimer); reject(new Error("invalid_response" /* INVALID_RESPONSE */)); break; } } }; _this.handlers.add(handler); messageChannel.port1.addEventListener('message', handler.onMessage); _this.target.postMessage({ eventType: eventType, eventId: eventId, data: data }, [messageChannel.port2]); }).finally(function () { if (handler) { _this.removeMessageHandler(handler); } })]; }); }); }; return Sender; }()); /** * @license * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var DB_NAME = 'firebaseLocalStorageDb'; var DB_VERSION = 1; var DB_OBJECTSTORE_NAME = 'firebaseLocalStorage'; var DB_DATA_KEYPATH = 'fbase_key'; /** * Promise wrapper for IDBRequest * * Unfortunately we can't cleanly extend Promise<T> since promises are not callable in ES6 * */ var DBPromise = /** @class */ (function () { function DBPromise(request) { this.request = request; } DBPromise.prototype.toPromise = function () { var _this = this; return new Promise(function (resolve, reject) { _this.request.addEventListener('success', function () { resolve(_this.request.result); }); _this.request.addEventListener('error', function () { reject(_this.request.error); }); }); }; return DBPromise; }()); function getObjectStore(db, isReadWrite) { return db .transaction([DB_OBJECTSTORE_NAME], isReadWrite ? 'readwrite' : 'readonly') .objectStore(DB_OBJECTSTORE_NAME); } function _deleteDatabase() { var request = indexedDB.deleteDatabase(DB_NAME); return new DBPromise(request).toPromise(); } function _openDatabase() { var _this = this; var request = indexedDB.open(DB_NAME, DB_VERSION); return new Promise(function (resolve, reject) { request.addEventListener('error', function () { reject(request.error); }); request.addEventListener('upgradeneeded', function () { var db = request.result; try { db.createObjectStore(DB_OBJECTSTORE_NAME, { keyPath: DB_DATA_KEYPATH }); } catch (e) { reject(e); } }); request.addEventListener('success', function () { return tslib.__awaiter(_this, void 0, void 0, function () { var db, _a; return tslib.__generator(this, function (_b) { switch (_b.label) { case 0: db = request.result; if (!!db.objectStoreNames.contains(DB_OBJECTSTORE_NAME)) return [3 /*break*/, 3]; // Need to close the database or else you get a `blocked` event db.close(); return [4 /*yield*/, _deleteDatabase()]; case 1: _b.sent(); _a = resolve; return [4 /*yield*/, _openDatabase()]; case 2: _a.apply(void 0, [_b.sent()]); return [3 /*break*/, 4]; case 3: resolve(db); _b.label = 4; case 4: return [2 /*return*/]; } }); }); }); }); } function _putObject(db, key, value) { return tslib.__awaiter(this, void 0, void 0, function () { var request; var _a; return tslib.__generator(this, function (_b) { request = getObjectStore(db, true).put((_a = {}, _a[DB_DATA_KEYPATH] = key, _a.value = value, _a)); return [2 /*return*/, new DBPromise(request).toPromise()]; }); }); } function getObject(db, key) { return tslib.__awaiter(this, void 0, void 0, function () { var request, data; return tslib.__generator(this, function (_a) { switch (_a.label) { case 0: request = getObjectStore(db, false).get(key); return [4 /*yield*/, new DBPromise(request).toPromise()]; case 1: data = _a.sent(); return [2 /*return*/, data === undefined ? null : data.value]; } }); }); } function _deleteObject(db, key) { var request = getObjectStore(db, true).delete(key); return new DBPromise(request).toPromise(); } var _POLLING_INTERVAL_MS = 800; var _TRANSACTION_RETRY_COUNT = 3; var IndexedDBLocalPersistence = /** @class */ (function () { function IndexedDBLocalPersistence() { this.type = "LOCAL" /* LOCAL */; this._shouldAllowMigration = true; this.listeners = {}; this.localCache = {}; // setTimeout return value is platform specific // eslint-disable-next-line @typescript-eslint/no-explicit-any this.pollTimer = null; this.pendingWrites = 0; this.receiver = null; this.sender = null; this.serviceWorkerReceiverAvailable = false; this.activeServiceWorker = null; // Fire & forget the service worker registration as it may never resolve this._workerInitializationPromise = this.initializeServiceWorkerMessaging().then(function () { }, function () { }); } IndexedDBLocalPersistence.prototype._openDb = function () { return tslib.__awaiter(this, void 0, void 0, function () { var _a; return tslib.__generator(this, function (_b) { switch (_b.label) { case 0: if (this.db) { return [2 /*return*/, this.db]; } _a = this; return [4 /*yield*/, _openDatabase()]; case 1: _a.db = _b.sent(); return [2 /*return*/, this.db]; } }); }); }; IndexedDBLocalPersistence.prototype._withRetries = function (op) { return tslib.__awaiter(this, void 0, void 0, function () { var numAttempts, db, e_1; return tslib.__generator(this, function (_a) { switch (_a.label) { case 0: numAttempts = 0; _a.label = 1; case 1: _a.label = 2; case 2: _a.trys.push([2, 5, , 6]); return [4 /*yield*/, this._openDb()]; case 3: db = _a.sent(); return [4 /*yield*/, op(db)]; case 4: return [2 /*return*/, _a.sent()]; case 5: e_1 = _a.sent(); if (numAttempts++ > _TRANSACTION_RETRY_COUNT) { throw e_1; } if (this.db) { this.db.close(); this.db = undefined; } return [3 /*break*/, 6]; case 6: return [3 /*break*/, 1]; case 7: return [2 /*return*/]; } }); }); }; /** * IndexedDB events do not propagate from the main window to the worker context. We rely on a * postMessage interface to send these events to the worker ourselves. */ IndexedDBLocalPersistence.prototype.initializeServiceWorkerMessaging = function () { return tslib.__awaiter(this, void 0, void 0, function () { return tslib.__generator(this, function (_a) { return [2 /*return*/, phone._isWorker() ? this.initializeReceiver() : this.initializeSender()]; }); }); }; /** * As the worker we should listen to events from the main window. */ IndexedDBLocalPersistence.prototype.initializeReceiver = function () { return tslib.__awaiter(this, void 0, void 0, function () { var _this = this; return tslib.__generator(this, function (_a) { this.receiver = Receiver._getInstance(phone._getWorkerGlobalScope()); // Refresh from persistence if we receive a KeyChanged message. this.receiver._subscribe("keyChanged" /* KEY_CHANGED */, function (_origin, data) { return tslib.__awaiter(_this, void 0, void 0, function () { var keys; return tslib.__generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this._poll()]; case 1: keys = _a.sent(); return [2 /*return*/, { keyProcessed: keys.includes(data.key) }]; } }); }); }); // Let the sender know that we are listening so they give us more timeout. this.receiver._subscribe("ping" /* PING */, function (_origin, _data) { return tslib.__awaiter(_this, void 0, void 0, function () { return tslib.__generator(this, function (_a) { return [2 /*return*/, ["keyChanged" /* KEY_CHANGED */]]; }); }); }); return [2 /*return*/]; }); }); }; /** * As the main window, we should let the worker know when keys change (set and remove). * * @remarks * {@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/ready | ServiceWorkerContainer.ready} * may not resolve. */ IndexedDBLocalPersistence.prototype.initializeSender = function () { var _a, _b; return tslib.__awaiter(this, void 0, void 0, function () { var _c, results; return tslib.__generator(this, function (_d) { switch (_d.label) { case 0: // Check to see if there's an active service worker. _c = this; return [4 /*yield*/, phone._getActiveServiceWorker()]; case 1: // Check to see if there's an active service worker. _c.activeServiceWorker = _d.sent(); if (!this.activeServiceWorker) { return [2 /*return*/]; } this.sender = new Sender(this.activeServiceWorker); return [4 /*yield*/, this.sender._send("ping" /* PING */, {}, 800 /* LONG_ACK */)]; case 2: results = _d.sent(); if (!results) { return [2 /*return*/]; } if (((_a = results[0]) === null || _a === void 0 ? void 0 : _a.fulfilled) && ((_b = results[0]) === null || _b === void 0 ? void 0 : _b.value.includes("keyChanged" /* KEY_CHANGED */))) { this.serviceWorkerReceiverAvailable = true; } return [2 /*return*/]; } }); }); }; /** * Let the worker know about a changed key, the exact key doesn't technically matter since the * worker will just trigger a full sync anyway. * * @remarks * For now, we only support one service worker per page. * * @param key - Storage key which changed. */ IndexedDBLocalPersistence.prototype.notifyServiceWorker = function (key) { return tslib.__awaiter(this, void 0, void 0, function () { return tslib.__generator(this, function (_b) { switch (_b.label) { case 0: if (!this.sender || !this.activeServiceWorker || phone._getServiceWorkerController() !== this.activeServiceWorker) { return [2 /*return*/]; } _b.label = 1; case 1: _b.trys.push([1, 3, , 4]); return [4 /*yield*/, this.sender._send("keyChanged" /* KEY_CHANGED */, { key: key }, // Use long timeout if receiver has previously responded to a ping from us. this.serviceWorkerReceiverAvailable ? 800 /* LONG_ACK */ : 50 /* ACK */)]; case 2: _b.sent(); return [3 /*break*/, 4]; case 3: _b.sent(); return [3 /*break*/, 4]; case 4: return [2 /*return*/]; } }); }); }; IndexedDBLocalPersistence.prototype._isAvailable = function () { return tslib.__awaiter(this, void 0, void 0, function () { var db; return tslib.__generator(this, function (_b) { switch (_b.label) { case 0: _b.trys.push([0, 4, , 5]); if (!indexedDB) { return [2 /*return*/, false]; } return [4 /*yield*/, _openDatabase()]; case 1: db = _b.sent(); return [4 /*yield*/, _putObject(db, phone.STORAGE_AVAILABLE_KEY, '1')]; case 2: _b.sent(); return [4 /*yield*/, _deleteObject(db, phone.STORAGE_AVAILABLE_KEY)]; case 3: _b.sent(); return [2 /*return*/, true]; case 4: _b.sent(); return [3 /*break*/, 5]; case 5: return [2 /*return*/, false]; } }); }); }; IndexedDBLocalPersistence.prototype._withPendingWrite = function (write) { return tslib.__awaiter(this, void 0, void 0, function () { return tslib.__generator(this, function (_a) { switch (_a.label) { case 0: this.pendingWrites++; _a.label = 1; case 1: _a.trys.push([1, , 3, 4]); return [4 /*yield*/, write()]; case 2: _a.sent(); return [3 /*break*/, 4]; case 3: this.pendingWrites--; return [7 /*endfinally*/]; case 4: return [2 /*return*/]; } }); }); }; IndexedDBLocalPersistence.prototype._set = function (key, value) { return tslib.__awaiter(this, void 0, void 0, function () { var _this = this; return tslib.__generator(this, function (_a) { return [2 /*return*/, this._withPendingWrite(function () { return tslib.__awaiter(_this, void 0, void 0, function () { return tslib.__generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this._withRetries(function (db) { return _putObject(db, key, value); })]; case 1: _a.sent(); this.localCache[key] = value; return [2 /*return*/, this.notifyServiceWorker(key)]; } }); }); })]; }); }); }; IndexedDBLocalPersistence.prototype._get = function (key) { return tslib.__awaiter(this, void 0, void 0, function () { var obj; return tslib.__generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this._withRetries(function (db) { return getObject(db, key); })]; case 1: obj = (_a.sent()); this.localCache[key] = obj; return [2 /*return*/, obj]; } }); }); }; IndexedDBLocalPersistence.prototype._remove = function (key) { return tslib.__awaiter(this, void 0, void 0, function () { var _this = this; return tslib.__generator(this, function (_a) { return [2 /*return*/, this._withPendingWrite(function () { return tslib.__awaiter(_this, void 0, void 0, function () { return tslib.__generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this._withRetries(function (db) { return _deleteObject(db, key); })]; case 1: _a.sent(); delete this.localCache[key]; return [2 /*return*/, this.notifyServiceWorker(key)]; } }); }); })]; }); }); }; IndexedDBLocalPersistence.prototype._poll = function () { return tslib.__awaiter(this, void 0, void 0, function () { var result, keys, keysInResult, _i, result_1, _a, key, value, _b, _c, localKey; return tslib.__generator(this, function (_d) { switch (_d.label) { case 0: return [4 /*yield*/, this._withRetries(function (db) { var getAllRequest = getObjectStore(db, false).getAll(); return new DBPromise(getAllRequest).toPromise(); })]; case 1: result = _d.sent(); if (!result) { return [2 /*return*/, []]; } // If we have pending writes in progress abort, we'll get picked up on the next poll if (this.pendingWrites !== 0) { return [2 /*return*/, []]; } keys = []; keysInResult = new Set(); for (_i = 0, result_1 = result; _i < result_1.length; _i++) { _a = result_1[_i], key = _a.fbase_key, value = _a.value; keysInResult.add(key); if (JSON.stringify(this.localCache[key]) !== JSON.stringify(value)) { this.notifyListeners(key, value); keys.push(key); } } for (_b = 0, _c = Object.keys(this.localCache); _b < _c.length; _b++) { localKey = _c[_b]; if (this.localCache[localKey] && !keysInResult.has(localKey)) { // Deleted this.notifyListeners(localKey, null); keys.push(localKey); } } return [2 /*return*/, keys]; } }); }); }; IndexedDBLocalPersistence.prototype.notifyListeners = function (key, newValue) { this.localCache[key] = newValue; var listeners = this.listeners[key]; if (listeners) { for (var _i = 0, _a = Array.from(listeners); _i < _a.length; _i++) { var listener = _a[_i]; listener(newValue); } } }; IndexedDBLocalPersistence.prototype.startPolling = function () { var _this = this; this.stopPolling(); this.pollTimer = setInterval(function () { return tslib.__awaiter(_this, void 0, void 0, function () { return tslib.__generator(this, function (_a) { return [2 /*return*/, this._poll()]; }); }); }, _POLLING_INTERVAL_MS); }; IndexedDBLocalPersistence.prototype.stopPolling = function () { if (this.pollTimer) { clearInterval(this.pollTimer); this.pollTimer = null; } }; IndexedDBLocalPersistence.prototype._addListener = function (key, listener) {