@mcesystems/adb-kit 1.0.65 → 1.0.69

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/README.md CHANGED
@@ -1,191 +1,200 @@
1
1
  # @mcesystems/adb-kit
2
2
 
3
- ADB (Android Debug Bridge) toolkit for device management. Provides a TypeScript wrapper around adbkit for Android device operations.
3
+ ## 1. Description
4
4
 
5
- ## Features
5
+ **What it does:** ADB (Android Debug Bridge) toolkit for device management. Provides a TypeScript wrapper around adbkit for Android device operations: listing devices, reading device properties, installing/uninstalling apps, checking and waiting for USB debugging, and port forwarding.
6
6
 
7
- - **Device Operations**: Get device properties, install/uninstall apps
8
- - **USB Debugging Detection**: Check and wait for USB debugging enabled
9
- - **Port Forwarding**: Forward local ports to device services
10
- - **Cross-platform**: Works on Windows, macOS, and Linux
7
+ **When it's used:** Use this package when your application needs to communicate with Android devices over ADB—for example device management tools, test runners, or installers that target Android hardware.
11
8
 
12
- ## Installation
9
+ ## 2. Install
13
10
 
14
11
  ```bash
15
12
  npm install @mcesystems/adb-kit
16
13
  ```
17
14
 
18
- ## Requirements
15
+ **Requirements:** Node.js 18+, Android device with USB debugging enabled, and ADB binaries (see Resources).
19
16
 
20
- - Node.js 18+
21
- - Android device with USB debugging enabled
22
- - ADB binaries (see Platform Setup)
17
+ ## 3. Resources
23
18
 
24
- ### Platform Setup
19
+ This package needs ADB binaries to run. You can obtain them in one of these ways:
25
20
 
26
- #### Using the Export Script (Recommended for Distribution)
21
+ **Option A Export script (recommended for distribution)**
22
+ Bundle ADB for your app using the provided script:
27
23
 
28
- Use the export script to bundle ADB binaries for your application:
29
24
  ```bash
30
- # Using npx (after installing the package)
31
25
  npx export-adb-resources /path/to/your-app/resources/adb-kit
32
26
 
33
- # Or export for all platforms at once
27
+ # All platforms
34
28
  npx export-adb-resources /path/to/your-app/resources/adb-kit --all
35
-
36
- # Or run the script directly
37
- npx tsx node_modules/@mcesystems/adb-kit/scripts/export-resources.ts /path/to/your-app/resources
38
29
  ```
39
30
 
40
- See [scripts/README.md](./scripts/README.md) for detailed prerequisites and instructions.
41
-
42
- #### Windows & macOS (Development)
31
+ See [scripts/README.md](./scripts/README.md) for arguments, options, output layout, and prerequisites.
43
32
 
44
- Set the `AdbBinPath` environment variable to the ADB binary location, or ensure `adb` is in your system PATH.
33
+ **Option B Development**
34
+ - **Windows / macOS:** Set `AdbBinPath` to your ADB binary directory, or have `adb` on system PATH.
35
+ - **Linux:** Install via package manager (e.g. `sudo apt install adb`).
45
36
 
46
- #### Linux
47
-
48
- Install ADB via your package manager:
49
- ```bash
50
- sudo apt install adb # Debian/Ubuntu
51
- sudo dnf install android-tools # Fedora
52
- ```
37
+ The package resolves ADB in this order: `AdbBinPath` env, then bundled resources under `dist/resources/bin/<platform>/`, then system PATH.
53
38
 
54
- ## Usage
39
+ ## 4. Examples
55
40
 
56
- ### Basic Device Operations
41
+ **Create a device kit and read properties**
57
42
 
58
43
  ```typescript
59
44
  import { AdbDeviceKit } from '@mcesystems/adb-kit';
60
45
 
61
- // Create a device kit for a specific device
62
46
  const device = new AdbDeviceKit('device-serial-number', 1);
63
-
64
- // Get device properties
65
47
  const [manufacturer, model] = await device.getDeviceProperties(['Manufacturer', 'Model']);
66
- console.log(`Device: ${manufacturer} ${model}`);
67
-
68
- // Get all properties
69
48
  const allProps = await device.getAllDeviceProperties();
70
-
71
- // List connected devices
72
49
  const devices = await device.listDevices();
73
50
  ```
