summaryrefslogtreecommitdiffstats
path: root/src/contexts/Connection.tsx
diff options
context:
space:
mode:
authorGravatar Tom van der Lee <tomvanderlee@users.noreply.github.com>2024-08-30 15:47:20 +0200
committerGravatar GitHub <noreply@github.com>2024-08-30 15:47:20 +0200
commitfe61544bafafc8b4de78cc71cb641af2dfb7b72d (patch)
tree7edff48c1638fcc26915dc1f40982ec2563cf3bf /src/contexts/Connection.tsx
parentf183536067dc694f37445148c15821f1621f5034 (diff)
parent2f2048160fac06e94703bb03eea43185fc01f76c (diff)
downloadclient-2.1.0.tar.gz
client-2.1.0.tar.bz2
client-2.1.0.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.tsx288
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 @@
1import * as React from "react";
2
3import {
4 Context,
5 createContext,
6 PropsWithChildren,
7 useCallback,
8 useEffect,
9 useMemo,
10 useState,
11} from "react";
12import {
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";
32import { getHost } from "~/utils";
33
34interface Config {
35 url: string;
36}
37
38interface 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
47export const ConnectionContext = createContext<Partial<ConnectionApi>>(
48 {}
49) as Context<ConnectionApi>;
50
51export 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);
233 ws.removeEventListener("close", onClose);
234 ws.removeEventListener("open", onOpen);
235 };
236 }, [ws]);
237