@oiij/use
Version:
Som Composable Functions for Vue 3
150 lines (148 loc) • 4.3 kB
JavaScript
import { onUnmounted, ref, shallowRef, toValue, watchEffect } from "vue";
import { createEventHook } from "@vueuse/core";
//#region src/composables/use-web-socket.ts
const ReadyState = {
0: "CONNECTING",
1: "OPEN",
2: "CLOSING",
3: "CLOSED"
};
function useWebSocket(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 socket = shallowRef(null);
const status = ref("PENDING");
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 onCloseEvent = createEventHook();
const onErrorEvent = createEventHook();
function setStatus() {
if (socket.value) status.value = ReadyState[socket.value.readyState];
}
function connect(url$1, protocols) {
if (socket.value) destroy();
if (url$1) urlRef.value = url$1;
if (protocols) _options.protocols = protocols;
if (!urlRef.value) throw new Error("WebSocket url is not defined");
socket.value = new WebSocket(urlRef.value, _options.protocols);
controller.value = new AbortController();
socket.value.addEventListener("open", onOpen, { signal: controller.value.signal });
socket.value.addEventListener("message", onMessage, { signal: controller.value.signal });
socket.value.addEventListener("close", onClose, { signal: controller.value.signal });
socket.value.addEventListener("error", onError, { signal: controller.value.signal });
}
function close() {
if (socket.value?.readyState === 1) socket.value?.close();
}
if (!manual) connect();
function sendRaw(data$1) {
socket.value?.send(data$1);
}
function send(data$1) {
sendRaw(JSON.stringify(data$1));
}
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.warn("Failed to parse message:", err);
}
}
function onClose(ev) {
setStatus();
error.value = null;
data.value = null;
messageEvent.value = null;
onCloseEvent.trigger(ev);
}
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 (socket.value) socket.value.addEventListener(type, handler, { signal: controller.value.signal });
}
function destroy() {
close();
controller.value.abort();
socket.value = null;
}
onUnmounted(() => {
destroy();
});
return {
socket,
url: urlRef,
status,
data,
dataRecord,
messageEvent,
messageEventRecord,
error,
controller,
connect,
close,
sendRaw,
send,
destroy,
registerHandler,
cancelHandler,
registerEvent,
onOpen: onOpenEvent.on,
onMessage: onMessageEvent.on,
onClose: onCloseEvent.on,
onError: onErrorEvent.on
};
}
//#endregion
export { useWebSocket };