74
51
 
75
- ### App Management
52
+ **Install/uninstall and check app**
76
53
 
77
54
  ```typescript
78
- const device = new AdbDeviceKit('device-serial-number', 1);
79
-
80
- // Install an APK
81
55
  await device.installApp('/path/to/app.apk');
82
-
83
- // Check if app is installed
84
56
  const isInstalled = await device.isAppInstalled('com.example.app');
85
-
86
- // Uninstall an app
87
57
  await device.uninstallApp('com.example.app');
88
58
  ```
89
59
 
90
- ### USB Debugging
60
+ **USB debugging**
91
61
 
92
62
  ```typescript
93
- const device = new AdbDeviceKit('device-serial-number', 1);
94
-
95
- // Check if USB debugging is enabled
96
63
  const hasDebugging = await device.hasUsbDebugging();
97
-
98
- // Wait for USB debugging to be enabled (with timeout)
99
- await device.waitForUsbDebugging(30000); // 30 seconds
64
+ await device.waitForUsbDebugging(30000); // 30s timeout
100
65
  ```
101
66
 
102
- ### Port Forwarding
67
+ **Port forwarding**
103
68
 
104
69
  ```typescript
105
- const device = new AdbDeviceKit('device-serial-number', 1);
106
-
107
- // Forward a local port to a device service
108
70
  const localPort = await device.startPortForward('tcp:8080');
109
- console.log(`Forwarded to local port: ${localPort}`);
110
71
  ```
111
72
 
112
- ### Access ADB Client
73
+ **Low-level clients**
113
74
 
114
75
  ```typescript
115
- const device = new AdbDeviceKit('device-serial-number', 1);
116
-
117
- // Get the underlying adbkit client for advanced operations
118
76
  const client = await device.getClient();
119
-
120
- // Get the device client for direct device operations
121
77
  const deviceClient = await device.getDeviceClient();
122
78
  ```
123
79
 
124
- ## API Reference
80
+ **USB debugging robustness example**
81
+
82
+ Interactive example that runs the same flow 5 times to verify `waitForUsbDebugging` handling: start without USB debugging (device not found), short 5s timeout (user lets it pass), then wait up to 30s while the user enables USB debugging, show device recognized, then user disables USB debugging and repeats.
83
+
84
+ ```bash
85
+ # From packages/adb-kit (set ADB_DEVICE_SERIAL if USB debugging is off)
86
+ pnpm example:usb-debugging
87
+ ```
88
+
89
+ See [examples/usb-debugging-robustness.ts](./examples/usb-debugging-robustness.ts) for the script.
90
+
91
+ For more scenarios and step-by-step explanations, see [Example.md](./Example.md).
92
+
93
+ ## 5. API
125
94
 
126
95
  ### AdbDeviceKit
127
96
 
128
- **Constructor:**
129
- - `new AdbDeviceKit(deviceId: string, port: number)`: Create a device kit
97
+ **Constructor**
98
+
99
+ - **Input:** `deviceId: string`, `port: number`
100
+ - **Output:** new `AdbDeviceKit` instance bound to that device and logical port.
101
+
102
+ **listDevices()**
130
103
 
131
- **Device Info:**
132
- - `getDeviceId()`: Get the device serial number
133
- - `getPort()`: Get the logical port number
134
- - `getDeviceProperties(properties: DeviceProperty[])`: Get specific device properties
135
- - `getAllDeviceProperties()`: Get all device properties
104
+ - **Input:** none
105
+ - **Output:** `Promise<AdbDevice[]>` — list of connected devices (`AdbDevice`: `{ id: string; type: AdbDeviceType }`).
136
106
 
