reactoradar 1.5.3 → 1.5.4
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 +83 -26
- package/main.js +25 -4
- package/package.json +1 -1
- package/styles.css +11 -6
package/app.js
CHANGED
|
@@ -209,7 +209,7 @@ if (window.electronAPI) {
|
|
|
209
209
|
const btn = $('btnCDP');
|
|
210
210
|
if (btn) {
|
|
211
211
|
const hasCDP = targets?.length > 0;
|
|
212
|
-
const port = getStoredMetroPort();
|
|
212
|
+
const port = state.ports?.METRO || getStoredMetroPort();
|
|
213
213
|
btn.textContent = hasCDP
|
|
214
214
|
? `JS Debugger (:${port}) [${targets.length}] ↗`
|
|
215
215
|
: `JS Debugger (:${port}) ↗`;
|
|
@@ -268,21 +268,7 @@ if (window.electronAPI) {
|
|
|
268
268
|
window.electronAPI.on('update-available', ({ current, latest }) => {
|
|
269
269
|
// Show in settings only, not as a banner
|
|
270
270
|
state._updateAvailable = { current, latest };
|
|
271
|
-
|
|
272
|
-
if (el) el.innerHTML = `v${current} <span style="color:var(--green);font-size:10px;margin-left:6px">v${latest} available</span>`;
|
|
273
|
-
// Add update button in settings if not already there
|
|
274
|
-
if (!$('updateBtn')) {
|
|
275
|
-
const aboutEl = document.querySelector('.settings-about');
|
|
276
|
-
if (aboutEl) {
|
|
277
|
-
const btn = document.createElement('div');
|
|
278
|
-
btn.style.cssText = 'margin-top:10px';
|
|
279
|
-
btn.innerHTML = '<button id="updateBtn" class="tb-btn primary" style="font-size:11px">Download v' + latest + '</button>';
|
|
280
|
-
aboutEl.appendChild(btn);
|
|
281
|
-
$('updateBtn')?.addEventListener('click', () => {
|
|
282
|
-
window.electronAPI?.openExternal('https://github.com/sharanagouda/react-native-debugger/releases');
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
}
|
|
271
|
+
_applyUpdateBanner();
|
|
286
272
|
});
|
|
287
273
|
|
|
288
274
|
window.electronAPI.on('trigger-open-cdp', () => {
|
|
@@ -299,6 +285,31 @@ if (window.electronAPI) {
|
|
|
299
285
|
}
|
|
300
286
|
|
|
301
287
|
// ─── Device Connection Status (inline in titlebar) ───────────────────────────
|
|
288
|
+
// Reusable — called from IPC handler AND from initSettingsPanel
|
|
289
|
+
function _applyUpdateBanner() {
|
|
290
|
+
const info = state._updateAvailable;
|
|
291
|
+
if (!info) return;
|
|
292
|
+
const { current, latest } = info;
|
|
293
|
+
const el = $('aboutVersion');
|
|
294
|
+
if (el && !el.dataset.updateApplied) {
|
|
295
|
+
el.innerHTML = `v${current} <span style="color:var(--green);font-size:10px;margin-left:6px">v${latest} available</span>`;
|
|
296
|
+
el.dataset.updateApplied = '1';
|
|
297
|
+
}
|
|
298
|
+
// Add update button in settings if not already there
|
|
299
|
+
if (!$('updateBtn')) {
|
|
300
|
+
const aboutEl = document.querySelector('.settings-about');
|
|
301
|
+
if (aboutEl) {
|
|
302
|
+
const btn = document.createElement('div');
|
|
303
|
+
btn.style.cssText = 'margin-top:10px';
|
|
304
|
+
btn.innerHTML = '<button id="updateBtn" class="tb-btn primary" style="font-size:11px;padding:6px 16px">Download v' + latest + '</button>';
|
|
305
|
+
aboutEl.appendChild(btn);
|
|
306
|
+
$('updateBtn')?.addEventListener('click', () => {
|
|
307
|
+
window.electronAPI?.openExternal('https://github.com/sharanagouda/react-native-debugger/releases');
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
302
313
|
function updateDeviceBanner(service, connected) {
|
|
303
314
|
state.connections[service] = connected;
|
|
304
315
|
const el = $('deviceStatus');
|
|
@@ -1273,9 +1284,13 @@ function renderNetwork() {
|
|
|
1273
1284
|
rows.appendChild(frag);
|
|
1274
1285
|
}
|
|
1275
1286
|
|
|
1287
|
+
function _isHttpError(r) {
|
|
1288
|
+
return r.phase === 'error' || (r.status && r.status >= 400);
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1276
1291
|
function buildNetRow(r, wfMin, wfRange) {
|
|
1277
1292
|
const row = document.createElement('div');
|
|
1278
|
-
row.className = 'net-row' + (r.id === state.network.selectedId ? ' selected' : '') + (r
|
|
1293
|
+
row.className = 'net-row' + (r.id === state.network.selectedId ? ' selected' : '') + (_isHttpError(r) ? ' error' : '');
|
|
1279
1294
|
row.dataset.id = r.id;
|
|
1280
1295
|
|
|
1281
1296
|
const urlObj = tryURL(r.url);
|
|
@@ -1291,7 +1306,9 @@ function buildNetRow(r, wfMin, wfRange) {
|
|
|
1291
1306
|
const method = r.method || '?';
|
|
1292
1307
|
const mClass = ['GET','POST','PUT','PATCH','DELETE'].includes(method) ? `m-${method}` : 'm-other';
|
|
1293
1308
|
const fullPath = urlObj ? urlObj.pathname + urlObj.search : r.url || '';
|
|
1294
|
-
|
|
1309
|
+
const isErr = _isHttpError(r);
|
|
1310
|
+
const pathCls = isErr ? ' net-path-error' : '';
|
|
1311
|
+
nameCell.innerHTML = `<span class="method-badge ${mClass}">${method}</span> <span class="net-path${pathCls}" title="${esc(r.url)}">${esc(fullPath)}</span><span class="net-host">${esc(host)}</span>`;
|
|
1295
1312
|
row.appendChild(nameCell);
|
|
1296
1313
|
|
|
1297
1314
|
// Status
|
|
@@ -1301,7 +1318,13 @@ function buildNetRow(r, wfMin, wfRange) {
|
|
|
1301
1318
|
statusCell.style.width = NET_COLS[1].width + 'px';
|
|
1302
1319
|
let statusStr = '...', sCls = 's-pending';
|
|
1303
1320
|
if (r.phase === 'error') { statusStr = 'ERR'; sCls = 's-err'; }
|
|
1304
|
-
else if (r.status) {
|
|
1321
|
+
else if (r.status) {
|
|
1322
|
+
statusStr = String(r.status);
|
|
1323
|
+
const group = Math.floor(r.status / 100);
|
|
1324
|
+
// 1xx info, 2xx success, 3xx redirect, 4xx client error, 5xx server error
|
|
1325
|
+
if (group >= 4) sCls = 's-err';
|
|
1326
|
+
else sCls = `s-${group}`;
|
|
1327
|
+
}
|
|
1305
1328
|
statusCell.className += ` ${sCls}`;
|
|
1306
1329
|
statusCell.textContent = statusStr;
|
|
1307
1330
|
row.appendChild(statusCell);
|
|
@@ -1350,6 +1373,7 @@ function buildNetRow(r, wfMin, wfRange) {
|
|
|
1350
1373
|
const width = Math.max(2, ((r.duration || 50) / wfRange) * 100);
|
|
1351
1374
|
let barCls = 'pending';
|
|
1352
1375
|
if (r.phase === 'error') barCls = 'err';
|
|
1376
|
+
else if (r.status && r.status >= 400) barCls = 'err';
|
|
1353
1377
|
else if (r.status) barCls = `s${Math.floor(r.status/100)}`;
|
|
1354
1378
|
wfCell.innerHTML = `<div class="wf-bar ${barCls}" style="left:${left}%;width:${width}%"></div>`;
|
|
1355
1379
|
}
|
|
@@ -1416,8 +1440,12 @@ function renderNetDetailTabs(r) {
|
|
|
1416
1440
|
}
|
|
1417
1441
|
|
|
1418
1442
|
function renderNetDetailContent(r) {
|
|
1419
|
-
|
|
1443
|
+
let body = $('netDetailContent');
|
|
1420
1444
|
if (!body) return;
|
|
1445
|
+
// Clone-replace to remove all stale event listeners (prevents contextmenu leak)
|
|
1446
|
+
const fresh = body.cloneNode(false);
|
|
1447
|
+
body.parentNode.replaceChild(fresh, body);
|
|
1448
|
+
body = fresh;
|
|
1421
1449
|
const tab = r._tab || 'headers';
|
|
1422
1450
|
|
|
1423
1451
|
if (tab === 'headers') {
|
|
@@ -1436,7 +1464,7 @@ function renderNetDetailContent(r) {
|
|
|
1436
1464
|
<div class="kv-grid">
|
|
1437
1465
|
<span class="kv-key">Request URL</span><span class="kv-val">${esc(r.url)}</span>
|
|
1438
1466
|
<span class="kv-key">Method</span><span class="kv-val">${esc(r.method)}</span>
|
|
1439
|
-
<span class="kv-key">Status</span><span class="kv-val ${r.status ? 's-' + Math.floor(r.status/100) : 's-pending'}">${r.status || 'Pending'} ${r.statusText || ''}</span>
|
|
1467
|
+
<span class="kv-key">Status</span><span class="kv-val ${r.phase === 'error' ? 's-err' : r.status ? (r.status >= 400 ? 's-err' : 's-' + Math.floor(r.status/100)) : 's-pending'}">${r.phase === 'error' ? (r.status || 'ERR') : (r.status || 'Pending')} ${r.statusText || (r.phase === 'error' ? r.error || 'Network Error' : '')}</span>
|
|
1440
1468
|
</div>
|
|
1441
1469
|
${renderH('Response Headers', rsH)}
|
|
1442
1470
|
${renderH('Request Headers', rqH)}`;
|
|
@@ -1460,16 +1488,27 @@ function renderNetDetailContent(r) {
|
|
|
1460
1488
|
}
|
|
1461
1489
|
}
|
|
1462
1490
|
} else if (tab === 'preview') {
|
|
1463
|
-
|
|
1491
|
+
const isErrStatus = _isHttpError(r);
|
|
1492
|
+
if (r.phase === 'error' && !r.responseBody) { body.innerHTML = `<span style="color:var(--red)">${esc(r.error || 'Request failed')}</span>`; return; }
|
|
1464
1493
|
if (!r.responseBody && r.phase !== 'response') { body.innerHTML = '<span style="color:var(--text-dim)">Pending...</span>'; return; }
|
|
1465
1494
|
// Render as collapsible JSON tree with right-click copy
|
|
1466
1495
|
const val = r.responseBody;
|
|
1467
1496
|
let treeData = val;
|
|
1468
1497
|
if (typeof val === 'string') {
|
|
1469
|
-
try { treeData = JSON.parse(val); } catch {
|
|
1498
|
+
try { treeData = JSON.parse(val); } catch {
|
|
1499
|
+
body.innerHTML = `<span style="color:${isErrStatus ? 'var(--red)' : 'inherit'}">${esc(val)}</span>`;
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1470
1502
|
}
|
|
1471
1503
|
if (treeData && typeof treeData === 'object') {
|
|
1472
1504
|
body.innerHTML = '';
|
|
1505
|
+
// Show error status banner above the response body
|
|
1506
|
+
if (isErrStatus) {
|
|
1507
|
+
const errBanner = document.createElement('div');
|
|
1508
|
+
errBanner.style.cssText = 'color:var(--red);font-weight:600;padding:4px 0 8px;font-size:11px;border-bottom:1px solid rgba(255,94,114,.15);margin-bottom:8px';
|
|
1509
|
+
errBanner.textContent = `${r.status || 'ERR'} ${r.statusText || r.error || 'Error'}`;
|
|
1510
|
+
body.appendChild(errBanner);
|
|
1511
|
+
}
|
|
1473
1512
|
body.appendChild(createTreeNode(null, treeData, false));
|
|
1474
1513
|
// Right-click on preview to copy the whole object or clicked node value
|
|
1475
1514
|
body.addEventListener('contextmenu', (e) => {
|
|
@@ -1477,12 +1516,27 @@ function renderNetDetailContent(r) {
|
|
|
1477
1516
|
showPreviewCopyMenu(e, treeData);
|
|
1478
1517
|
});
|
|
1479
1518
|
} else {
|
|
1480
|
-
body.innerHTML =
|
|
1519
|
+
body.innerHTML = isErrStatus
|
|
1520
|
+
? `<span style="color:var(--red)">${esc(String(r.responseBody))}</span>`
|
|
1521
|
+
: '<span style="color:var(--text-dim)">No preview available</span>';
|
|
1481
1522
|
}
|
|
1482
1523
|
} else if (tab === 'response') {
|
|
1483
|
-
|
|
1524
|
+
const isErrStatus = _isHttpError(r);
|
|
1525
|
+
if (r.phase === 'error' && !r.responseBody) { body.innerHTML = `<span style="color:var(--red)">${esc(r.error || 'Request failed')}</span>`; return; }
|
|
1484
1526
|
if (!r.responseBody && r.phase !== 'response') { body.innerHTML = '<span style="color:var(--text-dim)">Pending...</span>'; return; }
|
|
1485
|
-
|
|
1527
|
+
if (isErrStatus) {
|
|
1528
|
+
const errBanner = document.createElement('div');
|
|
1529
|
+
errBanner.style.cssText = 'color:var(--red);font-weight:600;padding:4px 0 8px;font-size:11px;border-bottom:1px solid rgba(255,94,114,.15);margin-bottom:8px';
|
|
1530
|
+
errBanner.textContent = `${r.status || 'ERR'} ${r.statusText || r.error || 'Error'}`;
|
|
1531
|
+
body.innerHTML = '';
|
|
1532
|
+
body.appendChild(errBanner);
|
|
1533
|
+
const raw = document.createElement('div');
|
|
1534
|
+
raw.style.color = 'var(--red)';
|
|
1535
|
+
raw.innerHTML = renderJSON(r.responseBody);
|
|
1536
|
+
body.appendChild(raw);
|
|
1537
|
+
} else {
|
|
1538
|
+
body.innerHTML = renderJSON(r.responseBody);
|
|
1539
|
+
}
|
|
1486
1540
|
}
|
|
1487
1541
|
}
|
|
1488
1542
|
|
|
@@ -2433,6 +2487,9 @@ function initSettingsPanel() {
|
|
|
2433
2487
|
setStoredFontSize(size);
|
|
2434
2488
|
applyFontSize(size);
|
|
2435
2489
|
});
|
|
2490
|
+
|
|
2491
|
+
// Apply update banner if update info arrived before settings panel was created
|
|
2492
|
+
_applyUpdateBanner();
|
|
2436
2493
|
}
|
|
2437
2494
|
|
|
2438
2495
|
// Apply saved theme + font size + app name on load
|
package/main.js
CHANGED
|
@@ -107,6 +107,18 @@ async function createMainWindow() {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
// ─── Update Checker ──────────────────────────────────────────────────────────
|
|
110
|
+
function _semverCompare(a, b) {
|
|
111
|
+
// Returns 1 if a > b, -1 if a < b, 0 if equal
|
|
112
|
+
const pa = (a || '').split('.').map(Number);
|
|
113
|
+
const pb = (b || '').split('.').map(Number);
|
|
114
|
+
for (let i = 0; i < 3; i++) {
|
|
115
|
+
const va = pa[i] || 0, vb = pb[i] || 0;
|
|
116
|
+
if (va > vb) return 1;
|
|
117
|
+
if (va < vb) return -1;
|
|
118
|
+
}
|
|
119
|
+
return 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
110
122
|
function checkForUpdates() {
|
|
111
123
|
const currentVersion = require('./package.json').version;
|
|
112
124
|
https.get('https://registry.npmjs.org/reactoradar/latest', (res) => {
|
|
@@ -115,9 +127,16 @@ function checkForUpdates() {
|
|
|
115
127
|
res.on('end', () => {
|
|
116
128
|
try {
|
|
117
129
|
const latest = JSON.parse(data).version;
|
|
118
|
-
if (latest && latest
|
|
119
|
-
//
|
|
120
|
-
|
|
130
|
+
if (latest && _semverCompare(latest, currentVersion) > 0) {
|
|
131
|
+
// Send with retries to ensure renderer catches it after did-finish-load
|
|
132
|
+
const payload = { current: currentVersion, latest };
|
|
133
|
+
[500, 2000, 5000].forEach(delay => {
|
|
134
|
+
setTimeout(() => {
|
|
135
|
+
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
136
|
+
mainWindow.webContents.send('update-available', payload);
|
|
137
|
+
}
|
|
138
|
+
}, delay);
|
|
139
|
+
});
|
|
121
140
|
console.log(`[Update] New version available: ${latest} (current: ${currentVersion})`);
|
|
122
141
|
}
|
|
123
142
|
} catch {}
|
|
@@ -336,7 +355,9 @@ function setupIPC() {
|
|
|
336
355
|
// clear-all is handled by renderer via clear-all-ui IPC from menu
|
|
337
356
|
|
|
338
357
|
ipcMain.on('set-metro-port', (_, port) => {
|
|
339
|
-
|
|
358
|
+
const p = parseInt(port);
|
|
359
|
+
if (isNaN(p) || p < 1024 || p > 65535) return;
|
|
360
|
+
PORTS.METRO = p;
|
|
340
361
|
fetchCDPTargets();
|
|
341
362
|
mainWindow?.webContents.send('ports', PORTS);
|
|
342
363
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reactoradar",
|
|
3
3
|
"productName": "ReactoRadar",
|
|
4
|
-
"version": "1.5.
|
|
4
|
+
"version": "1.5.4",
|
|
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
|
@@ -741,7 +741,11 @@ body {
|
|
|
741
741
|
}
|
|
742
742
|
.net-row:hover { background: var(--bg3); }
|
|
743
743
|
.net-row.selected { background: var(--bg4); }
|
|
744
|
-
.net-row.error { background: rgba(255,94,114,.
|
|
744
|
+
.net-row.error { background: rgba(255,94,114,.06); border-bottom-color: rgba(255,94,114,.10); }
|
|
745
|
+
.net-row.error:hover { background: rgba(255,94,114,.10); }
|
|
746
|
+
.net-row.error .net-path { color: var(--red); }
|
|
747
|
+
.net-row.error .net-host { color: rgba(255,94,114,.6); }
|
|
748
|
+
.net-path-error { color: var(--red) !important; }
|
|
745
749
|
.net-cell {
|
|
746
750
|
padding: 6px 8px;
|
|
747
751
|
overflow: hidden;
|
|
@@ -757,12 +761,13 @@ body {
|
|
|
757
761
|
.net-cell-name .method-badge { flex-shrink: 0; vertical-align: middle; }
|
|
758
762
|
|
|
759
763
|
.net-status { font-weight: 700; }
|
|
760
|
-
.s-
|
|
761
|
-
.s-
|
|
762
|
-
.s-
|
|
763
|
-
.s-
|
|
764
|
+
.s-1 { color: var(--text-mid); } /* 1xx informational */
|
|
765
|
+
.s-2 { color: var(--green); } /* 2xx success */
|
|
766
|
+
.s-3 { color: var(--yellow); } /* 3xx redirect */
|
|
767
|
+
.s-4 { color: var(--red); } /* 4xx client error */
|
|
768
|
+
.s-5 { color: var(--red); } /* 5xx server error */
|
|
764
769
|
.s-pending { color: var(--text-dim); }
|
|
765
|
-
.s-err { color: var(--red); }
|
|
770
|
+
.s-err { color: var(--red); font-weight: 700; }
|
|
766
771
|
|
|
767
772
|
.net-type { color: var(--text-dim); font-size: 10px; }
|
|
768
773
|
.net-initiator { color: var(--text-dim); font-size: 10px; }
|