UNPKG

reactant-share

Version:

A framework for building shared web applications with Reactant

145 lines (142 loc) 6.38 kB
import { __awaiter, __generator, __read } from './node_modules/tslib/tslib.es6.js'; import { createId } from './utils.js'; var lockMap = new Map(); var tabId = createId(); var lockStorageKey = 'reactant:lock'; var tabStorageKey = 'reactant:tab'; var heartbeatTimer; var isListenUnload = false; var storage; var clearTabLocks = function (tabIds, _storage) { if (tabIds.length === 0) return; Object.keys(localStorage).forEach(function (key) { var _a; if (!key.indexOf(lockStorageKey)) { var lockQueue = JSON.parse((_a = localStorage.getItem(key)) !== null && _a !== void 0 ? _a : '[]'); var newValue = JSON.stringify(lockQueue.filter(function (item) { return tabIds.indexOf(item.tabId) === -1; } // && localStorage.getItem(`${tabStorageKey}:${item.tabId}`) )); _storage.setItem(key, newValue); } }); tabIds.forEach(function (_tabId) { return _storage.removeItem("".concat(tabStorageKey, ":").concat(_tabId)); }); }; var addUnloadListener = function () { if (isListenUnload) return; isListenUnload = true; /** * After unload event, It is known that the clear of localStorage in some Firefox scenarios and Safari v10 does not execute properly. */ window.addEventListener('pagehide', function () { // do not use `unload` event // https://developer.chrome.com/docs/web-platform/deprecating-unload clearTabLocks([tabId], localStorage); }); }; var filterInvalidTabs = function () { var invalidTabIds = []; Object.keys(localStorage).forEach(function (key) { var _a; if (!key.indexOf(tabStorageKey)) { var timestamp = localStorage.getItem(key); // TODO: think about Wakeup // Maximum is thread lock for 1 second + 1.99 second. if (timestamp && Date.now() - Number(timestamp) > 2999) { var expiredTabId = key.replace("".concat(tabStorageKey, ":"), ''); invalidTabIds.push(expiredTabId); } } else if (!key.indexOf(lockStorageKey)) { var lockQueue = JSON.parse((_a = localStorage.getItem(key)) !== null && _a !== void 0 ? _a : '[]'); lockQueue.forEach(function (item) { if (!localStorage.getItem("".concat(tabStorageKey, ":").concat(item.tabId))) { invalidTabIds.push(item.tabId); } }); } }); return invalidTabIds; }; var heartbeat = function () { if (typeof heartbeatTimer === 'undefined') { var tabHeartbeatKey_1 = "".concat(tabStorageKey, ":").concat(tabId); storage.setItem(tabHeartbeatKey_1, Date.now().toString()); heartbeatTimer = window.setInterval(function () { return storage.setItem(tabHeartbeatKey_1, Date.now().toString()); }, 1000); } }; var createFrameStorage = function () { var iframe = document.createElement('iframe'); iframe.src = 'about:blank'; iframe.setAttribute('style', 'display: none;'); document.body.appendChild(iframe); storage = iframe.contentWindow.localStorage; }; var simpleLock = function (name, callback) { createFrameStorage(); addUnloadListener(); heartbeat(); return new Promise(function (resolve, reject) { var _a; var lockId = createId(); lockMap.set(name, (_a = lockMap.get(name)) !== null && _a !== void 0 ? _a : new Map()); lockMap.get(name).set(lockId, callback); var storageKey = "".concat(lockStorageKey, ":").concat(name); var oldStorageValue = localStorage.getItem(storageKey); var lockQueue = JSON.parse(oldStorageValue !== null && oldStorageValue !== void 0 ? oldStorageValue : '[]'); lockQueue.push({ tabId: tabId, lockId: lockId }); var listener = function (event) { return __awaiter(void 0, void 0, void 0, function () { var _a, lock, result, e_1, currentLockQueue; var _b; return __generator(this, function (_c) { switch (_c.label) { case 0: if (!(event.key === storageKey && event.newValue)) return [3 /*break*/, 5]; _a = __read(JSON.parse(event.newValue), 1), lock = _a[0]; if (!((lock === null || lock === void 0 ? void 0 : lock.tabId) === tabId && (lock === null || lock === void 0 ? void 0 : lock.lockId) === lockId)) return [3 /*break*/, 5]; window.removeEventListener('storage', listener); _c.label = 1; case 1: _c.trys.push([1, 3, , 4]); return [4 /*yield*/, lockMap.get(name).get(lockId)({ name: name, mode: 'exclusive', })]; case 2: result = _c.sent(); resolve(result); return [3 /*break*/, 4]; case 3: e_1 = _c.sent(); reject(e_1); return [3 /*break*/, 4]; case 4: lockMap.get(name).delete(lockId); currentLockQueue = JSON.parse((_b = localStorage.getItem(storageKey)) !== null && _b !== void 0 ? _b : '[]'); currentLockQueue.splice(0, 1); storage.setItem(storageKey, JSON.stringify(currentLockQueue)); _c.label = 5; case 5: return [2 /*return*/]; } }); }); }; window.addEventListener('storage', listener); storage.setItem(storageKey, JSON.stringify(lockQueue)); clearTabLocks(filterInvalidTabs(), storage); }); }; var useLock = function (name, callback) { var _a; var isPrimitiveLock = !!((_a = navigator.locks) === null || _a === void 0 ? void 0 : _a.request); if (isPrimitiveLock) { return navigator.locks.request(name, callback).catch(function (e) { if (e.code === 18 && e.toString().startsWith('SecurityError')) { return simpleLock(name, callback); } return e; }); } return simpleLock(name, callback); }; export { useLock };