137
- **Device State:**
138
- - `hasUsbDebugging()`: Check if USB debugging is enabled
139
- - `waitForUsbDebugging(timeout?)`: Wait for USB debugging
107
+ **getDeviceProperties(properties)**
140
108
 
141
- **App Management:**
142
- - `installApp(apkPath: string)`: Install an APK
143
- - `uninstallApp(packageName: string)`: Uninstall an app
144
- - `isAppInstalled(packageName: string)`: Check if app is installed
109
+ - **Input:** `properties: DeviceProperty[]` — e.g. `['Manufacturer', 'Model']`
110
+ - **Output:** `Promise<string[]>` values in the same order as `properties`; failed props are `""`.
145
111
 
146
- **Port Forwarding:**
147
- - `startPortForward(serviceName: string)`: Forward to a device service
112
+ **getAllDeviceProperties()**
148
113
 
149
- **Advanced:**
150
- - `getClient()`: Get the adbkit client
151
- - `getDeviceClient()`: Get the device client
152
- - `listDevices()`: List all connected devices
114
+ - **Input:** none
115
+ - **Output:** `Promise<Record<string, string>>` all device properties from `getprop`.
116
+
117
+ **installApp(appPath)**
118
+
119
+ - **Input:** `appPath: string` — path to APK
120
+ - **Output:** `Promise<void>` — resolves when install finishes; rejects on failure.
121
+
122
+ **uninstallApp(packageName)**
123
+
124
+ - **Input:** `packageName: string` — e.g. `'com.example.app'`
125
+ - **Output:** `Promise<void>` — resolves when uninstall finishes; rejects on failure.
126
+
127
+ **isAppInstalled(packageName)**
128
+
129
+ - **Input:** `packageName: string`
130
+ - **Output:** `Promise<boolean>` — whether the package is installed.
131
+
132
+ **hasUsbDebugging()**
133
+
134
+ - **Input:** none
135
+ - **Output:** `Promise<boolean>` — whether this device appears in the ADB device list.
136
+
137
+ **waitForUsbDebugging(timeout?, numberOfAllowedAttempts?)**
138
+
139
+ - **Input:** `timeout?: number` (default `120000` ms), `numberOfAllowedAttempts?: number` (default `5`, accepted for backward compatibility; give-up is determined by timeout and tracker end only)
140
+ - **Output:** `Promise<boolean>` — `true` when device is present and ready, `false` when tracking ends without device; rejects on timeout, device removal, or connection error.
141
+
142
+ **startPortForward(serviceName)**
143
+
144
+ - **Input:** `serviceName: string` — e.g. `'tcp:8080'`
145
+ - **Output:** `Promise<number | null>` — local port number used for forwarding, or `null` if forwarding failed. Reuses same port on repeated calls.
146
+
147
+ **getClient()**
148
+
149
+ - **Input:** none
150
+ - **Output:** `Promise<AdbClient>` — underlying adbkit client.
151
+
152
+ **getDeviceClient()**
153
+
154
+ - **Input:** none
155
+ - **Output:** `Promise<DeviceClient>` — adbkit device client for this device.
156
+
157
+ **getDeviceId()**
158
+
159
+ - **Input:** none
160
+ - **Output:** `string` — device serial (last segment after `\` on Windows).
161
+
162
+ **getPort()**
163
+
164
+ - **Input:** none
165
+ - **Output:** `number` — logical port passed to the constructor.
153
166
 
154
167
  ### DeviceProperty
155
168
 
156
- Available device properties:
157
- - `Manufacturer`, `Name`, `Model`, `Brand`, `Device`
158
- - `Android Version`, `Platform`, `CPU`, `CPU.abi2`
159
- - `Description`, `Fingerprint`
160
- - `GSM Flexversion`, `GSM IMEI`
161
- - `Locale Language`, `Locale Region`
162
- - `Wifi Channels`, `Board Platform`, `Product Board`
163
- - `Display ID`, `Version Incremental`, `Version SDK`
164
- - `Version Codename`, `Version Release`
165
- - `Build Date`, `Build Type`, `Build User`
169
+ Supported property names for `getDeviceProperties()`:
170
+ `Manufacturer`, `Name`, `Model`, `Brand`, `Device`, `Android Version`, `Platform`, `CPU`, `CPU.abi2`, `Description`, `Fingerprint`, `GSM Flexversion`, `GSM IMEI`, `Locale Language`, `Locale Region`, `Wifi Channels`, `Board Platform`, `Product Board`, `Display ID`, `Version Incremental`, `Version SDK`, `Version Codename`, `Version Release`, `Build Date`, `Build Type`, `Build User`.
166
171
 
167
- ## Development
172
+ ## 6. Flow
168
173
 
169
- ```bash
170
- # Build the package
171
- npm run build
174
+ Example flow: wait for device, read identity, install an app, then forward a port.
175
+
176
+ ```typescript
177
+ import { AdbDeviceKit } from '@mcesystems/adb-kit';
172
178
 
173
- # Run tests
174
- npm test
179
+ const deviceId = 'ABC123'; // from your device discovery
180
+ const device = new AdbDeviceKit(deviceId, 1);
175
181
 
176
- # Clean build artifacts
177
- npm run clean
178
- ```
182
+ // 1. Wait for USB debugging (e.g. after user enables it)
183
+ await device.waitForUsbDebugging(60000);
179
184
 
180
- ### Setting Up ADB for Development
185
+ // 2. Identify device
186
+ const [manufacturer, model] = await device.getDeviceProperties(['Manufacturer', 'Model']);
187
+ console.log(`Device: ${manufacturer} ${model}`);
181
188
 
182
- For development, you can either:
183
- 1. Use the export script to download ADB to a local path
184
- 2. Install ADB via Homebrew (`brew install android-platform-tools`) or your system package manager
185
- 3. Set `AdbBinPath` environment variable to your ADB installation
189
+ // 3. Install app
190
+ await device.installApp('./build/app.apk');
186
191
 
187
- ## License
192
+ // 4. Forward a device service to a local port
193
+ const localPort = await device.startPortForward('tcp:8080');
194
+ console.log(`Service reachable at localhost:${localPort}`);
195
+ ```
188
196
 
189
- ISC
197
+ ## 7. TODO
190
198
 
191
- ADB Platform Tools are licensed under the Apache License 2.0.
199
+ - [ ] Optional: Re-authorize handling when ADB reports vendor keys not set (revoke and re-authorize device).
200
+ - [ ] Optional: Add Example.md with more detailed scenarios and explanations.
package/dist/index.js CHANGED
@@ -29847,7 +29847,7 @@ var require_adb = __commonJS({
29847
29847
  Object.defineProperty(exports2, "__esModule", { value: true });
29848
29848
  var client_1 = __importDefault(require_client2());
29849
29849
  var util_1 = __importDefault(require_util3());
29850
- var Adb = class {
29850
+ var Adb2 = class {
29851
29851
  static createClient(options = {}) {
29852
29852
  const opts = {
29853
29853
  bin: options.bin,
@@ -29864,8 +29864,8 @@ var require_adb = __commonJS({
29864
29864
  return new client_1.default(opts);
29865
29865
  }
29866
29866
  };
29867
- Adb.util = util_1.default;
29868
- exports2.default = Adb;
29867
+ Adb2.util = util_1.default;
29868
+ exports2.default = Adb2;
29869
29869
  }
29870
29870
  });
29871
29871
 
@@ -30213,7 +30213,8 @@ var require_dist = __commonJS({
30213
30213
  // src/index.ts
30214
30214
  var index_exports = {};
30215
30215
  __export(index_exports, {
30216
- AdbDeviceKit: () => AdbDeviceKit
30216
+ AdbDeviceKit: () => AdbDeviceKit,
30217
+ readAll: () => readAll
30217
30218
  });
30218
30219
  module.exports = __toCommonJS(index_exports);
30219
30220
 
@@ -31284,6 +31285,23 @@ function ensureAdbPathFromResources() {
31284
31285
  logInfo(`Using ADB from resources: ${adbPath}`);
31285
31286
  }
31286
31287
  }
31288
+ function isConnectionErrorMessage(err) {
31289
+ const msg = err.message ?? "";
31290
+ const code = err.code ?? "";
31291
+ const name = err.name ?? "";
31292
+ return msg.includes("ECONNRESET") || msg.includes("EPIPE") || msg.includes("Premature end of stream") || code === "ECONNRESET" || code === "EPIPE" || name === "PrematureEOFError";
31293
+ }
31294
+ var connectionErrorHandlerInstalled = false;
31295
+ function installConnectionErrorHandler() {
31296
+ if (connectionErrorHandlerInstalled) return;
31297
+ connectionErrorHandlerInstalled = true;
31298
+ process.on("unhandledRejection", (reason) => {
31299
+ if (reason instanceof Error && isConnectionErrorMessage(reason)) {
31300
+ logDetail(`Suppressed expected ADB connection error: ${reason.message}`);
31301
+ return;
31302
+ }
31303
+ });
31304
+ }
31287
31305
  var deviceProps = {
31288
31306
  Manufacturer: "ro.product.manufacturer",
31289
31307
  Name: "ro.product.name",
@@ -31318,9 +31336,8 @@ var AdbDeviceKit = class {
31318
31336
  setLogLevel(process.env.LOG_LEVEL ?? "none");
31319
31337
  logNamespace(`adb:${deviceId}`);
31320
31338
  ensureAdbPathFromResources();
31321
- this.client = adbkit.default.createClient({
31322
- bin: process.env.ADB_PATH ?? getAdbBinaryPath() ?? "."
31323
- });
31339
+ installConnectionErrorHandler();
31340
+ this.client = this.connect();
31324
31341
  this.deviceId = deviceId.split("\\").pop() ?? deviceId;
31325
31342
  logInfo(`Device ID: ${this.deviceId}`);
31326
31343
  this.device = this.client.getDevice(this.deviceId);
@@ -31329,6 +31346,18 @@ var AdbDeviceKit = class {
31329
31346
  device;
31330
31347
  deviceId;
31331
31348
  devicePort = null;
31349
+ connect() {
31350
+ const adbBin = process.env.ADB_PATH ?? getAdbBinaryPath() ?? "adb";
31351
+ const client = adbkit.default.createClient({ bin: adbBin });
31352
+ const errorHandler = (err) => {
31353
+ logError(`ADB client connection error (e.g. device disconnected): ${err.message}`);
31354
+ client.removeListener("error", errorHandler);
31355
+ this.client = this.connect();
31356
+ this.device = this.client.getDevice(this.deviceId);
31357
+ };
31358
+ client.on("error", errorHandler);
31359
+ return client;
31360
+ }
31332
31361
  async listDevices() {
31333
31362
  const devices = await this.client.listDevices();
31334
31363
  return devices;
@@ -31381,39 +31410,84 @@ var AdbDeviceKit = class {
31381
31410
  const devices = await this.listDevices();
31382
31411
  return !!devices.find((device) => device.id === this.deviceId);
31383
31412
  }
31384
- async waitForUsbDebugging(timeout2 = 12e4, numberOfAllowedAttempts = 5) {
31413
+ /**
31414
+ * Creates an isolated ADB client for tracking operations.
31415
+ * This client is separate from the main client so connection errors
31416
+ * during tracking don't affect other operations.
31417
+ */
31418
+ createTrackingClient() {
31419
+ const adbBin = process.env.ADB_PATH ?? getAdbBinaryPath() ?? "adb";
31420
+ return adbkit.default.createClient({ bin: adbBin });
31421
+ }
31422
+ async waitForUsbDebugging(timeout2 = 12e4, _numberOfAllowedAttempts = 5) {
31385
31423
  if (await this.hasUsbDebugging()) {
31386
31424
  logDetail("USB debugging is already enabled");
31387
31425
  return true;
31388
31426
  }
31389
- let count = numberOfAllowedAttempts;
31390
- const tracker = await this.client.trackDevices();
31427
+ const trackingClient = this.createTrackingClient();
31428
+ const tracker = await trackingClient.trackDevices();
31429
+ let settled = false;
31391
31430
  return new Promise((resolve, reject) => {
31392
31431
  const timeoutId = setTimeout(() => {
31393
31432
  logError("Timeout waiting for USB debugging");
31394
- reject(new Error("Timeout waiting for USB debugging"));
31433
+ settle({ type: "reject", err: new Error("Timeout waiting for USB debugging") });
31395
31434
  }, timeout2);
31396
- tracker.on("remove", (_) => {
31435
+ const cleanupTrackingClient = () => {
31436
+ try {
31437
+ tracker.end();
31438
+ } catch {
31439
+ }
31440
+ trackingClient.on("error", () => {
31441
+ });
31442
+ };
31443
+ const settle = (result) => {
31444
+ if (settled) return;
31445
+ settled = true;
31397
31446
  clearTimeout(timeoutId);
31447
+ cleanupTrackingClient();
31448
+ if (result.type === "resolve") {
31449
+ resolve(result.value);
31450
+ } else {
31451
+ logError(result.err.message);
31452
+ reject(result.err);
31453
+ }
31454
+ };
31455
+ trackingClient.on("error", (err) => {
31456
+ if (isConnectionErrorMessage(err)) {
31457
+ logDetail(`Tracking client connection closed: ${err.message}`);
31458
+ settle({ type: "resolve", value: false });
31459
+ } else {
31460
+ logError(`Tracking client error: ${err.message}`);
31461
+ settle({ type: "reject", err });
31462
+ }
31463
+ });
31464
+ tracker.on("error", (err) => {
31465
+ if (isConnectionErrorMessage(err)) {
31466
+ logDetail(`Tracker connection closed: ${err.message}`);
31467
+ settle({ type: "resolve", value: false });
31468
+ } else {
31469
+ logError(`Tracker error: ${err.message}`);
31470
+ settle({ type: "reject", err });
31471
+ }
31472
+ });
31473
+ tracker.on("end", () => {
31474
+ logDetail("Device tracking ended");
31475
+ settle({ type: "resolve", value: false });
31476
+ });
31477
+ tracker.on("remove", (_) => {
31398
31478
  logError("Device removed from tracker");
31479
+ settle({ type: "reject", err: new Error("Device removed while waiting for USB debugging") });
31399
31480
  });
31400
31481
  tracker.on("add", (device) => {
31401
31482
  if (device.type === "device") {
31402
31483
  logDetail("Device added to tracker");
31403
- clearTimeout(timeoutId);
31404
- resolve(true);
31484
+ settle({ type: "resolve", value: true });
31405
31485
  }
31406
31486
  });
31407
31487
  tracker.on("change", (device) => {
31408
- count--;
31409
- if (device.type === "device" && count > 0) {
31410
- clearTimeout(timeoutId);
31488
+ if (device.type === "device") {
31411
31489
  logDetail("Device changed in tracker");
31412
- resolve(true);
31413
- } else if (count === 0) {
31414
- logDetail("No device found in tracker");
31415
- this.client.kill();
31416
- resolve(false);
31490
+ settle({ type: "resolve", value: true });
31417
31491
  }
31418
31492
  });
31419
31493
  });
@@ -31439,8 +31513,13 @@ var AdbDeviceKit = class {
31439
31513
  return this.device;
31440
31514
  }
31441
31515
  };
31516
+
31517
+ // src/index.ts
31518
+ var import_adbkit = __toESM(require_dist());
31519
+ var readAll = import_adbkit.default.util.readAll;
31442
31520
  // Annotate the CommonJS export names for ESM import in node:
31443
31521
  0 && (module.exports = {
31444
- AdbDeviceKit
31522
+ AdbDeviceKit,
31523
+ readAll
31445
31524
  });
31446
31525
  //# sourceMappingURL=index.js.map