reactoradar 1.6.4 → 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/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
@@ -35,6 +35,7 @@ function _send(channel, ...args) {
35
35
  let reduxClients = new Set();
36
36
  let storageClients = new Set();
37
37
  let networkClients = new Set();
38
+ const _bridgeServers = []; // track bridge WSS instances for cleanup on quit
38
39
 
39
40
  // ─── Set dock icon ASAP (before app ready) ──────────────────────────────────
40
41
  const _appIcon = nativeImage.createFromPath(path.join(__dirname, 'ReactoRadar.png'));
@@ -74,15 +75,16 @@ if (gotLock) app.whenReady().then(async () => {
74
75
 
75
76
  await createMainWindow();
76
77
 
77
- // Send version to renderer — try package.json, fallback to app.getVersion()
78
+ // Send version + install type to renderer — try package.json, fallback to app.getVersion()
78
79
  let appVersion;
79
80
  try { appVersion = require('./package.json').version; } catch { appVersion = app.getVersion(); }
81
+ const isPackaged = app.isPackaged;
80
82
  // Send multiple times to ensure renderer catches it (covers race conditions)
81
83
  mainWindow.webContents.on('did-finish-load', () => {
82
84
  // Send immediately + retries
83
- _send('app-version', appVersion);
85
+ _send('app-version', appVersion, isPackaged);
84
86
  [500, 2000, 5000].forEach(delay => {
85
- setTimeout(() => _send('app-version', appVersion), delay);
87
+ setTimeout(() => _send('app-version', appVersion, isPackaged), delay);
86
88
  });
87
89
  });
88
90
 
@@ -102,12 +104,27 @@ app.on('window-all-closed', () => {
102
104
 
103
105
  app.on('before-quit', () => {
104
106
  _forceQuit = true;
107
+ // Free renderer memory before shutdown (logs are not cleared — user may still see them briefly)
108
+ _send('device-all-disconnected');
109
+ // Close CDP DevTools window if open
110
+ if (devtoolsWindow && !devtoolsWindow.isDestroyed()) {
111
+ devtoolsWindow.destroy();
112
+ devtoolsWindow = null;
113
+ }
105
114
  // Close all WS servers gracefully
106
115
  if (reactDTServer) {
107
116
  reactDTServer.close();
108
117
  reactDTClients.forEach(ws => ws.close());
109
118
  reactDTClients.clear();
110
119
  }
120
+ // Close bridge servers and disconnect all clients
121
+ _bridgeServers.forEach(wss => {
122
+ wss.clients.forEach(ws => ws.close());
123
+ wss.close();
124
+ });
125
+ reduxClients.clear();
126
+ storageClients.clear();
127
+ networkClients.clear();
111
128
  });
112
129
 
113
130
  app.on('activate', () => {
@@ -336,6 +353,10 @@ function startReactDevToolsServer() {
336
353
  });
337
354
  });
338
355
 
356
+ ws.on('error', (err) => {
357
+ console.warn(`[ReactDT] Client error:`, err.message);
358
+ });
359
+
339
360
  ws.on('close', () => {
340
361
  reactDTClients.delete(ws);
341
362
  console.log(`[ReactDT] Client disconnected (total: ${reactDTClients.size})`);
@@ -354,6 +375,7 @@ function startReactDevToolsServer() {
354
375
  function startBridgeServers() {
355
376
  // Redux Bridge
356
377
  startBridge(PORTS.REDUX_BRIDGE, 'redux', reduxClients, (event) => {
378
+ // console.log('[REDUX-DEBUG] Event from SDK:', event?.type, event?.action?.type);
357
379
  _send('redux-event', event);
358
380
  });
359
381
 
@@ -380,6 +402,7 @@ function startBridgeServers() {
380
402
  function startBridge(port, name, clients, onEvent) {
381
403
  try {
382
404
  const wss = new WebSocketServer({ port });
405
+ _bridgeServers.push(wss);
383
406
  wss.on('error', (err) => {
384
407
  if (err.code === 'EADDRINUSE') {
385
408
  console.error(`[${name}] Port ${port} is already in use — another ReactoRadar or debugger may be running.`);
@@ -404,10 +427,19 @@ function startBridge(port, name, clients, onEvent) {
404
427
  }
405
428
  });
406
429
 
430
+ ws.on('error', (err) => {
431
+ console.warn(`[${name}] Client error:`, err.message);
432
+ });
433
+
407
434
  ws.on('close', () => {
408
435
  clients.delete(ws);
409
436
  if (clients.size === 0) {
410
437
  _send(`${name}-connected`, false);
438
+ // When every bridge has zero clients, tell the renderer to clear old data
439
+ if (reduxClients.size === 0 && storageClients.size === 0 && networkClients.size === 0) {
440
+ console.log('[Bridge] All device connections closed — sending clear signal');
441
+ _send('device-all-disconnected');
442
+ }
411
443
  }
412
444
  });
413
445
  });
@@ -589,6 +621,9 @@ function setupIPC() {
589
621
  });
590
622
 
591
623
  ipcMain.handle('fetch-changelog', async (_, version) => {
624
+ if (!version || typeof version !== 'string' || !/^[\d]+\.[\d]+\.[\d]+/.test(version)) {
625
+ return 'Invalid version.';
626
+ }
592
627
  return new Promise((resolve) => {
593
628
  https.get(`https://api.github.com/repos/sharanagouda/reactoradar/releases/tags/v${version}`, {
594
629
  headers: { 'User-Agent': 'ReactoRadar', 'Accept': 'application/vnd.github.v3+json' }
@@ -603,6 +638,44 @@ function setupIPC() {
603
638
  });
604
639
  });
605
640
 
641
+ // Fetch all releases for version history / rollback
642
+ ipcMain.handle('fetch-releases', async () => {
643
+ return new Promise((resolve) => {
644
+ https.get('https://api.github.com/repos/sharanagouda/reactoradar/releases?per_page=20', {
645
+ headers: { 'User-Agent': 'ReactoRadar', 'Accept': 'application/vnd.github.v3+json' }
646
+ }, (res) => {
647
+ let data = '';
648
+ res.on('data', d => data += d);
649
+ res.on('end', () => {
650
+ try {
651
+ const releases = JSON.parse(data);
652
+ if (!Array.isArray(releases)) { resolve([]); return; }
653
+ const mapped = [];
654
+ for (const r of releases) {
655
+ if (!r || typeof r !== 'object') continue;
656
+ const tag = r.tag_name || '';
657
+ const version = tag.replace(/^v/, '');
658
+ if (!version) continue; // skip entries with no version
659
+ const assets = Array.isArray(r.assets) ? r.assets : [];
660
+ mapped.push({
661
+ version,
662
+ tag,
663
+ name: r.name || tag || version,
664
+ date: r.published_at || null,
665
+ prerelease: !!r.prerelease,
666
+ body: r.body || '',
667
+ dmgUrl: (assets.find(a => a && a.name && a.name.endsWith('.dmg')) || {}).browser_download_url || '',
668
+ zipUrl: (assets.find(a => a && a.name && a.name.endsWith('.zip')) || {}).browser_download_url || '',
669
+ htmlUrl: r.html_url || '',
670
+ });
671
+ }
672
+ resolve(mapped);
673
+ } catch { resolve([]); }
674
+ });
675
+ }).on('error', () => resolve([]));
676
+ });
677
+ });
678
+
606
679
  ipcMain.on('install-update', () => {
607
680
  if (autoUpdater) {
608
681
  autoUpdater.quitAndInstall(false, true);
@@ -654,10 +727,9 @@ function setupIPC() {
654
727
  });
655
728
 
656
729
  ipcMain.on('start-native-logs', (_, platform) => {
657
- // Kill existing process group
730
+ // Kill existing process
658
731
  if (_nativeLogProcess) {
659
- try { process.kill(-_nativeLogProcess.pid); } catch {}
660
- try { _nativeLogProcess.kill(); } catch {}
732
+ try { _nativeLogProcess.kill('SIGTERM'); } catch {}
661
733
  _nativeLogProcess = null;
662
734
  }
663
735
 
@@ -665,9 +737,9 @@ function setupIPC() {
665
737
  let cmd, args;
666
738
 
667
739
  if (platform === 'android') {
668
- // adb logcat — filter for app-relevant logs
740
+ // adb logcat — show only new logs from now (not historical buffer)
669
741
  cmd = 'adb';
670
- args = ['logcat', '-v', 'threadtime', '*:W']; // Warnings and above
742
+ args = ['logcat', '-v', 'threadtime', '-T', '1', '*:W']; // -T 1 = last 1 line then real-time
671
743
  } else if (platform === 'ios-sim') {
672
744
  // xcrun simctl for iOS Simulator — use syslog style for parseable output
673
745
  cmd = 'xcrun';
@@ -682,9 +754,10 @@ function setupIPC() {
682
754
  }
683
755
 
684
756
  try {
685
- _nativeLogProcess = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'], detached: true });
757
+ _nativeLogProcess = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'] });
686
758
 
687
759
  _send('native-status', { connected: true, platform });
760
+ console.log(`[NativeLogs] Started ${cmd} ${args.join(' ')} (pid: ${_nativeLogProcess.pid})`);
688
761
 
689
762
  let buffer = '';
690
763
  _nativeLogProcess.stdout.on('data', (chunk) => {
@@ -703,6 +776,10 @@ function setupIPC() {
703
776
  if (text) _send('native-log', { level: 'error', message: text, source: 'stderr', ts: Date.now() });
704
777
  });
705
778
 
779
+ // Guard against stream errors (broken pipe, etc.)
780
+ _nativeLogProcess.stdout.on('error', () => {});
781
+ _nativeLogProcess.stderr.on('error', () => {});
782
+
706
783
  _nativeLogProcess.on('close', (code) => {
707
784
  _nativeLogProcess = null;
708
785
  _send('native-status', { connected: false, error: code ? `Process exited with code ${code}` : 'Disconnected' });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "reactoradar",
3
3
  "productName": "ReactoRadar",
4
- "version": "1.6.4",
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/preload.js CHANGED
@@ -11,7 +11,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
11
11
  'ports', 'cdp-targets', 'redux-event', 'storage-event', 'network-event',
12
12
  'console-event', 'perf-event', 'ga4-event', 'redux-connected', 'storage-connected', 'network-connected',
13
13
  'react-dt-status', 'trigger-open-cdp', 'clear-all-ui', 'theme-changed', 'update-available', 'update-downloaded', 'app-version', 'focus-search',
14
- 'native-log', 'native-status',
14
+ 'native-log', 'native-status', 'device-all-disconnected',
15
15
  ];
16
16
  if (allowed.includes(channel)) {
17
17
  ipcRenderer.removeAllListeners(channel);
@@ -36,4 +36,5 @@ contextBridge.exposeInMainWorld('electronAPI', {
36
36
  stopNativeLogs: () => ipcRenderer.send('stop-native-logs'),
37
37
  detectNativePlatform: () => ipcRenderer.invoke('detect-native-platform'),
38
38
  fetchChangelog: (version) => ipcRenderer.invoke('fetch-changelog', version),
39
+ fetchReleases: () => ipcRenderer.invoke('fetch-releases'),
39
40
  });
package/styles.css CHANGED
@@ -1320,6 +1320,123 @@ mark { background: rgba(79,172,255,.2); color: var(--accent); border-radius: 2px
1320
1320
  text-decoration: underline;
1321
1321
  }
1322
1322
 
1323
+ /* Version History */
1324
+ .version-history-list {
1325
+ max-height: 300px;
1326
+ overflow-y: auto;
1327
+ border: 1px solid var(--border);
1328
+ border-radius: 6px;
1329
+ background: var(--bg2);
1330
+ }
1331
+ .version-history-list::-webkit-scrollbar { width: 3px; }
1332
+ .version-history-list::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 3px; }
1333
+ .version-row {
1334
+ display: flex;
1335
+ align-items: center;
1336
+ justify-content: space-between;
1337
+ padding: 8px 12px;
1338
+ border-bottom: 1px solid var(--border);
1339
+ font-size: 11px;
1340
+ transition: background 0.15s;
1341
+ }
1342
+ .version-row:last-child { border-bottom: none; }
1343
+ .version-row:hover { background: var(--bg3); }
1344
+ .version-row.version-current { background: color-mix(in srgb, var(--accent) 8%, transparent); }
1345
+ .version-info { display: flex; flex-direction: column; gap: 2px; }
1346
+ .version-tag { color: var(--text); font-weight: 600; font-size: 12px; }
1347
+ .version-date { color: var(--text-dim); font-size: 10px; }
1348
+ .version-badge {
1349
+ display: inline-block;
1350
+ background: var(--green);
1351
+ color: #000;
1352
+ font-size: 9px;
1353
+ font-weight: 700;
1354
+ padding: 1px 5px;
1355
+ border-radius: 3px;
1356
+ margin-left: 6px;
1357
+ vertical-align: middle;
1358
+ text-transform: uppercase;
1359
+ }
1360
+ .version-pre {
1361
+ display: inline-block;
1362
+ background: var(--yellow);
1363
+ color: #000;
1364
+ font-size: 9px;
1365
+ font-weight: 700;
1366
+ padding: 1px 5px;
1367
+ border-radius: 3px;
1368
+ margin-left: 4px;
1369
+ vertical-align: middle;
1370
+ }
1371
+ .version-actions { display: flex; gap: 6px; align-items: center; }
1372
+ .version-installed {
1373
+ font-size: 10px;
1374
+ color: var(--green);
1375
+ font-weight: 600;
1376
+ }
1377
+ .version-install-btn {
1378
+ background: var(--accent);
1379
+ color: #fff;
1380
+ border: none;
1381
+ border-radius: 4px;
1382
+ padding: 3px 10px;
1383
+ font-size: 10px;
1384
+ font-weight: 600;
1385
+ cursor: pointer;
1386
+ transition: opacity 0.15s;
1387
+ }
1388
+ .version-install-btn:hover { opacity: 0.85; }
1389
+ .version-notes-btn {
1390
+ background: transparent;
1391
+ color: var(--text-mid);
1392
+ border: 1px solid var(--border);
1393
+ border-radius: 4px;
1394
+ padding: 3px 8px;
1395
+ font-size: 10px;
1396
+ cursor: pointer;
1397
+ transition: color 0.15s, border-color 0.15s;
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; }
1426
+ .version-npm-btn {
1427
+ background: transparent;
1428
+ color: var(--accent);
1429
+ border: 1px solid var(--accent);
1430
+ border-radius: 4px;
1431
+ padding: 3px 8px;
1432
+ font-size: 10px;
1433
+ font-family: var(--font-mono, monospace);
1434
+ cursor: pointer;
1435
+ transition: background 0.15s, color 0.15s;
1436
+ }
1437
+ .version-npm-btn:hover { background: var(--accent); color: #fff; }
1438
+ .version-notes-btn:hover { color: var(--accent); border-color: var(--accent); }
1439
+
1323
1440
  /* ─────────────────────────────────────────────────────────────────────────────
1324
1441
  SOURCES PANEL
1325
1442
  ───────────────────────────────────────────────────────────────────────────── */
@@ -1684,7 +1801,7 @@ mark { background: rgba(79,172,255,.2); color: var(--accent); border-radius: 2px
1684
1801
 
1685
1802
  /* ── Detail Panel Search ───────────────────────────────────────────────────── */
1686
1803
  .detail-search-wrap { display: flex; align-items: center; gap: 4px; margin-left: auto; padding: 0 6px; }
1687
- .detail-search-input { width: 150px; font-size: 10px; padding: 3px 6px; border: 1px solid var(--border); background: var(--bg1); color: var(--text); border-radius: 3px; outline: none; }
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; }
1688
1805
  .detail-search-input:focus { border-color: var(--accent); }
1689
1806
  .detail-search-count { font-size: 9px; color: var(--text-dim); white-space: nowrap; min-width: 45px; }
1690
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; }
@@ -1695,13 +1812,19 @@ mark { background: rgba(79,172,255,.2); color: var(--accent); border-radius: 2px
1695
1812
  .detail-search-hl.active { background: rgba(255,213,79,.7); outline: 1px solid rgba(255,213,79,.9); }
1696
1813
 
1697
1814
  /* ── Changelog Modal ───────────────────────────────────────────────────────── */
1698
- .changelog-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,.5); z-index: 9999; display: flex; align-items: center; justify-content: center; }
1699
- .changelog-modal { background: var(--bg1); border: 1px solid var(--border); border-radius: 10px; width: 520px; max-width: 90vw; max-height: 70vh; display: flex; flex-direction: column; box-shadow: 0 8px 32px rgba(0,0,0,.4); }
1700
- .changelog-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid var(--border); }
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; }
1701
1820
  .changelog-title { font-size: 13px; font-weight: 700; color: var(--text); }
1702
- .changelog-close { background: transparent; border: none; color: var(--text-dim); font-size: 18px; cursor: pointer; padding: 0 4px; }
1703
- .changelog-close:hover { color: var(--text); }
1704
- .changelog-body { flex: 1; overflow-y: auto; padding: 16px; font-size: 11px; line-height: 1.6; color: var(--text-mid); }
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; }
1705
1828
 
1706
1829
  /* ── Support Button ────────────────────────────────────────────────────────── */
1707
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; }