sanity
Version:
Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches
53 lines (43 loc) • 1.66 kB
text/typescript
import {fromEvent, merge, type Observable, of, Subject} from 'rxjs'
import {filter, map, shareReplay} from 'rxjs/operators'
import {isNonNullable} from '../../../util'
import * as storage from './storage'
export interface BroadcastChannel<T> {
messages: Observable<T>
broadcast: (message: T) => void
}
export function createBroadcastChannel<T>(namespace: string): BroadcastChannel<T> {
const storageEvents$ =
typeof window === 'undefined'
? of<StorageEvent>() // No storage events in non-browser environments
: fromEvent<StorageEvent>(window, 'storage')
const storageKey = `__studio_local_storage_messaging_${namespace}`
// note: the the `storageEvents$` stream does not emit for the current
// window/tab. this subject is used to re-broadcast messages broadcast outward
// to itself
const broadcastedMessages$ = new Subject<T>()
const messages$ = merge(
broadcastedMessages$,
storageEvents$.pipe(
filter((event) => event.key === storageKey),
map((event) => event.newValue),
filter(isNonNullable),
map((newValue) => JSON.parse(newValue)),
),
).pipe(
// this is important to ensure all new subscribers get a message on subscribe
shareReplay(1),
)
function broadcast(message: T) {
try {
storage.setItem(storageKey, JSON.stringify(message))
// clear the value afterwards so that next message will still emit a
// new event even if it's identical to the previous one
storage.removeItem(storageKey)
broadcastedMessages$.next(message)
} catch (err) {
// intentional noop
}
}
return {messages: messages$, broadcast}
}