network-terminal 1.0.6 → 1.0.8

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.
@@ -1,219 +0,0 @@
1
- import React, { useState, useCallback, useEffect } from 'react';
2
- import { NetworkLog, NetworkTerminalProps } from '../types';
3
- import { Terminal } from './Terminal';
4
- import { useNetworkInterceptor } from '../hooks/useNetworkInterceptor';
5
-
6
- const styles = {
7
- floatingButton: {
8
- position: 'fixed' as const,
9
- bottom: '16px',
10
- right: '16px',
11
- zIndex: 9999,
12
- backgroundColor: '#1f2937',
13
- color: '#4ade80',
14
- padding: '8px 16px',
15
- borderRadius: '8px',
16
- fontFamily: 'monospace',
17
- fontSize: '14px',
18
- boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1)',
19
- border: '1px solid #374151',
20
- cursor: 'pointer',
21
- },
22
- container: {
23
- position: 'fixed' as const,
24
- bottom: 0,
25
- left: 0,
26
- right: 0,
27
- zIndex: 9999,
28
- backgroundColor: '#111827',
29
- borderTop: '2px solid #22c55e',
30
- boxShadow: '0 -10px 15px -3px rgba(0, 0, 0, 0.1)',
31
- },
32
- containerTop: {
33
- position: 'fixed' as const,
34
- top: 0,
35
- left: 0,
36
- right: 0,
37
- zIndex: 9999,
38
- backgroundColor: '#111827',
39
- borderBottom: '2px solid #22c55e',
40
- boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1)',
41
- },
42
- mainHeader: {
43
- display: 'flex',
44
- alignItems: 'center',
45
- justifyContent: 'space-between',
46
- padding: '8px 16px',
47
- backgroundColor: '#1f2937',
48
- borderBottom: '1px solid #374151',
49
- },
50
- headerLeft: {
51
- display: 'flex',
52
- alignItems: 'center',
53
- gap: '12px',
54
- },
55
- title: {
56
- color: '#4ade80',
57
- fontFamily: 'monospace',
58
- fontWeight: 'bold',
59
- },
60
- hint: {
61
- color: '#6b7280',
62
- fontFamily: 'monospace',
63
- fontSize: '12px',
64
- },
65
- headerRight: {
66
- display: 'flex',
67
- alignItems: 'center',
68
- gap: '8px',
69
- },
70
- clearButton: {
71
- color: '#9ca3af',
72
- fontSize: '14px',
73
- fontFamily: 'monospace',
74
- padding: '4px 12px',
75
- borderRadius: '4px',
76
- border: 'none',
77
- background: 'transparent',
78
- cursor: 'pointer',
79
- },
80
- closeButton: {
81
- color: '#9ca3af',
82
- fontSize: '18px',
83
- fontWeight: 'bold',
84
- padding: '0 8px',
85
- border: 'none',
86
- background: 'transparent',
87
- cursor: 'pointer',
88
- borderRadius: '4px',
89
- },
90
- terminalsContainer: {
91
- display: 'flex',
92
- gap: '8px',
93
- padding: '8px',
94
- height: '450px',
95
- },
96
- terminalWrapper: {
97
- flex: 1,
98
- display: 'flex',
99
- flexDirection: 'column' as const,
100
- },
101
- } as const;
102
-
103
- export const NetworkTerminal: React.FC<NetworkTerminalProps> = ({
104
- maxLogs = 100,
105
- defaultVisible = false,
106
- position = 'bottom',
107
- height = '450px',
108
- zIndex = 9999,
109
- }) => {
110
- const [logs, setLogs] = useState<NetworkLog[]>([]);
111
- const [isVisible, setIsVisible] = useState(defaultVisible);
112
- const [requestExpanded, setRequestExpanded] = useState(true);
113
- const [responseExpanded, setResponseExpanded] = useState(true);
114
-
115
- const addLog = useCallback(
116
- (log: NetworkLog) => {
117
- setLogs((prev) => [...prev.slice(-(maxLogs - 1)), log]);
118
- },
119
- [maxLogs]
120
- );
121
-
122
- const updateLog = useCallback((id: string, updates: Partial<NetworkLog>) => {
123
- setLogs((prev) =>
124
- prev.map((log) => (log.id === id ? { ...log, ...updates } : log))
125
- );
126
- }, []);
127
-
128
- useNetworkInterceptor({
129
- enabled: isVisible,
130
- onLogAdd: addLog,
131
- onLogUpdate: updateLog,
132
- });
133
-
134
- useEffect(() => {
135
- const handleKeyDown = (e: KeyboardEvent) => {
136
- if (e.ctrlKey && e.shiftKey && e.key === 'N') {
137
- e.preventDefault();
138
- setIsVisible((prev) => !prev);
139
- }
140
- };
141
-
142
- window.addEventListener('keydown', handleKeyDown);
143
- return () => window.removeEventListener('keydown', handleKeyDown);
144
- }, []);
145
-
146
- const clearLogs = () => setLogs([]);
147
-
148
- if (!isVisible) {
149
- return (
150
- <button
151
- onClick={() => setIsVisible(true)}
152
- style={{
153
- ...styles.floatingButton,
154
- zIndex,
155
- }}
156
- title="Open Network Terminal (Ctrl+Shift+N)"
157
- >
158
- {'>'}_ Network
159
- </button>
160
- );
161
- }
162
-
163
- const containerStyle =
164
- position === 'top'
165
- ? { ...styles.containerTop, zIndex }
166
- : { ...styles.container, zIndex };
167
-
168
- return (
169
- <div style={containerStyle}>
170
- <div style={styles.mainHeader}>
171
- <div style={styles.headerLeft}>
172
- <span style={styles.title}>{'>'}_ Network Terminal</span>
173
- <span style={styles.hint}>Press Ctrl+Shift+N to toggle</span>
174
- </div>
175
- <div style={styles.headerRight}>
176
- <button
177
- onClick={clearLogs}
178
- style={styles.clearButton}
179
- onMouseEnter={(e) => (e.currentTarget.style.color = '#fff')}
180
- onMouseLeave={(e) => (e.currentTarget.style.color = '#9ca3af')}
181
- >
182
- Clear All
183
- </button>
184
- <button
185
- onClick={() => setIsVisible(false)}
186
- style={styles.closeButton}
187
- onMouseEnter={(e) => (e.currentTarget.style.color = '#f87171')}
188
- onMouseLeave={(e) => (e.currentTarget.style.color = '#9ca3af')}
189
- >
190
- {'\u00D7'}
191
- </button>
192
- </div>
193
- </div>
194
-
195
- <div style={{ ...styles.terminalsContainer, height }}>
196
- <div style={styles.terminalWrapper}>
197
- <Terminal
198
- title="Requests"
199
- logs={logs}
200
- type="request"
201
- onClear={clearLogs}
202
- expanded={requestExpanded}
203
- onToggleExpand={() => setRequestExpanded(!requestExpanded)}
204
- />
205
- </div>
206
- <div style={styles.terminalWrapper}>
207
- <Terminal
208
- title="Responses"
209
- logs={logs}
210
- type="response"
211
- onClear={clearLogs}
212
- expanded={responseExpanded}
213
- onToggleExpand={() => setResponseExpanded(!responseExpanded)}
214
- />
215
- </div>
216
- </div>
217
- </div>
218
- );
219
- };
@@ -1,86 +0,0 @@
1
- import React, { useRef, useState, useEffect } from 'react';
2
- import { TerminalProps } from '../types';
3
- import { TerminalHeader } from './TerminalHeader';
4
- import { LogEntry } from './LogEntry';
5
-
6
- const styles = {
7
- container: {
8
- display: 'flex',
9
- flexDirection: 'column' as const,
10
- backgroundColor: '#111827',
11
- borderRadius: '8px',
12
- border: '1px solid #374151',
13
- overflow: 'hidden',
14
- transition: 'all 0.2s',
15
- flex: 1,
16
- },
17
- collapsed: {
18
- height: '48px',
19
- flex: 'none' as const,
20
- },
21
- body: {
22
- flex: 1,
23
- overflow: 'auto',
24
- padding: '16px',
25
- fontFamily: 'monospace',
26
- fontSize: '14px',
27
- minHeight: '200px',
28
- maxHeight: '400px',
29
- },
30
- empty: {
31
- color: '#6b7280',
32
- fontStyle: 'italic',
33
- },
34
- } as const;
35
-
36
- export const Terminal: React.FC<TerminalProps> = ({
37
- title,
38
- logs,
39
- type,
40
- onClear,
41
- expanded,
42
- onToggleExpand,
43
- }) => {
44
- const terminalRef = useRef<HTMLDivElement>(null);
45
- const [autoScroll, setAutoScroll] = useState(true);
46
-
47
- useEffect(() => {
48
- if (autoScroll && terminalRef.current) {
49
- terminalRef.current.scrollTop = terminalRef.current.scrollHeight;
50
- }
51
- }, [logs, autoScroll]);
52
-
53
- const handleScroll = () => {
54
- if (terminalRef.current) {
55
- const { scrollTop, scrollHeight, clientHeight } = terminalRef.current;
56
- setAutoScroll(scrollHeight - scrollTop - clientHeight < 50);
57
- }
58
- };
59
-
60
- return (
61
- <div
62
- style={{
63
- ...styles.container,
64
- ...(expanded ? {} : styles.collapsed),
65
- }}
66
- >
67
- <TerminalHeader
68
- title={title}
69
- count={logs.length}
70
- expanded={expanded}
71
- onClear={onClear}
72
- onToggleExpand={onToggleExpand}
73
- />
74
-
75
- {expanded && (
76
- <div ref={terminalRef} onScroll={handleScroll} style={styles.body}>
77
- {logs.length === 0 ? (
78
- <div style={styles.empty}>Waiting for network requests...</div>
79
- ) : (
80
- logs.map((log) => <LogEntry key={log.id} log={log} type={type} />)
81
- )}
82
- </div>
83
- )}
84
- </div>
85
- );
86
- };
@@ -1,93 +0,0 @@
1
- import React from 'react';
2
- import { TerminalHeaderProps } from '../types';
3
-
4
- const styles = {
5
- header: {
6
- display: 'flex',
7
- alignItems: 'center',
8
- justifyContent: 'space-between',
9
- padding: '8px 16px',
10
- backgroundColor: '#1f2937',
11
- borderBottom: '1px solid #374151',
12
- },
13
- left: {
14
- display: 'flex',
15
- alignItems: 'center',
16
- gap: '8px',
17
- },
18
- trafficLights: {
19
- display: 'flex',
20
- gap: '6px',
21
- },
22
- dot: {
23
- width: '12px',
24
- height: '12px',
25
- borderRadius: '50%',
26
- },
27
- title: {
28
- color: '#d1d5db',
29
- fontFamily: 'monospace',
30
- fontSize: '14px',
31
- marginLeft: '8px',
32
- },
33
- count: {
34
- color: '#6b7280',
35
- fontFamily: 'monospace',
36
- fontSize: '12px',
37
- },
38
- right: {
39
- display: 'flex',
40
- alignItems: 'center',
41
- gap: '8px',
42
- },
43
- button: {
44
- color: '#9ca3af',
45
- fontSize: '12px',
46
- fontFamily: 'monospace',
47
- padding: '4px 8px',
48
- borderRadius: '4px',
49
- border: 'none',
50
- background: 'transparent',
51
- cursor: 'pointer',
52
- },
53
- } as const;
54
-
55
- export const TerminalHeader: React.FC<TerminalHeaderProps> = ({
56
- title,
57
- count,
58
- expanded,
59
- onClear,
60
- onToggleExpand,
61
- }) => {
62
- return (
63
- <div style={styles.header}>
64
- <div style={styles.left}>
65
- <div style={styles.trafficLights}>
66
- <div style={{ ...styles.dot, backgroundColor: '#ef4444' }} />
67
- <div style={{ ...styles.dot, backgroundColor: '#eab308' }} />
68
- <div style={{ ...styles.dot, backgroundColor: '#22c55e' }} />
69
- </div>
70
- <span style={styles.title}>{title}</span>
71
- <span style={styles.count}>({count} entries)</span>
72
- </div>
73
- <div style={styles.right}>
74
- <button
75
- onClick={onClear}
76
- style={styles.button}
77
- onMouseEnter={(e) => (e.currentTarget.style.color = '#fff')}
78
- onMouseLeave={(e) => (e.currentTarget.style.color = '#9ca3af')}
79
- >
80
- Clear
81
- </button>
82
- <button
83
- onClick={onToggleExpand}
84
- style={styles.button}
85
- onMouseEnter={(e) => (e.currentTarget.style.color = '#fff')}
86
- onMouseLeave={(e) => (e.currentTarget.style.color = '#9ca3af')}
87
- >
88
- {expanded ? '\u25BC' : '\u25B2'}
89
- </button>
90
- </div>
91
- </div>
92
- );
93
- };
@@ -1,190 +0,0 @@
1
- import { useEffect, useRef, useCallback } from 'react';
2
- import { NetworkLog, UseNetworkInterceptorProps } from '../types';
3
-
4
- export const useNetworkInterceptor = ({
5
- enabled,
6
- onLogAdd,
7
- onLogUpdate,
8
- }: UseNetworkInterceptorProps) => {
9
- const originalFetch = useRef<typeof fetch | null>(null);
10
- const originalXHROpen = useRef<typeof XMLHttpRequest.prototype.open | null>(null);
11
- const originalXHRSend = useRef<typeof XMLHttpRequest.prototype.send | null>(null);
12
-
13
- const generateId = useCallback((prefix: string) => {
14
- return `${prefix}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
15
- }, []);
16
-
17
- // Intercept Fetch
18
- useEffect(() => {
19
- if (!enabled) return;
20
-
21
- originalFetch.current = window.fetch;
22
-
23
- window.fetch = async function (input, init) {
24
- const id = generateId('fetch');
25
- const startTime = performance.now();
26
-
27
- const url =
28
- typeof input === 'string'
29
- ? input
30
- : input instanceof URL
31
- ? input.href
32
- : input.url;
33
-
34
- const method = init?.method || 'GET';
35
-
36
- let requestBody: unknown = null;
37
- if (init?.body) {
38
- try {
39
- requestBody =
40
- typeof init.body === 'string' ? JSON.parse(init.body) : init.body;
41
- } catch {
42
- requestBody = init.body;
43
- }
44
- }
45
-
46
- onLogAdd({
47
- id,
48
- timestamp: new Date(),
49
- method: method.toUpperCase(),
50
- url,
51
- requestBody,
52
- type: 'fetch',
53
- });
54
-
55
- try {
56
- const response = await originalFetch.current!(input, init);
57
- const duration = Math.round(performance.now() - startTime);
58
-
59
- const clonedResponse = response.clone();
60
- let responseBody: unknown = null;
61
-
62
- try {
63
- responseBody = await clonedResponse.json();
64
- } catch {
65
- try {
66
- responseBody = await clonedResponse.text();
67
- } catch {
68
- responseBody = '[Could not read response body]';
69
- }
70
- }
71
-
72
- onLogUpdate(id, {
73
- status: response.status,
74
- statusText: response.statusText,
75
- responseBody,
76
- duration,
77
- });
78
-
79
- return response;
80
- } catch (error: unknown) {
81
- const duration = Math.round(performance.now() - startTime);
82
- const errorMessage =
83
- error instanceof Error ? error.message : 'Network Error';
84
- onLogUpdate(id, {
85
- error: errorMessage,
86
- duration,
87
- });
88
- throw error;
89
- }
90
- };
91
-
92
- return () => {
93
- if (originalFetch.current) {
94
- window.fetch = originalFetch.current;
95
- }
96
- };
97
- }, [enabled, onLogAdd, onLogUpdate, generateId]);
98
-
99
- // Intercept XHR
100
- useEffect(() => {
101
- if (!enabled) return;
102
-
103
- originalXHROpen.current = XMLHttpRequest.prototype.open;
104
- originalXHRSend.current = XMLHttpRequest.prototype.send;
105
-
106
- XMLHttpRequest.prototype.open = function (
107
- method: string,
108
- url: string | URL
109
- ) {
110
- (this as XMLHttpRequest & { _networkTerminal: NetworkLog & { startTime: number } })._networkTerminal = {
111
- id: generateId('xhr'),
112
- method: method.toUpperCase(),
113
- url: url.toString(),
114
- startTime: 0,
115
- timestamp: new Date(),
116
- type: 'xhr',
117
- };
118
- return originalXHROpen.current!.apply(
119
- this,
120
- arguments as unknown as Parameters<typeof XMLHttpRequest.prototype.open>
121
- );
122
- };
123
-
124
- XMLHttpRequest.prototype.send = function (body?: Document | XMLHttpRequestBodyInit | null) {
125
- const meta = (this as XMLHttpRequest & { _networkTerminal?: NetworkLog & { startTime: number } })._networkTerminal;
126
-
127
- if (meta) {
128
- meta.startTime = performance.now();
129
-
130
- let requestBody: unknown = null;
131
- if (body) {
132
- try {
133
- requestBody = typeof body === 'string' ? JSON.parse(body) : body;
134
- } catch {
135
- requestBody = body;
136
- }
137
- }
138
-
139
- onLogAdd({
140
- id: meta.id,
141
- timestamp: new Date(),
142
- method: meta.method,
143
- url: meta.url,
144
- requestBody,
145
- type: 'xhr',
146
- });
147
-
148
- this.addEventListener('load', function () {
149
- const duration = Math.round(performance.now() - meta.startTime);
150
-
151
- let responseBody: unknown = null;
152
- try {
153
- responseBody = JSON.parse(this.responseText);
154
- } catch {
155
- responseBody = this.responseText;
156
- }
157
-
158
- onLogUpdate(meta.id, {
159
- status: this.status,
160
- statusText: this.statusText,
161
- responseBody,
162
- duration,
163
- });
164
- });
165
-
166
- this.addEventListener('error', function () {
167
- const duration = Math.round(performance.now() - meta.startTime);
168
- onLogUpdate(meta.id, {
169
- error: 'Network Error',
170
- duration,
171
- });
172
- });
173
- }
174
-
175
- return originalXHRSend.current!.apply(
176
- this,
177
- arguments as unknown as Parameters<typeof XMLHttpRequest.prototype.send>
178
- );
179
- };
180
-
181
- return () => {
182
- if (originalXHROpen.current) {
183
- XMLHttpRequest.prototype.open = originalXHROpen.current;
184
- }
185
- if (originalXHRSend.current) {
186
- XMLHttpRequest.prototype.send = originalXHRSend.current;
187
- }
188
- };
189
- }, [enabled, onLogAdd, onLogUpdate, generateId]);
190
- };
package/src/index.ts DELETED
@@ -1,17 +0,0 @@
1
- export { NetworkTerminal } from './components/NetworkTerminal';
2
- export { Terminal } from './components/Terminal';
3
- export { TerminalHeader } from './components/TerminalHeader';
4
- export { LogEntry } from './components/LogEntry';
5
- export { useNetworkInterceptor } from './hooks/useNetworkInterceptor';
6
- export { formatJson, truncate, formatTime } from './utils/formatters';
7
- export { getStatusColor, getMethodColor } from './utils/colors';
8
- export { networkTerminal } from './vite-plugin';
9
- export type {
10
- NetworkLog,
11
- NetworkTerminalProps,
12
- TerminalProps,
13
- TerminalHeaderProps,
14
- LogEntryProps,
15
- UseNetworkInterceptorProps,
16
- } from './types';
17
- export type { NetworkTerminalPluginOptions } from './vite-plugin';
package/src/types.ts DELETED
@@ -1,50 +0,0 @@
1
- export interface NetworkLog {
2
- id: string;
3
- timestamp: Date;
4
- method: string;
5
- url: string;
6
- status?: number;
7
- statusText?: string;
8
- requestHeaders?: Record<string, string>;
9
- requestBody?: unknown;
10
- responseBody?: unknown;
11
- duration?: number;
12
- error?: string;
13
- type: 'fetch' | 'xhr';
14
- }
15
-
16
- export interface TerminalProps {
17
- title: string;
18
- logs: NetworkLog[];
19
- type: 'request' | 'response';
20
- onClear: () => void;
21
- expanded: boolean;
22
- onToggleExpand: () => void;
23
- }
24
-
25
- export interface LogEntryProps {
26
- log: NetworkLog;
27
- type: 'request' | 'response';
28
- }
29
-
30
- export interface TerminalHeaderProps {
31
- title: string;
32
- count: number;
33
- expanded: boolean;
34
- onClear: () => void;
35
- onToggleExpand: () => void;
36
- }
37
-
38
- export interface UseNetworkInterceptorProps {
39
- enabled: boolean;
40
- onLogAdd: (log: NetworkLog) => void;
41
- onLogUpdate: (id: string, updates: Partial<NetworkLog>) => void;
42
- }
43
-
44
- export interface NetworkTerminalProps {
45
- maxLogs?: number;
46
- defaultVisible?: boolean;
47
- position?: 'bottom' | 'top';
48
- height?: string;
49
- zIndex?: number;
50
- }
@@ -1,18 +0,0 @@
1
- export const getStatusColor = (status?: number): string => {
2
- if (!status) return '#9ca3af'; // gray-400
3
- if (status >= 200 && status < 300) return '#4ade80'; // green-400
4
- if (status >= 300 && status < 400) return '#facc15'; // yellow-400
5
- if (status >= 400 && status < 500) return '#fb923c'; // orange-400
6
- return '#f87171'; // red-400
7
- };
8
-
9
- export const getMethodColor = (method: string): string => {
10
- const colors: Record<string, string> = {
11
- GET: '#22d3ee', // cyan-400
12
- POST: '#4ade80', // green-400
13
- PUT: '#facc15', // yellow-400
14
- PATCH: '#fb923c', // orange-400
15
- DELETE: '#f87171', // red-400
16
- };
17
- return colors[method.toUpperCase()] || '#9ca3af';
18
- };
@@ -1,26 +0,0 @@
1
- export const formatJson = (data: unknown): string => {
2
- if (!data) return '';
3
- try {
4
- if (typeof data === 'string') {
5
- const parsed = JSON.parse(data);
6
- return JSON.stringify(parsed, null, 2);
7
- }
8
- return JSON.stringify(data, null, 2);
9
- } catch {
10
- return String(data);
11
- }
12
- };
13
-
14
- export const truncate = (str: string, maxLength: number): string => {
15
- if (str.length <= maxLength) return str;
16
- return str.slice(0, maxLength) + '...';
17
- };
18
-
19
- export const formatTime = (date: Date): string => {
20
- return date.toLocaleTimeString('en-US', {
21
- hour12: false,
22
- hour: '2-digit',
23
- minute: '2-digit',
24
- second: '2-digit',
25
- });
26
- };