about summary refs log tree commit diff
path: root/src/state/persisted/index.web.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/persisted/index.web.ts')
-rw-r--r--src/state/persisted/index.web.ts41
1 files changed, 35 insertions, 6 deletions
diff --git a/src/state/persisted/index.web.ts b/src/state/persisted/index.web.ts
index d71b59096..7521776bc 100644
--- a/src/state/persisted/index.web.ts
+++ b/src/state/persisted/index.web.ts
@@ -47,18 +47,36 @@ export async function write<K extends keyof Schema>(
     // Don't fire the update listeners yet to avoid a loop.
     // If there was a change, we'll receive the broadcast event soon enough which will do that.
   }
+  try {
+    if (JSON.stringify({v: _state[key]}) === JSON.stringify({v: value})) {
+      // Fast path for updates that are guaranteed to be noops.
+      // This is good mostly because it avoids useless broadcasts to other tabs.
+      return
+    }
+  } catch (e) {
+    // Ignore and go through the normal path.
+  }
   _state = {
     ..._state,
     [key]: value,
   }
   writeToStorage(_state)
-  broadcast.postMessage({event: UPDATE_EVENT})
+  broadcast.postMessage({event: {type: UPDATE_EVENT, key}})
+  broadcast.postMessage({event: UPDATE_EVENT}) // Backcompat while upgrading
 }
 write satisfies PersistedApi['write']
 
-export function onUpdate(cb: () => void): () => void {
-  _emitter.addListener('update', cb)
-  return () => _emitter.removeListener('update', cb)
+export function onUpdate<K extends keyof Schema>(
+  key: K,
+  cb: (v: Schema[K]) => void,
+): () => void {
+  const listener = () => cb(get(key))
+  _emitter.addListener('update', listener) // Backcompat while upgrading
+  _emitter.addListener('update:' + key, listener)
+  return () => {
+    _emitter.removeListener('update', listener) // Backcompat while upgrading
+    _emitter.removeListener('update:' + key, listener)
+  }
 }
 onUpdate satisfies PersistedApi['onUpdate']
 
@@ -72,12 +90,23 @@ export async function clearStorage() {
 clearStorage satisfies PersistedApi['clearStorage']
 
 async function onBroadcastMessage({data}: MessageEvent) {
-  if (typeof data === 'object' && data.event === UPDATE_EVENT) {
+  if (
+    typeof data === 'object' &&
+    (data.event === UPDATE_EVENT || // Backcompat while upgrading
+      data.event?.type === UPDATE_EVENT)
+  ) {
     // read next state, possibly updated by another tab
     const next = readFromStorage()
+    if (next === _state) {
+      return
+    }
     if (next) {
       _state = next
-      _emitter.emit('update')
+      if (typeof data.event.key === 'string') {
+        _emitter.emit('update:' + data.event.key)
+      } else {
+        _emitter.emit('update') // Backcompat while upgrading
+      }
     } else {
       logger.error(
         `persisted state: handled update update from broadcast channel, but found no data`,