reactant-share
Version:
A framework for building shared web applications with Reactant
145 lines (142 loc) • 6.38 kB
JavaScript
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 };