UNPKG

@sanity/client

Version:

Client for retrieving, creating and patching data from Sanity.io

122 lines (108 loc) 4.01 kB
import {Observable, of, throwError} from 'rxjs' import {filter, map} from 'rxjs/operators' import type {ObservableSanityClient, SanityClient} from '../SanityClient' import { type Any, type ListenEvent, type ListenOptions, type ListenParams, type MutationEvent, } from '../types' import defaults from '../util/defaults' import {pick} from '../util/pick' import {_getDataUrl} from './dataMethods' import {encodeQueryString} from './encodeQueryString' import {connectEventSource} from './eventsource' import {eventSourcePolyfill} from './eventsourcePolyfill' import {reconnectOnConnectionFailure} from './reconnectOnConnectionFailure' // Limit is 16K for a _request_, eg including headers. Have to account for an // unknown range of headers, but an average EventSource request from Chrome seems // to have around 700 bytes of cruft, so let us account for 1.2K to be "safe" const MAX_URL_LENGTH = 16000 - 1200 const possibleOptions = [ 'includePreviousRevision', 'includeResult', 'includeMutations', 'includeAllVersions', 'visibility', 'effectFormat', 'tag', ] const defaultOptions = { includeResult: true, } /** * Set up a listener that will be notified when mutations occur on documents matching the provided query/filter. * * @param query - GROQ-filter to listen to changes for * @param params - Optional query parameters * @param options - Optional listener options * @public */ export function _listen<R extends Record<string, Any> = Record<string, Any>>( this: SanityClient | ObservableSanityClient, query: string, params?: ListenParams, ): Observable<MutationEvent<R>> /** * Set up a listener that will be notified when mutations occur on documents matching the provided query/filter. * * @param query - GROQ-filter to listen to changes for * @param params - Optional query parameters * @param options - Optional listener options * @public */ export function _listen<R extends Record<string, Any> = Record<string, Any>>( this: SanityClient | ObservableSanityClient, query: string, params?: ListenParams, options?: ListenOptions, ): Observable<ListenEvent<R>> /** @public */ export function _listen<R extends Record<string, Any> = Record<string, Any>>( this: SanityClient | ObservableSanityClient, query: string, params?: ListenParams, opts: ListenOptions = {}, ): Observable<MutationEvent<R> | ListenEvent<R>> { const {url, token, withCredentials, requestTagPrefix, headers: configHeaders} = this.config() const tag = opts.tag && requestTagPrefix ? [requestTagPrefix, opts.tag].join('.') : opts.tag const options = {...defaults(opts, defaultOptions), tag} const listenOpts = pick(options, possibleOptions) const qs = encodeQueryString({query, params, options: {tag, ...listenOpts}}) const uri = `${url}${_getDataUrl(this, 'listen', qs)}` if (uri.length > MAX_URL_LENGTH) { return throwError(() => new Error('Query too large for listener')) } const listenFor = options.events ? options.events : ['mutation'] const esOptions: EventSourceInit & {headers?: Record<string, string>} = {} if (withCredentials) { esOptions.withCredentials = true } if (token || configHeaders) { esOptions.headers = {} if (token) { esOptions.headers.Authorization = `Bearer ${token}` } if (configHeaders) { Object.assign(esOptions.headers, configHeaders) } } const initEventSource = () => // use polyfill if there is no global EventSource or if we need to set headers (typeof EventSource === 'undefined' || esOptions.headers ? eventSourcePolyfill : of(EventSource) ).pipe(map((EventSource) => new EventSource(uri, esOptions))) return connectEventSource(initEventSource, listenFor).pipe( reconnectOnConnectionFailure(), filter((event) => listenFor.includes(event.type)), map( (event) => ({ type: event.type, ...('data' in event ? (event.data as object) : {}), }) as MutationEvent<R> | ListenEvent<R>, ), ) }