This commit is contained in:
Benjamin Toby
2025-07-20 10:35:54 +01:00
parent dd1d05251d
commit a0a0ab8ee4
99 changed files with 5678 additions and 909 deletions
@@ -1,31 +1,42 @@
import React from "react";
type Param = {
elementRef?: React.MutableRefObject<Element | undefined>;
elementRef?: React.RefObject<Element | undefined>;
className?: string;
elId?: string;
options?: IntersectionObserverInit;
removeIntersected?: boolean;
delay?: number;
};
let timeout: any;
export default function useIntersectionObserver({
elementRef,
className,
options,
removeIntersected,
delay,
elId,
}: Param) {
const [isIntersecting, setIsIntersecting] = React.useState(false);
const [refresh, setRefresh] = React.useState(0);
const observerTriggerDelay = delay || 200;
const observerCallback: IntersectionObserverCallback = React.useCallback(
(entries, observer) => {
const entry = entries[0];
window.clearTimeout(timeout);
if (entry.isIntersecting) {
setIsIntersecting(true);
timeout = setTimeout(() => {
setIsIntersecting(true);
if (removeIntersected) {
observer.unobserve(entry.target);
}
if (removeIntersected) {
observer.unobserve(entry.target);
}
}, observerTriggerDelay);
} else {
setIsIntersecting(false);
}
@@ -34,7 +45,9 @@ export default function useIntersectionObserver({
);
React.useEffect(() => {
const element = elementRef?.current;
const element = elId
? document.getElementById(elId)
: elementRef?.current;
const elements = className
? document.querySelectorAll(`.${className}`)
: null;
+29
View File
@@ -0,0 +1,29 @@
import React from "react";
type Params = {
timeout?: number;
};
let timeout: any;
export default function twuiUseReady(params?: Params) {
const [ready, setReady] = React.useState(false);
const finalTimeout = params?.timeout || 300;
React.useEffect(() => {
requestAnimationFrame(() => {
window.clearTimeout(timeout);
timeout = setTimeout(() => {
setReady(true);
}, finalTimeout);
});
return function () {
window.clearTimeout(timeout);
};
}, []);
return { ready };
}
+102 -6
View File
@@ -4,12 +4,10 @@ export type UseWebsocketHookParams = {
debounce?: number;
url: string;
disableReconnect?: boolean;
keepAliveDuration?: number;
refreshConnection?: number;
};
let reconnectInterval: any;
let msgInterval: any;
let sendInterval: any;
export const WebSocketEventNames = ["wsDataEvent", "wsMessageEvent"] as const;
let tries = 0;
@@ -28,8 +26,25 @@ let tries = 0;
*/
export default function useWebSocket<
T extends { [key: string]: any } = { [key: string]: any }
>({ url, debounce, disableReconnect }: UseWebsocketHookParams) {
>({
url,
debounce,
disableReconnect,
keepAliveDuration,
refreshConnection,
}: UseWebsocketHookParams) {
const DEBOUNCE = debounce || 200;
const KEEP_ALIVE_DURATION = keepAliveDuration || 1000 * 30;
const KEEP_ALIVE_TIMEOUT = 1000 * 60 * 3;
const KEEP_ALIVE_MESSAGE = "twui::ping";
let uptime = 0;
let reconnectInterval: any;
let msgInterval: any;
let sendInterval: any;
let keepAliveInterval: any;
const [socket, setSocket] = React.useState<WebSocket | undefined>(
undefined
@@ -38,6 +53,9 @@ export default function useWebSocket<
const messageQueueRef = React.useRef<string[]>([]);
const sendMessageQueueRef = React.useRef<string[]>([]);
/**
* # Dispatch Custom Event
*/
const dispatchCustomEvent = React.useCallback(
(evtName: (typeof WebSocketEventNames)[number], value: string | T) => {
const event = new CustomEvent(evtName, {
@@ -51,6 +69,9 @@ export default function useWebSocket<
[]
);
/**
* # Connect to Websocket
*/
const connect = React.useCallback(() => {
const wsURL = url;
if (!wsURL) return;
@@ -59,22 +80,41 @@ export default function useWebSocket<
ws.onopen = (ev) => {
window.clearInterval(reconnectInterval);
window.clearInterval(keepAliveInterval);
keepAliveInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(KEEP_ALIVE_MESSAGE);
uptime += KEEP_ALIVE_DURATION;
if (uptime >= KEEP_ALIVE_TIMEOUT) {
console.log("Websocket connection timed out ...");
window.clearInterval(keepAliveInterval);
ws.close();
}
}
}, KEEP_ALIVE_DURATION);
setSocket(ws);
tries = 0;
console.log(`Websocket connected to ${wsURL}`);
uptime = 0;
};
ws.onmessage = (ev) => {
window.clearInterval(msgInterval);
messageQueueRef.current.push(ev.data);
msgInterval = setInterval(handleReceivedMessageQueue, DEBOUNCE);
if (ev.data !== KEEP_ALIVE_MESSAGE) {
uptime = 0;
}
};
ws.onclose = (ev) => {
console.log("Websocket closed!");
if (disableReconnect) return;
console.log("Websocket closed ... Attempting to reconnect ...");
console.log("Attempting to reconnect ...");
console.log("URL:", url);
window.clearInterval(keepAliveInterval);
reconnectInterval = setInterval(() => {
if (tries >= 3) {
@@ -89,6 +129,31 @@ export default function useWebSocket<
};
}, []);
/**
* # Window Close Handler
*/
const handleWindowClose = React.useCallback(() => {
console.log("Window Unloaded ...");
}, [socket]);
/**
* # Window Focus Handler
*/
const handleWindowFocus = React.useCallback(() => {
if (socket?.readyState === WebSocket.CLOSED) {
console.log("Websocket closed ... Attempting to reconnect ...");
connect();
}
if (socket?.readyState === WebSocket.OPEN) {
console.log("Websocket connection alive ...");
socket.send(KEEP_ALIVE_MESSAGE);
uptime = 0;
}
}, [socket]);
/**
* # Initial Connection
*/
React.useEffect(() => {
connect();
@@ -97,6 +162,36 @@ export default function useWebSocket<
};
}, []);
/**
* # Window Close and Focus Handlers
*/
React.useEffect(() => {
if (!socket) return;
window.addEventListener("beforeunload", handleWindowClose, {
once: true,
});
window.addEventListener("focus", handleWindowFocus);
return function () {
window.removeEventListener("focus", handleWindowFocus);
window.removeEventListener("beforeunload", handleWindowClose);
};
}, [socket]);
/**
* # Refresh Connection
*/
React.useEffect(() => {
console.log("Refreshing connection ...");
if (!socket) return;
if (socket.readyState !== WebSocket.CLOSED) {
socket?.close();
}
connect();
}, [refreshConnection]);
/**
* Received Message Queue Handler
*/
@@ -113,6 +208,7 @@ export default function useWebSocket<
}
} else {
window.clearInterval(msgInterval);
uptime = 0;
}
}, []);