diff options
| author | 2024-08-30 15:47:20 +0200 | |
|---|---|---|
| committer | 2024-08-30 15:47:20 +0200 | |
| commit | fe61544bafafc8b4de78cc71cb641af2dfb7b72d (patch) | |
| tree | 7edff48c1638fcc26915dc1f40982ec2563cf3bf /src/contexts/Connection.tsx | |
| parent | f183536067dc694f37445148c15821f1621f5034 (diff) | |
| parent | 2f2048160fac06e94703bb03eea43185fc01f76c (diff) | |
| download | client-fe61544bafafc8b4de78cc71cb641af2dfb7b72d.tar.gz client-fe61544bafafc8b4de78cc71cb641af2dfb7b72d.tar.bz2 client-fe61544bafafc8b4de78cc71cb641af2dfb7b72d.zip | |
Merge pull request #15 from tomvanderlee/feature/websocketsv2.1.0
Feature/websockets
Diffstat (limited to 'src/contexts/Connection.tsx')
| -rw-r--r-- | src/contexts/Connection.tsx | 288 |
1 files changed, 288 insertions, 0 deletions
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 @@ | |||
| 1 | import * as React from "react"; | ||
| 2 | |||
| 3 | import { | ||
| 4 | Context, | ||
| 5 | createContext, | ||
| 6 | PropsWithChildren, | ||
| 7 | useCallback, | ||
| 8 | useEffect, | ||
| 9 | useMemo, | ||
| 10 | useState, | ||
| 11 | } from "react"; | ||
| 12 | import { | ||
| 13 | Call, | ||
| 14 | Frame, | ||
| 15 | Frames, | ||
| 16 | Historic, | ||
| 17 | ReadyState, | ||
| 18 | Request, | ||
| 19 | RequestResponseType, | ||
| 20 | Requests, | ||
| 21 | Response, | ||
| 22 | ResponsePayload, | ||
| 23 | Responses, | ||
| 24 | WebsocketConnect, | ||
| 25 | WebsocketConnected, | ||
| 26 | WebsocketConnectedPayload, | ||
| 27 | WebsocketDisconnectPayload, | ||
| 28 | WebsocketInboundPayload, | ||
| 29 | WebsocketOutboundPayload, | ||
| 30 | WebsocketType, | ||
| 31 | } from "~/types"; | ||
| 32 | import { getHost } from "~/utils"; | ||
| 33 | |||
| 34 | interface Config { | ||
| 35 | url: string; | ||
| 36 | } | ||
| 37 | |||
| 38 | interface ConnectionApi { | ||
| 39 | config: Config | null; | ||
| 40 | calls: Call[]; | ||
| 41 | selectedCall: Call | null; | ||
| 42 | setSelectedCall: (call: Call | null) => void; | ||
| 43 | readyState: ReadyState; | ||
| 44 | clear: () => void; | ||
| 45 | } | ||
| 46 | |||
| 47 | export const ConnectionContext = createContext<Partial<ConnectionApi>>( | ||
| 48 | {} | ||
| 49 | ) as Context<ConnectionApi>; | ||
| 50 | |||
| 51 | export default function ConnectionProvider({ | ||
| 52 | children, | ||
| 53 | }: PropsWithChildren<any>): JSX.Element { | ||
| 54 | const [selectedCall, setSelectedCall] = useState<Call | null>(null); | ||
| 55 | const [config, setConfig] = useState<Config | null>(null); | ||
| 56 | |||
| 57 | const wsHost = useMemo(getHost, []); | ||
| 58 | |||
| 59 | const [initialConnection, setInitialConnection] = useState(true); | ||
| 60 | const [requests, setRequests] = useState<Requests>([]); | ||
| 61 | const [responses, setResponses] = useState<Responses>({}); | ||
| 62 | const [websocketFrames, setWebsocketFrames] = useState<Frames>({}); | ||
| 63 | |||
| 64 | const connect = useCallback( | ||
| 65 | () => new WebSocket(`ws://${wsHost}/inspect/`), | ||
| 66 | [wsHost] | ||
| 67 | ); | ||
| 68 | |||
| 69 | const [ws, setWs] = useState<WebSocket>(() => connect()); | ||
| 70 | const [readyState, setReadyState] = useState<ReadyState>(ws.readyState); | ||
| 71 | |||
| 72 | useEffect(() => { | ||
| 73 | setReadyState(ws.readyState); | ||
| 74 | |||
| 75 | const onClose = () => { | ||
| 76 | setReadyState(ws.readyState); | ||
| 77 | setWs(connect()); | ||
| 78 | }; | ||
| 79 | |||
| 80 | const onOpen = async () => { | ||
| 81 | const response = await fetch(`http://${getHost()}/config/`); | ||
| 82 | const config = await response.json(); | ||
| 83 | setConfig(config); | ||
| 84 | |||
| 85 | setInitialConnection(false); | ||
| 86 | setReadyState(ws.readyState); | ||
| 87 | }; | ||
| 88 | |||
| 89 | const onMessage = ({ data }: { data: string }) => { | ||
| 90 | const { type, payload } = JSON.parse(data) as | ||
| 91 | | Historic | ||
| 92 | | RequestResponseType | ||
| 93 | | WebsocketType; | ||
| 94 | |||
| 95 | switch (type) { | ||
| 96 | case "historic": | ||
| 97 | if (initialConnection) { | ||
| 98 | const requests = ( | ||
| 99 | payload as (RequestResponseType | WebsocketType)[] | ||
| 100 | ) | ||
| 101 | .filter( | ||
| 102 | ({ type }) => type === "request" || type == "websocket_connect" | ||
| 103 | ) | ||
| 104 | .map((payload) => { | ||
| 105 | if ( | ||
| 106 | (payload as WebsocketConnect).payload.method === undefined | ||
| 107 | ) { | ||
| 108 | (payload as Request | WebsocketConnect).payload.method = | ||
| 109 | "GET"; | ||
| 110 | } | ||
| 111 | |||
| 112 | return payload; | ||
| 113 | }); | ||
| 114 | const responses = ( | ||
| 115 | payload as (RequestResponseType | WebsocketType)[] | ||
| 116 | ) | ||
| 117 | .filter( | ||
| 118 | ({ type }) => | ||
| 119 | type === "response" || type == "websocket_connected" | ||
| 120 | ) | ||
| 121 | .map(({ type, ...item }) => { | ||
| 122 | if (type == "websocket_connected") { | ||
| 123 | (item.payload as WebsocketConnectedPayload).status = 101; | ||
| 124 | } | ||
| 125 | |||
| 126 | return { type, ...item }; | ||
| 127 | }) | ||
| 128 | .reduce<{ [id: string]: Response | WebsocketConnected }>( | ||
| 129 | (out, item) => ({ | ||
| 130 | ...out, | ||
| 131 | [item.payload.id]: item as Response | WebsocketConnected, | ||
| 132 | }), | ||
| 133 | {} | ||
| 134 | ); | ||
| 135 | const frames = (payload as (RequestResponseType | WebsocketType)[]) | ||
| 136 | .filter( | ||
| 137 | ({ type }) => | ||
| 138 | type == "websocket_inbound" || | ||
| 139 | type == "websocket_outbound" || | ||
| 140 | type == "websocket_disconnect" | ||
| 141 | ) | ||
| 142 | .reduce<Frames>((out, item) => { | ||
| 143 | if (!out.hasOwnProperty(item.payload.id)) { | ||
| 144 | out[item.payload.id] = []; | ||
| 145 | } | ||
| 146 | |||
| 147 | out[item.payload.id].push(item as Frame); | ||
| 148 | return out; | ||
| 149 | }, {}); | ||
| 150 | setRequests((rqs) => [ | ||
| 151 | ...rqs, | ||
| 152 | ...requests.map( | ||
| 153 | (payload) => payload as Request | WebsocketConnect | ||
| 154 | ), | ||
| 155 | ]); | ||
| 156 | setResponses((rps) => ({ | ||
| 157 | ...rps, | ||
| 158 | ...responses, | ||
| 159 | })); | ||
| 160 | setWebsocketFrames((frms) => ({ | ||
| 161 | ...frms, | ||
| 162 | ...frames, | ||
| 163 | })); | ||
| 164 | } | ||
| 165 | break; | ||
| 166 | case "request": | ||
| 167 | case "websocket_connect": | ||
| 168 | setRequests((rqs) => [ | ||
| 169 | ...rqs, | ||
| 170 | { | ||
| 171 | type, | ||
| 172 | payload: | ||
| 173 | type === "request" | ||
| 174 | ? payload | ||
| 175 | : { | ||
| 176 | ...payload, | ||
| 177 | method: "GET", | ||
| 178 | }, | ||
| 179 | } as Request | WebsocketConnect, | ||
| 180 | ]); | ||
| 181 | break; | ||
| 182 | case "response": | ||
| 183 | case "websocket_connected": | ||
| 184 | if (type == "websocket_connected") { | ||
| 185 | (payload as WebsocketConnectedPayload).status = 101; | ||
| 186 | } | ||
| 187 | setResponses((rps) => ({ | ||
| 188 | ...rps, | ||
| 189 | [(payload as ResponsePayload | WebsocketConnectedPayload).id]: { | ||
| 190 | type, | ||
| 191 | payload, | ||
| 192 | } as Response | WebsocketConnected, | ||
| 193 | })); | ||
| 194 | break; | ||
| 195 | case "websocket_inbound": | ||
| 196 | case "websocket_outbound": | ||
| 197 | case "websocket_disconnect": | ||
| 198 | setWebsocketFrames((frms) => { | ||
| 199 | const id = ( | ||
| 200 | payload as | ||
| 201 | | WebsocketInboundPayload | ||
| 202 | | WebsocketOutboundPayload | ||
| 203 | | WebsocketDisconnectPayload | ||
| 204 | ).id; | ||
| 205 | |||
| 206 | const newFrms = { ...frms }; | ||
| 207 | |||
| 208 | if (!newFrms.hasOwnProperty(id)) { | ||
| 209 | newFrms[id] = []; | ||
| 210 | } | ||
| 211 | |||
| 212 | // @ts-ignore | ||
| 213 | newFrms[id].push({ | ||
| 214 | type, | ||
| 215 | payload: payload as | ||
| 216 | | WebsocketInboundPayload | ||
| 217 | | WebsocketOutboundPayload | ||
| 218 | | WebsocketDisconnectPayload, | ||
| 219 | }); | ||
| 220 | |||
| 221 | return newFrms; | ||
| 222 | }); | ||
| 223 | break; | ||
| 224 | } | ||
| 225 | }; | ||
| 226 | |||
| 227 | ws.addEventListener("message", onMessage); | ||
| 228 | ws.addEventListener("close", onClose); | ||
| 229 | ws.addEventListener("open", onOpen); | ||
| 230 | |||
| 231 | return () => { | ||
| 232 | ws.removeEventListener("message", onMessage); | ||
