Add storage change propagation for same-window States

The browser's default storage event only triggers when a change happens
within another window, meaning that different StorageStates sharing the
same storage object would not be informed of each other's updates.
This commit is contained in:
Talia 2023-09-20 12:48:51 +02:00
parent 80b88ec647
commit 24f7cffa82
2 changed files with 29 additions and 14 deletions

View file

@ -99,11 +99,9 @@
</p> </p>
<p> <p>
This comes with a series of limitations: This comes with the disadvantage that all values must be stored in a
<ul> serialised form, which won't work for all data and will break identity.
<li>Two <code>StorageState</code>s backed by the same <code>Storage</code> object won't be notified of each other's changes.</li> Specifically, all values are converted to JSON strings for storage.
<li>As only strings can be stored, the library converts all values to and from JSON.</li>
</ul>
</p> </p>
<p> <p>

View file

@ -87,6 +87,16 @@ export class State extends EventTarget {
} }
} }
export class StorageChangeEvent extends Event {
constructor(storage, key, value, targetState) {
super("storagechange")
this.storageArea = storage
this.key = key
this.newValue = value
this.targetState = targetState
}
}
export class StoredState extends State { export class StoredState extends State {
#storage #storage
#valueKey #valueKey
@ -97,9 +107,10 @@ export class StoredState extends State {
this.#valueKey = options.key ?? 'value' this.#valueKey = options.key ?? 'value'
// Initialise storage from defaults // Initialise storage from defaults
for (const [key, value] of Object.entries(init)) { for (let [prop, value] of Object.entries(init)) {
if (this.#storage[key] == undefined) if (prop === this.#valueKey) prop = 'value'
this.set(key, value) if (this.#storage[prop] == undefined)
this.set(prop, value)
} }
// Emit change events for any changed keys // Emit change events for any changed keys
@ -111,16 +122,22 @@ export class StoredState extends State {
} }
// Listen for changes from other windows // Listen for changes from other windows
addEventListener("storage", event => { const handler = event => {
let prop = event.key if (event.targetState !== this && event.storageArea == this.#storage) {
if (prop === this.#valueKey) prop = 'value' let prop = event.key
this.emit(prop, JSON.parse(event.newValue)) if (prop === this.#valueKey) prop = 'value'
}) this.emit(prop, JSON.parse(event.newValue))
}
}
addEventListener("storage", handler)
addEventListener("storagechange", handler)
} }
set(prop, value) { set(prop, value) {
if (prop == "value") prop = this.#valueKey if (prop == "value") prop = this.#valueKey
this.#storage[prop] = JSON.stringify(value) const json = JSON.stringify(value)
dispatchEvent(new StorageChangeEvent(this.#storage, prop, json, this))
this.#storage[prop] = json
} }
get(prop) { get(prop) {