Skip to content

Commit 62e9f5b

Browse files
author
刘威
committed
fix: show app version and harden device detection
1 parent 1fc289f commit 62e9f5b

File tree

9 files changed

+191
-6
lines changed

9 files changed

+191
-6
lines changed

AGENTS.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Repository Guidelines
2+
3+
## Project Structure & Module Organization
4+
This repository is an Electron app. Main-process code lives in `src/main`, with ADB lifecycle code in `src/main/adb` and local proxy code in `src/main/proxy`. The preload bridge is `src/preload/index.js`. Renderer HTML, JS, CSS, and bundled browser-side libraries live in `src/renderer`. Build assets and icons are in `assets/` and `img/`. Packaging-only ADB binaries are staged in `bundled-tools/platform-tools`. Utility and manual verification scripts live in `scripts/`.
5+
6+
## Build, Test, and Development Commands
7+
- `npm start`: run the Electron app locally.
8+
- `npm run prepare:platform-tools`: download platform-tools into `bundled-tools/platform-tools` for packaging.
9+
- `npm run build`: build with `electron-builder.yml`.
10+
- `npm run build:win`, `npm run build:mac`, `npm run build:linux`: platform-specific builds.
11+
- `npm run debug:auto` or `npm run debug:screenshot`: quick UI/debug helpers.
12+
- `node scripts/test-browser-features.js` or other `scripts/test-*.js`: ad hoc verification scripts. There is no formal unit test suite yet.
13+
14+
## Coding Style & Naming Conventions
15+
Use plain JavaScript with CommonJS modules, 2-space indentation, semicolons, and single quotes. Prefer descriptive names over abbreviations. Use `camelCase` for variables/functions, `PascalCase` for classes, and lowercase filenames unless existing files establish a pattern such as `README.md`. Keep renderer logic in `src/renderer/app.js`; keep ADB-specific logic under `src/main/adb`.
16+
17+
## Testing Guidelines
18+
Testing is script-driven. Add focused validation scripts in `scripts/` when fixing nontrivial behavior, especially around ADB startup, window events, and SSH/terminal flows. Name them by behavior, for example `test-ssh-connection.js`. Before release work, verify the relevant path locally and check application logs under `%APPDATA%/adb-proxy-browser/logs/app.log`.
19+
20+
## Commit & Pull Request Guidelines
21+
Follow the existing commit style: `fix: ...`, `feat: ...`, `chore: ...`, `ci: ...`. Keep subjects short and imperative. For releases, use a separate version bump commit such as `chore: bump version to 0.7.49`. PRs should state user-visible impact, platform scope, and validation performed. Include screenshots for renderer changes and call out any packaging or workflow changes that affect release artifacts.
22+
23+
## Packaging Notes
24+
Release builds must use `electron-builder.yml`. The embedded ADB flow depends on `extraResources` copying `bundled-tools/platform-tools` into `resources/platform-tools`, so avoid splitting build config across multiple sources without updating both the workflow and packaging commands.

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "adb-proxy-browser",
3-
"version": "0.7.49",
3+
"version": "0.7.50",
44
"description": "A customized browser that routes traffic through ADB tunnel to phone proxy (Clash)",
55
"main": "src/main/index.js",
66
"scripts": {

src/main/adb/device.js

Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ const ADB_SERVER_PORT = 5037;
1010
const HEALTH_CHECK_INTERVAL = 5000;
1111
const INITIAL_DEVICE_RETRY_COUNT = 6;
1212
const INITIAL_DEVICE_RETRY_DELAY_MS = 500;
13+
const BACKGROUND_DEVICE_REFRESH_COUNT = 15;
14+
const BACKGROUND_DEVICE_REFRESH_DELAY_MS = 2000;
1315

1416
// Platform-specific adb binary paths
1517
const ADB_PATHS = {
@@ -68,6 +70,8 @@ class DeviceManager extends EventEmitter {
6870
this._serverRunning = false;
6971
this._healthCheckInterval = null;
7072
this._reconnecting = false;
73+
this._backgroundRefreshTimeout = null;
74+
this._backgroundRefreshRemaining = 0;
7175
}
7276

7377
/**
@@ -343,6 +347,7 @@ class DeviceManager extends EventEmitter {
343347
// though a USB device is already authorized. Retry a few times so the UI
344348
// does not get stuck on "No Devices" after startup.
345349
await this._updateDevicesWithRetry(INITIAL_DEVICE_RETRY_COUNT, INITIAL_DEVICE_RETRY_DELAY_MS);
350+
this._scheduleBackgroundRefreshes();
346351

347352
console.log('[ADB] Device tracking started');
348353
} catch (err) {
@@ -369,16 +374,123 @@ class DeviceManager extends EventEmitter {
369374
* Update device list
370375
*/
371376
async _updateDevices() {
372-
if (!this.client) return;
377+
if (!this.client) return this.devices;
373378

374379
try {
375-
this.devices = await this.client.listDevices();
380+
let devices = await this.client.listDevices();
381+
382+
// adbkit can occasionally return an empty list during server/device warmup
383+
// while `adb devices` already reports the device. Fall back to the binary
384+
// query so the UI does not stay stuck on "No Devices".
385+
if (devices.length === 0) {
386+
const cliDevices = await this._listDevicesViaAdbBinary();
387+
if (cliDevices.length > 0) {
388+
console.warn(`[ADB] adbkit returned 0 devices, using adb binary result (${cliDevices.length})`);
389+
devices = cliDevices;
390+
}
391+
}
392+
393+
this.devices = devices;
376394
this.emit('devices:updated', this.devices);
395+
return this.devices;
377396
} catch (err) {
378397
console.error('[ADB] Failed to list devices:', err.message);
398+
const cliDevices = await this._listDevicesViaAdbBinary();
399+
if (cliDevices.length > 0) {
400+
console.warn(`[ADB] Recovered device list via adb binary after adbkit error (${cliDevices.length})`);
401+
this.devices = cliDevices;
402+
this.emit('devices:updated', this.devices);
403+
}
404+
return this.devices;
379405
}
380406
}
381407

408+
async _listDevicesViaAdbBinary() {
409+
if (!this._adbPath) return [];
410+
411+
return new Promise((resolve) => {
412+
const proc = spawn(this._adbPath, ['devices'], {
413+
stdio: ['ignore', 'pipe', 'pipe']
414+
});
415+
416+
let stdout = '';
417+
let stderr = '';
418+
let settled = false;
419+
const finish = (devices) => {
420+
if (settled) return;
421+
settled = true;
422+
clearTimeout(timeout);
423+
resolve(devices);
424+
};
425+
426+
proc.stdout.on('data', (data) => { stdout += data.toString(); });
427+
proc.stderr.on('data', (data) => { stderr += data.toString(); });
428+
429+
proc.on('error', (err) => {
430+
console.error('[ADB] Failed to run "adb devices":', err.message);
431+
finish([]);
432+
});
433+
434+
proc.on('close', (code) => {
435+
if (code !== 0 && stderr.trim()) {
436+
console.error(`[ADB] "adb devices" exited with code ${code}: ${stderr.trim()}`);
437+
finish([]);
438+
return;
439+
}
440+
441+
finish(this._parseAdbDevicesOutput(stdout));
442+
});
443+
444+
const timeout = setTimeout(() => {
445+
if (!proc.killed) {
446+
proc.kill();
447+
}
448+
console.warn('[ADB] "adb devices" timed out');
449+
finish([]);
450+
}, 5000);
451+
});
452+
}
453+
454+
_parseAdbDevicesOutput(output) {
455+
return output
456+
.split(/\r?\n/)
457+
.map((line) => line.trim())
458+
.filter((line) => line && !line.startsWith('List of devices attached'))
459+
.map((line) => {
460+
const parts = line.split(/\s+/);
461+
if (parts.length < 2) return null;
462+
return {
463+
id: parts[0],
464+
type: parts[1]
465+
};
466+
})
467+
.filter(Boolean);
468+
}
469+
470+
_scheduleBackgroundRefreshes() {
471+
if (this.devices.length > 0 || this._backgroundRefreshTimeout) {
472+
return;
473+
}
474+
475+
this._backgroundRefreshRemaining = BACKGROUND_DEVICE_REFRESH_COUNT;
476+
const tick = async () => {
477+
this._backgroundRefreshTimeout = null;
478+
479+
if (!this.client || this.devices.length > 0 || this._backgroundRefreshRemaining <= 0) {
480+
return;
481+
}
482+
483+
this._backgroundRefreshRemaining -= 1;
484+
await this._updateDevices();
485+
486+
if (this.devices.length === 0 && this._backgroundRefreshRemaining > 0) {
487+
this._backgroundRefreshTimeout = setTimeout(tick, BACKGROUND_DEVICE_REFRESH_DELAY_MS);
488+
}
489+
};
490+
491+
this._backgroundRefreshTimeout = setTimeout(tick, BACKGROUND_DEVICE_REFRESH_DELAY_MS);
492+
}
493+
382494
/**
383495
* Check if ADB server is running
384496
*/
@@ -413,6 +525,12 @@ class DeviceManager extends EventEmitter {
413525
async close() {
414526
this._stopHealthCheck();
415527

528+
if (this._backgroundRefreshTimeout) {
529+
clearTimeout(this._backgroundRefreshTimeout);
530+
this._backgroundRefreshTimeout = null;
531+
this._backgroundRefreshRemaining = 0;
532+
}
533+
416534
if (this.tracker) {
417535
try {
418536
await this.tracker.end();

src/main/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,11 @@ function setupIpc() {
755755
return true;
756756
});
757757

758+
// App: Get running version
759+
ipcMain.handle('app:getVersion', () => {
760+
return app.getVersion();
761+
});
762+
758763
// ADB: Check if bundled ADB exists
759764
ipcMain.handle('adb:hasBundled', () => {
760765
const { hasBundledAdb } = require('./adb/download');

src/preload/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
3535
// Config
3636
getConfig: () => ipcRenderer.invoke('config:get'),
3737
setConfig: (config) => ipcRenderer.invoke('config:set', config),
38+
getAppVersion: () => ipcRenderer.invoke('app:getVersion'),
3839

3940
// History
4041
getHistory: () => ipcRenderer.invoke('history:getAll'),

src/renderer/app.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ const elements = {
1818
remotePort: document.getElementById('remote-port'),
1919
proxyType: document.getElementById('proxy-type'),
2020
btnSettings: document.getElementById('btn-settings'),
21+
appVersionChip: document.getElementById('app-version-chip'),
22+
appVersionText: document.getElementById('app-version-text'),
2123

2224
// Modal
2325
settingsModal: document.getElementById('settings-modal'),
@@ -1792,6 +1794,18 @@ async function init() {
17921794
// Setup terminal resize handle
17931795
setupTerminalResize();
17941796

1797+
try {
1798+
const version = await window.electronAPI.getAppVersion();
1799+
if (elements.appVersionChip) {
1800+
elements.appVersionChip.textContent = `v${version}`;
1801+
}
1802+
if (elements.appVersionText) {
1803+
elements.appVersionText.textContent = `Version ${version}`;
1804+
}
1805+
} catch (err) {
1806+
console.warn('Failed to load app version:', err);
1807+
}
1808+
17951809
// Listen for ADB errors
17961810
window.electronAPI.onAdbError((error) => {
17971811
state.adbError = error;

src/renderer/index.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
</div>
4444

4545
<div class="toolbar-right">
46+
<span id="app-version-chip" class="app-version-chip" title="Running version">v--</span>
47+
4648
<!-- Connection Status (Compact) -->
4749
<div class="connection-status" id="connection-status" title="Click to manage connection">
4850
<span class="status-indicator disconnected"></span>
@@ -105,6 +107,7 @@
105107
<div class="welcome-container">
106108
<h1>ADB Proxy Browser</h1>
107109
<p class="subtitle">Browse through your phone's proxy connection</p>
110+
<p id="app-version-text" class="app-version-text">Version --</p>
108111

109112
<!-- Device Status -->
110113
<div class="device-panel">

src/renderer/styles.css

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,20 @@ body {
9898
gap: 4px;
9999
}
100100

101+
.app-version-chip {
102+
display: inline-flex;
103+
align-items: center;
104+
height: 24px;
105+
padding: 0 8px;
106+
border: 1px solid var(--border-color);
107+
border-radius: 999px;
108+
background-color: var(--bg-primary);
109+
color: var(--text-secondary);
110+
font-size: 11px;
111+
font-weight: 600;
112+
letter-spacing: 0.04em;
113+
}
114+
101115
/* Connection Status (Minimal) */
102116
.connection-status {
103117
display: flex;
@@ -374,7 +388,13 @@ body {
374388
.subtitle {
375389
color: var(--text-secondary);
376390
font-size: 14px;
377-
margin-bottom: 32px;
391+
margin-bottom: 6px;
392+
}
393+
394+
.app-version-text {
395+
color: var(--text-muted);
396+
font-size: 12px;
397+
margin-bottom: 26px;
378398
}
379399

380400
/* Device Panel (Compact) */

0 commit comments

Comments
 (0)