@cubist-labs/cubesigner-sdk-browser-storage 0.4.21-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +34 -0
- package/dist/index.js +130 -0
- package/dist/locks.d.ts +25 -0
- package/dist/locks.js +31 -0
- package/package.json +37 -0
- package/src/index.ts +151 -0
- package/src/locks.ts +42 -0
- package/tsconfig.json +14 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { SessionData, SessionManager, SessionMetadata } from "@cubist-labs/cubesigner-sdk";
|
|
2
|
+
import { EventEmitter } from "@cubist-labs/cubesigner-sdk";
|
|
3
|
+
type BrowserStorageEvents = {
|
|
4
|
+
logout(): void;
|
|
5
|
+
login(): void;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* A manager that persists into browser storage and uses weblocks to safely perform refreshes
|
|
9
|
+
*/
|
|
10
|
+
export declare class BrowserStorageManager extends EventEmitter<BrowserStorageEvents> implements SessionManager {
|
|
11
|
+
#private;
|
|
12
|
+
/**
|
|
13
|
+
*
|
|
14
|
+
* @param {string} key The storage key to use
|
|
15
|
+
* @param {Storage} [storage] The storage object to use (defaults to localStorage)
|
|
16
|
+
*/
|
|
17
|
+
constructor(key: string, storage?: Storage);
|
|
18
|
+
/**
|
|
19
|
+
* Loads the metadata for a session from storage
|
|
20
|
+
* @return {SessionMetadata} The session metadata
|
|
21
|
+
*/
|
|
22
|
+
metadata(): Promise<SessionMetadata>;
|
|
23
|
+
/**
|
|
24
|
+
* Loads the current access token from storage
|
|
25
|
+
* @return {string} The access token
|
|
26
|
+
*/
|
|
27
|
+
token(): Promise<string>;
|
|
28
|
+
/**
|
|
29
|
+
* Directly set the session (updating all consumers of the session storage)
|
|
30
|
+
* @param {SessionData} [session] The new session
|
|
31
|
+
*/
|
|
32
|
+
setSession(session?: SessionData): Promise<any>;
|
|
33
|
+
}
|
|
34
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
3
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
4
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
5
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
6
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
7
|
+
};
|
|
8
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
9
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
10
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
|
+
};
|
|
13
|
+
var _BrowserStorageManager_instances, _BrowserStorageManager_key, _BrowserStorageManager_storage, _BrowserStorageManager_lock, _BrowserStorageManager_emitIfNecessary, _BrowserStorageManager_loadSession;
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.BrowserStorageManager = void 0;
|
|
16
|
+
const cubesigner_sdk_1 = require("@cubist-labs/cubesigner-sdk");
|
|
17
|
+
const locks_1 = require("./locks");
|
|
18
|
+
/**
|
|
19
|
+
* A manager that persists into browser storage and uses weblocks to safely perform refreshes
|
|
20
|
+
*/
|
|
21
|
+
class BrowserStorageManager extends cubesigner_sdk_1.EventEmitter {
|
|
22
|
+
/**
|
|
23
|
+
*
|
|
24
|
+
* @param {string} key The storage key to use
|
|
25
|
+
* @param {Storage} [storage] The storage object to use (defaults to localStorage)
|
|
26
|
+
*/
|
|
27
|
+
constructor(key, storage = globalThis.localStorage) {
|
|
28
|
+
super();
|
|
29
|
+
_BrowserStorageManager_instances.add(this);
|
|
30
|
+
/** The storage key for the session data */
|
|
31
|
+
_BrowserStorageManager_key.set(this, void 0);
|
|
32
|
+
_BrowserStorageManager_storage.set(this, void 0);
|
|
33
|
+
_BrowserStorageManager_lock.set(this, void 0);
|
|
34
|
+
__classPrivateFieldSet(this, _BrowserStorageManager_key, key, "f");
|
|
35
|
+
__classPrivateFieldSet(this, _BrowserStorageManager_storage, storage, "f");
|
|
36
|
+
(__classPrivateFieldSet(this, _BrowserStorageManager_lock, `CS_SDK_STORAGE_LOCK_${key}`, "f")),
|
|
37
|
+
// Set up listeners to emit events when users log in or log out
|
|
38
|
+
window.addEventListener("storage", (ev) => {
|
|
39
|
+
// We only care about events on our storage object, using our key
|
|
40
|
+
if (ev.storageArea !== storage || ev.key !== key)
|
|
41
|
+
return;
|
|
42
|
+
__classPrivateFieldGet(this, _BrowserStorageManager_instances, "m", _BrowserStorageManager_emitIfNecessary).call(this, ev.oldValue, ev.newValue);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Loads the metadata for a session from storage
|
|
47
|
+
* @return {SessionMetadata} The session metadata
|
|
48
|
+
*/
|
|
49
|
+
async metadata() {
|
|
50
|
+
const sess = __classPrivateFieldGet(this, _BrowserStorageManager_instances, "m", _BrowserStorageManager_loadSession).call(this);
|
|
51
|
+
if (!sess) {
|
|
52
|
+
throw new cubesigner_sdk_1.NoSessionFoundError();
|
|
53
|
+
}
|
|
54
|
+
return (0, cubesigner_sdk_1.metadata)(sess);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Loads the current access token from storage
|
|
58
|
+
* @return {string} The access token
|
|
59
|
+
*/
|
|
60
|
+
async token() {
|
|
61
|
+
const sess = await (0, locks_1.readTestAndSet)(__classPrivateFieldGet(this, _BrowserStorageManager_lock, "f"), {
|
|
62
|
+
read: () => __classPrivateFieldGet(this, _BrowserStorageManager_instances, "m", _BrowserStorageManager_loadSession).call(this),
|
|
63
|
+
test: (v) => v !== undefined && (0, cubesigner_sdk_1.isStale)(v),
|
|
64
|
+
set: async (oldSession) => {
|
|
65
|
+
if ((0, cubesigner_sdk_1.isRefreshable)(oldSession)) {
|
|
66
|
+
try {
|
|
67
|
+
const newSession = await (0, cubesigner_sdk_1.refresh)(oldSession);
|
|
68
|
+
__classPrivateFieldGet(this, _BrowserStorageManager_storage, "f").setItem(__classPrivateFieldGet(this, _BrowserStorageManager_key, "f"), JSON.stringify(newSession));
|
|
69
|
+
console.log("Successfully refreshed");
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
if (e instanceof cubesigner_sdk_1.ErrResponse && e.status === 403) {
|
|
73
|
+
__classPrivateFieldGet(this, _BrowserStorageManager_storage, "f").removeItem(__classPrivateFieldGet(this, _BrowserStorageManager_key, "f"));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// If the session is not refreshable, we should remove it
|
|
79
|
+
__classPrivateFieldGet(this, _BrowserStorageManager_storage, "f").removeItem(__classPrivateFieldGet(this, _BrowserStorageManager_key, "f"));
|
|
80
|
+
}
|
|
81
|
+
// Notify that the session has changed
|
|
82
|
+
__classPrivateFieldGet(this, _BrowserStorageManager_instances, "m", _BrowserStorageManager_emitIfNecessary).call(this, "", __classPrivateFieldGet(this, _BrowserStorageManager_storage, "f").getItem(__classPrivateFieldGet(this, _BrowserStorageManager_key, "f")));
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
if (!sess) {
|
|
86
|
+
throw new cubesigner_sdk_1.NoSessionFoundError();
|
|
87
|
+
}
|
|
88
|
+
return sess.token;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Directly set the session (updating all consumers of the session storage)
|
|
92
|
+
* @param {SessionData} [session] The new session
|
|
93
|
+
*/
|
|
94
|
+
async setSession(session) {
|
|
95
|
+
return await navigator.locks.request(__classPrivateFieldGet(this, _BrowserStorageManager_lock, "f"), {
|
|
96
|
+
mode: "exclusive",
|
|
97
|
+
}, async () => {
|
|
98
|
+
const oldValue = __classPrivateFieldGet(this, _BrowserStorageManager_storage, "f").getItem(__classPrivateFieldGet(this, _BrowserStorageManager_key, "f"));
|
|
99
|
+
const newValue = session ? JSON.stringify(session) : null;
|
|
100
|
+
// Unlike during refresh, we don't pre-empt the read locks
|
|
101
|
+
// because this operation is synchronous
|
|
102
|
+
if (newValue) {
|
|
103
|
+
__classPrivateFieldGet(this, _BrowserStorageManager_storage, "f").setItem(__classPrivateFieldGet(this, _BrowserStorageManager_key, "f"), newValue);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
__classPrivateFieldGet(this, _BrowserStorageManager_storage, "f").removeItem(__classPrivateFieldGet(this, _BrowserStorageManager_key, "f"));
|
|
107
|
+
}
|
|
108
|
+
// Storage events don't fire if the store occurred on the same page,
|
|
109
|
+
// so we have to manually invoke our emit logic
|
|
110
|
+
__classPrivateFieldGet(this, _BrowserStorageManager_instances, "m", _BrowserStorageManager_emitIfNecessary).call(this, oldValue, newValue);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
exports.BrowserStorageManager = BrowserStorageManager;
|
|
115
|
+
_BrowserStorageManager_key = new WeakMap(), _BrowserStorageManager_storage = new WeakMap(), _BrowserStorageManager_lock = new WeakMap(), _BrowserStorageManager_instances = new WeakSet(), _BrowserStorageManager_emitIfNecessary = function _BrowserStorageManager_emitIfNecessary(oldValue, newValue) {
|
|
116
|
+
if (newValue === null && oldValue !== null) {
|
|
117
|
+
this.emit("logout");
|
|
118
|
+
}
|
|
119
|
+
// There is now a session when there didn't used to be.
|
|
120
|
+
else if (oldValue === null && newValue !== null) {
|
|
121
|
+
this.emit("login");
|
|
122
|
+
}
|
|
123
|
+
}, _BrowserStorageManager_loadSession = function _BrowserStorageManager_loadSession() {
|
|
124
|
+
const stored = __classPrivateFieldGet(this, _BrowserStorageManager_storage, "f").getItem(__classPrivateFieldGet(this, _BrowserStorageManager_key, "f"));
|
|
125
|
+
if (stored === null) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
return JSON.parse(stored);
|
|
129
|
+
};
|
|
130
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7O0FBQ0EsZ0VBUXFDO0FBQ3JDLG1DQUF5QztBQU96Qzs7R0FFRztBQUNILE1BQWEscUJBQ1gsU0FBUSw2QkFBa0M7SUFRMUM7Ozs7T0FJRztJQUNILFlBQVksR0FBVyxFQUFFLFVBQW1CLFVBQVUsQ0FBQyxZQUFZO1FBQ2pFLEtBQUssRUFBRSxDQUFDOztRQVhWLDJDQUEyQztRQUMzQyw2Q0FBYTtRQUNiLGlEQUFrQjtRQUNsQiw4Q0FBYztRQVNaLHVCQUFBLElBQUksOEJBQVEsR0FBRyxNQUFBLENBQUM7UUFDaEIsdUJBQUEsSUFBSSxrQ0FBWSxPQUFPLE1BQUEsQ0FBQztRQUN4QixDQUFDLHVCQUFBLElBQUksK0JBQVMsdUJBQXVCLEdBQUcsRUFBRSxNQUFBLENBQUM7WUFDekMsK0RBQStEO1lBQy9ELE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxTQUFTLEVBQUUsQ0FBQyxFQUFFLEVBQUUsRUFBRTtnQkFDeEMsaUVBQWlFO2dCQUNqRSxJQUFJLEVBQUUsQ0FBQyxXQUFXLEtBQUssT0FBTyxJQUFJLEVBQUUsQ0FBQyxHQUFHLEtBQUssR0FBRztvQkFBRSxPQUFPO2dCQUN6RCx1QkFBQSxJQUFJLGdGQUFpQixNQUFyQixJQUFJLEVBQWtCLEVBQUUsQ0FBQyxRQUFRLEVBQUUsRUFBRSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1lBQ2xELENBQUMsQ0FBQyxDQUFDO0lBQ1AsQ0FBQztJQStCRDs7O09BR0c7SUFDSCxLQUFLLENBQUMsUUFBUTtRQUNaLE1BQU0sSUFBSSxHQUFHLHVCQUFBLElBQUksNEVBQWEsTUFBakIsSUFBSSxDQUFlLENBQUM7UUFDakMsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO1lBQ1YsTUFBTSxJQUFJLG9DQUFtQixFQUFFLENBQUM7UUFDbEMsQ0FBQztRQUNELE9BQU8sSUFBQSx5QkFB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QUFRLENBQUMsQ0FBQztRQUM1QyxDQUFDLENBQ0YsQ0FBQztJQUNKLENBQUM7Q0FDRjtBQWxJRCxzREFrSUM7b1JBbEdrQixRQUF1QixFQUFFLFFBQXVCO0lBQy9ELElBQUksUUFBUSxLQUFLLElBQUksSUFBSSxRQUFRLEtBQUssSUFBSSxFQUFFLENBQUM7UUFDM0MsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUN0QixDQUFDO0lBRUQsdURBQXVEO1NBQ2xELElBQUksUUFBUSxLQUFLLElBQUksSUFBSSxRQUFRLEtBQUssSUFBSSxFQUFFLENBQUM7UUFDaEQsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUNyQixDQUFDO0FBQ0gsQ0FBQztJQU9DLE1BQU0sTUFBTSxHQUFHLHVCQUFBLElBQUksc0NBQVMsQ0FBQyxPQUFPLENBQUMsdUJBQUEsSUFBSSxrQ0FBSyxDQUFDLENBQUM7SUFDaEQsSUFBSSxNQUFNLEtBQUssSUFBSSxFQUFFLENBQUM7UUFDcEIsT0FBTztJQUNULENBQUM7SUFFRCxPQUFPLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLENBQUM7QUFDNUIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB0eXBlIHsgU2Vzc2lvbkRhdGEsIFNlc3Npb25NYW5hZ2VyLCBTZXNzaW9uTWV0YWRhdGEgfSBmcm9tIFwiQGN1YmlzdC1sYWJzL2N1YmVzaWduZXItc2RrXCI7XG5pbXBvcnQge1xuICBFdmVudEVtaXR0ZXIsXG4gIE5vU2Vzc2lvbkZvdW5kRXJyb3IsXG4gIGlzU3RhbGUsXG4gIGlzUmVmcmVzaGFibGUsXG4gIG1ldGFkYXRhLFxuICByZWZyZXNoLFxuICBFcnJSZXNwb25zZSxcbn0gZnJvbSBcIkBjdWJpc3QtbGFicy9jdWJlc2lnbmVyLXNka1wiO1xuaW1wb3J0IHsgcmVhZFRlc3RBbmRTZXQgfSBmcm9tIFwiLi9sb2Nrc1wiO1xuXG50eXBlIEJyb3dzZXJTdG9yYWdlRXZlbnRzID0ge1xuICBsb2dvdXQoKTogdm9pZDtcbiAgbG9naW4oKTogdm9pZDtcbn07XG5cbi8qKlxuICogQSBtYW5hZ2VyIHRoYXQgcGVyc2lzdHMgaW50byBicm93c2VyIHN0b3JhZ2UgYW5kIHVzZXMgd2VibG9ja3MgdG8gc2FmZWx5IHBlcmZvcm0gcmVmcmVzaGVzXG4gKi9cbmV4cG9ydCBjbGFzcyBCcm93c2VyU3RvcmFnZU1hbmFnZXJcbiAgZXh0ZW5kcyBFdmVudEVtaXR0ZXI8QnJvd3NlclN0b3JhZ2VFdmVudHM+XG4gIGltcGxlbWVudHMgU2Vzc2lvbk1hbmFnZXJcbntcbiAgLyoqIFRoZSBzdG9yYWdlIGtleSBmb3IgdGhlIHNlc3Npb24gZGF0YSAqL1xuICAja2V5OiBzdHJpbmc7XG4gICNzdG9yYWdlOiBTdG9yYWdlO1xuICAjbG9jazogc3RyaW5nO1xuXG4gIC8qKlxuICAgKlxuICAgKiBAcGFyYW0ge3N0cmluZ30ga2V5IFRoZSBzdG9yYWdlIGtleSB0byB1c2VcbiAgICogQHBhcmFtIHtTdG9yYWdlfSBbc3RvcmFnZV0gVGhlIHN0b3JhZ2Ugb2JqZWN0IHRvIHVzZSAoZGVmYXVsdHMgdG8gbG9jYWxTdG9yYWdlKVxuICAgKi9cbiAgY29uc3RydWN0b3Ioa2V5OiBzdHJpbmcsIHN0b3JhZ2U6IFN0b3JhZ2UgPSBnbG9iYWxUaGlzLmxvY2FsU3RvcmFnZSkge1xuICAgIHN1cGVyKCk7XG4gICAgdGhpcy4ja2V5ID0ga2V5O1xuICAgIHRoaXMuI3N0b3JhZ2UgPSBzdG9yYWdlO1xuICAgICh0aGlzLiNsb2NrID0gYENTX1NES19TVE9SQUdFX0xPQ0tfJHtrZXl9YCksXG4gICAgICAvLyBTZXQgdXAgbGlzdGVuZXJzIHRvIGVtaXQgZXZlbnRzIHdoZW4gdXNlcnMgbG9nIGluIG9yIGxvZyBvdXRcbiAgICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKFwic3RvcmFnZVwiLCAoZXYpID0+IHtcbiAgICAgICAgLy8gV2Ugb25seSBjYXJlIGFib3V0IGV2ZW50cyBvbiBvdXIgc3RvcmFnZSBvYmplY3QsIHVzaW5nIG91ciBrZXlcbiAgICAgICAgaWYgKGV2LnN0b3JhZ2VBcmVhICE9PSBzdG9yYWdlIHx8IGV2LmtleSAhPT0ga2V5KSByZXR1cm47XG4gICAgICAgIHRoaXMuI2VtaXRJZk5lY2Vzc2FyeShldi5vbGRWYWx1ZSwgZXYubmV3VmFsdWUpO1xuICAgICAgfSk7XG4gIH1cblxuICAvKipcbiAgICogRW1pdHMgYSBgbG9naW5gIG9yIGBsb2dvdXRgIGV2ZW50IGlmIG5lY2Vzc2FyeVxuICAgKiBAcGFyYW0ge251bGwgfCBzdHJpbmd9IG9sZFZhbHVlIFRoZSBwcmV2aW91c2x5IHN0b3JlZCB2YWx1ZVxuICAgKiBAcGFyYW0ge251bGwgfCBzdHJpbmd9IG5ld1ZhbHVlIFRoZSBuZXdseSBzdG9yZWQgdmFsdWVcbiAgICovXG4gICNlbWl0SWZOZWNlc3Nhcnkob2xkVmFsdWU6IG51bGwgfCBzdHJpbmcsIG5ld1ZhbHVlOiBzdHJpbmcgfCBudWxsKSB7XG4gICAgaWYgKG5ld1ZhbHVlID09PSBudWxsICYmIG9sZFZhbHVlICE9PSBudWxsKSB7XG4gICAgICB0aGlzLmVtaXQoXCJsb2dvdXRcIik7XG4gICAgfVxuXG4gICAgLy8gVGhlcmUgaXMgbm93IGEgc2Vzc2lvbiB3aGVuIHRoZXJlIGRpZG4ndCB1c2VkIHRvIGJlLlxuICAgIGVsc2UgaWYgKG9sZFZhbHVlID09PSBudWxsICYmIG5ld1ZhbHVlICE9PSBudWxsKSB7XG4gICAgICB0aGlzLmVtaXQoXCJsb2dpblwiKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogTG9hZHMgYSBzZXNzaW9uIGZyb20gdGhlIGNvbmZpZ3VyZWQgc3RvcmFnZSBhdCB0aGUgY29uZmlndXJlZCBrZXlcbiAgICogQHJldHVybiB7U2Vzc2lvbkRhdGEgfCB1bmRlZmluZWR9IFRoZSBzdG9yZWQgZGF0YSBvciB1bmRlZmluZWQgaWYgbm90IHByZXNlbnRcbiAgICovXG4gICNsb2FkU2Vzc2lvbigpOiBTZXNzaW9uRGF0YSB8IHVuZGVmaW5lZCB7XG4gICAgY29uc3Qgc3RvcmVkID0gdGhpcy4jc3RvcmFnZS5nZXRJdGVtKHRoaXMuI2tleSk7XG4gICAgaWYgKHN0b3JlZCA9PT0gbnVsbCkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIHJldHVybiBKU09OLnBhcnNlKHN0b3JlZCk7XG4gIH1cblxuICAvKipcbiAgICogTG9hZHMgdGhlIG1ldGFkYXRhIGZvciBhIHNlc3Npb24gZnJvbSBzdG9yYWdlXG4gICAqIEByZXR1cm4ge1Nlc3Npb25NZXRhZGF0YX0gVGhlIHNlc3Npb24gbWV0YWRhdGFcbiAgICovXG4gIGFzeW5jIG1ldGFkYXRhKCk6IFByb21pc2U8U2Vzc2lvbk1ldGFkYXRhPiB7XG4gICAgY29uc3Qgc2VzcyA9IHRoaXMuI2xvYWRTZXNzaW9uKCk7XG4gICAgaWYgKCFzZXNzKSB7XG4gICAgICB0aHJvdyBuZXcgTm9TZXNzaW9uRm91bmRFcnJvcigpO1xuICAgIH1cbiAgICByZXR1cm4gbWV0YWRhdGEoc2Vzcyk7XG4gIH1cblxuICAvKipcbiAgICogTG9hZHMgdGhlIGN1cnJlbnQgYWNjZXNzIHRva2VuIGZyb20gc3RvcmFnZVxuICAgKiBAcmV0dXJuIHtzdHJpbmd9IFRoZSBhY2Nlc3MgdG9rZW5cbiAgICovXG4gIGFzeW5jIHRva2VuKCk6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgY29uc3Qgc2VzcyA9IGF3YWl0IHJlYWRUZXN0QW5kU2V0PFNlc3Npb25EYXRhIHwgdW5kZWZpbmVkPih0aGlzLiNsb2NrLCB7XG4gICAgICByZWFkOiAoKSA9PiB0aGlzLiNsb2FkU2Vzc2lvbigpLFxuICAgICAgdGVzdDogKHYpID0+IHYgIT09IHVuZGVmaW5lZCAmJiBpc1N0YWxlKHYpLFxuICAgICAgc2V0OiBhc3luYyAob2xkU2Vzc2lvbjogU2Vzc2lvbkRhdGEpID0+IHtcbiAgICAgICAgaWYgKGlzUmVmcmVzaGFibGUob2xkU2Vzc2lvbikpIHtcbiAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgY29uc3QgbmV3U2Vzc2lvbiA9IGF3YWl0IHJlZnJlc2gob2xkU2Vzc2lvbik7XG4gICAgICAgICAgICB0aGlzLiNzdG9yYWdlLnNldEl0ZW0odGhpcy4ja2V5LCBKU09OLnN0cmluZ2lmeShuZXdTZXNzaW9uKSk7XG4gICAgICAgICAgICBjb25zb2xlLmxvZyhcIlN1Y2Nlc3NmdWxseSByZWZyZXNoZWRcIik7XG4gICAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgICAgaWYgKGUgaW5zdGFuY2VvZiBFcnJSZXNwb25zZSAmJiBlLnN0YXR1cyA9PT0gNDAzKSB7XG4gICAgICAgICAgICAgIHRoaXMuI3N0b3JhZ2UucmVtb3ZlSXRlbSh0aGlzLiNrZXkpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAvLyBJZiB0aGUgc2Vzc2lvbiBpcyBub3QgcmVmcmVzaGFibGUsIHdlIHNob3VsZCByZW1vdmUgaXRcbiAgICAgICAgICB0aGlzLiNzdG9yYWdlLnJlbW92ZUl0ZW0odGhpcy4ja2V5KTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIE5vdGlmeSB0aGF0IHRoZSBzZXNzaW9uIGhhcyBjaGFuZ2VkXG4gICAgICAgIHRoaXMuI2VtaXRJZk5lY2Vzc2FyeShcIlwiLCB0aGlzLiNzdG9yYWdlLmdldEl0ZW0odGhpcy4ja2V5KSk7XG4gICAgICB9LFxuICAgIH0pO1xuICAgIGlmICghc2Vzcykge1xuICAgICAgdGhyb3cgbmV3IE5vU2Vzc2lvbkZvdW5kRXJyb3IoKTtcbiAgICB9XG4gICAgcmV0dXJuIHNlc3MudG9rZW47XG4gIH1cblxuICAvKipcbiAgICogRGlyZWN0bHkgc2V0IHRoZSBzZXNzaW9uICh1cGRhdGluZyBhbGwgY29uc3VtZXJzIG9mIHRoZSBzZXNzaW9uIHN0b3JhZ2UpXG4gICAqIEBwYXJhbSB7U2Vzc2lvbkRhdGF9IFtzZXNzaW9uXSBUaGUgbmV3IHNlc3Npb25cbiAgICovXG4gIGFzeW5jIHNldFNlc3Npb24oc2Vzc2lvbj86IFNlc3Npb25EYXRhKSB7XG4gICAgcmV0dXJuIGF3YWl0IG5hdmlnYXRvci5sb2Nrcy5yZXF1ZXN0KFxuICAgICAgdGhpcy4jbG9jayxcbiAgICAgIHtcbiAgICAgICAgbW9kZTogXCJleGNsdXNpdmVcIixcbiAgICAgIH0sXG4gICAgICBhc3luYyAoKSA9PiB7XG4gICAgICAgIGNvbnN0IG9sZFZhbHVlID0gdGhpcy4jc3RvcmFnZS5nZXRJdGVtKHRoaXMuI2tleSk7XG4gICAgICAgIGNvbnN0IG5ld1ZhbHVlID0gc2Vzc2lvbiA/IEpTT04uc3RyaW5naWZ5KHNlc3Npb24pIDogbnVsbDtcblxuICAgICAgICAvLyBVbmxpa2UgZHVyaW5nIHJlZnJlc2gsIHdlIGRvbid0IHByZS1lbXB0IHRoZSByZWFkIGxvY2tzXG4gICAgICAgIC8vIGJlY2F1c2UgdGhpcyBvcGVyYXRpb24gaXMgc3luY2hyb25vdXNcbiAgICAgICAgaWYgKG5ld1ZhbHVlKSB7XG4gICAgICAgICAgdGhpcy4jc3RvcmFnZS5zZXRJdGVtKHRoaXMuI2tleSwgbmV3VmFsdWUpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHRoaXMuI3N0b3JhZ2UucmVtb3ZlSXRlbSh0aGlzLiNrZXkpO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gU3RvcmFnZSBldmVudHMgZG9uJ3QgZmlyZSBpZiB0aGUgc3RvcmUgb2NjdXJyZWQgb24gdGhlIHNhbWUgcGFnZSxcbiAgICAgICAgLy8gc28gd2UgaGF2ZSB0byBtYW51YWxseSBpbnZva2Ugb3VyIGVtaXQgbG9naWNcbiAgICAgICAgdGhpcy4jZW1pdElmTmVjZXNzYXJ5KG9sZFZhbHVlLCBuZXdWYWx1ZSk7XG4gICAgICB9LFxuICAgICk7XG4gIH1cbn1cbiJdfQ==
|
package/dist/locks.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Used as an argument to {@link readTestAndSet}.
|
|
3
|
+
* Defines the behavior to perform during the locking strategy
|
|
4
|
+
*/
|
|
5
|
+
export type ReadTestAndSet<T> = {
|
|
6
|
+
/** How to read the data */
|
|
7
|
+
read(): T;
|
|
8
|
+
/** If true, perform set, if false, return v */
|
|
9
|
+
test(v: T): boolean;
|
|
10
|
+
/** Persist the value */
|
|
11
|
+
set(prev: T): Promise<void>;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* A locking primitive for contentious reads and writes.
|
|
15
|
+
*
|
|
16
|
+
* Guarantees:
|
|
17
|
+
* 1. Set is only called when test(read()) returns true
|
|
18
|
+
* 2. Only 1 set() call is active at a time
|
|
19
|
+
* 3. read() never occurs while set() is running
|
|
20
|
+
*
|
|
21
|
+
* @param {string} lock The name of the lock to use
|
|
22
|
+
* @param {ReadTestAndSet} spec The set of read, test, and set functions to use
|
|
23
|
+
* @return {Promise<T>} The current value
|
|
24
|
+
*/
|
|
25
|
+
export declare function readTestAndSet<T>(lock: string, { read, test, set }: ReadTestAndSet<T>): Promise<T>;
|
package/dist/locks.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readTestAndSet = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* A locking primitive for contentious reads and writes.
|
|
6
|
+
*
|
|
7
|
+
* Guarantees:
|
|
8
|
+
* 1. Set is only called when test(read()) returns true
|
|
9
|
+
* 2. Only 1 set() call is active at a time
|
|
10
|
+
* 3. read() never occurs while set() is running
|
|
11
|
+
*
|
|
12
|
+
* @param {string} lock The name of the lock to use
|
|
13
|
+
* @param {ReadTestAndSet} spec The set of read, test, and set functions to use
|
|
14
|
+
* @return {Promise<T>} The current value
|
|
15
|
+
*/
|
|
16
|
+
async function readTestAndSet(lock, { read, test, set }) {
|
|
17
|
+
const value = await navigator.locks.request(lock, { mode: "shared" }, async () => read());
|
|
18
|
+
if (test(value)) {
|
|
19
|
+
return navigator.locks.request(lock, { mode: "exclusive" }, async () => {
|
|
20
|
+
const prev = read();
|
|
21
|
+
if (test(prev)) {
|
|
22
|
+
await set(prev);
|
|
23
|
+
return read();
|
|
24
|
+
}
|
|
25
|
+
return prev;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
exports.readTestAndSet = readTestAndSet;
|
|
31
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibG9ja3MuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvbG9ja3MudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBYUE7Ozs7Ozs7Ozs7O0dBV0c7QUFDSSxLQUFLLFVBQVUsY0FBYyxDQUNsQyxJQUFZLEVBQ1osRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLEdBQUcsRUFBcUI7SUFFdEMsTUFBTSxLQUFLLEdBQUcsTUFBTSxTQUFTLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLEVBQUUsS0FBSyxJQUFJLEVBQUUsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDO0lBQzFGLElBQUksSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7UUFDaEIsT0FBTyxTQUFTLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsRUFBRSxJQUFJLEVBQUUsV0FBVyxFQUFFLEVBQUUsS0FBSyxJQUFJLEVBQUU7WUFDckUsTUFBTSxJQUFJLEdBQUcsSUFBSSxFQUFFLENBQUM7WUFDcEIsSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztnQkFDZixNQUFNLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQztnQkFDaEIsT0FBTyxJQUFJLEVBQUUsQ0FBQztZQUNoQixDQUFDO1lBQ0QsT0FBTyxJQUFJLENBQUM7UUFDZCxDQUFDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFDRCxPQUFPLEtBQUssQ0FBQztBQUNmLENBQUM7QUFoQkQsd0NBZ0JDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBVc2VkIGFzIGFuIGFyZ3VtZW50IHRvIHtAbGluayByZWFkVGVzdEFuZFNldH0uXG4gKiBEZWZpbmVzIHRoZSBiZWhhdmlvciB0byBwZXJmb3JtIGR1cmluZyB0aGUgbG9ja2luZyBzdHJhdGVneVxuICovXG5leHBvcnQgdHlwZSBSZWFkVGVzdEFuZFNldDxUPiA9IHtcbiAgLyoqIEhvdyB0byByZWFkIHRoZSBkYXRhICovXG4gIHJlYWQoKTogVDtcbiAgLyoqIElmIHRydWUsIHBlcmZvcm0gc2V0LCBpZiBmYWxzZSwgcmV0dXJuIHYgKi9cbiAgdGVzdCh2OiBUKTogYm9vbGVhbjtcbiAgLyoqIFBlcnNpc3QgdGhlIHZhbHVlICovXG4gIHNldChwcmV2OiBUKTogUHJvbWlzZTx2b2lkPjtcbn07XG5cbi8qKlxuICogQSBsb2NraW5nIHByaW1pdGl2ZSBmb3IgY29udGVudGlvdXMgcmVhZHMgYW5kIHdyaXRlcy5cbiAqXG4gKiBHdWFyYW50ZWVzOlxuICogIDEuIFNldCBpcyBvbmx5IGNhbGxlZCB3aGVuIHRlc3QocmVhZCgpKSByZXR1cm5zIHRydWVcbiAqICAyLiBPbmx5IDEgc2V0KCkgY2FsbCBpcyBhY3RpdmUgYXQgYSB0aW1lXG4gKiAgMy4gcmVhZCgpIG5ldmVyIG9jY3VycyB3aGlsZSBzZXQoKSBpcyBydW5uaW5nXG4gKlxuICogQHBhcmFtIHtzdHJpbmd9IGxvY2sgVGhlIG5hbWUgb2YgdGhlIGxvY2sgdG8gdXNlXG4gKiBAcGFyYW0ge1JlYWRUZXN0QW5kU2V0fSBzcGVjIFRoZSBzZXQgb2YgcmVhZCwgdGVzdCwgYW5kIHNldCBmdW5jdGlvbnMgdG8gdXNlXG4gKiBAcmV0dXJuIHtQcm9taXNlPFQ+fSBUaGUgY3VycmVudCB2YWx1ZVxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gcmVhZFRlc3RBbmRTZXQ8VD4oXG4gIGxvY2s6IHN0cmluZyxcbiAgeyByZWFkLCB0ZXN0LCBzZXQgfTogUmVhZFRlc3RBbmRTZXQ8VD4sXG4pOiBQcm9taXNlPFQ+IHtcbiAgY29uc3QgdmFsdWUgPSBhd2FpdCBuYXZpZ2F0b3IubG9ja3MucmVxdWVzdChsb2NrLCB7IG1vZGU6IFwic2hhcmVkXCIgfSwgYXN5bmMgKCkgPT4gcmVhZCgpKTtcbiAgaWYgKHRlc3QodmFsdWUpKSB7XG4gICAgcmV0dXJuIG5hdmlnYXRvci5sb2Nrcy5yZXF1ZXN0KGxvY2ssIHsgbW9kZTogXCJleGNsdXNpdmVcIiB9LCBhc3luYyAoKSA9PiB7XG4gICAgICBjb25zdCBwcmV2ID0gcmVhZCgpO1xuICAgICAgaWYgKHRlc3QocHJldikpIHtcbiAgICAgICAgYXdhaXQgc2V0KHByZXYpO1xuICAgICAgICByZXR1cm4gcmVhZCgpO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHByZXY7XG4gICAgfSk7XG4gIH1cbiAgcmV0dXJuIHZhbHVlO1xufVxuIl19
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cubist-labs/cubesigner-sdk-browser-storage",
|
|
3
|
+
"version": "0.4.21-0",
|
|
4
|
+
"description": "Browser-based storage for CubeSigner SDK sessions",
|
|
5
|
+
"license": "MIT OR Apache-2.0",
|
|
6
|
+
"author": "Cubist, Inc.",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"tsconfig.json",
|
|
11
|
+
"src/**",
|
|
12
|
+
"dist/**",
|
|
13
|
+
"../../NOTICE",
|
|
14
|
+
"../../LICENSE-APACHE",
|
|
15
|
+
"../../LICENSE-MIT"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"prepack": "tsc",
|
|
20
|
+
"test:playwright": "playwright test",
|
|
21
|
+
"test:build": "esbuild test/site/testscript.ts --bundle --format=esm --outfile='test/site/testscript.js' --external:crypto",
|
|
22
|
+
"test:server": "http-server ./test/site -c-1"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@cubist-labs/cubesigner-sdk": "^0.4.21-0"
|
|
26
|
+
},
|
|
27
|
+
"directories": {
|
|
28
|
+
"test": "test"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@cubist-labs/cubesigner-sdk": "^0.4.21-0",
|
|
32
|
+
"@cubist-labs/cubesigner-sdk-fs-storage": "^0.4.21-0",
|
|
33
|
+
"@playwright/test": "^1.42.1",
|
|
34
|
+
"esbuild": "^0.20.1",
|
|
35
|
+
"http-server": "^14.1.1"
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type { SessionData, SessionManager, SessionMetadata } from "@cubist-labs/cubesigner-sdk";
|
|
2
|
+
import {
|
|
3
|
+
EventEmitter,
|
|
4
|
+
NoSessionFoundError,
|
|
5
|
+
isStale,
|
|
6
|
+
isRefreshable,
|
|
7
|
+
metadata,
|
|
8
|
+
refresh,
|
|
9
|
+
ErrResponse,
|
|
10
|
+
} from "@cubist-labs/cubesigner-sdk";
|
|
11
|
+
import { readTestAndSet } from "./locks";
|
|
12
|
+
|
|
13
|
+
type BrowserStorageEvents = {
|
|
14
|
+
logout(): void;
|
|
15
|
+
login(): void;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A manager that persists into browser storage and uses weblocks to safely perform refreshes
|
|
20
|
+
*/
|
|
21
|
+
export class BrowserStorageManager
|
|
22
|
+
extends EventEmitter<BrowserStorageEvents>
|
|
23
|
+
implements SessionManager
|
|
24
|
+
{
|
|
25
|
+
/** The storage key for the session data */
|
|
26
|
+
#key: string;
|
|
27
|
+
#storage: Storage;
|
|
28
|
+
#lock: string;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
*
|
|
32
|
+
* @param {string} key The storage key to use
|
|
33
|
+
* @param {Storage} [storage] The storage object to use (defaults to localStorage)
|
|
34
|
+
*/
|
|
35
|
+
constructor(key: string, storage: Storage = globalThis.localStorage) {
|
|
36
|
+
super();
|
|
37
|
+
this.#key = key;
|
|
38
|
+
this.#storage = storage;
|
|
39
|
+
(this.#lock = `CS_SDK_STORAGE_LOCK_${key}`),
|
|
40
|
+
// Set up listeners to emit events when users log in or log out
|
|
41
|
+
window.addEventListener("storage", (ev) => {
|
|
42
|
+
// We only care about events on our storage object, using our key
|
|
43
|
+
if (ev.storageArea !== storage || ev.key !== key) return;
|
|
44
|
+
this.#emitIfNecessary(ev.oldValue, ev.newValue);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Emits a `login` or `logout` event if necessary
|
|
50
|
+
* @param {null | string} oldValue The previously stored value
|
|
51
|
+
* @param {null | string} newValue The newly stored value
|
|
52
|
+
*/
|
|
53
|
+
#emitIfNecessary(oldValue: null | string, newValue: string | null) {
|
|
54
|
+
if (newValue === null && oldValue !== null) {
|
|
55
|
+
this.emit("logout");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// There is now a session when there didn't used to be.
|
|
59
|
+
else if (oldValue === null && newValue !== null) {
|
|
60
|
+
this.emit("login");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Loads a session from the configured storage at the configured key
|
|
66
|
+
* @return {SessionData | undefined} The stored data or undefined if not present
|
|
67
|
+
*/
|
|
68
|
+
#loadSession(): SessionData | undefined {
|
|
69
|
+
const stored = this.#storage.getItem(this.#key);
|
|
70
|
+
if (stored === null) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return JSON.parse(stored);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Loads the metadata for a session from storage
|
|
79
|
+
* @return {SessionMetadata} The session metadata
|
|
80
|
+
*/
|
|
81
|
+
async metadata(): Promise<SessionMetadata> {
|
|
82
|
+
const sess = this.#loadSession();
|
|
83
|
+
if (!sess) {
|
|
84
|
+
throw new NoSessionFoundError();
|
|
85
|
+
}
|
|
86
|
+
return metadata(sess);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Loads the current access token from storage
|
|
91
|
+
* @return {string} The access token
|
|
92
|
+
*/
|
|
93
|
+
async token(): Promise<string> {
|
|
94
|
+
const sess = await readTestAndSet<SessionData | undefined>(this.#lock, {
|
|
95
|
+
read: () => this.#loadSession(),
|
|
96
|
+
test: (v) => v !== undefined && isStale(v),
|
|
97
|
+
set: async (oldSession: SessionData) => {
|
|
98
|
+
if (isRefreshable(oldSession)) {
|
|
99
|
+
try {
|
|
100
|
+
const newSession = await refresh(oldSession);
|
|
101
|
+
this.#storage.setItem(this.#key, JSON.stringify(newSession));
|
|
102
|
+
console.log("Successfully refreshed");
|
|
103
|
+
} catch (e) {
|
|
104
|
+
if (e instanceof ErrResponse && e.status === 403) {
|
|
105
|
+
this.#storage.removeItem(this.#key);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
// If the session is not refreshable, we should remove it
|
|
110
|
+
this.#storage.removeItem(this.#key);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Notify that the session has changed
|
|
114
|
+
this.#emitIfNecessary("", this.#storage.getItem(this.#key));
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
if (!sess) {
|
|
118
|
+
throw new NoSessionFoundError();
|
|
119
|
+
}
|
|
120
|
+
return sess.token;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Directly set the session (updating all consumers of the session storage)
|
|
125
|
+
* @param {SessionData} [session] The new session
|
|
126
|
+
*/
|
|
127
|
+
async setSession(session?: SessionData) {
|
|
128
|
+
return await navigator.locks.request(
|
|
129
|
+
this.#lock,
|
|
130
|
+
{
|
|
131
|
+
mode: "exclusive",
|
|
132
|
+
},
|
|
133
|
+
async () => {
|
|
134
|
+
const oldValue = this.#storage.getItem(this.#key);
|
|
135
|
+
const newValue = session ? JSON.stringify(session) : null;
|
|
136
|
+
|
|
137
|
+
// Unlike during refresh, we don't pre-empt the read locks
|
|
138
|
+
// because this operation is synchronous
|
|
139
|
+
if (newValue) {
|
|
140
|
+
this.#storage.setItem(this.#key, newValue);
|
|
141
|
+
} else {
|
|
142
|
+
this.#storage.removeItem(this.#key);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Storage events don't fire if the store occurred on the same page,
|
|
146
|
+
// so we have to manually invoke our emit logic
|
|
147
|
+
this.#emitIfNecessary(oldValue, newValue);
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
package/src/locks.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Used as an argument to {@link readTestAndSet}.
|
|
3
|
+
* Defines the behavior to perform during the locking strategy
|
|
4
|
+
*/
|
|
5
|
+
export type ReadTestAndSet<T> = {
|
|
6
|
+
/** How to read the data */
|
|
7
|
+
read(): T;
|
|
8
|
+
/** If true, perform set, if false, return v */
|
|
9
|
+
test(v: T): boolean;
|
|
10
|
+
/** Persist the value */
|
|
11
|
+
set(prev: T): Promise<void>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A locking primitive for contentious reads and writes.
|
|
16
|
+
*
|
|
17
|
+
* Guarantees:
|
|
18
|
+
* 1. Set is only called when test(read()) returns true
|
|
19
|
+
* 2. Only 1 set() call is active at a time
|
|
20
|
+
* 3. read() never occurs while set() is running
|
|
21
|
+
*
|
|
22
|
+
* @param {string} lock The name of the lock to use
|
|
23
|
+
* @param {ReadTestAndSet} spec The set of read, test, and set functions to use
|
|
24
|
+
* @return {Promise<T>} The current value
|
|
25
|
+
*/
|
|
26
|
+
export async function readTestAndSet<T>(
|
|
27
|
+
lock: string,
|
|
28
|
+
{ read, test, set }: ReadTestAndSet<T>,
|
|
29
|
+
): Promise<T> {
|
|
30
|
+
const value = await navigator.locks.request(lock, { mode: "shared" }, async () => read());
|
|
31
|
+
if (test(value)) {
|
|
32
|
+
return navigator.locks.request(lock, { mode: "exclusive" }, async () => {
|
|
33
|
+
const prev = read();
|
|
34
|
+
if (test(prev)) {
|
|
35
|
+
await set(prev);
|
|
36
|
+
return read();
|
|
37
|
+
}
|
|
38
|
+
return prev;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"module": "Node16",
|
|
5
|
+
"moduleResolution": "node16",
|
|
6
|
+
"outDir": "./dist"
|
|
7
|
+
},
|
|
8
|
+
"typedocOptions": {
|
|
9
|
+
"out": "./docs",
|
|
10
|
+
"entryPoints": ["src/index.ts"]
|
|
11
|
+
},
|
|
12
|
+
"exclude": ["node_modules", "dist"],
|
|
13
|
+
"include": ["src/**/*.ts"]
|
|
14
|
+
}
|