UNPKG

@oiij/use

Version:

Som Composable Functions for Vue 3

132 lines (130 loc) 3.87 kB
import { onUnmounted, ref, shallowRef, toValue, watchEffect } from "vue"; import { createEventHook } from "@vueuse/core"; //#region src/composables/use-event-source.ts const ReadyState = { 0: "CONNECTING", 1: "OPEN", 2: "CLOSED" }; function useEventSource(url, options) { const { manual = false, autoRetry, parseMessage = false, handlerKey = "type", ..._options } = options ?? {}; const { retries = 3, delay = 1e3, onFailed } = typeof autoRetry === "boolean" ? {} : autoRetry ?? {}; let retryCount = 0; const urlRef = ref(toValue(url)); watchEffect(() => { if (urlRef.value !== toValue(url)) { urlRef.value = toValue(url); if (!manual) connect(); } }); const handlerMap = /* @__PURE__ */ new Map(); const source = shallowRef(null); const status = ref("CLOSED"); const error = ref(null); const data = ref(null); const dataRecord = ref([]); const messageEvent = ref(null); const messageEventRecord = ref([]); const controller = shallowRef(new AbortController()); const onOpenEvent = createEventHook(); const onMessageEvent = createEventHook(); const onErrorEvent = createEventHook(); function setStatus() { if (source.value) status.value = ReadyState[source.value.readyState]; } function connect(url$1, options$1) { if (source.value) destroy(); if (url$1) urlRef.value = url$1; if (options$1) Object.assign(_options, options$1); if (!urlRef.value) throw new Error("EventSource url is not defined"); source.value = new EventSource(urlRef.value, options$1); controller.value = new AbortController(); source.value.addEventListener("open", onOpen, { signal: controller.value.signal }); source.value.addEventListener("message", onMessage, { signal: controller.value.signal }); source.value.addEventListener("error", onError, { signal: controller.value.signal }); } function close() { if (source.value?.readyState === 1) source.value?.close(); setStatus(); } if (!manual) connect(); function onOpen(ev) { setStatus(); error.value = null; data.value = null; messageEvent.value = null; retryCount = 0; onOpenEvent.trigger(ev); } async function onMessage(ev) { setStatus(); data.value = ev.data; dataRecord.value.push(ev.data); messageEvent.value = ev; messageEventRecord.value.push(ev); onMessageEvent.trigger(ev); if (parseMessage && typeof ev.data === "string") try { const dataJson = typeof parseMessage === "function" ? await Promise.try(parseMessage, ev.data) : JSON.parse(ev.data); if (dataJson?.[handlerKey]) handlerMap.get(dataJson[handlerKey])?.forEach((f) => { f(dataJson); }); } catch (err) { console.error("Failed to parse message:", err); } } function onError(ev) { setStatus(); error.value = ev; onErrorEvent.trigger(ev); if (autoRetry) if (retryCount < retries) { retryCount++; setTimeout(() => { connect(); }, delay); } else { onFailed?.(); retryCount = 0; } } function registerHandler(type, handler) { if (!handlerMap.has(type)) handlerMap.set(type, []); handlerMap.get(type)?.push(handler); return () => cancelHandler(type, handler); } function cancelHandler(type, handler) { if (handlerMap.has(type)) handlerMap.set(type, handlerMap.get(type)?.filter((f) => f !== handler) || []); } function registerEvent(type, handler) { if (source.value) source.value.addEventListener(type, handler, { signal: controller.value.signal }); } function destroy() { close(); controller.value.abort(); source.value = null; } onUnmounted(() => { destroy(); }); return { source, url: urlRef, status, data, dataRecord, messageEvent, messageEventRecord, error, controller, connect, close, destroy, registerHandler, cancelHandler, registerEvent, onOpen: onOpenEvent.on, onMessage: onMessageEvent.on, onError: onErrorEvent.on }; } //#endregion export { useEventSource };