From 648b804e72d4831e41e02dfd7d6b5a9ac7660b58 Mon Sep 17 00:00:00 2001 From: Tom van der Lee Date: Fri, 30 Aug 2024 11:19:30 +0200 Subject: Added ui --- src/contexts/Connection.tsx | 288 ++++++++++++++++++++++++++++++++++++++++++++ src/contexts/DarkMode.tsx | 7 +- 2 files changed, 290 insertions(+), 5 deletions(-) create mode 100644 src/contexts/Connection.tsx (limited to 'src/contexts') diff --git a/src/contexts/Connection.tsx b/src/contexts/Connection.tsx new file mode 100644 index 0000000..42482e1 --- /dev/null +++ b/src/contexts/Connection.tsx @@ -0,0 +1,288 @@ +import * as React from "react"; + +import { + Context, + createContext, + PropsWithChildren, + useCallback, + useEffect, + useMemo, + useState, +} from "react"; +import { + Call, + Frame, + Frames, + Historic, + ReadyState, + Request, + RequestResponseType, + Requests, + Response, + ResponsePayload, + Responses, + WebsocketConnect, + WebsocketConnected, + WebsocketConnectedPayload, + WebsocketDisconnectPayload, + WebsocketInboundPayload, + WebsocketOutboundPayload, + WebsocketType, +} from "~/types"; +import { getHost } from "~/utils"; + +interface Config { + url: string; +} + +interface ConnectionApi { + config: Config | null; + calls: Call[]; + selectedCall: Call | null; + setSelectedCall: (call: Call | null) => void; + readyState: ReadyState; + clear: () => void; +} + +export const ConnectionContext = createContext>( + {} +) as Context; + +export default function ConnectionProvider({ + children, +}: PropsWithChildren): JSX.Element { + const [selectedCall, setSelectedCall] = useState(null); + const [config, setConfig] = useState(null); + + 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 = async () => { + const response = await fetch(`http://${getHost()}/config/`); + const config = await response.json(); + setConfig(config); + + 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) => { + let call: Call; + if (request.type === "request") { + call = { + type: "RequestResponse", + request: request.payload, + response: responses[request.payload.id]?.payload as ResponsePayload, + }; + } else { + call = { + type: "WebsocketConnection", + request: request.payload, + response: responses[request.payload.id] + ?.payload as WebsocketConnectedPayload, + frames: websocketFrames[request.payload.id] ?? [], + }; + } + + if ( + selectedCall !== null && + selectedCall.request.id === call.request.id + ) { + setSelectedCall(call); + } + + return call; + }), + [requests, responses, websocketFrames] + ); + + return ( + { + setRequests([]); + setResponses({}); + setWebsocketFrames({}); + }, + }} + > + {children} + + ); +} diff --git a/src/contexts/DarkMode.tsx b/src/contexts/DarkMode.tsx index 4326fe9..fd536b2 100644 --- a/src/contexts/DarkMode.tsx +++ b/src/contexts/DarkMode.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import { Context, createContext, - ReactNode, + PropsWithChildren, useEffect, useMemo, useState, @@ -17,11 +17,8 @@ interface DarkModeApi { export const DarkModeContext = createContext>( {} ) as Context; -interface DarkModeProviderProps { - children: ReactNode; -} -export default function DarkModeProvider({ children }: DarkModeProviderProps) { +export default function DarkModeProvider({ children }: PropsWithChildren) { const themeConfig = useMemo(() => new ThemeConfig(), []); const [darkMode, setDarkMode] = useState( () => themeConfig.getTheme() === "dark" -- cgit v1.2.3