UNPKG

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

112 lines (92 loc) 2.95 kB
import {type BifurClient} from '@sanity/bifur-client' import {observableCallback} from 'observable-callback' import {concat, fromEvent, merge, NEVER, type Observable, of, throwError, timer} from 'rxjs' import {map, mergeMapTo, startWith, take, takeUntil} from 'rxjs/operators' import {catchWithCount} from './utils/catchWithCount' /** @internal */ export interface ConnectionStatusStore { connectionStatus$: Observable<ConnectionStatus> } const onOnline$ = typeof window === 'undefined' ? of({}) : fromEvent(window, 'online') const onOffline$ = typeof window === 'undefined' ? of({}) : fromEvent(window, 'offline') const expBackoff = (retryCount: number) => Math.pow(2, retryCount) * 100 /** @internal */ export type ConnectingStatus = { type: 'connecting' } /** @internal */ export type ErrorStatus = { type: 'error' error: Error attemptNo: number isOffline: boolean retryAt: Date } /** @internal */ export type RetryingStatus = { type: 'retrying' } /** @internal */ export type ConnectedStatus = {type: 'connected'; lastHeartbeat: Date} /** @internal */ export const CONNECTING: ConnectingStatus = {type: 'connecting'} /** @internal */ export type ConnectionStatus = ConnectingStatus | ErrorStatus | ConnectedStatus | RetryingStatus const _callback = observableCallback() const onRetry$ = _callback[0] /** @internal */ export const onRetry = _callback[1] const createErrorStatus = ({ error, isOffline, attemptNo, retryAt, }: { error: Error isOffline: boolean attemptNo: number retryAt: Date }): ErrorStatus => ({ type: 'error', error, attemptNo, isOffline, retryAt, }) /** @internal */ export interface ConnectionStatusStoreOptions { bifur: BifurClient } /** * This is the beginning of what should be the data store tracking connection status in the Sanity studio. * * @internal */ export function createConnectionStatusStore({ bifur, }: ConnectionStatusStoreOptions): ConnectionStatusStore { const connectionStatus$: Observable<ConnectionStatus> = merge( bifur.heartbeats, onOffline$.pipe(mergeMapTo(throwError(new Error('The browser went offline')))), ).pipe( map((ts): ConnectionStatus => ({type: 'connected', lastHeartbeat: ts})), catchWithCount((error, successiveErrorsCount, caught) => { const timeUntilRetry = Math.min(1000 * 240, expBackoff(successiveErrorsCount)) const retryAt = new Date(new Date().getTime() + timeUntilRetry) const expiry$ = timer(retryAt) const isOffline = !navigator.onLine const initialErrorStatus = createErrorStatus({ error, retryAt, isOffline, attemptNo: successiveErrorsCount, }) const triggerRetry$ = NEVER.pipe( takeUntil(isOffline ? onOnline$ : merge(expiry$, onOnline$, onRetry$)), ) return concat(of(initialErrorStatus), triggerRetry$.pipe(take(1)), caught) }), startWith(CONNECTING), ) return {connectionStatus$} }