UNPKG

react-eventsource-hook

Version:

React hook for the EventSource interface (Server-Sent Events) with retry backoff, pause on hidden, and reconnect/close controls.

142 lines 4.15 kB
// src/use-eventsource.ts import { useState, useEffect, useRef, useCallback } from "react"; import { fetchEventSource } from "@microsoft/fetch-event-source"; function useEventSource(options) { const { url, withCredentials = false, init, fetch: customFetch, retryIntervalMs, maxRetryIntervalMs, pauseOnHidden = true, onopen: onOpen, onmessage: onMessage, onerror: onError, onclose: onClose } = options; const [readyState, setReadyState] = useState(0); const [lastEventId, setLastEventId] = useState(""); const [error, setError] = useState(); const [events, setEvents] = useState([]); const controllerRef = useRef(null); const retryCountRef = useRef(0); const onOpenRef = useRef(void 0); const onMessageRef = useRef(void 0); const onErrorRef = useRef(void 0); const onCloseRef = useRef(void 0); useEffect(() => { onOpenRef.current = onOpen; }, [onOpen]); useEffect(() => { onMessageRef.current = onMessage; }, [onMessage]); useEffect(() => { onErrorRef.current = onError; }, [onError]); useEffect(() => { onCloseRef.current = onClose; }, [onClose]); const start = useCallback(() => { var _a, _b; (_a = controllerRef.current) == null ? void 0 : _a.abort(); const controller = new AbortController(); controllerRef.current = controller; setReadyState(0); const requestInit = { ...init }; if (withCredentials && !requestInit.credentials) { requestInit.credentials = "include"; } const headersRecord = {}; const existing = new Headers((_b = requestInit.headers) != null ? _b : {}); existing.forEach((value, key) => { headersRecord[key] = value; }); if (lastEventId) { headersRecord["Last-Event-ID"] = lastEventId; } fetchEventSource(url, { ...requestInit, headers: headersRecord, signal: controller.signal, fetch: customFetch, async onopen(response) { var _a2; setReadyState(1); retryCountRef.current = 0; (_a2 = onOpenRef.current) == null ? void 0 : _a2.call(onOpenRef, response); }, onmessage(message) { var _a2, _b2; setLastEventId((_a2 = message.id) != null ? _a2 : ""); setEvents((prev) => [...prev, message]); (_b2 = onMessageRef.current) == null ? void 0 : _b2.call(onMessageRef, message); }, onerror(err) { var _a2; setError(err); (_a2 = onErrorRef.current) == null ? void 0 : _a2.call(onErrorRef, err); setReadyState(2); if (retryIntervalMs != null) { const delay = Math.min( retryIntervalMs * 2 ** retryCountRef.current, maxRetryIntervalMs != null ? maxRetryIntervalMs : Infinity ); retryCountRef.current += 1; setTimeout(() => { if (!controller.signal.aborted) start(); }, delay); } else { throw err; } }, onclose() { var _a2; setReadyState(2); (_a2 = onCloseRef.current) == null ? void 0 : _a2.call(onCloseRef); } }); }, [ url, init, customFetch, withCredentials, retryIntervalMs, maxRetryIntervalMs ]); useEffect(() => { start(); return () => { var _a; (_a = controllerRef.current) == null ? void 0 : _a.abort(); }; }, [start]); useEffect(() => { if (!pauseOnHidden) return; const handler = () => { var _a; if (document.hidden) { (_a = controllerRef.current) == null ? void 0 : _a.abort(); } else { start(); } }; document.addEventListener("visibilitychange", handler); return () => { document.removeEventListener("visibilitychange", handler); }; }, [pauseOnHidden, start]); const close = useCallback(() => { var _a; (_a = controllerRef.current) == null ? void 0 : _a.abort(); setReadyState(2); }, []); const reconnect = useCallback(() => { start(); }, [start]); return { readyState, lastEventId, error, events, close, reconnect }; } export { useEventSource }; //# sourceMappingURL=index.js.map