'use strict'; function base64UrlEncode(input) { return window.btoa(String.fromCharCode(...input)).replace(/\+/g, "-").replace(/\//g, "_").replace(/={1,2}$/, ""); } function base64UrlDecode(input) { return new Uint8Array( window.atob(input.replace(/-/g, "+").replace(/_/g, "/")).split("").map((x) => x.charCodeAt(0)) ); } var __accessCheck = (obj, member, msg) => { if (!member.has(obj)) throw TypeError("Cannot " + msg); }; var __privateGet = (obj, member, getter) => { __accessCheck(obj, member, "read from private field"); return getter ? getter.call(obj) : member.get(obj); }; var __privateAdd = (obj, member, value) => { if (member.has(obj)) throw TypeError("Cannot add the same private member more than once"); member instanceof WeakSet ? member.add(obj) : member.set(obj, value); }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); setter ? setter.call(obj, value) : member.set(obj, value); return value; }; var _internalState; class LocalStateSync { constructor({ encryptionKey, ...config }) { __privateAdd(this, _internalState, void 0); __privateSet(this, _internalState, { state: "idle" }); this.config = { ...config, stateParser: config.stateParser ?? JSON.parse, stateSerializer: config.stateSerializer ?? JSON.stringify }; this.setup(encryptionKey).then(this.loadFromLocalStorage.bind(this)); } async setState(state) { if (typeof window === "undefined") { console.warn("LocalStateSync is disabled in Node.js"); return; } if (__privateGet(this, _internalState).state !== "loaded") { throw new Error("LocalStateSync is not ready"); } const encryptedState = await this.encryptState(state); window.localStorage.setItem(__privateGet(this, _internalState).storageKey, encryptedState); } clearState() { if (typeof window === "undefined") { console.warn("LocalStateSync is disabled in Node.js"); return; } if (__privateGet(this, _internalState).state !== "loaded") { throw new Error("LocalStateSync is not ready"); } window.localStorage.removeItem(__privateGet(this, _internalState).storageKey); } async setup(encodedEncryptionKey) { if (typeof window === "undefined") { console.warn("LocalStateSync is disabled in Node.js"); return; } const keyBuffer = base64UrlDecode(encodedEncryptionKey); if (keyBuffer.byteLength !== 32) { throw new Error( "LocalStateSync: encryptionKey must be 32 bytes (48 base64url characters)" ); } const encryptionKey = await window.crypto.subtle.importKey( "raw", keyBuffer, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"] ); const storageKey = base64UrlEncode( new Uint8Array(await window.crypto.subtle.digest("SHA-256", keyBuffer)) ); __privateSet(this, _internalState, { state: "loaded", storageKey, encryptionKey }); addEventListener("storage", this.handleStorageEvent.bind(this)); } async loadFromLocalStorage() { if (__privateGet(this, _internalState).state !== "loaded") { return; } const value = window.localStorage.getItem(__privateGet(this, _internalState).storageKey); if (!value) { return; } try { const state = await this.decryptState(value); this.config.onStateUpdated(state); } catch { } } async handleStorageEvent(event) { if (__privateGet(this, _internalState).state !== "loaded") { return; } if (event.key !== __privateGet(this, _internalState).storageKey || !event.newValue) { return; } try { const state = await this.decryptState(event.newValue); this.config.onStateUpdated(state); } catch { } } async decryptState(storageValue) { if (__privateGet(this, _internalState).state !== "loaded") { throw new Error("LocalStateSync is not ready"); } const [iv, ciphertext] = storageValue.split("."); const cleartext = await window.crypto.subtle.decrypt( { name: "AES-GCM", iv: base64UrlDecode(iv) }, __privateGet(this, _internalState).encryptionKey, base64UrlDecode(ciphertext) ); const serializedState = new TextDecoder().decode(cleartext); return this.config.stateParser(serializedState); } async encryptState(state) { if (__privateGet(this, _internalState).state !== "loaded") { throw new Error("LocalStateSync is not ready"); } const serializedState = this.config.stateSerializer(state); const iv = window.crypto.getRandomValues(new Uint8Array(12)); const ciphertext = await window.crypto.subtle.encrypt( { name: "AES-GCM", iv }, __privateGet(this, _internalState).encryptionKey, new TextEncoder().encode(serializedState) ); return [ base64UrlEncode(iv), base64UrlEncode(new Uint8Array(ciphertext)) ].join("."); } } _internalState = new WeakMap(); exports.LocalStateSync = LocalStateSync;