reactoradar 1.6.7 → 1.6.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.
- package/app.js +18 -19
- package/init.js +19 -7
- package/package.json +1 -1
- package/panels/console.js +7 -6
- package/panels/ga4.js +4 -1
- package/panels/native.js +4 -0
- package/panels/network.js +5 -1
- package/panels/performance.js +9 -2
- package/panels/react.js +2 -0
- package/panels/redux.js +4 -1
- package/panels/settings.js +15 -56
- package/panels/sources.js +7 -0
- package/panels/storage.js +7 -1
- package/sdk/RNDebugSDK.js +100 -38
package/app.js
CHANGED
|
@@ -136,6 +136,8 @@ document.addEventListener('keydown', (e) => {
|
|
|
136
136
|
}
|
|
137
137
|
});
|
|
138
138
|
|
|
139
|
+
// Global filter removed — each panel has its own search input
|
|
140
|
+
|
|
139
141
|
// ─── Clear (each panel has its own clear button now) ─────────────────────────
|
|
140
142
|
|
|
141
143
|
function clearActiveTab() {
|
|
@@ -236,28 +238,28 @@ function clearAll() {
|
|
|
236
238
|
if (ga4Detail) ga4Detail.innerHTML = '';
|
|
237
239
|
// Native logs
|
|
238
240
|
_nativeState.logs = [];
|
|
239
|
-
const
|
|
240
|
-
if (
|
|
241
|
+
const nativeList = $('nativeLogList');
|
|
242
|
+
if (nativeList) nativeList.innerHTML = '';
|
|
241
243
|
// Performance
|
|
242
244
|
perfState.fps = [];
|
|
243
245
|
perfState.jsThread = [];
|
|
244
246
|
perfState.uiThread = [];
|
|
245
247
|
perfState.data = [];
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
const
|
|
248
|
+
const perfFPS = $('perfFPS'); if (perfFPS) perfFPS.textContent = '—';
|
|
249
|
+
const perfJS = $('perfJS'); if (perfJS) perfJS.textContent = '—';
|
|
250
|
+
const perfUI = $('perfUI'); if (perfUI) perfUI.textContent = '—';
|
|
249
251
|
clearPerfCanvas('perfFPSCanvas');
|
|
250
252
|
clearPerfCanvas('perfJSCanvas');
|
|
251
253
|
clearPerfCanvas('perfUICanvas');
|
|
252
254
|
// Memory
|
|
253
|
-
const
|
|
254
|
-
const
|
|
255
|
-
const
|
|
255
|
+
const memHU = $('memHeapUsed'); if (memHU) memHU.textContent = '—';
|
|
256
|
+
const memHT = $('memHeapTotal'); if (memHT) memHT.textContent = '—';
|
|
257
|
+
const memN = $('memNative'); if (memN) memN.textContent = '—';
|
|
256
258
|
// Badges
|
|
257
|
-
$('cBadge').textContent = '0';
|
|
258
|
-
$('nBadge').textContent = '0';
|
|
259
|
-
$('rBadge').textContent = '0';
|
|
260
|
-
$('sBadge').textContent = '0';
|
|
259
|
+
const cB = $('cBadge'); if (cB) cB.textContent = '0';
|
|
260
|
+
const nB = $('nBadge'); if (nB) nB.textContent = '0';
|
|
261
|
+
const rB = $('rBadge'); if (rB) rB.textContent = '0';
|
|
262
|
+
const sB = $('sBadge'); if (sB) sB.textContent = '0';
|
|
261
263
|
if ($('ga4Badge')) $('ga4Badge').textContent = '0';
|
|
262
264
|
if ($('nativeBadge')) $('nativeBadge').textContent = '0';
|
|
263
265
|
// Re-render all
|
|
@@ -281,13 +283,8 @@ function freeMemory() {
|
|
|
281
283
|
if (state.console.logs.length > 200) {
|
|
282
284
|
state.console.logs = state.console.logs.slice(-200);
|
|
283
285
|
}
|
|
284
|
-
//
|
|
285
|
-
|
|
286
|
-
state.redux.actions = state.redux.actions.slice(-50);
|
|
287
|
-
state.redux.states = state.redux.states.slice(-50);
|
|
288
|
-
state.redux.actions.forEach((a, i) => a.index = i);
|
|
289
|
-
state.redux.selected = -1;
|
|
290
|
-
}
|
|
286
|
+
// Drop full Redux state snapshots (keep action metadata)
|
|
287
|
+
state.redux.states = [];
|
|
291
288
|
// Drop storage values (keep keys for reference)
|
|
292
289
|
for (const k in state.storage.entries) {
|
|
293
290
|
state.storage.entries[k] = null;
|
|
@@ -327,11 +324,13 @@ function updateDeviceBanner(service, connected) {
|
|
|
327
324
|
}
|
|
328
325
|
}
|
|
329
326
|
|
|
327
|
+
|
|
330
328
|
function takeScreenshot() {
|
|
331
329
|
const btn = $('btnScreenshot');
|
|
332
330
|
if (!btn) return;
|
|
333
331
|
const origText = btn.innerHTML;
|
|
334
332
|
btn.innerHTML = '<span style="opacity:0.6">Saving...</span>';
|
|
333
|
+
// Use Electron's native capturePage — always works, no DOM rendering issues
|
|
335
334
|
window.electronAPI?.captureScreenshot();
|
|
336
335
|
btn.innerHTML = '<span style="color:var(--green)">Saved!</span>';
|
|
337
336
|
setTimeout(() => { btn.innerHTML = origText; }, 2000);
|
package/init.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
// ─── CDP Button ───────────────────────────────────────────────────────────────
|
|
7
7
|
$('btnCDP')?.addEventListener('click', () => {
|
|
8
|
-
window
|
|
8
|
+
// Tell main process to open the CDP DevTools window with the best available target
|
|
9
|
+
window.electronAPI?.openCDPTarget(null); // null = use latest known target
|
|
9
10
|
});
|
|
10
11
|
|
|
11
12
|
// ─── Screenshot Button ────────────────────────────────────────────────────────
|
|
@@ -37,7 +38,6 @@ if (window.electronAPI) {
|
|
|
37
38
|
window.electronAPI.on('network-event', handleNetworkEvent);
|
|
38
39
|
window.electronAPI.on('storage-event', handleStorageEvent);
|
|
39
40
|
|
|
40
|
-
window.electronAPI.on('console-event', addConsoleLog);
|
|
41
41
|
window.electronAPI.on('ga4-event', handleGA4Event);
|
|
42
42
|
|
|
43
43
|
window.electronAPI.on('perf-event', event => {
|
|
@@ -66,6 +66,7 @@ if (window.electronAPI) {
|
|
|
66
66
|
|
|
67
67
|
// Cmd+F — focus the search input for the active panel
|
|
68
68
|
function _handleFind() {
|
|
69
|
+
// If network detail is open, focus the detail search
|
|
69
70
|
if (state.activePanel === 'network' && state.network.selectedId) {
|
|
70
71
|
const wrap = $('detailSearchWrap');
|
|
71
72
|
const input = $('detailSearchInput');
|
|
@@ -88,12 +89,14 @@ if (window.electronAPI) {
|
|
|
88
89
|
const el = $(inputId);
|
|
89
90
|
if (el) { el.focus(); el.select(); }
|
|
90
91
|
}
|
|
92
|
+
// Also show/focus Console bottom find bar
|
|
91
93
|
if (state.activePanel === 'console') {
|
|
92
94
|
const bar = $('consoleFindBar');
|
|
93
95
|
if (bar) { bar.style.display = 'flex'; $('consoleFindInput')?.focus(); }
|
|
94
96
|
}
|
|
95
97
|
}
|
|
96
98
|
window.electronAPI.on('focus-search', _handleFind);
|
|
99
|
+
// Direct keyboard fallback — Electron menu accelerators can miss in some contexts
|
|
97
100
|
document.addEventListener('keydown', (e) => {
|
|
98
101
|
if ((e.metaKey || e.ctrlKey) && e.key === 'f') {
|
|
99
102
|
e.preventDefault();
|
|
@@ -104,6 +107,7 @@ if (window.electronAPI) {
|
|
|
104
107
|
window.electronAPI.on('app-version', (version, isPackaged) => {
|
|
105
108
|
state._appVersion = version;
|
|
106
109
|
state._isPackaged = !!isPackaged;
|
|
110
|
+
// Update anywhere the version is displayed
|
|
107
111
|
document.querySelectorAll('#aboutVersion').forEach(el => el.textContent = 'v' + version);
|
|
108
112
|
});
|
|
109
113
|
|
|
@@ -121,21 +125,27 @@ if (window.electronAPI) {
|
|
|
121
125
|
window.electronAPI?.openCDPTarget(null);
|
|
122
126
|
});
|
|
123
127
|
|
|
128
|
+
// Theme toggle from menu shortcut (Cmd+Shift+T)
|
|
124
129
|
window.electronAPI.on('theme-changed', theme => {
|
|
125
130
|
document.documentElement.setAttribute('data-theme', theme);
|
|
126
131
|
setStoredTheme(theme);
|
|
127
132
|
document.querySelectorAll('#themeSwitcher .theme-card')
|
|
128
133
|
.forEach(b => b.classList.toggle('active', b.dataset.theme === theme));
|
|
129
134
|
});
|
|
135
|
+
|
|
136
|
+
// Console event IPC
|
|
137
|
+
window.electronAPI.on('console-event', addConsoleLog);
|
|
130
138
|
}
|
|
131
139
|
|
|
132
140
|
// ─── Memory Monitor ──────────────────────────────────────────────────────────
|
|
141
|
+
// Check memory usage periodically and warn user before it causes blank screen
|
|
133
142
|
let _memoryWarningShown = false;
|
|
134
143
|
setInterval(() => {
|
|
135
144
|
if (!window.performance || !performance.memory) return;
|
|
136
145
|
const used = performance.memory.usedJSHeapSize;
|
|
137
146
|
const limit = performance.memory.jsHeapSizeLimit;
|
|
138
147
|
const pct = used / limit;
|
|
148
|
+
// Warn at 70% usage
|
|
139
149
|
if (pct > 0.7 && !_memoryWarningShown) {
|
|
140
150
|
_memoryWarningShown = true;
|
|
141
151
|
const banner = document.createElement('div');
|
|
@@ -147,6 +157,7 @@ setInterval(() => {
|
|
|
147
157
|
+ `<button class="memory-warn-btn" id="memWarnDismiss">Dismiss</button>`;
|
|
148
158
|
document.body.prepend(banner);
|
|
149
159
|
$('memWarnClear')?.addEventListener('click', () => {
|
|
160
|
+
// Clear all panel data
|
|
150
161
|
state.console.logs = []; _consolePending = [];
|
|
151
162
|
_lastLogMsg = ''; _lastLogRow = null; _lastLogCount = 1;
|
|
152
163
|
$('cBadge').textContent = '0'; renderConsole();
|
|
@@ -158,17 +169,19 @@ setInterval(() => {
|
|
|
158
169
|
});
|
|
159
170
|
$('memWarnDismiss')?.addEventListener('click', () => { banner.remove(); });
|
|
160
171
|
}
|
|
172
|
+
// Reset flag when memory drops
|
|
161
173
|
if (pct < 0.5) _memoryWarningShown = false;
|
|
162
|
-
}, 30000);
|
|
174
|
+
}, 30000); // Check every 30 seconds
|
|
175
|
+
|
|
176
|
+
// Apply saved theme + font size + font family + app name on load
|
|
163
177
|
|
|
164
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
165
|
-
// Apply saved settings on load
|
|
166
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
167
178
|
applyTheme(getStoredTheme());
|
|
168
179
|
applyFontSize(getStoredFontSize());
|
|
169
180
|
applyFontFamily(getStoredFontFamily());
|
|
170
181
|
applyAppName(getStoredAppName());
|
|
171
182
|
applyTabVisibility();
|
|
183
|
+
|
|
184
|
+
// Send stored metro port to backend
|
|
172
185
|
window.electronAPI?.setMetroPort(getStoredMetroPort());
|
|
173
186
|
|
|
174
187
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -182,6 +195,5 @@ initMemoryPanel();
|
|
|
182
195
|
initReduxPanel();
|
|
183
196
|
initStoragePanel();
|
|
184
197
|
initReactPanel();
|
|
185
|
-
initSourcesPanel();
|
|
186
198
|
initNativeLogsPanel();
|
|
187
199
|
initSettingsPanel();
|
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.8",
|
|
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/panels/console.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
// ─── Console Panel
|
|
2
|
-
|
|
1
|
+
// ─── Console Panel + Shared Renderers ─────────────────────────────────────
|
|
3
2
|
// Load saved log level filters from localStorage
|
|
4
3
|
function getStoredLogLevels() {
|
|
5
4
|
try {
|
|
@@ -14,6 +13,7 @@ function setStoredLogLevels(levels) {
|
|
|
14
13
|
|
|
15
14
|
function initConsolePanel() {
|
|
16
15
|
const panel = $('panel-console');
|
|
16
|
+
if (!panel) return;
|
|
17
17
|
const levels = getStoredLogLevels();
|
|
18
18
|
state.console.levelFilters = levels;
|
|
19
19
|
state.console.showRedux = !!levels.redux;
|
|
@@ -321,10 +321,10 @@ function flushConsoleBatch() {
|
|
|
321
321
|
}
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
-
// NOTE: console-event IPC listener is registered in init.js (not here)
|
|
325
|
-
// to keep all IPC registrations in one place and avoid duplicate listener issues.
|
|
326
324
|
|
|
327
|
-
//
|
|
325
|
+
// NOTE: console-event IPC listener is registered in init.js
|
|
326
|
+
|
|
327
|
+
// ─── Object Tree Renderer (Chrome DevTools-like) ─────────────────────────────
|
|
328
328
|
// Builds interactive, collapsible DOM nodes for objects/arrays.
|
|
329
329
|
|
|
330
330
|
// Collect all entries for an object: own data properties + prototype getter values.
|
|
@@ -709,7 +709,7 @@ function buildLogRow(l) {
|
|
|
709
709
|
return div;
|
|
710
710
|
}
|
|
711
711
|
|
|
712
|
-
// ─── Shared
|
|
712
|
+
// ─── Shared context menu helper ──────────────────────────────────────────────
|
|
713
713
|
function showContextMenu(e, items) {
|
|
714
714
|
document.querySelectorAll('.ctx-menu').forEach(el => el.remove());
|
|
715
715
|
const menu = document.createElement('div');
|
|
@@ -786,3 +786,4 @@ function renderConsole() {
|
|
|
786
786
|
list.appendChild(frag);
|
|
787
787
|
list.scrollTop = list.scrollHeight;
|
|
788
788
|
}
|
|
789
|
+
|
package/panels/ga4.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
// ─── GA4 Events Panel ──────────────────────────────────────────────────────
|
|
1
2
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
-
// GA4 EVENT INSPECTOR
|
|
3
|
+
// GA4 EVENT INSPECTOR
|
|
3
4
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
5
|
const ga4State = { events: [], selected: -1, searchFilter: '', sortDir: 'desc' };
|
|
5
6
|
|
|
6
7
|
function initGA4Panel() {
|
|
7
8
|
const panel = $('panel-ga4');
|
|
9
|
+
if (!panel) return;
|
|
8
10
|
panel.innerHTML = `
|
|
9
11
|
<div class="panel-toolbar">
|
|
10
12
|
<span class="panel-label">GA4 Events</span>
|
|
@@ -326,3 +328,4 @@ function renderGA4Summary() {
|
|
|
326
328
|
summary.appendChild(chip);
|
|
327
329
|
});
|
|
328
330
|
}
|
|
331
|
+
|
package/panels/native.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
// ─── Native Logs Panel ─────────────────────────────────────────────────────
|
|
2
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3
|
+
// NATIVE LOGS PANEL
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
5
|
const _nativeState = { logs: [], connected: false, platform: null, levelFilter: 'all', searchFilter: '' };
|
|
3
6
|
const MAX_NATIVE_LOGS = 2000;
|
|
4
7
|
|
|
@@ -254,3 +257,4 @@ function _renderNativeLogs() {
|
|
|
254
257
|
list.innerHTML = '';
|
|
255
258
|
_nativeState.logs.forEach(log => _appendNativeLog(log));
|
|
256
259
|
}
|
|
260
|
+
|
package/panels/network.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// ─── Network Panel ─────────────────────────────────────────────────────────
|
|
2
|
-
|
|
2
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3
|
+
// NETWORK PANEL (Chrome DevTools-style)
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3
5
|
const NET_COLS = [
|
|
4
6
|
{ key: 'name', label: 'Name', width: 380, min: 150 },
|
|
5
7
|
{ key: 'status', label: 'Status', width: 60, min: 40 },
|
|
@@ -12,6 +14,7 @@ const NET_COLS = [
|
|
|
12
14
|
|
|
13
15
|
function initNetworkPanel() {
|
|
14
16
|
const panel = $('panel-network');
|
|
17
|
+
if (!panel) return;
|
|
15
18
|
panel.innerHTML = `
|
|
16
19
|
<div class="panel-toolbar">
|
|
17
20
|
<span class="panel-label">Network</span>
|
|
@@ -966,3 +969,4 @@ function buildCurlCommand(r) {
|
|
|
966
969
|
}
|
|
967
970
|
return cmd;
|
|
968
971
|
}
|
|
972
|
+
|
package/panels/performance.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
// ─── Performance + Memory
|
|
1
|
+
// ─── Performance + Memory Panels ───────────────────────────────────────────
|
|
2
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3
|
+
// PERFORMANCE PANEL — FPS, render timing, JS thread
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
5
|
const perfState = { fps: [], jsThread: [], uiThread: [], recording: false, data: [] };
|
|
3
6
|
|
|
4
7
|
function initPerformancePanel() {
|
|
5
8
|
const panel = $('panel-performance');
|
|
9
|
+
if (!panel) return;
|
|
6
10
|
panel.innerHTML = `
|
|
7
11
|
<div class="panel-toolbar">
|
|
8
12
|
<span class="panel-label">Performance</span>
|
|
@@ -130,9 +134,12 @@ function handlePerfEvent(event) {
|
|
|
130
134
|
}
|
|
131
135
|
}
|
|
132
136
|
|
|
133
|
-
//
|
|
137
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
138
|
+
// MEMORY PANEL — Heap snapshot summary via Hermes CDP
|
|
139
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
134
140
|
function initMemoryPanel() {
|
|
135
141
|
const panel = $('panel-memory');
|
|
142
|
+
if (!panel) return;
|
|
136
143
|
panel.innerHTML = `
|
|
137
144
|
<div class="panel-toolbar">
|
|
138
145
|
<span class="panel-label">Memory</span>
|
package/panels/react.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// ─── React Tree Panel ──────────────────────────────────────────────────────
|
|
2
2
|
function initReactPanel() {
|
|
3
3
|
const panel = $('panel-react');
|
|
4
|
+
if (!panel) return;
|
|
4
5
|
panel.innerHTML = `
|
|
5
6
|
<div class="panel-toolbar">
|
|
6
7
|
<span class="panel-label">React Tree</span>
|
|
@@ -19,3 +20,4 @@ function initReactPanel() {
|
|
|
19
20
|
window.electronAPI?.openReactDevTools();
|
|
20
21
|
});
|
|
21
22
|
}
|
|
23
|
+
|
package/panels/redux.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
// ─── Redux Panel ───────────────────────────────────────────────────────────
|
|
1
2
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
-
// REDUX PANEL
|
|
3
|
+
// REDUX PANEL
|
|
3
4
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
5
|
function initReduxPanel() {
|
|
5
6
|
const panel = $('panel-redux');
|
|
7
|
+
if (!panel) return;
|
|
6
8
|
panel.innerHTML = `
|
|
7
9
|
<div class="panel-toolbar">
|
|
8
10
|
<span class="panel-label">Redux</span>
|
|
@@ -436,3 +438,4 @@ function renderRedux() {
|
|
|
436
438
|
selEl.scrollIntoView({ block: 'nearest', behavior: 'auto' });
|
|
437
439
|
}
|
|
438
440
|
}
|
|
441
|
+
|
package/panels/settings.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
// ─── Settings Panel
|
|
2
|
-
|
|
3
|
-
// ─── Theme helpers ───────────────────────────────────────────────────────────
|
|
1
|
+
// ─── Settings Panel + Shared Utilities ─────────────────────────────────────
|
|
4
2
|
function getStoredTheme() {
|
|
5
3
|
try { return localStorage.getItem('rn-debug-theme') || 'dark'; } catch { return 'dark'; }
|
|
6
4
|
}
|
|
@@ -303,6 +301,7 @@ function applyFontSize(size) {
|
|
|
303
301
|
|
|
304
302
|
function initSettingsPanel() {
|
|
305
303
|
const panel = $('panel-settings');
|
|
304
|
+
if (!panel) return;
|
|
306
305
|
const current = getStoredTheme();
|
|
307
306
|
const currentSize = getStoredFontSize();
|
|
308
307
|
panel.innerHTML = `
|
|
@@ -594,13 +593,12 @@ function _loadVersionHistory() {
|
|
|
594
593
|
} catch { /* skip bad date */ }
|
|
595
594
|
}
|
|
596
595
|
|
|
597
|
-
// Build action
|
|
596
|
+
// Build action button based on install type
|
|
598
597
|
let actionHtml = '';
|
|
599
598
|
if (isCurrent) {
|
|
600
599
|
actionHtml = '<span class="version-installed">Installed</span>';
|
|
601
600
|
} else if (isPackaged) {
|
|
602
|
-
|
|
603
|
-
actionHtml = '<div class="version-dl-wrap"><button class="version-install-btn" title="Download this version">Download ▾</button></div>';
|
|
601
|
+
actionHtml = '<button class="version-install-btn" title="Download .dmg for this version">Download</button>';
|
|
604
602
|
} else {
|
|
605
603
|
actionHtml = `<button class="version-npm-btn" title="Copy npm install command">npx @${esc(r.version)}</button>`;
|
|
606
604
|
}
|
|
@@ -615,57 +613,14 @@ function _loadVersionHistory() {
|
|
|
615
613
|
<button class="version-notes-btn" title="View release notes">Notes</button>
|
|
616
614
|
</div>`;
|
|
617
615
|
|
|
618
|
-
// DMG
|
|
619
|
-
const dlWrap = row.querySelector('.version-dl-wrap');
|
|
616
|
+
// DMG download button — opens the .dmg asset or release page
|
|
620
617
|
const installBtn = row.querySelector('.version-install-btn');
|
|
621
|
-
if (installBtn
|
|
622
|
-
installBtn.addEventListener('click', (
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
const menu = document.createElement('div');
|
|
628
|
-
menu.className = 'version-dl-menu';
|
|
629
|
-
|
|
630
|
-
// .dmg link
|
|
631
|
-
if (r.dmgUrl) {
|
|
632
|
-
const dmgItem = document.createElement('div');
|
|
633
|
-
dmgItem.className = 'version-dl-item';
|
|
634
|
-
dmgItem.innerHTML = `<span class="version-dl-icon">💿</span><div><div class="version-dl-name">.dmg Installer</div><div class="version-dl-hint">macOS installer (Apple Silicon)</div></div>`;
|
|
635
|
-
dmgItem.addEventListener('click', () => { window.electronAPI.openExternal(r.dmgUrl); menu.remove(); });
|
|
636
|
-
menu.appendChild(dmgItem);
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
// .zip link
|
|
640
|
-
if (r.zipUrl) {
|
|
641
|
-
const zipItem = document.createElement('div');
|
|
642
|
-
zipItem.className = 'version-dl-item';
|
|
643
|
-
zipItem.innerHTML = `<span class="version-dl-icon">📦</span><div><div class="version-dl-name">.zip Archive</div><div class="version-dl-hint">Portable zip archive</div></div>`;
|
|
644
|
-
zipItem.addEventListener('click', () => { window.electronAPI.openExternal(r.zipUrl); menu.remove(); });
|
|
645
|
-
menu.appendChild(zipItem);
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
// GitHub release page fallback
|
|
649
|
-
if (r.htmlUrl) {
|
|
650
|
-
const ghItem = document.createElement('div');
|
|
651
|
-
ghItem.className = 'version-dl-item';
|
|
652
|
-
ghItem.innerHTML = `<span class="version-dl-icon">🔗</span><div><div class="version-dl-name">GitHub Release</div><div class="version-dl-hint">View all assets on GitHub</div></div>`;
|
|
653
|
-
ghItem.addEventListener('click', () => { window.electronAPI.openExternal(r.htmlUrl); menu.remove(); });
|
|
654
|
-
menu.appendChild(ghItem);
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
// No assets fallback
|
|
658
|
-
if (!r.dmgUrl && !r.zipUrl && !r.htmlUrl) {
|
|
659
|
-
menu.innerHTML = '<div style="padding:8px 12px;color:var(--text-dim);font-size:10px">No downloads available</div>';
|
|
618
|
+
if (installBtn) {
|
|
619
|
+
installBtn.addEventListener('click', () => {
|
|
620
|
+
const url = r.dmgUrl || r.htmlUrl || '';
|
|
621
|
+
if (url) {
|
|
622
|
+
window.electronAPI.openExternal(url);
|
|
660
623
|
}
|
|
661
|
-
|
|
662
|
-
dlWrap.appendChild(menu);
|
|
663
|
-
|
|
664
|
-
// Close on outside click
|
|
665
|
-
setTimeout(() => {
|
|
666
|
-
const close = (ev) => { if (!menu.contains(ev.target) && ev.target !== installBtn) { menu.remove(); document.removeEventListener('click', close); } };
|
|
667
|
-
document.addEventListener('click', close);
|
|
668
|
-
}, 0);
|
|
669
624
|
});
|
|
670
625
|
}
|
|
671
626
|
|
|
@@ -707,7 +662,8 @@ function _loadVersionHistory() {
|
|
|
707
662
|
});
|
|
708
663
|
}
|
|
709
664
|
|
|
710
|
-
|
|
665
|
+
|
|
666
|
+
// ─── Update Banner & Changelog (shared, used by IPC handlers) ──────────────
|
|
711
667
|
// Reusable — called from IPC handler AND from initSettingsPanel
|
|
712
668
|
function _applyUpdateBanner() {
|
|
713
669
|
const info = state._updateAvailable;
|
|
@@ -830,3 +786,6 @@ async function _showChangelog(version) {
|
|
|
830
786
|
if (body) body.innerHTML = '<div style="color:var(--red);padding:20px;text-align:center">Could not fetch release notes</div>';
|
|
831
787
|
}
|
|
832
788
|
}
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
|
package/panels/sources.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
// ─── Sources Panel ─────────────────────────────────────────────────────────
|
|
2
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3
|
+
// SOURCES PANEL (placeholder — use JS Debugger button for breakpoints)
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
5
|
function initSourcesPanel() {
|
|
3
6
|
const panel = $('panel-sources');
|
|
7
|
+
if (!panel) return;
|
|
4
8
|
panel.innerHTML = `
|
|
5
9
|
<div class="panel-toolbar">
|
|
6
10
|
<span class="panel-label">Sources</span>
|
|
@@ -280,3 +284,6 @@ async function loadSourceFile(filepath) {
|
|
|
280
284
|
function updateSourcesPanel(targets) {
|
|
281
285
|
// No-op: file list is populated by fetchSourceFileList from Metro source map
|
|
282
286
|
}
|
|
287
|
+
|
|
288
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
289
|
+
// PERFORMANCE PANEL — FPS, render timing, JS thread
|
package/panels/storage.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
// ─── AsyncStorage Panel ────────────────────────────────────────────────────
|
|
1
2
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
-
// ASYNC STORAGE PANEL
|
|
3
|
+
// ASYNC STORAGE PANEL
|
|
3
4
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
5
|
function initStoragePanel() {
|
|
5
6
|
const panel = $('panel-storage');
|
|
7
|
+
if (!panel) return;
|
|
6
8
|
panel.innerHTML = `
|
|
7
9
|
<div class="panel-toolbar">
|
|
8
10
|
<span class="panel-label">AsyncStorage</span>
|
|
@@ -183,3 +185,7 @@ function formatSize(bytes) {
|
|
|
183
185
|
if (bytes < 1024) return `${bytes}b`;
|
|
184
186
|
return `${(bytes/1024).toFixed(1)}kb`;
|
|
185
187
|
}
|
|
188
|
+
|
|
189
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
190
|
+
// REACT TREE PANEL
|
|
191
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
package/sdk/RNDebugSDK.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* watchAsyncStorage(); // call once early in app
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
if (!__DEV__) {
|
|
17
|
+
if (typeof __DEV__ === 'undefined' || !__DEV__) {
|
|
18
18
|
module.exports = { reduxEnhancer: x => x, reduxMiddleware: () => next => action => next(action), watchAsyncStorage: () => {} };
|
|
19
19
|
} else {
|
|
20
20
|
|
|
@@ -80,29 +80,38 @@ function _shouldIntercept() {
|
|
|
80
80
|
|
|
81
81
|
// ─── WebSocket Factory ────────────────────────────────────────────────────────
|
|
82
82
|
function makeChannel(port, name, onMessage) {
|
|
83
|
-
let ws = null, queue = [], connected = false;
|
|
83
|
+
let ws = null, queue = [], connected = false, retryDelay = 2000;
|
|
84
84
|
|
|
85
85
|
function connect() {
|
|
86
|
+
ws = null;
|
|
87
|
+
connected = false;
|
|
86
88
|
try {
|
|
87
89
|
ws = new WebSocket(`ws://${HOST}:${port}`);
|
|
88
90
|
ws.onopen = () => {
|
|
89
91
|
connected = true;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
retryDelay = 2000;
|
|
93
|
+
const pending = queue.splice(0);
|
|
94
|
+
for (const m of pending) {
|
|
95
|
+
try { if (ws.readyState === WebSocket.OPEN) ws.send(m); else { queue.push(m); break; } }
|
|
96
|
+
catch { queue.push(m); break; }
|
|
97
|
+
}
|
|
92
98
|
};
|
|
93
99
|
ws.onmessage = (evt) => {
|
|
94
100
|
if (onMessage) {
|
|
95
101
|
try { onMessage(JSON.parse(evt.data)); } catch {}
|
|
96
102
|
}
|
|
97
103
|
};
|
|
98
|
-
ws.onclose = () => { connected = false; setTimeout(connect,
|
|
104
|
+
ws.onclose = () => { connected = false; setTimeout(connect, retryDelay); retryDelay = Math.min(retryDelay * 1.5, 30000); };
|
|
99
105
|
ws.onerror = () => {};
|
|
100
|
-
} catch { setTimeout(connect,
|
|
106
|
+
} catch { setTimeout(connect, retryDelay); retryDelay = Math.min(retryDelay * 1.5, 30000); }
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
function send(obj) {
|
|
104
|
-
|
|
105
|
-
|
|
110
|
+
let msg;
|
|
111
|
+
try {
|
|
112
|
+
msg = JSON.stringify({ ...obj, ts: Date.now() }, (_, v) => typeof v === 'bigint' ? v.toString() : v);
|
|
113
|
+
} catch { return; }
|
|
114
|
+
if (connected && ws?.readyState === WebSocket.OPEN) { try { ws.send(msg); } catch {} }
|
|
106
115
|
else { queue.push(msg); if (queue.length > 300) queue.shift(); }
|
|
107
116
|
}
|
|
108
117
|
|
|
@@ -186,21 +195,25 @@ function _extractCaller() {
|
|
|
186
195
|
}
|
|
187
196
|
|
|
188
197
|
LEVELS.forEach(level => {
|
|
189
|
-
|
|
198
|
+
const orig = console[level];
|
|
199
|
+
if (typeof orig !== 'function') return;
|
|
200
|
+
_console[level] = orig.bind(console);
|
|
190
201
|
console[level] = (...args) => {
|
|
191
|
-
_console[level](...args);
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
202
|
+
try { _console[level](...args); } catch {}
|
|
203
|
+
try {
|
|
204
|
+
// Skip interception when SDK is paused or debugger is attached
|
|
205
|
+
// This prevents double-logging and message queue deadlocks with CDP
|
|
206
|
+
if (!_shouldIntercept()) return;
|
|
207
|
+
const structuredArgs = args.map(serializeArg);
|
|
208
|
+
const message = args.map(a => {
|
|
209
|
+
if (typeof a === 'string') return a;
|
|
210
|
+
try { return JSON.stringify(a, null, 2); } catch { return String(a); }
|
|
211
|
+
}).join(' ');
|
|
212
|
+
// Stack trace capture controlled by toggle (disabled by default for performance)
|
|
213
|
+
// When enabled: captures for all levels. When disabled: skips entirely.
|
|
214
|
+
const caller = _stackTraceEnabled ? _extractCaller() : '';
|
|
215
|
+
mainCh.send({ type: 'console', level, message, args: structuredArgs, caller });
|
|
216
|
+
} catch {} // end of interception try
|
|
204
217
|
};
|
|
205
218
|
});
|
|
206
219
|
|
|
@@ -250,17 +263,31 @@ global.fetch = async (input, init = {}) => {
|
|
|
250
263
|
const t0 = Date.now();
|
|
251
264
|
try {
|
|
252
265
|
const resp = await _fetch(input, init);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
266
|
+
try {
|
|
267
|
+
const contentType = resp.headers?.get?.('content-type') || '';
|
|
268
|
+
const contentLen = parseInt(resp.headers?.get?.('content-length') || '0', 10);
|
|
269
|
+
if (contentLen > 1_000_000 || /image|video|audio|octet-stream|font/i.test(contentType)) {
|
|
270
|
+
const rHeaders = {};
|
|
271
|
+
resp.headers?.forEach?.((v, k) => { rHeaders[k] = v; });
|
|
272
|
+
mainCh.send({ type: 'network', phase: 'response', id, url: String(typeof input === 'string' ? input : input?.url || ''),
|
|
273
|
+
method, status: resp.status, statusText: resp.statusText, duration: Date.now() - t0,
|
|
274
|
+
responseHeaders: rHeaders, responseBody: `[Binary ${contentType} — ${contentLen} bytes]` });
|
|
275
|
+
} else {
|
|
276
|
+
try {
|
|
277
|
+
const clone = resp.clone();
|
|
278
|
+
clone.text().then(body => {
|
|
279
|
+
if (!_networkCaptureEnabled) return;
|
|
280
|
+
let parsed = body;
|
|
281
|
+
try { parsed = JSON.parse(body); } catch {}
|
|
282
|
+
const rHeaders = {};
|
|
283
|
+
clone.headers?.forEach?.((v, k) => { rHeaders[k] = v; });
|
|
284
|
+
mainCh.send({ type: 'network', phase: 'response', id, url, method,
|
|
285
|
+
status: resp.status, statusText: resp.statusText,
|
|
286
|
+
duration: Date.now() - t0, responseHeaders: rHeaders, responseBody: parsed });
|
|
287
|
+
}).catch(() => {});
|
|
288
|
+
} catch {} // clone failed — skip capture
|
|
289
|
+
}
|
|
290
|
+
} catch {} // header access failed
|
|
264
291
|
return resp;
|
|
265
292
|
} catch (err) {
|
|
266
293
|
mainCh.send({ type: 'network', phase: 'error', id, url, method,
|
|
@@ -292,6 +319,7 @@ global.fetch = async (input, init = {}) => {
|
|
|
292
319
|
meta.method = (method || 'GET').toUpperCase();
|
|
293
320
|
meta.url = String(url);
|
|
294
321
|
meta.t0 = Date.now();
|
|
322
|
+
meta.sent = false;
|
|
295
323
|
return _open.apply(xhr, arguments);
|
|
296
324
|
};
|
|
297
325
|
|
|
@@ -452,13 +480,33 @@ function reduxEnhancer(createStore) {
|
|
|
452
480
|
let actionCount = 0;
|
|
453
481
|
|
|
454
482
|
// Send initial state
|
|
455
|
-
|
|
483
|
+
try {
|
|
484
|
+
const initState = store.getState();
|
|
485
|
+
let safeInit;
|
|
486
|
+
try {
|
|
487
|
+
const s = JSON.stringify(initState, (_, v) => typeof v === 'bigint' ? v.toString() : v);
|
|
488
|
+
safeInit = s.length > 1_000_000 ? { __truncated: true, sizeBytes: s.length, keys: Object.keys(initState) } : JSON.parse(s);
|
|
489
|
+
} catch { safeInit = { __error: 'State not serializable' }; }
|
|
490
|
+
reduxCh.send({ type: 'redux', action: { type: '@@INIT' }, nextState: safeInit, index: actionCount++ });
|
|
491
|
+
} catch {}
|
|
456
492
|
|
|
457
493
|
const origDispatch = store.dispatch;
|
|
458
494
|
store.dispatch = (action) => {
|
|
459
495
|
const result = origDispatch(action);
|
|
460
|
-
|
|
461
|
-
|
|
496
|
+
try {
|
|
497
|
+
const nextState = store.getState();
|
|
498
|
+
const safeAction = typeof action === 'function'
|
|
499
|
+
? { type: `[Function: ${action.name || 'thunk'}]` }
|
|
500
|
+
: (action || { type: '@@UNKNOWN' });
|
|
501
|
+
let safeState;
|
|
502
|
+
try {
|
|
503
|
+
const s = JSON.stringify(nextState, (_, v) => typeof v === 'bigint' ? v.toString() : v);
|
|
504
|
+
safeState = s.length > 1_000_000
|
|
505
|
+
? { __truncated: true, sizeBytes: s.length, keys: Object.keys(nextState) }
|
|
506
|
+
: JSON.parse(s);
|
|
507
|
+
} catch { safeState = { __error: 'State not serializable' }; }
|
|
508
|
+
reduxCh.send({ type: 'redux', action: safeAction, nextState: safeState, index: actionCount++ });
|
|
509
|
+
} catch {}
|
|
462
510
|
return result;
|
|
463
511
|
};
|
|
464
512
|
return store;
|
|
@@ -467,9 +515,23 @@ function reduxEnhancer(createStore) {
|
|
|
467
515
|
|
|
468
516
|
// ─── Redux Toolkit middleware (alternative) ───────────────────────────────────
|
|
469
517
|
// If you use RTK configureStore, add this to middleware array instead:
|
|
518
|
+
let _mwActionCount = 0;
|
|
470
519
|
const reduxMiddleware = store => next => action => {
|
|
471
520
|
const result = next(action);
|
|
472
|
-
|
|
521
|
+
try {
|
|
522
|
+
const nextState = store.getState();
|
|
523
|
+
const safeAction = typeof action === 'function'
|
|
524
|
+
? { type: `[Function: ${action.name || 'thunk'}]` }
|
|
525
|
+
: (action || { type: '@@UNKNOWN' });
|
|
526
|
+
let safeState;
|
|
527
|
+
try {
|
|
528
|
+
const s = JSON.stringify(nextState, (_, v) => typeof v === 'bigint' ? v.toString() : v);
|
|
529
|
+
safeState = s.length > 1_000_000
|
|
530
|
+
? { __truncated: true, sizeBytes: s.length, keys: Object.keys(nextState) }
|
|
531
|
+
: JSON.parse(s);
|
|
532
|
+
} catch { safeState = { __error: 'State not serializable' }; }
|
|
533
|
+
reduxCh.send({ type: 'redux', action: safeAction, nextState: safeState, index: _mwActionCount++ });
|
|
534
|
+
} catch {}
|
|
473
535
|
return result;
|
|
474
536
|
};
|
|
475
537
|
|
|
@@ -510,7 +572,7 @@ function watchAsyncStorage() {
|
|
|
510
572
|
RNAsyncStorage.mergeItem = async (key, value, ...rest) => {
|
|
511
573
|
const result = await _mergeItem(key, value, ...rest);
|
|
512
574
|
// Read back merged value
|
|
513
|
-
RNAsyncStorage.getItem(key).then(v => storageCh.send({ type: 'storage', action: 'set', key, value: v }));
|
|
575
|
+
RNAsyncStorage.getItem(key).then(v => storageCh.send({ type: 'storage', action: 'set', key, value: v })).catch(() => {});
|
|
514
576
|
return result;
|
|
515
577
|
};
|
|
516
578
|
|