summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorGravatar Tom van der Lee <tom@vanderlee.io>2022-01-18 22:03:20 +0100
committerGravatar Tom van der Lee <tom@vanderlee.io>2022-01-18 22:03:20 +0100
commitfc845f2661be61b1a86501eed306cb0d7cb60d73 (patch)
treed2801f1fe6888e9f538a423a60fbee48098cf6ea /src
parentfea814336bc91908fb460de0e41629923564f31a (diff)
downloadclient-fc845f2661be61b1a86501eed306cb0d7cb60d73.tar.gz
client-fc845f2661be61b1a86501eed306cb0d7cb60d73.tar.bz2
client-fc845f2661be61b1a86501eed306cb0d7cb60d73.zip
Show historic requests for the session on page loadv1.1.0
Diffstat (limited to 'src')
-rw-r--r--src/components/App/App.tsx46
-rw-r--r--src/hooks/useRequests.tsx69
2 files changed, 92 insertions, 23 deletions
diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx
index 3a7fe9b..e361aa5 100644
--- a/src/components/App/App.tsx
+++ b/src/components/App/App.tsx
@@ -1,5 +1,5 @@
1import * as React from "react"; 1import * as React from "react";
2import useRequests, {RequestResponse} from "../../hooks/useRequests"; 2import useRequests, {RequestResponse, ReadyState} from "../../hooks/useRequests";
3import {useEffect, useMemo, useState} from "react"; 3import {useEffect, useMemo, useState} from "react";
4 4
5import styles from './App.module.scss'; 5import styles from './App.module.scss';
@@ -11,34 +11,56 @@ interface Config {
11 url: string 11 url: string
12} 12}
13 13
14type ReadyStateMap = {
15 [ReadyState.CONNECTING]: string,
16 [ReadyState.OPEN]: string,
17 [ReadyState.CLOSING]: string,
18 [ReadyState.CLOSED]: string,
19}
20
21const statusMap: ReadyStateMap = {
22 [ReadyState.CONNECTING]: '🔴',
23 [ReadyState.OPEN]: '🟢',
24 [ReadyState.CLOSING]: '🔴',
25 [ReadyState.CLOSED]: '🔴',
26}
27
14export default function App() { 28export default function App() {
29
15 const [config, setConfig]= useState<Config | null>(null) 30 const [config, setConfig]= useState<Config | null>(null)
31
32 const { calls, readyState } = useRequests({
33 onConnect: async () => {
34 const response = await fetch(`http://${getHost()}/config/`)
35 const config = await response.json()
36 setConfig(config)
37 }
38 });
39
16 useEffect(() => { 40 useEffect(() => {
17 fetch(`http://${getHost()}/config/`) 41 const url = new URL(config?.url ?? 'https://loading...');
18 .then(response => response.json() as Promise<Config>) 42 document.title = `${statusMap[readyState]} ${url.host} | TTUN`;
19 .then(setConfig) 43 }, [readyState, config?.url])
20 }, [])
21 44
22 const requests = useRequests();
23 const [selectedRequestIndex, setSelectedRequestIndex] = useState<number | null>(null); 45 const [selectedRequestIndex, setSelectedRequestIndex] = useState<number | null>(null);
24 const selectedRequest = useMemo<RequestResponse | null>(() => ( 46 const selectedRequest = useMemo<RequestResponse | null>(() => (
25 selectedRequestIndex === null 47 selectedRequestIndex === null
26 ? null 48 ? null
27 : requests[selectedRequestIndex] 49 : calls[selectedRequestIndex]
28 ), [selectedRequestIndex, requests]); 50 ), [selectedRequestIndex, calls]);
29 51
30 return config && ( 52 return config && (
31 <div className={styles.app}> 53 <div className={styles.app}>
32 <header className={styles.header}> 54 <header className={styles.header}>
33 TTUN 55 {statusMap[readyState]} TTUN
34 <a href={config.url} target="_blank">{config.url}</a> 56 <a href={config.url} target="_blank">{config.url}</a>
35 </header> 57 </header>
36 <main className={styles.main}> 58 <main className={styles.main}>
37 <ul className={styles.sidebar}> 59 <ul className={styles.sidebar}>
38 { 60 {
39 requests.length > 0 61 calls.length > 0
40 ? requests.slice(0).reverse().map((requestResponse, index) => ( 62 ? calls.slice(0).reverse().map((requestResponse, index) => (
41 <li onClick={() => setSelectedRequestIndex(requests.length - index - 1)} key={`request-${index}`}> 63 <li onClick={() => setSelectedRequestIndex(calls.length - index - 1)} key={`request-${index}`}>
42 <RequestSummary requestResponse={requestResponse} /> 64 <RequestSummary requestResponse={requestResponse} />
43 </li> 65 </li>
44 )) 66 ))
diff --git a/src/hooks/useRequests.tsx b/src/hooks/useRequests.tsx
index 3ad5cc7..2b8393e 100644
--- a/src/hooks/useRequests.tsx
+++ b/src/hooks/useRequests.tsx
@@ -33,25 +33,67 @@ interface Response {
33 payload: ResponsePayload, 33 payload: ResponsePayload,
34} 34}
35 35
36interface Historic {
37 type: 'historic'
38 payload: (Request | Response)[]
39}
40
36export interface RequestResponse { 41export interface RequestResponse {
37 request: RequestPayload 42 request: RequestPayload
38 response?: ResponsePayload 43 response?: ResponsePayload
39} 44}
40 45
41export default function useRequests(): RequestResponse[] { 46export enum ReadyState {
47 CONNECTING = 0,
48 OPEN = 1,
49 CLOSING = 2,
50 CLOSED = 3,
51}
52
53export interface useRequestsProps {
54 onConnect: () => Promise<void>
55}
56
57export interface UseRequests {
58 calls: RequestResponse[]
59 readyState: ReadyState
60}
61
62export default function useRequests({ onConnect }: useRequestsProps): UseRequests {
42 const wsHost = useMemo(getHost, []); 63 const wsHost = useMemo(getHost, []);
43 const connect = useCallback(() => new WebSocket(`ws://${wsHost}/inspect/`), [wsHost])
44 64
45 const [requests, setRequests] = useState<RequestPayload[]>([]); 65 const [requests, setRequests] = useState<RequestPayload[]>([]);
46 const [responses, setResponses] = useState<ResponsePayload[]>([]); 66 const [responses, setResponses] = useState<ResponsePayload[]>([]);
47 const [ws, setWs] = useState(() => connect()); 67
68
69 const connect = useCallback(() => (
70 new WebSocket(`ws://${wsHost}/inspect/`)
71 ), [wsHost]);
72
73 const [ws, setWs] = useState<WebSocket>(() => connect());
74 const [readyState, setReadyState] = useState<ReadyState>(ws.readyState);
48 75
49 useEffect(() => { 76 useEffect(() => {
50 const reconnect = () => setWs(() => connect()) 77 setReadyState(ws.readyState);
78
79 const onClose = () => {
80 setReadyState(ws.readyState);
81 setWs(connect());
82 }
83 const onOpen = () => {
84 onConnect();
85 setReadyState(ws.readyState);
86 }
51 const onMessage = ({ data }) => { 87 const onMessage = ({ data }) => {
52 const { type, payload } = JSON.parse(data) as Request | Response 88 const { type, payload } = JSON.parse(data) as Historic | Request | Response
53 89
54 switch (type) { 90 switch (type) {
91 case 'historic':
92 const requests = (payload as (Request | Response)[]).filter(({ type }) => type === 'request');
93 const responses = (payload as (Request | Response)[]).filter(({ type }) => type === 'response');
94 setRequests((rqs) => [...rqs, ...requests.map(({ payload }) => payload as RequestPayload)]);
95 setResponses((rps) => [...rps, ...responses.map(({ payload }) => payload as ResponsePayload)]);
96 break
55 case 'request': 97 case 'request':
56 setRequests((rqs) => [...rqs, payload as RequestPayload]) 98 setRequests((rqs) => [...rqs, payload as RequestPayload])
57 break 99 break
@@ -62,16 +104,21 @@ export default function useRequests(): RequestResponse[] {
62 } 104 }
63 105
64 ws.addEventListener('message', onMessage) 106 ws.addEventListener('message', onMessage)
65 ws.addEventListener('close', reconnect) 107 ws.addEventListener('close', onClose)
108 ws.addEventListener('open', onOpen)
66 109
67 return () => { 110 return () => {
68 ws.removeEventListener('message', onMessage); 111 ws.removeEventListener('message', onMessage);
69 ws.removeEventListener('close', reconnect); 112 ws.removeEventListener('close', onClose);
113 ws.removeEventListener('open', onOpen)
70 } 114 }
71 }, [ws]) 115 }, [ws])
72 116
73 return useMemo<RequestResponse[]>(() => requests.map((request) => ({ 117 return {
74 request: request, 118 calls: useMemo<RequestResponse[]>(() => requests.map((request) => ({
75 response: responses.find(({ id }) => id === request.id) 119 request: request,
76 })), [requests, responses]) 120 response: responses.find(({id}) => id === request.id)
121 })), [requests, responses]),
122 readyState,
123 }
77} 124}