"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var _this = this; Object.defineProperty(exports, "__esModule", { value: true }); var processLock_1 = require("./processLock"); /** * @author: SuperTokens (https://github.com/supertokens) * This library was created as a part of a larger project, SuperTokens(https://supertokens.io) - the best session management solution. * You can also check out our other projects on https://github.com/supertokens * * To contribute to this package visit https://github.com/supertokens/browser-tabs-lock * If you face any problems you can file an issue on https://github.com/supertokens/browser-tabs-lock/issues * * If you have any questions or if you just want to say hi visit https://supertokens.io/discord */ /** * @constant * @type {string} * @default * @description All the locks taken by this package will have this as prefix */ var LOCK_STORAGE_KEY = 'browser-tabs-lock-key'; var DEFAULT_STORAGE_HANDLER = { key: function (index) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { throw new Error("Unsupported"); }); }); }, getItem: function (key) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { throw new Error("Unsupported"); }); }); }, clear: function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, window.localStorage.clear()]; }); }); }, removeItem: function (key) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { throw new Error("Unsupported"); }); }); }, setItem: function (key, value) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { throw new Error("Unsupported"); }); }); }, keySync: function (index) { return window.localStorage.key(index); }, getItemSync: function (key) { return window.localStorage.getItem(key); }, clearSync: function () { return window.localStorage.clear(); }, removeItemSync: function (key) { return window.localStorage.removeItem(key); }, setItemSync: function (key, value) { return window.localStorage.setItem(key, value); }, }; /** * @function delay * @param {number} milliseconds - How long the delay should be in terms of milliseconds * @returns {Promise} */ function delay(milliseconds) { return new Promise(function (resolve) { return setTimeout(resolve, milliseconds); }); } /** * @function generateRandomString * @params {number} length - How long the random string should be * @returns {string} * @description returns random string whose length is equal to the length passed as parameter */ function generateRandomString(length) { var CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz'; var randomstring = ''; for (var i = 0; i < length; i++) { var INDEX = Math.floor(Math.random() * CHARS.length); randomstring += CHARS[INDEX]; } return randomstring; } /** * @function getLockId * @returns {string} * @description Generates an id which will be unique for the browser tab */ function getLockId() { return Date.now().toString() + generateRandomString(15); } var SuperTokensLock = /** @class */ (function () { function SuperTokensLock(storageHandler) { this.acquiredIatSet = new Set(); this.storageHandler = undefined; this.id = getLockId(); this.acquireLock = this.acquireLock.bind(this); this.releaseLock = this.releaseLock.bind(this); this.releaseLock__private__ = this.releaseLock__private__.bind(this); this.waitForSomethingToChange = this.waitForSomethingToChange.bind(this); this.refreshLockWhileAcquired = this.refreshLockWhileAcquired.bind(this); this.storageHandler = storageHandler; if (SuperTokensLock.waiters === undefined) { SuperTokensLock.waiters = []; } } /** * @async * @memberOf Lock * @function acquireLock * @param {string} lockKey - Key for which the lock is being acquired * @param {number} [timeout=5000] - Maximum time for which the function will wait to acquire the lock * @returns {Promise} * @description Will return true if lock is being acquired, else false. * Also the lock can be acquired for maximum 10 secs */ SuperTokensLock.prototype.acquireLock = function (lockKey, timeout) { if (timeout === void 0) { timeout = 5000; } return __awaiter(this, void 0, void 0, function () { var iat, MAX_TIME, STORAGE_KEY, STORAGE, lockObj, TIMEOUT_KEY, lockObjPostDelay, parsedLockObjPostDelay; return __generator(this, function (_a) { switch (_a.label) { case 0: iat = Date.now() + generateRandomString(4); MAX_TIME = Date.now() + timeout; STORAGE_KEY = LOCK_STORAGE_KEY + "-" + lockKey; STORAGE = this.storageHandler === undefined ? DEFAULT_STORAGE_HANDLER : this.storageHandler; _a.label = 1; case 1: if (!(Date.now() < MAX_TIME)) return [3 /*break*/, 8]; return [4 /*yield*/, delay(30)]; case 2: _a.sent(); lockObj = STORAGE.getItemSync(STORAGE_KEY); if (!(lockObj === null)) return [3 /*break*/, 5]; TIMEOUT_KEY = this.id + "-" + lockKey + "-" + iat; // there is a problem if setItem happens at the exact same time for 2 different processes.. so we add some random delay here. return [4 /*yield*/, delay(Math.floor(Math.random() * 25))]; case 3: // there is a problem if setItem happens at the exact same time for 2 different processes.. so we add some random delay here. _a.sent(); STORAGE.setItemSync(STORAGE_KEY, JSON.stringify({ id: this.id, iat: iat, timeoutKey: TIMEOUT_KEY, timeAcquired: Date.now(), timeRefreshed: Date.now() })); return [4 /*yield*/, delay(30)]; case 4: _a.sent(); // this is to prevent race conditions. This time must be more than the time it takes for storage.setItem lockObjPostDelay = STORAGE.getItemSync(STORAGE_KEY); if (lockObjPostDelay !== null) { parsedLockObjPostDelay = JSON.parse(lockObjPostDelay); if (parsedLockObjPostDelay.id === this.id && parsedLockObjPostDelay.iat === iat) { this.acquiredIatSet.add(iat); this.refreshLockWhileAcquired(STORAGE_KEY, iat); return [2 /*return*/, true]; } } return [3 /*break*/, 7]; case 5: SuperTokensLock.lockCorrector(this.storageHandler === undefined ? DEFAULT_STORAGE_HANDLER : this.storageHandler); return [4 /*yield*/, this.waitForSomethingToChange(MAX_TIME)]; case 6: _a.sent(); _a.label = 7; case 7: iat = Date.now() + generateRandomString(4); return [3 /*break*/, 1]; case 8: return [2 /*return*/, false]; } }); }); }; SuperTokensLock.prototype.refreshLockWhileAcquired = function (storageKey, iat) { return __awaiter(this, void 0, void 0, function () { var _this = this; return __generator(this, function (_a) { setTimeout(function () { return __awaiter(_this, void 0, void 0, function () { var STORAGE, lockObj, parsedLockObj; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, processLock_1.default().lock(iat)]; case 1: _a.sent(); if (!this.acquiredIatSet.has(iat)) { processLock_1.default().unlock(iat); return [2 /*return*/]; } STORAGE = this.storageHandler === undefined ? DEFAULT_STORAGE_HANDLER : this.storageHandler; lockObj = STORAGE.getItemSync(storageKey); if (lockObj !== null) { parsedLockObj = JSON.parse(lockObj); parsedLockObj.timeRefreshed = Date.now(); STORAGE.setItemSync(storageKey, JSON.stringify(parsedLockObj)); processLock_1.default().unlock(iat); } else { processLock_1.default().unlock(iat); return [2 /*return*/]; } this.refreshLockWhileAcquired(storageKey, iat); return [2 /*return*/]; } }); }); }, 1000); return [2 /*return*/]; }); }); }; SuperTokensLock.prototype.waitForSomethingToChange = function (MAX_TIME) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, new Promise(function (resolve) { var resolvedCalled = false; var startedAt = Date.now(); var MIN_TIME_TO_WAIT = 50; // ms var removedListeners = false; function stopWaiting() { if (!removedListeners) { window.removeEventListener('storage', stopWaiting); SuperTokensLock.removeFromWaiting(stopWaiting); clearTimeout(timeOutId); removedListeners = true; } if (!resolvedCalled) { resolvedCalled = true; var timeToWait = MIN_TIME_TO_WAIT - (Date.now() - startedAt); if (timeToWait > 0) { setTimeout(resolve, timeToWait); } else { resolve(null); } } } window.addEventListener('storage', stopWaiting); SuperTokensLock.addToWaiting(stopWaiting); var timeOutId = setTimeout(stopWaiting, Math.max(0, MAX_TIME - Date.now())); })]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }; SuperTokensLock.addToWaiting = function (func) { this.removeFromWaiting(func); if (SuperTokensLock.waiters === undefined) { return; } SuperTokensLock.waiters.push(func); }; SuperTokensLock.removeFromWaiting = function (func) { if (SuperTokensLock.waiters === undefined) { return; } SuperTokensLock.waiters = SuperTokensLock.waiters.filter(function (i) { return i !== func; }); }; SuperTokensLock.notifyWaiters = function () { if (SuperTokensLock.waiters === undefined) { return; } var waiters = SuperTokensLock.waiters.slice(); // so that if Lock.waiters is changed it's ok. waiters.forEach(function (i) { return i(); }); }; /** * @function releaseLock * @memberOf Lock * @param {string} lockKey - Key for which lock is being released * @returns {void} * @description Release a lock. */ SuperTokensLock.prototype.releaseLock = function (lockKey) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.releaseLock__private__(lockKey)]; case 1: return [2 /*return*/, _a.sent()]; } }); }); }; /** * @function releaseLock * @memberOf Lock * @param {string} lockKey - Key for which lock is being released * @returns {void} * @description Release a lock. */ SuperTokensLock.prototype.releaseLock__private__ = function (lockKey) { return __awaiter(this, void 0, void 0, function () { var STORAGE, STORAGE_KEY, lockObj, parsedlockObj; return __generator(this, function (_a) { switch (_a.label) { case 0: STORAGE = this.storageHandler === undefined ? DEFAULT_STORAGE_HANDLER : this.storageHandler; STORAGE_KEY = LOCK_STORAGE_KEY + "-" + lockKey; lockObj = STORAGE.getItemSync(STORAGE_KEY); if (lockObj === null) { return [2 /*return*/]; } parsedlockObj = JSON.parse(lockObj); if (!(parsedlockObj.id === this.id)) return [3 /*break*/, 2]; return [4 /*yield*/, processLock_1.default().lock(parsedlockObj.iat)]; case 1: _a.sent(); this.acquiredIatSet.delete(parsedlockObj.iat); STORAGE.removeItemSync(STORAGE_KEY); processLock_1.default().unlock(parsedlockObj.iat); SuperTokensLock.notifyWaiters(); _a.label = 2; case 2: return [2 /*return*/]; } }); }); }; /** * @function lockCorrector * @returns {void} * @description If a lock is acquired by a tab and the tab is closed before the lock is * released, this function will release those locks */ SuperTokensLock.lockCorrector = function (storageHandler) { var MIN_ALLOWED_TIME = Date.now() - 5000; var STORAGE = storageHandler; var KEYS = []; var currIndex = 0; while (true) { var key = STORAGE.keySync(currIndex); if (key === null) { break; } KEYS.push(key); currIndex++; } var notifyWaiters = false; for (var i = 0; i < KEYS.length; i++) { var LOCK_KEY = KEYS[i]; if (LOCK_KEY.includes(LOCK_STORAGE_KEY)) { var lockObj = STORAGE.getItemSync(LOCK_KEY); if (lockObj !== null) { var parsedlockObj = JSON.parse(lockObj); if ((parsedlockObj.timeRefreshed === undefined && parsedlockObj.timeAcquired < MIN_ALLOWED_TIME) || (parsedlockObj.timeRefreshed !== undefined && parsedlockObj.timeRefreshed < MIN_ALLOWED_TIME)) { STORAGE.removeItemSync(LOCK_KEY); notifyWaiters = true; } } } } if (notifyWaiters) { SuperTokensLock.notifyWaiters(); } }; SuperTokensLock.waiters = undefined; return SuperTokensLock; }()); exports.default = SuperTokensLock;