import { useCallback, useEffect, useRef, useState } from 'react'; import Sockette from 'sockette'; import { debounce } from 'lodash'; import { addAccessTokenParameter } from '../api/authentication'; interface WebSocketIdMessage { type: 'id'; id: string; } interface WebSocketPayloadMessage { type: 'payload'; origin_id: string; payload: D; } export type WebSocketMessage = WebSocketIdMessage | WebSocketPayloadMessage; export const useWs = (wsUrl: string, wsThrottle: number = 100) => { const ws = useRef(); const clientId = useRef(); const [connected, setConnected] = useState(false); const [data, setData] = useState(); const [transmit, setTransmit] = useState(); const [clear, setClear] = useState(); const onMessage = useCallback((event: MessageEvent) => { const rawData = event.data; if (typeof rawData === 'string' || rawData instanceof String) { const message = JSON.parse(rawData as string) as WebSocketMessage; switch (message.type) { case 'id': clientId.current = message.id; break; case 'payload': if (clientId.current) { setData((existingData) => (clientId.current === message.origin_id && existingData) || message.payload); } break; } } }, []); const doSaveData = useCallback((newData: D, clearData: boolean = false) => { if (!ws.current) { return; } if (clearData) { setData(undefined); } ws.current.json(newData); }, []); const saveData = useRef(debounce(doSaveData, wsThrottle)); const updateData = ( newData: React.SetStateAction, transmitData: boolean = true, clearData: boolean = false ) => { setData(newData); setTransmit(transmitData); setClear(clearData); }; useEffect(() => { if (!transmit) { return; } data && saveData.current(data, clear); setTransmit(false); setClear(false); }, [doSaveData, data, transmit, clear]); useEffect(() => { const instance = new Sockette(addAccessTokenParameter(wsUrl), { onmessage: onMessage, onopen: () => { setConnected(true); }, onclose: () => { clientId.current = undefined; setConnected(false); setData(undefined); } }); ws.current = instance; return instance.close; }, [wsUrl, onMessage]); return { connected, data, updateData } as const; };