Refactor AccountStorage to use private fields for state management
| @@ -7098,7 +7098,7 @@ export async function saveSettings(loopCounter = 0) { | |||
| 7098 | url: '/api/settings/save', | 7098 | url: '/api/settings/save', |
| 7099 | data: JSON.stringify({ | 7099 | data: JSON.stringify({ |
| 7100 | firstRun: firstRun, | 7100 | firstRun: firstRun, |
| 7101 | accountStorage: accountStorage.state, | 7101 | accountStorage: accountStorage.getState(), |
| 7102 | currentVersion: currentVersion, | 7102 | currentVersion: currentVersion, |
| 7103 | username: name1, | 7103 | username: name1, |
| 7104 | active_character: active_character, | 7104 | active_character: active_character, |
| @@ -34,10 +34,15 @@ const MIGRATABLE_KEYS = [ | |||
| 34 | * Provides access to account storage of arbitrary key-value pairs. | 34 | * Provides access to account storage of arbitrary key-value pairs. |
| 35 | */ | 35 | */ |
| 36 | class AccountStorage { | 36 | class AccountStorage { |
| 37 | constructor() { | 37 | /** |
| 38 | this.state = {}; | 38 | * @type {Record<string, string>} Storage state |
| 39 | this.ready = false; | 39 | */ |
| 40 | } | 40 | #state = {}; |
| 41 | |||
| 42 | /** | ||
| 43 | * @type {boolean} If the storage was initialized | ||
| 44 | */ | ||
| 45 | #ready = false; | ||
| 41 | 46 | ||
| 42 | #migrateLocalStorage() { | 47 | #migrateLocalStorage() { |
| 43 | for (let i = 0; i < globalThis.localStorage.length; i++) { | 48 | for (let i = 0; i < globalThis.localStorage.length; i++) { |
| @@ -45,7 +50,7 @@ class AccountStorage { | |||
| 45 | const value = globalThis.localStorage.getItem(key); | 50 | const value = globalThis.localStorage.getItem(key); |
| 46 | 51 | ||
| 47 | if (MIGRATABLE_KEYS.some(k => k.test(key))) { | 52 | if (MIGRATABLE_KEYS.some(k => k.test(key))) { |
| 48 | this.state[key] = value; | 53 | this.#state[key] = value; |
| 49 | globalThis.localStorage.removeItem(key); | 54 | globalThis.localStorage.removeItem(key); |
| 50 | } | 55 | } |
| 51 | } | 56 | } |
| @@ -57,16 +62,16 @@ class AccountStorage { | |||
| 57 | */ | 62 | */ |
| 58 | init(state) { | 63 | init(state) { |
| 59 | if (state && typeof state === 'object') { | 64 | if (state && typeof state === 'object') { |
| 60 | this.state = Object.assign(this.state, state); | 65 | this.#state = Object.assign(this.#state, state); |
| 61 | } | 66 | } |
| 62 | 67 | ||
| 63 | if (!Object.hasOwn(this.state, MIGRATED_MARKER)) { | 68 | if (!Object.hasOwn(this.#state, MIGRATED_MARKER)) { |
| 64 | this.#migrateLocalStorage(); | 69 | this.#migrateLocalStorage(); |
| 65 | this.state[MIGRATED_MARKER] = 1; | 70 | this.#state[MIGRATED_MARKER] = '1'; |
| 66 | saveSettingsDebounced(); | 71 | saveSettingsDebounced(); |
| 67 | } | 72 | } |
| 68 | 73 | ||
| 69 | this.ready = true; | 74 | this.#ready = true; |
| 70 | } | 75 | } |
| 71 | 76 | ||
| 72 | /** | 77 | /** |
| @@ -75,11 +80,11 @@ class AccountStorage { | |||
| 75 | * @returns {string|null} Value of the key | 80 | * @returns {string|null} Value of the key |
| 76 | */ | 81 | */ |
| 77 | getItem(key) { | 82 | getItem(key) { |
| 78 | if (!this.ready) { | 83 | if (!this.#ready) { |
| 79 | console.warn(`AccountStorage not ready (trying to read from ${key})`); | 84 | console.warn(`AccountStorage not ready (trying to read from ${key})`); |
| 80 | } | 85 | } |
| 81 | 86 | ||
| 82 | return Object.hasOwn(this.state, key) ? String(this.state[key]) : null; | 87 | return Object.hasOwn(this.#state, key) ? String(this.#state[key]) : null; |
| 83 | } | 88 | } |
| 84 | 89 | ||
| 85 | /** | 90 | /** |
| @@ -88,11 +93,11 @@ class AccountStorage { | |||
| 88 | * @param {string} value Value to set | 93 | * @param {string} value Value to set |
| 89 | */ | 94 | */ |
| 90 | setItem(key, value) { | 95 | setItem(key, value) { |
| 91 | if (!this.ready) { | 96 | if (!this.#ready) { |
| 92 | console.warn(`AccountStorage not ready (trying to write to ${key})`); | 97 | console.warn(`AccountStorage not ready (trying to write to ${key})`); |
| 93 | } | 98 | } |
| 94 | 99 | ||
| 95 | this.state[key] = String(value); | 100 | this.#state[key] = String(value); |
| 96 | saveSettingsDebounced(); | 101 | saveSettingsDebounced(); |
| 97 | } | 102 | } |
| 98 | 103 | ||
| @@ -101,17 +106,25 @@ class AccountStorage { | |||
| 101 | * @param {string} key Key to remove | 106 | * @param {string} key Key to remove |
| 102 | */ | 107 | */ |
| 103 | removeItem(key) { | 108 | removeItem(key) { |
| 104 | if (!this.ready) { | 109 | if (!this.#ready) { |
| 105 | console.warn(`AccountStorage not ready (trying to remove ${key})`); | 110 | console.warn(`AccountStorage not ready (trying to remove ${key})`); |
| 106 | } | 111 | } |
| 107 | 112 | ||
| 108 | if (!Object.hasOwn(this.state, key)) { | 113 | if (!Object.hasOwn(this.#state, key)) { |
| 109 | return; | 114 | return; |
| 110 | } | 115 | } |
| 111 | 116 | ||
| 112 | delete this.state[key]; | 117 | delete this.#state[key]; |
| 113 | saveSettingsDebounced(); | 118 | saveSettingsDebounced(); |
| 114 | } | 119 | } |
| 120 | |||
| 121 | /** | ||
| 122 | * Gets a snapshot of the storage state. | ||
| 123 | * @returns {Record<string, string>} A deep clone of the storage state | ||
| 124 | */ | ||
| 125 | getState() { | ||
| 126 | return structuredClone(this.#state); | ||
| 127 | } | ||
| 115 | } | 128 | } |
| 116 | 129 | ||
| 117 | /** | 130 | /** |