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>
This comes with a series of limitations:
<ul>
<li>Two <code>StorageState</code>s backed by the same <code>Storage</code> object won't be notified of each other's changes.</li>
<li>As only strings can be stored, the library converts all values to and from JSON.</li>
</ul>
This comes with the disadvantage that all values must be stored in a
serialised form, which won't work for all data and will break identity.
Specifically, all values are converted to JSON strings for storage.
</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 {
#storage
#valueKey
@ -97,9 +107,10 @@ export class StoredState extends State {
this.#valueKey = options.key ?? 'value'
// Initialise storage from defaults
for (const [key, value] of Object.entries(init)) {
if (this.#storage[key] == undefined)
this.set(key, value)
for (let [prop, value] of Object.entries(init)) {
if (prop === this.#valueKey) prop = 'value'
if (this.#storage[prop] == undefined)
this.set(prop, value)
}
// Emit change events for any changed keys
@ -111,16 +122,22 @@ export class StoredState extends State {
}
// Listen for changes from other windows
addEventListener("storage", event => {
const handler = event => {
if (event.targetState !== this && event.storageArea == this.#storage) {
let prop = event.key
if (prop === this.#valueKey) prop = 'value'
this.emit(prop, JSON.parse(event.newValue))
})
}
}
addEventListener("storage", handler)
addEventListener("storagechange", handler)
}
set(prop, value) {
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) {