import dayjs from 'dayjs'; import _ from 'lodash'; import { useCallback, useEffect } from 'react'; import rawUseWebSocket, { ReadyState } from 'react-use-websocket'; import { randomAlphanums } from '../utils/crypto'; export enum RequestState { DOING, DONE, FAILED, } let lastAuthMs: number | undefined; export function useWebSocket(wsURL, clientID, token) { const ws = wsURL ? rawUseWebSocket(wsURL, { queryParams: { clientID }, share: true, shouldReconnect: () => true, reconnectAttempts: 50, reconnectInterval: 3000, }) : undefined; const rawWebsocket: any = ws?.getWebSocket(); if (rawWebsocket && ws?.lastJsonMessage && ws?.lastJsonMessage.method === 'authed') { rawWebsocket.authed = true; } useEffect(() => { const currAuthMs = new Date().getTime(); if ( rawWebsocket && !rawWebsocket.authed && token && ws?.readyState === ReadyState.OPEN && (!lastAuthMs || currAuthMs - lastAuthMs > 1000) ) { lastAuthMs = currAuthMs; ws?.sendJsonMessage({ method: 'auth', token, }); } }, [rawWebsocket, token, ws?.readyState]); // eslint-disable-line return ws; } const subscriptionCount: Record = {}; export function useChannel( channel: string, canSubscribe: boolean | string | undefined, onMessage?: (obj: any) => void, data?: any, wsURL?: string, clientID?: string, token?: string ) { const ws = useWebSocket(wsURL, clientID, token); const rawWebsocket = ws?.getWebSocket(); const sendJsonMessage = useCallback( (_request: Omit) => { if (ws?.sendJsonMessage && channel) { const request: ws.Request = { channel, id: randomAlphanums(6), state: 'doing', start: dayjs().valueOf(), end: -1, ..._request, }; ws?.sendJsonMessage(request); } }, [channel, ws] ); useEffect(() => { if (ws?.readyState === ReadyState.OPEN && canSubscribe) { subscriptionCount[channel] = (subscriptionCount[channel] ?? 0) + 1; sendJsonMessage({ method: 'subscribe', data, }); } return () => { if (ws?.readyState === ReadyState.OPEN && canSubscribe) { subscriptionCount[channel] = (subscriptionCount[channel] ?? 0) - 1; if (subscriptionCount[channel] === 0) { delete subscriptionCount.channel; sendJsonMessage({ method: 'unsubscribe', data, }); } } }; }, [rawWebsocket, channel, ws?.readyState, canSubscribe]); // eslint-disable-line useEffect(() => { const message = ws?.lastJsonMessage; if (!message) { return; } if (message.channel === channel && !_.includes(['unsubscribed', 'subscribed'], message.method)) { onMessage?.(message); } }, [ws?.lastJsonMessage]); // eslint-disable-line return { ...ws, sendJsonMessage }; }