pinggy 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/chunk-65R2GMKQ.js +2101 -0
- package/dist/index.cjs +1798 -1349
- package/dist/index.d.cts +616 -0
- package/dist/index.d.ts +616 -0
- package/dist/index.js +24 -2
- package/dist/{main-K44C44NW.js → main-2QDG7PWL.js} +166 -1705
- package/package.json +2 -3
- package/.github/workflows/npm-publish-github-packages.yml +0 -34
- package/.github/workflows/publish-binaries.yml +0 -223
- package/Makefile +0 -4
- package/caxa_build.js +0 -24
- package/dist/chunk-T5ESYDJY.js +0 -121
- package/ent.plist +0 -14
- package/jest.config.js +0 -19
- package/src/_tests_/build_config.test.ts +0 -91
- package/src/cli/buildConfig.ts +0 -535
- package/src/cli/defaults.ts +0 -20
- package/src/cli/extendedOptions.ts +0 -153
- package/src/cli/help.ts +0 -43
- package/src/cli/options.ts +0 -50
- package/src/cli/starCli.ts +0 -229
- package/src/index.ts +0 -31
- package/src/logger.ts +0 -138
- package/src/main.ts +0 -87
- package/src/remote_management/handler.ts +0 -244
- package/src/remote_management/remoteManagement.ts +0 -226
- package/src/remote_management/remote_schema.ts +0 -176
- package/src/remote_management/websocket_handlers.ts +0 -180
- package/src/tui/blessed/TunnelTui.ts +0 -340
- package/src/tui/blessed/components/DisplayUpdaters.ts +0 -189
- package/src/tui/blessed/components/KeyBindings.ts +0 -236
- package/src/tui/blessed/components/Modals.ts +0 -302
- package/src/tui/blessed/components/UIComponents.ts +0 -306
- package/src/tui/blessed/components/index.ts +0 -4
- package/src/tui/blessed/config.ts +0 -53
- package/src/tui/blessed/headerFetcher.ts +0 -42
- package/src/tui/blessed/index.ts +0 -2
- package/src/tui/blessed/qrCodeGenerator.ts +0 -20
- package/src/tui/blessed/webDebuggerConnection.ts +0 -128
- package/src/tui/ink/asciArt.ts +0 -7
- package/src/tui/ink/hooks/useQrCodes.ts +0 -27
- package/src/tui/ink/hooks/useReqResHeaders.ts +0 -27
- package/src/tui/ink/hooks/useTerminalSize.ts +0 -26
- package/src/tui/ink/hooks/useTerminalStats.ts +0 -24
- package/src/tui/ink/hooks/useWebDebugger.ts +0 -98
- package/src/tui/ink/index.tsx +0 -243
- package/src/tui/ink/layout/Borders.tsx +0 -15
- package/src/tui/ink/layout/Container.tsx +0 -15
- package/src/tui/ink/sections/DebuggerDetailModal.tsx +0 -53
- package/src/tui/ink/sections/KeyBindings.tsx +0 -58
- package/src/tui/ink/sections/QrCodeSection.tsx +0 -28
- package/src/tui/ink/sections/StatsSection.tsx +0 -20
- package/src/tui/ink/sections/URLsSection.tsx +0 -53
- package/src/tui/ink/utils/utils.ts +0 -35
- package/src/tui/spinner/spinner.ts +0 -64
- package/src/tunnel_manager/TunnelManager.ts +0 -1212
- package/src/types.ts +0 -255
- package/src/utils/FileServer.ts +0 -112
- package/src/utils/detect_vc_redist_on_windows.ts +0 -111
- package/src/utils/getFreePort.ts +0 -41
- package/src/utils/htmlTemplates.ts +0 -146
- package/src/utils/parseArgs.ts +0 -79
- package/src/utils/printer.ts +0 -81
- package/src/utils/util.ts +0 -18
- package/src/workers/file_serve_worker.ts +0 -33
- package/tsconfig.json +0 -17
- package/tsup.config.ts +0 -12
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { useEffect, useRef, useState } from "react";
|
|
3
|
-
import WebSocket from "ws";
|
|
4
|
-
import { ReqResPair, WebDebuggerSocketRequest } from "../../../types.js";
|
|
5
|
-
import { logger } from "../../../logger.js";
|
|
6
|
-
|
|
7
|
-
export function useWebDebugger(webDebuggerUrl?: string) {
|
|
8
|
-
const [pairs, setPairs] = useState<Map<number, ReqResPair>>(new Map());
|
|
9
|
-
const socketRef = useRef<WebSocket | null>(null);
|
|
10
|
-
const reconnectTimeout = useRef<NodeJS.Timeout | null>(null);
|
|
11
|
-
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
if (!webDebuggerUrl) return;
|
|
14
|
-
|
|
15
|
-
let isStopped = false;
|
|
16
|
-
|
|
17
|
-
const connect = () => {
|
|
18
|
-
const ws = new WebSocket(`ws://${webDebuggerUrl}/introspec/websocket`);
|
|
19
|
-
socketRef.current = ws;
|
|
20
|
-
|
|
21
|
-
ws.on("open", () => {
|
|
22
|
-
logger.info("Web debugger connected.");
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
ws.on("message", (data) => {
|
|
26
|
-
try {
|
|
27
|
-
const raw = data.toString();
|
|
28
|
-
const parsed = JSON.parse(raw);
|
|
29
|
-
const msg = {
|
|
30
|
-
Req: parsed.Req || parsed.req,
|
|
31
|
-
Res: parsed.Res || parsed.res,
|
|
32
|
-
} as Partial<WebDebuggerSocketRequest>;
|
|
33
|
-
|
|
34
|
-
setPairs((prev) => {
|
|
35
|
-
const newMap = new Map(prev);
|
|
36
|
-
|
|
37
|
-
if (msg.Req) {
|
|
38
|
-
const { key } = msg.Req;
|
|
39
|
-
const existing = newMap.get(key) as ReqResPair | undefined;
|
|
40
|
-
const merged = {
|
|
41
|
-
|
|
42
|
-
request: msg.Req,
|
|
43
|
-
response: existing?.response,
|
|
44
|
-
reqHeaders: existing?.reqHeaders ?? {},
|
|
45
|
-
resHeaders: existing?.resHeaders ?? {},
|
|
46
|
-
headersLoaded: existing?.headersLoaded ?? false,
|
|
47
|
-
} as ReqResPair;
|
|
48
|
-
newMap.set(key, merged);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (msg.Res) {
|
|
52
|
-
const { key } = msg.Res;
|
|
53
|
-
const existing = newMap.get(key) as ReqResPair | undefined;
|
|
54
|
-
const merged = {
|
|
55
|
-
|
|
56
|
-
request: existing?.request ?? ({} as any),
|
|
57
|
-
response: msg.Res,
|
|
58
|
-
reqHeaders: existing?.reqHeaders ?? {},
|
|
59
|
-
resHeaders: existing?.resHeaders ?? {},
|
|
60
|
-
headersLoaded: existing?.headersLoaded ?? false,
|
|
61
|
-
} as ReqResPair;
|
|
62
|
-
newMap.set(key, merged);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return newMap;
|
|
66
|
-
});
|
|
67
|
-
} catch (err: any) {
|
|
68
|
-
logger.error("Error parsing WebSocket message:", err.message || err);
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
ws.on("close", () => {
|
|
73
|
-
logger.warn("Web debugger disconnected. Reconnecting in 5s...");
|
|
74
|
-
if (!isStopped) {
|
|
75
|
-
reconnectTimeout.current = setTimeout(connect, 5000);
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
ws.on("error", (err) => {
|
|
80
|
-
logger.error(`WebSocket error: ${err.message}`);
|
|
81
|
-
});
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
connect();
|
|
85
|
-
|
|
86
|
-
return () => {
|
|
87
|
-
isStopped = true;
|
|
88
|
-
if (socketRef.current) {
|
|
89
|
-
socketRef.current.close();
|
|
90
|
-
}
|
|
91
|
-
if (reconnectTimeout.current) {
|
|
92
|
-
clearTimeout(reconnectTimeout.current);
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
}, [webDebuggerUrl]);
|
|
96
|
-
|
|
97
|
-
return { pairs };
|
|
98
|
-
}
|
package/src/tui/ink/index.tsx
DELETED
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pinggy TUI Main Component - Ink Version
|
|
3
|
-
* As of now not in use, kept for future reference.
|
|
4
|
-
* Now We are using the blessed based TUI located in src/tui/blessed
|
|
5
|
-
* Ink has some packaging issues with pkg it hard to bundle properly.
|
|
6
|
-
* If you want to use ink install ink and react as dependencies.
|
|
7
|
-
* Check this issue for more details:
|
|
8
|
-
* https://github.com/vadimdemedes/ink/issues/844
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import React, { useEffect, useState } from "react";
|
|
12
|
-
import { Box, Text, useApp, useInput } from "ink";
|
|
13
|
-
import { useTerminalSize } from "./hooks/useTerminalSize.js";
|
|
14
|
-
import { FinalConfig } from "../../types.js";
|
|
15
|
-
import { Container } from "./layout/Container.js";
|
|
16
|
-
import { Borders } from "./layout/Borders.js";
|
|
17
|
-
import { useQrCodes } from "./hooks/useQrCodes.js";
|
|
18
|
-
import { useTunnelStats } from "./hooks/useTerminalStats.js";
|
|
19
|
-
import { URLsSection } from "./sections/URLsSection.js";
|
|
20
|
-
import { QrCodeSection } from "./sections/QrCodeSection.js";
|
|
21
|
-
import { StatsSection } from "./sections/StatsSection.js";
|
|
22
|
-
import { useWebDebugger } from "./hooks/useWebDebugger.js";
|
|
23
|
-
import { DebuggerDetailModal } from "./sections/DebuggerDetailModal.js";
|
|
24
|
-
import { useReqResHeaders } from "./hooks/useReqResHeaders.js";
|
|
25
|
-
import { logger } from "../../logger.js";
|
|
26
|
-
import { getStatusColor } from "./utils/utils.js";
|
|
27
|
-
import { KeyBindings } from "./sections/KeyBindings.js";
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
interface TunnelAppProps {
|
|
31
|
-
urls: string[];
|
|
32
|
-
greet?: string;
|
|
33
|
-
tunnelConfig?: FinalConfig;
|
|
34
|
-
disconnectInfo?: {
|
|
35
|
-
disconnected: boolean;
|
|
36
|
-
error?: string;
|
|
37
|
-
messages?: string[];
|
|
38
|
-
} | null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const MIN_WIDTH_WARNING = 60;
|
|
42
|
-
const SIMPLE_LAYOUT_THRESHOLD = 80;
|
|
43
|
-
|
|
44
|
-
const TunnelTui = ({ urls, greet, tunnelConfig, disconnectInfo }: TunnelAppProps) => {
|
|
45
|
-
const { exit } = useApp();
|
|
46
|
-
const { columns: terminalWidth } = useTerminalSize();
|
|
47
|
-
const isQrCodeRequested = tunnelConfig?.qrCode || false;
|
|
48
|
-
|
|
49
|
-
// States
|
|
50
|
-
const [currentQrIndex, setCurrentQrIndex] = useState(0);
|
|
51
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
52
|
-
const [inDetailView, setInDetailView] = useState(false);
|
|
53
|
-
const [keyBindingView, setKeyBindingView] = useState(false);
|
|
54
|
-
|
|
55
|
-
// Hooks
|
|
56
|
-
const qrCodes = useQrCodes(urls, isQrCodeRequested);
|
|
57
|
-
const stats = useTunnelStats();
|
|
58
|
-
const { pairs } = useWebDebugger(tunnelConfig?.webDebugger);
|
|
59
|
-
const { headers, fetchHeaders, clear } = useReqResHeaders(tunnelConfig?.webDebugger);
|
|
60
|
-
|
|
61
|
-
const allPairs = [...pairs.values()];
|
|
62
|
-
|
|
63
|
-
useEffect(() => {
|
|
64
|
-
if (disconnectInfo?.disconnected) {
|
|
65
|
-
exit();
|
|
66
|
-
}
|
|
67
|
-
}, [disconnectInfo, exit]);
|
|
68
|
-
|
|
69
|
-
// Key handling
|
|
70
|
-
useInput((input, key) => {
|
|
71
|
-
if (inDetailView && key.escape) {
|
|
72
|
-
setInDetailView(false);
|
|
73
|
-
clear();
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
if (keyBindingView && key.escape) {
|
|
77
|
-
setKeyBindingView(false);
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (key.upArrow && selectedIndex > 0) setSelectedIndex((i) => i - 1);
|
|
82
|
-
if (key.downArrow && selectedIndex < allPairs.length - 1) setSelectedIndex((i) => i + 1);
|
|
83
|
-
|
|
84
|
-
if (key.return) {
|
|
85
|
-
const pair = allPairs[selectedIndex];
|
|
86
|
-
if (pair && pair.request && pair.request.key !== undefined && pair.request.key !== null) {
|
|
87
|
-
(async () => {
|
|
88
|
-
try {
|
|
89
|
-
await fetchHeaders(pair.request.key);
|
|
90
|
-
setInDetailView(true);
|
|
91
|
-
} catch (err) {
|
|
92
|
-
logger.error("Fetch error:", err);
|
|
93
|
-
}
|
|
94
|
-
})();
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
if (input === 'h') {
|
|
98
|
-
setKeyBindingView(i => !i);
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
if (qrCodes.length > 1 || urls.length > 1) {
|
|
102
|
-
if (key.rightArrow && currentQrIndex < urls.length - 1) setCurrentQrIndex(i => i + 1);
|
|
103
|
-
if (key.leftArrow && currentQrIndex > 0) setCurrentQrIndex(i => i - 1);
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
const visiblePairs = allPairs.slice(-10);
|
|
108
|
-
const startIndex = allPairs.length - visiblePairs.length;
|
|
109
|
-
|
|
110
|
-
if (terminalWidth < MIN_WIDTH_WARNING) {
|
|
111
|
-
return (
|
|
112
|
-
<Box flexDirection="column" alignItems="center" justifyContent="center" height="100%">
|
|
113
|
-
<Text color="redBright" bold>
|
|
114
|
-
Terminal is too narrow to show TUI ({terminalWidth} cols).
|
|
115
|
-
</Text>
|
|
116
|
-
<Text color="yellow">
|
|
117
|
-
Please resize your terminal to at least {MIN_WIDTH_WARNING} columns for proper display.
|
|
118
|
-
</Text>
|
|
119
|
-
</Box>
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// === Simplified layout for narrow terminals ===
|
|
124
|
-
if (terminalWidth < SIMPLE_LAYOUT_THRESHOLD) {
|
|
125
|
-
return (
|
|
126
|
-
<Box flexDirection="column" paddingX={1} height={"100%"}>
|
|
127
|
-
{greet && (
|
|
128
|
-
<Box justifyContent="center" marginBottom={1}>
|
|
129
|
-
<Text color="cyanBright" bold>{greet}</Text>
|
|
130
|
-
</Box>
|
|
131
|
-
)}
|
|
132
|
-
|
|
133
|
-
<URLsSection urls={urls} currentQrIndex={currentQrIndex} width="100%" />
|
|
134
|
-
|
|
135
|
-
<Box marginTop={1}>
|
|
136
|
-
<StatsSection stats={stats} />
|
|
137
|
-
</Box>
|
|
138
|
-
|
|
139
|
-
<Box flexDirection="column" marginTop={1}>
|
|
140
|
-
<Text color="yellowBright">HTTP Requests:</Text>
|
|
141
|
-
{visiblePairs.map((pair, i) => (
|
|
142
|
-
<Text key={i} color={selectedIndex === startIndex + i ? "greenBright" : "white"}>
|
|
143
|
-
{selectedIndex === startIndex + i ? "> " : " "}
|
|
144
|
-
{pair.request?.method || ""}
|
|
145
|
-
{" "}
|
|
146
|
-
{pair.response ? (
|
|
147
|
-
<Text color="cyan"> {pair.response.status}</Text>
|
|
148
|
-
) : (
|
|
149
|
-
<Text dimColor>...</Text>
|
|
150
|
-
)}
|
|
151
|
-
{" "}{pair.request?.uri || ""}
|
|
152
|
-
</Text>
|
|
153
|
-
))}
|
|
154
|
-
</Box>
|
|
155
|
-
|
|
156
|
-
{/* Qr codes are removed for small terminals */}
|
|
157
|
-
|
|
158
|
-
<Box marginTop={1} justifyContent="center">
|
|
159
|
-
<Text dimColor>Press Ctrl+C to stop the tunnel.Or h for key bindings</Text>
|
|
160
|
-
</Box>
|
|
161
|
-
|
|
162
|
-
{inDetailView && (
|
|
163
|
-
<DebuggerDetailModal
|
|
164
|
-
requestText={headers?.req}
|
|
165
|
-
responseText={headers?.res}
|
|
166
|
-
onClose={() => setInDetailView(false)}
|
|
167
|
-
/>
|
|
168
|
-
)}
|
|
169
|
-
</Box>
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return (
|
|
175
|
-
<>
|
|
176
|
-
<Container>
|
|
177
|
-
<Borders>
|
|
178
|
-
<Box flexDirection="column" height="100%" justifyContent="space-between">
|
|
179
|
-
{/* ===== Top content ===== */}
|
|
180
|
-
<Box flexDirection="column">
|
|
181
|
-
{greet && (
|
|
182
|
-
<Box justifyContent="center" width="94%" marginBottom={1}>
|
|
183
|
-
<Text color="cyanBright" bold>
|
|
184
|
-
{greet}
|
|
185
|
-
</Text>
|
|
186
|
-
</Box>
|
|
187
|
-
)}
|
|
188
|
-
|
|
189
|
-
{/* Upper Box (URL + stats) */}
|
|
190
|
-
<Box flexDirection="row" justifyContent="space-evenly" width="100%" paddingY={1}>
|
|
191
|
-
<URLsSection urls={urls} currentQrIndex={currentQrIndex} />
|
|
192
|
-
<StatsSection stats={stats} />
|
|
193
|
-
</Box>
|
|
194
|
-
|
|
195
|
-
{/* Lower Box (Requests + QR code) */}
|
|
196
|
-
<Box flexDirection="row" justifyContent="space-evenly" width="100%" paddingY={1}>
|
|
197
|
-
<Box flexDirection="column" marginBottom={1} width={isQrCodeRequested ? "60%" : "80%"}>
|
|
198
|
-
<Text color="yellowBright">HTTP Requests:</Text>
|
|
199
|
-
{visiblePairs.map((pair, i) => (
|
|
200
|
-
<Text key={i}>
|
|
201
|
-
<Text color={selectedIndex === startIndex + i ? "cyanBright" : getStatusColor(pair.response?.status || "")}>
|
|
202
|
-
|
|
203
|
-
{selectedIndex === startIndex + i ? "> " : " "}
|
|
204
|
-
{pair.request?.method || ""}
|
|
205
|
-
{pair.response ? (
|
|
206
|
-
<Text color={selectedIndex === startIndex + i ? "cyanBright" : getStatusColor(pair.response?.status || "")}>{" "} {pair.response.status}</Text>
|
|
207
|
-
) : (
|
|
208
|
-
<Text dimColor>...</Text>
|
|
209
|
-
)}
|
|
210
|
-
{" "}{pair.request?.uri || ""}
|
|
211
|
-
</Text>
|
|
212
|
-
</Text>
|
|
213
|
-
))}
|
|
214
|
-
</Box>
|
|
215
|
-
|
|
216
|
-
{isQrCodeRequested && (
|
|
217
|
-
<QrCodeSection qrCodes={qrCodes} urls={urls} currentQrIndex={currentQrIndex} />
|
|
218
|
-
)}
|
|
219
|
-
</Box>
|
|
220
|
-
</Box>
|
|
221
|
-
|
|
222
|
-
{/* ===== Bottom sticky message ===== */}
|
|
223
|
-
<Box justifyContent="center" marginTop={1}>
|
|
224
|
-
<Text dimColor>Press Ctrl+C twice to stop the tunnel. Or press h for key bindings.</Text>
|
|
225
|
-
</Box>
|
|
226
|
-
</Box>
|
|
227
|
-
</Borders>
|
|
228
|
-
</Container>
|
|
229
|
-
{inDetailView && (
|
|
230
|
-
<DebuggerDetailModal
|
|
231
|
-
requestText={headers?.req}
|
|
232
|
-
responseText={headers?.res}
|
|
233
|
-
onClose={() => setInDetailView(false)}
|
|
234
|
-
/>
|
|
235
|
-
)}
|
|
236
|
-
{
|
|
237
|
-
keyBindingView && <KeyBindings />
|
|
238
|
-
}
|
|
239
|
-
</>
|
|
240
|
-
);
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
export default TunnelTui;
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Box } from "ink";
|
|
3
|
-
|
|
4
|
-
export const Borders = ({ children }: { children: React.ReactNode }) => (
|
|
5
|
-
<Box
|
|
6
|
-
borderStyle="round"
|
|
7
|
-
borderColor="green"
|
|
8
|
-
padding={1}
|
|
9
|
-
flexDirection="column"
|
|
10
|
-
width="100%"
|
|
11
|
-
alignItems="center"
|
|
12
|
-
>
|
|
13
|
-
{children}
|
|
14
|
-
</Box>
|
|
15
|
-
);
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
import Gradient from "ink-gradient";
|
|
4
|
-
import { asciiArtPinggyLogo } from "../asciArt.js";
|
|
5
|
-
|
|
6
|
-
export const Container = ({ children }: { children: React.ReactNode }) => (
|
|
7
|
-
<Box flexDirection="column" height="100%" width="100%" padding={1}>
|
|
8
|
-
<Gradient name="fruit">
|
|
9
|
-
<Text>{asciiArtPinggyLogo}</Text>
|
|
10
|
-
</Gradient>
|
|
11
|
-
<Box marginTop={1} flexGrow={1} width="100%">
|
|
12
|
-
{children}
|
|
13
|
-
</Box>
|
|
14
|
-
</Box>
|
|
15
|
-
);
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
|
|
4
|
-
interface DebuggerDetailModalProps {
|
|
5
|
-
requestText?: string;
|
|
6
|
-
responseText?: string;
|
|
7
|
-
onClose: () => void;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const DebuggerDetailModal = ({
|
|
11
|
-
requestText,
|
|
12
|
-
responseText,
|
|
13
|
-
onClose,
|
|
14
|
-
}: DebuggerDetailModalProps) => {
|
|
15
|
-
return (
|
|
16
|
-
<Box
|
|
17
|
-
flexDirection="column"
|
|
18
|
-
width="100%"
|
|
19
|
-
height="100%"
|
|
20
|
-
position="absolute"
|
|
21
|
-
alignItems="center"
|
|
22
|
-
justifyContent="center"
|
|
23
|
-
>
|
|
24
|
-
<Box
|
|
25
|
-
flexDirection="column"
|
|
26
|
-
paddingX={2}
|
|
27
|
-
paddingY={1}
|
|
28
|
-
width="90%"
|
|
29
|
-
height="90%"
|
|
30
|
-
overflow="hidden"
|
|
31
|
-
backgroundColor="default"
|
|
32
|
-
borderStyle="round"
|
|
33
|
-
borderColor="green"
|
|
34
|
-
>
|
|
35
|
-
<Text color="cyanBright" bold>
|
|
36
|
-
Request
|
|
37
|
-
</Text>
|
|
38
|
-
<Text wrap="truncate-end">{requestText || "(no request data)"}</Text>
|
|
39
|
-
|
|
40
|
-
<Box marginTop={1} />
|
|
41
|
-
|
|
42
|
-
<Text color="magentaBright" bold>
|
|
43
|
-
Response
|
|
44
|
-
</Text>
|
|
45
|
-
<Text wrap="truncate-end">{responseText || "(no response data)"}</Text>
|
|
46
|
-
|
|
47
|
-
<Box marginTop={1} justifyContent="center">
|
|
48
|
-
<Text dimColor>Press ESC to close</Text>
|
|
49
|
-
</Box>
|
|
50
|
-
</Box>
|
|
51
|
-
</Box>
|
|
52
|
-
);
|
|
53
|
-
};
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export const KeyBindings = () => {
|
|
6
|
-
return (
|
|
7
|
-
<Box
|
|
8
|
-
flexDirection="column"
|
|
9
|
-
width="100%"
|
|
10
|
-
height="100%"
|
|
11
|
-
position="absolute"
|
|
12
|
-
alignItems="center"
|
|
13
|
-
justifyContent="center"
|
|
14
|
-
|
|
15
|
-
>
|
|
16
|
-
<Box
|
|
17
|
-
flexDirection="column"
|
|
18
|
-
paddingX={2}
|
|
19
|
-
paddingY={1}
|
|
20
|
-
width="60%"
|
|
21
|
-
height="80%"
|
|
22
|
-
overflow="hidden"
|
|
23
|
-
backgroundColor="default"
|
|
24
|
-
borderStyle="round"
|
|
25
|
-
borderColor="green"
|
|
26
|
-
|
|
27
|
-
>
|
|
28
|
-
<Box justifyContent="center" marginBottom={1} >
|
|
29
|
-
<Text color="cyanBright" bold> Key Bindings </Text>
|
|
30
|
-
</Box>
|
|
31
|
-
<Box flexDirection="column" padding={1} justifyContent="space-evenly" gap={2} >
|
|
32
|
-
{/* Header */}
|
|
33
|
-
<Box flexDirection="column">
|
|
34
|
-
<Text>
|
|
35
|
-
<Text bold>h</Text> This page
|
|
36
|
-
</Text>
|
|
37
|
-
<Text>
|
|
38
|
-
<Text bold>c</Text> Copy the selected URL to clipboard
|
|
39
|
-
</Text>
|
|
40
|
-
<Text>
|
|
41
|
-
<Text bold>Ctrl+c</Text> Exit
|
|
42
|
-
</Text>
|
|
43
|
-
</Box>
|
|
44
|
-
|
|
45
|
-
<Box marginTop={1} flexDirection="column">
|
|
46
|
-
<Text>Enter/Return Open selected request</Text>
|
|
47
|
-
<Text>Esc Return to main page</Text>
|
|
48
|
-
<Text>UP (↑) Scroll up the requests</Text>
|
|
49
|
-
<Text>Down (↓) Scroll down the requests</Text>
|
|
50
|
-
<Text>Left (←) Show qr code for previous url</Text>
|
|
51
|
-
<Text>Right (→) Show qr code for next url</Text>
|
|
52
|
-
<Text>Ctrl+c Force Exit</Text>
|
|
53
|
-
</Box>
|
|
54
|
-
</Box>
|
|
55
|
-
</Box>
|
|
56
|
-
</Box>
|
|
57
|
-
);
|
|
58
|
-
};
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
|
|
4
|
-
interface Props {
|
|
5
|
-
qrCodes: string[];
|
|
6
|
-
urls: string[];
|
|
7
|
-
currentQrIndex: number;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const QrCodeSection = ({ qrCodes, urls, currentQrIndex }: Props) => {
|
|
11
|
-
if (qrCodes.length === 0) return null;
|
|
12
|
-
|
|
13
|
-
return (
|
|
14
|
-
<Box flexDirection="column" alignItems="center" flexGrow={1} paddingX={1} width="40%" >
|
|
15
|
-
<Text color="greenBright" bold>
|
|
16
|
-
QR Code {currentQrIndex + 1}/{urls.length}
|
|
17
|
-
</Text>
|
|
18
|
-
|
|
19
|
-
<Box marginY={1} flexDirection="column" alignItems="center">
|
|
20
|
-
<Text>{qrCodes[currentQrIndex]}</Text>
|
|
21
|
-
</Box>
|
|
22
|
-
|
|
23
|
-
{urls.length > 1 && (
|
|
24
|
-
<Text color="yellow">← → to switch QR codes</Text>
|
|
25
|
-
)}
|
|
26
|
-
</Box>
|
|
27
|
-
);
|
|
28
|
-
};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
import { TunnelUsageType } from "@pinggy/pinggy";
|
|
4
|
-
import { getBytesInt } from "../utils/utils.js";
|
|
5
|
-
|
|
6
|
-
export const StatsSection = ({ stats }: { stats: TunnelUsageType }) => (
|
|
7
|
-
<Box flexDirection="column" paddingX={1} alignItems="center" width="40%">
|
|
8
|
-
<Box flexDirection="column" alignItems="flex-start">
|
|
9
|
-
<Text color="greenBright" bold>
|
|
10
|
-
Live Stats
|
|
11
|
-
</Text>
|
|
12
|
-
<Text>Elapsed: {stats.elapsedTime}s</Text>
|
|
13
|
-
<Text>Live Connections: {stats.numLiveConnections}</Text>
|
|
14
|
-
<Text>Total Connections: {stats.numTotalConnections}</Text>
|
|
15
|
-
<Text>Request : {getBytesInt(stats.numTotalReqBytes)}</Text>
|
|
16
|
-
<Text>Response : {getBytesInt(stats.numTotalResBytes)}</Text>
|
|
17
|
-
<Text>Total Transfer: {getBytesInt(stats.numTotalTxBytes)}</Text>
|
|
18
|
-
</Box>
|
|
19
|
-
</Box>
|
|
20
|
-
);
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Box, Text, useInput } from "ink";
|
|
3
|
-
import clipboardy from "clipboardy";
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
urls: string[];
|
|
7
|
-
currentQrIndex: number;
|
|
8
|
-
width?: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const URLsSection = ({ urls, currentQrIndex, width = "60%" }: Props) => {
|
|
12
|
-
const [copiedIndex, setCopiedIndex] = React.useState<number | null>(null);
|
|
13
|
-
|
|
14
|
-
useInput((input, key) => {
|
|
15
|
-
// Press 'c' to copy current URL
|
|
16
|
-
if (input === "c" && urls.length > 0) {
|
|
17
|
-
const urlToCopy = urls[currentQrIndex];
|
|
18
|
-
clipboardy.writeSync(urlToCopy);
|
|
19
|
-
setCopiedIndex(currentQrIndex);
|
|
20
|
-
|
|
21
|
-
// reset feedback after a second
|
|
22
|
-
setTimeout(() => setCopiedIndex(null), 1000);
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
return (<Box flexDirection="column" paddingX={1} alignItems="flex-start" width={width} >
|
|
27
|
-
<Text color="greenBright" bold>
|
|
28
|
-
Public URLs
|
|
29
|
-
</Text>
|
|
30
|
-
{urls.map((url, index) => {
|
|
31
|
-
const isSelected = index === currentQrIndex;
|
|
32
|
-
const isCopied = copiedIndex === index;
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<Text
|
|
36
|
-
key={`url-${url}-${index}`}
|
|
37
|
-
color={
|
|
38
|
-
isCopied
|
|
39
|
-
? "cyanBright"
|
|
40
|
-
: isSelected
|
|
41
|
-
? "yellowBright"
|
|
42
|
-
: "magentaBright"
|
|
43
|
-
}
|
|
44
|
-
bold={isSelected || isCopied}
|
|
45
|
-
>
|
|
46
|
-
{isSelected ? "→ " : "• "}
|
|
47
|
-
{url}
|
|
48
|
-
{isCopied && <Text color="greenBright"> [Copied!]</Text>}
|
|
49
|
-
</Text>
|
|
50
|
-
);
|
|
51
|
-
})}
|
|
52
|
-
</Box>);
|
|
53
|
-
};
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
export function getStatusColor(status: string): string {
|
|
2
|
-
|
|
3
|
-
const match = status.match(/\b(\d{3})\b/);
|
|
4
|
-
const statusCode = match ? parseInt(match[1], 10) : 0;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
switch (true) {
|
|
8
|
-
case statusCode >= 100 && statusCode < 200:
|
|
9
|
-
return "yellow";
|
|
10
|
-
case statusCode >= 200 && statusCode < 300:
|
|
11
|
-
return "green";
|
|
12
|
-
case statusCode >= 300 && statusCode < 400:
|
|
13
|
-
return "yellow";
|
|
14
|
-
case statusCode >= 400 && statusCode < 500:
|
|
15
|
-
return "red";
|
|
16
|
-
case statusCode >= 500:
|
|
17
|
-
return "pink";
|
|
18
|
-
default:
|
|
19
|
-
return "yellow";
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function getBytesInt(b: number): string {
|
|
24
|
-
if (b >= 1024 * 1024 * 1024) {
|
|
25
|
-
return `${(b / (1024 * 1024 * 1024)).toFixed(2)} G`;
|
|
26
|
-
}
|
|
27
|
-
if (b >= 1024 * 1024) {
|
|
28
|
-
return `${(b / (1024 * 1024)).toFixed(2)} M`;
|
|
29
|
-
}
|
|
30
|
-
if (b >= 1024) {
|
|
31
|
-
return `${(b / 1024).toFixed(2)} K`;
|
|
32
|
-
}
|
|
33
|
-
return `${b.toFixed(2)} `;
|
|
34
|
-
}
|
|
35
|
-
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import pico from 'picocolors';
|
|
2
|
-
|
|
3
|
-
interface Spinner {
|
|
4
|
-
interval: number;
|
|
5
|
-
frames: string[];
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
interface Spinners {
|
|
9
|
-
[key: string]: Spinner;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const spinners: Spinners = {
|
|
13
|
-
dots: {
|
|
14
|
-
interval: 80,
|
|
15
|
-
frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
|
|
16
|
-
},
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
let currentTimer: NodeJS.Timeout | null = null;
|
|
20
|
-
let currentText = '';
|
|
21
|
-
|
|
22
|
-
export function startSpinner(name = 'dots', text = 'Loading') {
|
|
23
|
-
const spinner = spinners[name];
|
|
24
|
-
let i = 0;
|
|
25
|
-
currentText = text;
|
|
26
|
-
|
|
27
|
-
// Clear any existing spinner
|
|
28
|
-
if (currentTimer) {
|
|
29
|
-
clearInterval(currentTimer);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
currentTimer = setInterval(() => {
|
|
33
|
-
const frame = spinner.frames[i = ++i % spinner.frames.length];
|
|
34
|
-
process.stdout.write(`\r${pico.cyan(frame)} ${text}`);
|
|
35
|
-
}, spinner.interval);
|
|
36
|
-
|
|
37
|
-
return () => stopSpinner();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function stopSpinner() {
|
|
41
|
-
if (currentTimer) {
|
|
42
|
-
clearInterval(currentTimer);
|
|
43
|
-
currentTimer = null;
|
|
44
|
-
process.stdout.write('\r\x1b[K'); // Clear the line
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function stopSpinnerSuccess(message?: string) {
|
|
49
|
-
if (currentTimer) {
|
|
50
|
-
clearInterval(currentTimer);
|
|
51
|
-
currentTimer = null;
|
|
52
|
-
const finalMessage = message || currentText;
|
|
53
|
-
process.stdout.write(`\r${pico.green('✔')} ${finalMessage}\n`);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function stopSpinnerFail(message?: string) {
|
|
58
|
-
if (currentTimer) {
|
|
59
|
-
clearInterval(currentTimer);
|
|
60
|
-
currentTimer = null;
|
|
61
|
-
const finalMessage = message || currentText;
|
|
62
|
-
process.stdout.write(`\r${pico.red('✖')} ${finalMessage}\n`);
|
|
63
|
-
}
|
|
64
|
-
}
|