import { useCallback, useEffect, useMemo, useState } from "react"; import { getHost } from "~/utils"; import { Call, Frame, Frames, Historic, ReadyState, Request, RequestResponseType, Requests, Response, ResponsePayload, Responses, WebsocketConnect, WebsocketConnected, WebsocketConnectedPayload, WebsocketDisconnectPayload, WebsocketInboundPayload, WebsocketOutboundPayload, WebsocketType, } from "~/types"; export interface useRequestsProps { onConnect: () => Promise; } export interface UseRequests { calls: Call[]; readyState: ReadyState; clear: () => void; } export default function useRequests({ onConnect, }: useRequestsProps): UseRequests { const wsHost = useMemo(getHost, []); const [initialConnection, setInitialConnection] = useState(true); const [requests, setRequests] = useState([]); const [responses, setResponses] = useState({}); const [websocketFrames, setWebsocketFrames] = useState({}); const connect = useCallback( () => new WebSocket(`ws://${wsHost}/inspect/`), [wsHost] ); const [ws, setWs] = useState(() => connect()); const [readyState, setReadyState] = useState(ws.readyState); useEffect(() => { setReadyState(ws.readyState); const onClose = () => { setReadyState(ws.readyState); setWs(connect()); }; const onOpen = () => { onConnect(); setInitialConnection(false); setReadyState(ws.readyState); }; const onMessage = ({ data }: { data: string }) => { const { type, payload } = JSON.parse(data) as | Historic | RequestResponseType | WebsocketType; switch (type) { case "historic": if (initialConnection) { const requests = ( payload as (RequestResponseType | WebsocketType)[] ) .filter( ({ type }) => type === "request" || type == "websocket_connect" ) .map((payload) => { if ( (payload as WebsocketConnect).payload.method === undefined ) { (payload as Request | WebsocketConnect).payload.method = "GET"; } return payload; }); const responses = ( payload as (RequestResponseType | WebsocketType)[] ) .filter( ({ type }) => type === "response" || type == "websocket_connected" ) .map(({ type, ...item }) => { if (type == "websocket_connected") { (item.payload as WebsocketConnectedPayload).status = 101; } return { type, ...item }; }) .reduce<{ [id: string]: Response | WebsocketConnected }>( (out, item) => ({ ...out, [item.payload.id]: item as Response | WebsocketConnected, }), {} ); const frames = (payload as (RequestResponseType | WebsocketType)[]) .filter( ({ type }) => type == "websocket_inbound" || type == "websocket_outbound" || type == "websocket_disconnect" ) .reduce((out, item) => { if (!out.hasOwnProperty(item.payload.id)) { out[item.payload.id] = []; } out[item.payload.id].push(item as Frame); return out; }, {}); setRequests((rqs) => [ ...rqs, ...requests.map( (payload) => payload as Request | WebsocketConnect ), ]); setResponses((rps) => ({ ...rps, ...responses, })); setWebsocketFrames((frms) => ({ ...frms, ...frames, })); } break; case "request": case "websocket_connect": setRequests((rqs) => [ ...rqs, { type, payload: type === "request" ? payload : { ...payload, method: "GET", }, } as Request | WebsocketConnect, ]); break; case "response": case "websocket_connected": if (type == "websocket_connected") { (payload as WebsocketConnectedPayload).status = 101; } setResponses((rps) => ({ ...rps, [(payload as ResponsePayload | WebsocketConnectedPayload).id]: { type, payload, } as Response | WebsocketConnected, })); break; case "websocket_inbound": case "websocket_outbound": case "websocket_disconnect": setWebsocketFrames((frms) => { const id = ( payload as | WebsocketInboundPayload | WebsocketOutboundPayload | WebsocketDisconnectPayload ).id; const newFrms = { ...frms }; if (!newFrms.hasOwnProperty(id)) { newFrms[id] = []; } // @ts-ignore newFrms[id].push({ type, payload: payload as | WebsocketInboundPayload | WebsocketOutboundPayload | WebsocketDisconnectPayload, }); return newFrms; }); break; } }; ws.addEventListener("message", onMessage); ws.addEventListener("close", onClose); ws.addEventListener("open", onOpen); return () => { ws.removeEventListener("message", onMessage); ws.removeEventListener("close", onClose); ws.removeEventListener("open", onOpen); }; }, [ws]); const calls = useMemo( () => requests.map((request) => { switch (request.type) { case "request": return { type: "RequestResponse", request: request.payload, response: responses[request.payload.id] ?.payload as ResponsePayload, }; case "websocket_connect": return { type: "WebsocketConnection", request: request.payload, response: responses[request.payload.id] ?.payload as WebsocketConnectedPayload, frames: websocketFrames[request.payload.id] ?? [], }; } }), [requests, responses, websocketFrames] ); return { calls, readyState, clear: () => { setRequests([]); setResponses({}); setWebsocketFrames({}); }, }; }