@firebase/auth
Version:
The Firebase Authenticaton component of the Firebase JS SDK.
1,156 lines (1,145 loc) • 161 kB
JavaScript
'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) {