reactoradar 1.6.5 → 1.6.6
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/app.js +22 -4448
- package/index.html +12 -0
- package/main.js +1 -0
- package/package.json +1 -1
- package/styles.css +40 -7
- package/src/main/main.js +0 -396
- package/src/main/preload.js +0 -28
- package/src/renderer/app.js +0 -221
- package/src/renderer/components/object-tree.js +0 -245
- package/src/renderer/index.html +0 -111
- package/src/renderer/panels/console.js +0 -248
- package/src/renderer/panels/memory.js +0 -60
- package/src/renderer/panels/network.js +0 -559
- package/src/renderer/panels/performance.js +0 -144
- package/src/renderer/panels/react.js +0 -31
- package/src/renderer/panels/redux.js +0 -159
- package/src/renderer/panels/settings.js +0 -93
- package/src/renderer/panels/sources.js +0 -189
- package/src/renderer/panels/storage.js +0 -134
- package/src/renderer/state.js +0 -132
- package/src/renderer/styles/components.css +0 -145
- package/src/renderer/styles/console.css +0 -73
- package/src/renderer/styles/main.css +0 -229
- package/src/renderer/styles/network.css +0 -242
- package/src/renderer/styles/performance.css +0 -45
- package/src/renderer/styles/redux.css +0 -77
- package/src/renderer/styles/settings.css +0 -63
- package/src/renderer/styles/sources.css +0 -48
- package/src/renderer/styles/storage.css +0 -28
- package/src/renderer/styles/theme-light.css +0 -57
package/index.html
CHANGED
|
@@ -90,6 +90,18 @@
|
|
|
90
90
|
|
|
91
91
|
</div>
|
|
92
92
|
|
|
93
|
+
<!-- Load order: app.js (state/helpers) → panels → init.js (IPC/boot) -->
|
|
93
94
|
<script src="app.js"></script>
|
|
95
|
+
<script src="panels/settings.js"></script>
|
|
96
|
+
<script src="panels/console.js"></script>
|
|
97
|
+
<script src="panels/network.js"></script>
|
|
98
|
+
<script src="panels/ga4.js"></script>
|
|
99
|
+
<script src="panels/redux.js"></script>
|
|
100
|
+
<script src="panels/storage.js"></script>
|
|
101
|
+
<script src="panels/performance.js"></script>
|
|
102
|
+
<script src="panels/native.js"></script>
|
|
103
|
+
<script src="panels/react.js"></script>
|
|
104
|
+
<script src="panels/sources.js"></script>
|
|
105
|
+
<script src="init.js"></script>
|
|
94
106
|
</body>
|
|
95
107
|
</html>
|
package/main.js
CHANGED
|
@@ -375,6 +375,7 @@ function startReactDevToolsServer() {
|
|
|
375
375
|
function startBridgeServers() {
|
|
376
376
|
// Redux Bridge
|
|
377
377
|
startBridge(PORTS.REDUX_BRIDGE, 'redux', reduxClients, (event) => {
|
|
378
|
+
// console.log('[REDUX-DEBUG] Event from SDK:', event?.type, event?.action?.type);
|
|
378
379
|
_send('redux-event', event);
|
|
379
380
|
});
|
|
380
381
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reactoradar",
|
|
3
3
|
"productName": "ReactoRadar",
|
|
4
|
-
"version": "1.6.
|
|
4
|
+
"version": "1.6.6",
|
|
5
5
|
"description": "macOS debugger for React Native — Console, Sources, Network, Performance, Memory, Redux, AsyncStorage, React tree. Supports RN 0.74+ with Hermes and New Architecture.",
|
|
6
6
|
"main": "main.js",
|
|
7
7
|
"bin": {
|
package/styles.css
CHANGED
|
@@ -1396,6 +1396,33 @@ mark { background: rgba(79,172,255,.2); color: var(--accent); border-radius: 2px
|
|
|
1396
1396
|
cursor: pointer;
|
|
1397
1397
|
transition: color 0.15s, border-color 0.15s;
|
|
1398
1398
|
}
|
|
1399
|
+
.version-dl-wrap { position: relative; }
|
|
1400
|
+
.version-dl-menu {
|
|
1401
|
+
position: absolute;
|
|
1402
|
+
top: 100%;
|
|
1403
|
+
right: 0;
|
|
1404
|
+
z-index: 100;
|
|
1405
|
+
min-width: 220px;
|
|
1406
|
+
background: var(--bg2);
|
|
1407
|
+
border: 1px solid var(--border);
|
|
1408
|
+
border-radius: 8px;
|
|
1409
|
+
box-shadow: 0 8px 24px rgba(0,0,0,.4);
|
|
1410
|
+
padding: 4px 0;
|
|
1411
|
+
margin-top: 4px;
|
|
1412
|
+
animation: changelogSlideIn 0.12s ease-out;
|
|
1413
|
+
}
|
|
1414
|
+
.version-dl-item {
|
|
1415
|
+
display: flex;
|
|
1416
|
+
align-items: center;
|
|
1417
|
+
gap: 10px;
|
|
1418
|
+
padding: 8px 14px;
|
|
1419
|
+
cursor: pointer;
|
|
1420
|
+
transition: background 0.12s;
|
|
1421
|
+
}
|
|
1422
|
+
.version-dl-item:hover { background: var(--bg3); }
|
|
1423
|
+
.version-dl-icon { font-size: 16px; flex-shrink: 0; }
|
|
1424
|
+
.version-dl-name { font-size: 11px; font-weight: 600; color: var(--text); }
|
|
1425
|
+
.version-dl-hint { font-size: 9px; color: var(--text-dim); margin-top: 1px; }
|
|
1399
1426
|
.version-npm-btn {
|
|
1400
1427
|
background: transparent;
|
|
1401
1428
|
color: var(--accent);
|
|
@@ -1774,7 +1801,7 @@ mark { background: rgba(79,172,255,.2); color: var(--accent); border-radius: 2px
|
|
|
1774
1801
|
|
|
1775
1802
|
/* ── Detail Panel Search ───────────────────────────────────────────────────── */
|
|
1776
1803
|
.detail-search-wrap { display: flex; align-items: center; gap: 4px; margin-left: auto; padding: 0 6px; }
|
|
1777
|
-
.detail-search-input { width: 150px; font-size: 10px; padding: 3px 6px; border: 1px solid var(--border); background: var(--
|
|
1804
|
+
.detail-search-input { width: 150px; font-size: 10px; padding: 3px 6px; border: 1px solid var(--border); background: var(--bg2); color: var(--text); border-radius: 3px; outline: none; }
|
|
1778
1805
|
.detail-search-input:focus { border-color: var(--accent); }
|
|
1779
1806
|
.detail-search-count { font-size: 9px; color: var(--text-dim); white-space: nowrap; min-width: 45px; }
|
|
1780
1807
|
.detail-search-nav { background: transparent; border: 1px solid var(--border); color: var(--text-dim); font-size: 9px; width: 18px; height: 18px; border-radius: 3px; cursor: pointer; display: flex; align-items: center; justify-content: center; }
|
|
@@ -1785,13 +1812,19 @@ mark { background: rgba(79,172,255,.2); color: var(--accent); border-radius: 2px
|
|
|
1785
1812
|
.detail-search-hl.active { background: rgba(255,213,79,.7); outline: 1px solid rgba(255,213,79,.9); }
|
|
1786
1813
|
|
|
1787
1814
|
/* ── Changelog Modal ───────────────────────────────────────────────────────── */
|
|
1788
|
-
.changelog-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,.
|
|
1789
|
-
|
|
1790
|
-
.changelog-
|
|
1815
|
+
.changelog-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,.6); backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); z-index: 9999; display: flex; align-items: center; justify-content: center; animation: changelogFadeIn 0.15s ease-out; }
|
|
1816
|
+
@keyframes changelogFadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
1817
|
+
.changelog-modal { background: var(--bg2); border: 1px solid var(--border); border-radius: 12px; width: 520px; max-width: 90vw; max-height: 70vh; display: flex; flex-direction: column; box-shadow: 0 12px 40px rgba(0,0,0,.5), 0 0 0 1px rgba(255,255,255,.05) inset; animation: changelogSlideIn 0.2s ease-out; }
|
|
1818
|
+
@keyframes changelogSlideIn { from { opacity: 0; transform: translateY(12px) scale(0.97); } to { opacity: 1; transform: translateY(0) scale(1); } }
|
|
1819
|
+
.changelog-header { display: flex; align-items: center; justify-content: space-between; padding: 14px 18px; border-bottom: 1px solid var(--border); background: var(--bg3); border-radius: 12px 12px 0 0; }
|
|
1791
1820
|
.changelog-title { font-size: 13px; font-weight: 700; color: var(--text); }
|
|
1792
|
-
.changelog-close { background: transparent; border: none; color: var(--text-dim); font-size: 18px; cursor: pointer; padding:
|
|
1793
|
-
.changelog-close:hover { color: var(--text); }
|
|
1794
|
-
.changelog-body { flex: 1; overflow-y: auto; padding:
|
|
1821
|
+
.changelog-close { background: transparent; border: none; color: var(--text-dim); font-size: 18px; cursor: pointer; padding: 2px 6px; border-radius: 4px; transition: background 0.12s, color 0.12s; }
|
|
1822
|
+
.changelog-close:hover { color: var(--text); background: var(--bg4); }
|
|
1823
|
+
.changelog-body { flex: 1; overflow-y: auto; padding: 18px; font-size: 11px; line-height: 1.7; color: var(--text-mid); }
|
|
1824
|
+
.changelog-body::-webkit-scrollbar { width: 4px; }
|
|
1825
|
+
.changelog-body::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 4px; }
|
|
1826
|
+
.changelog-link { color: var(--accent); text-decoration: none; cursor: pointer; border-bottom: 1px dotted var(--accent); transition: opacity 0.12s; }
|
|
1827
|
+
.changelog-link:hover { opacity: 0.8; }
|
|
1795
1828
|
|
|
1796
1829
|
/* ── Support Button ────────────────────────────────────────────────────────── */
|
|
1797
1830
|
.support-btn { background: linear-gradient(135deg, #ff813f, #ff5e72); color: #fff; border: none; padding: 8px 20px; border-radius: 8px; font-size: 12px; font-weight: 700; cursor: pointer; transition: all 0.15s; letter-spacing: 0.3px; }
|
package/src/main/main.js
DELETED
|
@@ -1,396 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { app, BrowserWindow, ipcMain, Menu, shell, nativeTheme } = require('electron');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const http = require('http');
|
|
6
|
-
const { WebSocketServer, WebSocket } = require('ws');
|
|
7
|
-
|
|
8
|
-
// ─── Ports ────────────────────────────────────────────────────────────────────
|
|
9
|
-
const PORTS = {
|
|
10
|
-
METRO: 8081, // Metro bundler (CDP proxy lives here)
|
|
11
|
-
REACT_DT: 8097, // react-devtools-core server port
|
|
12
|
-
REDUX_BRIDGE: 9090, // our custom Redux WS bridge
|
|
13
|
-
STORAGE_BRIDGE:9091, // AsyncStorage WS bridge
|
|
14
|
-
NETWORK_BRIDGE:9092, // Network intercept WS bridge
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
// ─── Windows ──────────────────────────────────────────────────────────────────
|
|
18
|
-
let mainWindow = null;
|
|
19
|
-
let devtoolsWindow = null; // hosts the embedded CDP DevTools frontend
|
|
20
|
-
|
|
21
|
-
// ─── State ────────────────────────────────────────────────────────────────────
|
|
22
|
-
let reduxClients = new Set();
|
|
23
|
-
let storageClients = new Set();
|
|
24
|
-
let networkClients = new Set();
|
|
25
|
-
|
|
26
|
-
// ─── App lifecycle ────────────────────────────────────────────────────────────
|
|
27
|
-
app.whenReady().then(async () => {
|
|
28
|
-
// Theme will be set by renderer via IPC once it reads localStorage
|
|
29
|
-
nativeTheme.themeSource = 'dark';
|
|
30
|
-
|
|
31
|
-
await createMainWindow();
|
|
32
|
-
startBridgeServers();
|
|
33
|
-
startReactDevToolsServer();
|
|
34
|
-
setupMetroCDPProxy();
|
|
35
|
-
setupIPC();
|
|
36
|
-
buildMenu();
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
app.on('window-all-closed', () => {
|
|
40
|
-
if (process.platform !== 'darwin') app.quit();
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
app.on('before-quit', () => {
|
|
44
|
-
// Close all WS servers gracefully
|
|
45
|
-
if (reactDTServer) {
|
|
46
|
-
reactDTServer.close();
|
|
47
|
-
reactDTClients.forEach(ws => ws.close());
|
|
48
|
-
reactDTClients.clear();
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
app.on('activate', () => {
|
|
53
|
-
if (BrowserWindow.getAllWindows().length === 0) createMainWindow();
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// ─── Main Window ──────────────────────────────────────────────────────────────
|
|
57
|
-
async function createMainWindow() {
|
|
58
|
-
mainWindow = new BrowserWindow({
|
|
59
|
-
width: 1400,
|
|
60
|
-
height: 900,
|
|
61
|
-
minWidth: 900,
|
|
62
|
-
minHeight: 600,
|
|
63
|
-
titleBarStyle: 'hiddenInset',
|
|
64
|
-
backgroundColor: '#0a0b0e',
|
|
65
|
-
vibrancy: 'under-window',
|
|
66
|
-
visualEffectState: 'active',
|
|
67
|
-
webPreferences: {
|
|
68
|
-
nodeIntegration: false,
|
|
69
|
-
contextIsolation: true,
|
|
70
|
-
preload: path.join(__dirname, 'preload.js'),
|
|
71
|
-
},
|
|
72
|
-
icon: path.join(__dirname, '../../assets/icon.png'),
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'));
|
|
76
|
-
|
|
77
|
-
// Open the JS Debugger panel (CDP DevTools) in a second window
|
|
78
|
-
mainWindow.webContents.on('did-finish-load', () => {
|
|
79
|
-
mainWindow.webContents.send('ports', PORTS);
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// ─── CDP DevTools Window (JS breakpoints, Sources, Console) ──────────────────
|
|
84
|
-
let lastKnownTargets = [];
|
|
85
|
-
|
|
86
|
-
function openCDPWindow(target) {
|
|
87
|
-
if (devtoolsWindow && !devtoolsWindow.isDestroyed()) {
|
|
88
|
-
devtoolsWindow.focus();
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Build the frontend URL from Metro's provided devtoolsFrontendUrl
|
|
93
|
-
// Metro /json/list returns: { devtoolsFrontendUrl: "/debugger-frontend/rn_fusebox.html?ws=...", ... }
|
|
94
|
-
let frontendUrl;
|
|
95
|
-
if (target.devtoolsFrontendUrl) {
|
|
96
|
-
// Metro provides the exact path — use it
|
|
97
|
-
frontendUrl = `http://localhost:${PORTS.METRO}${target.devtoolsFrontendUrl}`;
|
|
98
|
-
} else if (target.webSocketDebuggerUrl) {
|
|
99
|
-
// Fallback: construct URL manually with rn_fusebox (RN 0.76+) or rn_inspector (older)
|
|
100
|
-
const wsUrl = target.webSocketDebuggerUrl;
|
|
101
|
-
frontendUrl = `http://localhost:${PORTS.METRO}/debugger-frontend/rn_fusebox.html?ws=${encodeURIComponent(wsUrl)}&sources.hide_add_folder=true`;
|
|
102
|
-
} else {
|
|
103
|
-
console.warn('[CDP] No usable target URL');
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const titleSuffix = target.deviceName ? ` — ${target.deviceName}` : '';
|
|
108
|
-
devtoolsWindow = new BrowserWindow({
|
|
109
|
-
width: 1200,
|
|
110
|
-
height: 800,
|
|
111
|
-
titleBarStyle: 'hiddenInset',
|
|
112
|
-
backgroundColor: '#0a0b0e',
|
|
113
|
-
title: `JS Debugger${titleSuffix}`,
|
|
114
|
-
webPreferences: {
|
|
115
|
-
nodeIntegration: false,
|
|
116
|
-
contextIsolation: false,
|
|
117
|
-
},
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
console.log(`[CDP] Loading DevTools: ${frontendUrl}`);
|
|
121
|
-
devtoolsWindow.loadURL(frontendUrl);
|
|
122
|
-
|
|
123
|
-
devtoolsWindow.on('closed', () => { devtoolsWindow = null; });
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// ─── Metro CDP — fetch targets on demand (no continuous polling) ──────────────
|
|
127
|
-
// Continuous polling causes Metro's dev-middleware WebSocket to crash with
|
|
128
|
-
// "readyState 3 (CLOSED)" when connections are opened/closed rapidly.
|
|
129
|
-
// Instead, we fetch targets only when the user needs them.
|
|
130
|
-
function fetchCDPTargets(callback) {
|
|
131
|
-
http.get(`http://localhost:${PORTS.METRO}/json/list`, (res) => {
|
|
132
|
-
let data = '';
|
|
133
|
-
res.on('data', d => data += d);
|
|
134
|
-
res.on('end', () => {
|
|
135
|
-
try {
|
|
136
|
-
const targets = JSON.parse(data);
|
|
137
|
-
const rnTargets = targets.filter(t =>
|
|
138
|
-
t.type === 'node' || t.devtoolsFrontendUrl
|
|
139
|
-
);
|
|
140
|
-
lastKnownTargets = rnTargets;
|
|
141
|
-
mainWindow?.webContents.send('cdp-targets', rnTargets);
|
|
142
|
-
if (callback) callback(rnTargets);
|
|
143
|
-
} catch (_) {
|
|
144
|
-
if (callback) callback([]);
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
}).on('error', () => {
|
|
148
|
-
lastKnownTargets = [];
|
|
149
|
-
mainWindow?.webContents.send('cdp-targets', []);
|
|
150
|
-
if (callback) callback([]);
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function setupMetroCDPProxy() {
|
|
155
|
-
// Single fetch after app starts (not continuous polling)
|
|
156
|
-
setTimeout(() => fetchCDPTargets(), 3000);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// ─── React DevTools Relay Server (Component Tree + Profiler) ─────────────────
|
|
160
|
-
// React Native automatically connects to ws://localhost:8097 in dev mode.
|
|
161
|
-
// We run a simple WS relay on that port. When a standalone react-devtools
|
|
162
|
-
// window connects (via `npx react-devtools`) or when the RN app connects,
|
|
163
|
-
// we track the connection and relay messages between frontend ↔ backend.
|
|
164
|
-
let reactDTServer = null;
|
|
165
|
-
let reactDTClients = new Set();
|
|
166
|
-
|
|
167
|
-
function startReactDevToolsServer() {
|
|
168
|
-
try {
|
|
169
|
-
reactDTServer = new WebSocketServer({ port: PORTS.REACT_DT });
|
|
170
|
-
reactDTServer.on('error', (err) => {
|
|
171
|
-
console.warn(`[ReactDT] Server error: ${err.message}`);
|
|
172
|
-
if (err.code === 'EADDRINUSE') {
|
|
173
|
-
mainWindow?.webContents.send('react-dt-status', false);
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
reactDTServer.on('connection', (ws) => {
|
|
177
|
-
reactDTClients.add(ws);
|
|
178
|
-
console.log(`[ReactDT] Client connected (total: ${reactDTClients.size})`);
|
|
179
|
-
mainWindow?.webContents.send('react-dt-status', true);
|
|
180
|
-
|
|
181
|
-
// Relay messages between all connected clients (frontend ↔ backend)
|
|
182
|
-
ws.on('message', (data) => {
|
|
183
|
-
reactDTClients.forEach((client) => {
|
|
184
|
-
if (client !== ws && client.readyState === WebSocket.OPEN) {
|
|
185
|
-
client.send(data);
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
ws.on('close', () => {
|
|
191
|
-
reactDTClients.delete(ws);
|
|
192
|
-
console.log(`[ReactDT] Client disconnected (total: ${reactDTClients.size})`);
|
|
193
|
-
if (reactDTClients.size === 0) {
|
|
194
|
-
mainWindow?.webContents.send('react-dt-status', false);
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
console.log(`[ReactDT] Relay server on :${PORTS.REACT_DT}`);
|
|
199
|
-
} catch (e) {
|
|
200
|
-
console.warn('[ReactDT] Failed to start relay server:', e.message);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// ─── Bridge Servers (Redux, Storage, Network) ─────────────────────────────────
|
|
205
|
-
function startBridgeServers() {
|
|
206
|
-
// Redux Bridge
|
|
207
|
-
startBridge(PORTS.REDUX_BRIDGE, 'redux', reduxClients, (event) => {
|
|
208
|
-
mainWindow?.webContents.send('redux-event', event);
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
// AsyncStorage Bridge
|
|
212
|
-
startBridge(PORTS.STORAGE_BRIDGE, 'storage', storageClients, (event) => {
|
|
213
|
-
mainWindow?.webContents.send('storage-event', event);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
// Network + Console + Perf Bridge (port 9092 carries all types from RNDebugSDK)
|
|
217
|
-
startBridge(PORTS.NETWORK_BRIDGE, 'network', networkClients, (event) => {
|
|
218
|
-
if (event.type === 'control') return;
|
|
219
|
-
if (event.type === 'console') {
|
|
220
|
-
mainWindow?.webContents.send('console-event', event);
|
|
221
|
-
} else if (event.type === 'perf') {
|
|
222
|
-
mainWindow?.webContents.send('perf-event', event);
|
|
223
|
-
} else {
|
|
224
|
-
mainWindow?.webContents.send('network-event', event);
|
|
225
|
-
}
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function startBridge(port, name, clients, onEvent) {
|
|
230
|
-
const wss = new WebSocketServer({ port });
|
|
231
|
-
wss.on('connection', (ws) => {
|
|
232
|
-
clients.add(ws);
|
|
233
|
-
console.log(`[${name}] RN app connected`);
|
|
234
|
-
mainWindow?.webContents.send(`${name}-connected`, true);
|
|
235
|
-
|
|
236
|
-
ws.on('message', (raw) => {
|
|
237
|
-
try {
|
|
238
|
-
const event = JSON.parse(raw.toString());
|
|
239
|
-
onEvent(event);
|
|
240
|
-
} catch (_) {}
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
ws.on('close', () => {
|
|
244
|
-
clients.delete(ws);
|
|
245
|
-
if (clients.size === 0) {
|
|
246
|
-
mainWindow?.webContents.send(`${name}-connected`, false);
|
|
247
|
-
}
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
console.log(`[${name}] Bridge on :${port}`);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// ─── IPC from Renderer ────────────────────────────────────────────────────────
|
|
254
|
-
function setupIPC() {
|
|
255
|
-
ipcMain.on('open-cdp-target', (_, wsUrl) => {
|
|
256
|
-
// Always fetch fresh targets, then open
|
|
257
|
-
fetchCDPTargets((targets) => {
|
|
258
|
-
if (wsUrl && targets.length > 0) {
|
|
259
|
-
const target = targets.find(t => t.webSocketDebuggerUrl === wsUrl) || targets[0];
|
|
260
|
-
openCDPWindow(target);
|
|
261
|
-
} else if (targets.length > 0) {
|
|
262
|
-
const target = targets.find(t =>
|
|
263
|
-
t.reactNative?.capabilities?.prefersFuseboxFrontend
|
|
264
|
-
) || targets[0];
|
|
265
|
-
openCDPWindow(target);
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
ipcMain.on('open-react-devtools', () => {
|
|
271
|
-
// Open standalone react-devtools window
|
|
272
|
-
const rdtWin = new BrowserWindow({
|
|
273
|
-
width: 1100,
|
|
274
|
-
height: 700,
|
|
275
|
-
titleBarStyle: 'hiddenInset',
|
|
276
|
-
backgroundColor: '#0a0b0e',
|
|
277
|
-
title: 'React DevTools',
|
|
278
|
-
webPreferences: { nodeIntegration: false, contextIsolation: false },
|
|
279
|
-
});
|
|
280
|
-
// Metro serves the React DevTools frontend at /debugger-ui
|
|
281
|
-
rdtWin.loadURL(`http://localhost:${PORTS.METRO}/debugger-ui`);
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
ipcMain.on('clear-all', () => {
|
|
285
|
-
// Broadcast clear to all bridges
|
|
286
|
-
// (renderer handles local state clearing; no bridge action needed)
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
ipcMain.on('set-network-capture', (_, enabled) => {
|
|
290
|
-
// Broadcast to connected RN apps so they can stop/start intercepting
|
|
291
|
-
networkClients.forEach(ws => {
|
|
292
|
-
if (ws.readyState === WebSocket.OPEN) {
|
|
293
|
-
ws.send(JSON.stringify({ type: 'control', action: 'set-network-capture', enabled }));
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
ipcMain.on('set-network-throttle', (_, profile) => {
|
|
299
|
-
// Broadcast throttle config to connected RN apps
|
|
300
|
-
networkClients.forEach(ws => {
|
|
301
|
-
if (ws.readyState === WebSocket.OPEN) {
|
|
302
|
-
ws.send(JSON.stringify({ type: 'control', action: 'set-throttle', profile }));
|
|
303
|
-
}
|
|
304
|
-
});
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
ipcMain.on('set-theme', (_, theme) => {
|
|
308
|
-
nativeTheme.themeSource = theme === 'light' ? 'light' : 'dark';
|
|
309
|
-
const bg = theme === 'light' ? '#f5f6f8' : '#0a0b0e';
|
|
310
|
-
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
311
|
-
mainWindow.setBackgroundColor(bg);
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// ─── macOS App Menu ───────────────────────────────────────────────────────────
|
|
317
|
-
function buildMenu() {
|
|
318
|
-
const template = [
|
|
319
|
-
{
|
|
320
|
-
label: app.name,
|
|
321
|
-
submenu: [
|
|
322
|
-
{ role: 'about' },
|
|
323
|
-
{ type: 'separator' },
|
|
324
|
-
{ role: 'hide' }, { role: 'hideOthers' }, { role: 'unhide' },
|
|
325
|
-
{ type: 'separator' },
|
|
326
|
-
{ role: 'quit' },
|
|
327
|
-
],
|
|
328
|
-
},
|
|
329
|
-
{
|
|
330
|
-
label: 'Debugger',
|
|
331
|
-
submenu: [
|
|
332
|
-
{
|
|
333
|
-
label: 'Open JS Debugger (CDP)',
|
|
334
|
-
accelerator: 'Cmd+D',
|
|
335
|
-
click: () => { mainWindow?.webContents.send('trigger-open-cdp'); },
|
|
336
|
-
},
|
|
337
|
-
{
|
|
338
|
-
label: 'Open React DevTools',
|
|
339
|
-
accelerator: 'Cmd+R',
|
|
340
|
-
click: () => { ipcMain.emit('open-react-devtools'); },
|
|
341
|
-
},
|
|
342
|
-
{ type: 'separator' },
|
|
343
|
-
{
|
|
344
|
-
label: 'Clear All',
|
|
345
|
-
accelerator: 'Cmd+K',
|
|
346
|
-
click: () => { mainWindow?.webContents.send('clear-all-ui'); },
|
|
347
|
-
},
|
|
348
|
-
{ type: 'separator' },
|
|
349
|
-
{
|
|
350
|
-
label: 'Toggle Dark/Light Mode',
|
|
351
|
-
accelerator: 'Cmd+Shift+T',
|
|
352
|
-
click: () => {
|
|
353
|
-
const next = nativeTheme.themeSource === 'dark' ? 'light' : 'dark';
|
|
354
|
-
nativeTheme.themeSource = next;
|
|
355
|
-
const bg = next === 'light' ? '#f5f6f8' : '#0a0b0e';
|
|
356
|
-
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
357
|
-
mainWindow.setBackgroundColor(bg);
|
|
358
|
-
mainWindow.webContents.send('theme-changed', next);
|
|
359
|
-
}
|
|
360
|
-
},
|
|
361
|
-
},
|
|
362
|
-
],
|
|
363
|
-
},
|
|
364
|
-
{
|
|
365
|
-
label: 'Edit',
|
|
366
|
-
submenu: [
|
|
367
|
-
{ role: 'undo' },
|
|
368
|
-
{ role: 'redo' },
|
|
369
|
-
{ type: 'separator' },
|
|
370
|
-
{ role: 'cut' },
|
|
371
|
-
{ role: 'copy' },
|
|
372
|
-
{ role: 'paste' },
|
|
373
|
-
{ role: 'selectAll' },
|
|
374
|
-
],
|
|
375
|
-
},
|
|
376
|
-
{
|
|
377
|
-
label: 'View',
|
|
378
|
-
submenu: [
|
|
379
|
-
{ role: 'reload' }, { role: 'forceReload' },
|
|
380
|
-
{ type: 'separator' },
|
|
381
|
-
{ role: 'resetZoom' }, { role: 'zoomIn' }, { role: 'zoomOut' },
|
|
382
|
-
{ type: 'separator' },
|
|
383
|
-
{ role: 'togglefullscreen' },
|
|
384
|
-
],
|
|
385
|
-
},
|
|
386
|
-
{
|
|
387
|
-
label: 'Window',
|
|
388
|
-
submenu: [
|
|
389
|
-
{ role: 'minimize' }, { role: 'zoom' },
|
|
390
|
-
{ type: 'separator' },
|
|
391
|
-
{ role: 'front' },
|
|
392
|
-
],
|
|
393
|
-
},
|
|
394
|
-
];
|
|
395
|
-
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
|
|
396
|
-
}
|
package/src/main/preload.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { contextBridge, ipcRenderer } = require('electron');
|
|
4
|
-
|
|
5
|
-
const registeredChannels = new Set();
|
|
6
|
-
|
|
7
|
-
contextBridge.exposeInMainWorld('electronAPI', {
|
|
8
|
-
// Listen from main (idempotent — only one listener per channel)
|
|
9
|
-
on: (channel, cb) => {
|
|
10
|
-
const allowed = [
|
|
11
|
-
'ports', 'cdp-targets', 'redux-event', 'storage-event', 'network-event',
|
|
12
|
-
'console-event', 'perf-event', 'redux-connected', 'storage-connected', 'network-connected',
|
|
13
|
-
'react-dt-status', 'trigger-open-cdp', 'clear-all-ui', 'theme-changed',
|
|
14
|
-
];
|
|
15
|
-
if (allowed.includes(channel)) {
|
|
16
|
-
ipcRenderer.removeAllListeners(channel);
|
|
17
|
-
registeredChannels.add(channel);
|
|
18
|
-
ipcRenderer.on(channel, (_, ...args) => cb(...args));
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
// Send to main
|
|
22
|
-
openCDPTarget: (wsUrl) => ipcRenderer.send('open-cdp-target', wsUrl),
|
|
23
|
-
openReactDevTools: () => ipcRenderer.send('open-react-devtools'),
|
|
24
|
-
clearAll: () => ipcRenderer.send('clear-all'),
|
|
25
|
-
setTheme: (theme) => ipcRenderer.send('set-theme', theme),
|
|
26
|
-
setNetworkCapture: (enabled) => ipcRenderer.send('set-network-capture', enabled),
|
|
27
|
-
setNetworkThrottle: (profile) => ipcRenderer.send('set-network-throttle', profile),
|
|
28
|
-
});
|