adbkit

repository·master·Indexed 43 days ago

https://github.com/devicefarmer/adbkit

A pure Node.js and TypeScript client for the Android Debug Bridge (ADB) server, version 3.3.9. It provides programmatic APIs for interacting with Android devices, including file transfers (push/pull), APK installation, log processing, device tracking, and shell command execution.

Tokens
28.8K
Snippets
94
Records
135
Agent score
55%

What's inside adbkit

  1. Configure adbkit requirements and environment

    master

    Requirements

    • Node.js: version >= 0.10
    • ADB Command Line Tool: The host machine must have the adb binary installed and available in $PATH, or the ADB server must already be running.

    Note: adbkit is a client, not an implementation of the ADB server itself. If the initial connection fails, the library will attempt to start the server locally using the adb binary. If you are targeting a remote host, you are responsible for starting the server on that host.

    Linux Setup

    For Linux users, the adb tool requires access to the plugdev group. You may need to add your current user to this group: sudo usermod -a -G plugdev $USER

    Debugging

    adbkit uses the debug library with the namespace adb. To view debug output, set the DEBUG environment variable: DEBUG=adb:* node app.js

  2. Migrating from version 1.x to 2.x

    master

    If you are upgrading from version 1.x to 2.x, be aware of several breaking changes related to the transition from a callback-based API to a Promise-based API:

    • Promises instead of Callbacks: Most API methods now return Promises (using Bluebird) instead of relying solely on callbacks. This allows for better error propagation and the use of .finally() for resource cleanup.
    • Chaining Behavior: In version 1.x, many methods returned the current instance to allow chaining, which often resulted in commands running in parallel. In version 2.x, methods return a Promise, meaning you must use await or .then() to ensure serial execution.
    • Argument Consolidation: Some methods that previously accepted multiple callback arguments have been refactored to return a single resolved value in the Promise to better align with Promise patterns (e.g., client.framebuffer()).

    Note: Despite the shift to Promises, most API methods still accept callbacks for backward compatibility.

  3. Configure extras for StartActivityOptions

    master

    Extras are passed to the activity using the --e flag with a type prefix. You can provide extras in two ways:

    1. Short-hand Object Syntax

    Pass an object where keys are the extra names and values are the data. The type is inferred from the JavaScript type:

    • string -> s
    • boolean -> z
    • number -> i (integer)
    • null -> sn (null)
    • object -> Use long-hand syntax for complex types.

    Note: Short-hand syntax does not support arrays. If you need to pass an array, use the long-hand syntax.

    2. Long-hand Array Syntax

    Pass an array of Extra objects for full control over types. Supported types include:

    • string (s)
    • null (sn)
    • bool (z)
    • int (i)
    • long (l)
    • float (f)
    • uri (u)
    • component (cn)

    For array values, the type suffix a is appended (e.g., --esa for a string array).

    // Short-hand example
    const options = {
      extras: {
        my_string: 'hello',
        my_bool: true,
        my_int: 42,
        my_null: null
      }
    };
    
    // Long-hand example (required for arrays or specific types)
    const options = {
      extras: [
        { key: 'my_list', type: 'string', value: ['item1', 'item2'] },
        { key: 'my_long', type: 'long', value: 123456789 }
      ]
    };
  4. Pull a file from a device

    master

    Use client.pull(deviceId, remotePath) to pull a file from a device. The returned object is a stream that emits progress, end, and error events.

    import Bluebird from 'bluebird';
    import fs from 'fs';
    import Adb from '@devicefarmer/adbkit';
    const client = Adb.createClient();
    
    const test = async () => {
        try {
            const devices = await client.listDevices();
            await Bluebird.map(devices, async (device) => {
                const transfer = await client.pull(device.id, '/system/build.prop');
                const fn = `/tmp/${device.id}.build.prop`;
                await new Bluebird((resolve, reject) => {
                    transfer.on('progress', (stats) =>
                        console.log(`[${device.id}] Pulled ${stats.bytesTransferred} bytes so far`),
                    );
                    transfer.on('end', () => {
                        console.log(`[${device.id}] Pull complete`);
                        resolve(device.id);
                    });
                    transfer.on('error', reject);
                    transfer.pipe(fs.createWriteStream(fn));
                });
            });
            console.log('Done pulling /system/build.prop from all connected devices');
        } catch (err) {
            console.error('Something went wrong:', err.stack);
        }
    };
  5. Track device connections and disconnections

    master

    Use client.trackDevices() to get a tracker object that emits events when devices are plugged in or unplugged. The tracker emits add, remove, and end events.

    import Adb from '@devicefarmer/adbkit';
    const client = Adb.createClient();
    const test = async () => {
        try {
            const tracker = await client.trackDevices();
            tracker.on('add', (device) => console.log('Device %s was plugged in', device.id));
            tracker.on('remove', (device) => console.log('Device %s was unplugged', device.id));
            tracker.on('end', () => console.log('Tracking stopped'));
        } catch (err) {
            console.error('Something went wrong:', err.stack);
        }
    };
  6. List files in a directory on a device

    master

    Use client.readdir(deviceId, remotePath) to list files in a directory. The returned file objects have a .isFile() method to distinguish between files and directories.

    import Bluebird from 'bluebird';
    import Adb from '@devicefarmer/adbkit';
    const client = Adb.createClient();
    
    const test = async () => {
        try {
            const devices = await client.listDevices();
            await Bluebird.map(devices, async (device) => {
                const files = await client.readdir(device.id, '/sdcard');
                // Synchronous, so we don't have to care about returning at the
                // right time
                files.forEach((file) => {
                    if (file.isFile()) {
                        console.log(`[${device.id}] Found file "${file.name}"`);
                    }
                });
            });
            console.log('Done checking /sdcard files on connected devices');
        } catch (err) {
            console.error('Something went wrong:', err.stack);
        }
    };
  7. Run and manage long-running shell commands

    master

    When running long-running commands (like logcat), device.shell() returns a stream. You can listen to the data event to process output progressively and call conn.end() to terminate the process on the device.

    import Bluebird from 'bluebird';
    import Adb from '@devicefarmer/adbkit';
    
    const client = Adb.createClient();
    
    client.listDevices()
      .then(function(devices) {
        return Bluebird.map(devices, function(deviceInfo) {
          const device = client.getDevice(deviceInfo.id);
          return device.shell('logcat')
            .then(function(conn) {
              let line = 0;
              conn.on('data', function(data) {
                console.log(data.toString());
                line += 1;
                // Terminate the command after 100 lines
                if (line > 100) conn.end();
              });
              conn.on('close', function() {
                console.log('100 lines read already, bye');
              });
            });
        });
      })
      .then(() => console.log('Done.'))
      .catch(err => console.error('Error:', err.stack));
  8. Check for NFC support on devices

    master

    Use client.listDevices() to get all connected devices, then use client.getFeatures(deviceId) to inspect specific hardware features like android.hardware.nfc.

    import Bluebird from 'bluebird';
    import Adb from '@devicefarmer/adbkit';
    
    const client = Adb.createClient();
    
    const test = async () => {
        try {
            const devices = await client.listDevices();
            const supportedDevices = await Bluebird.filter(devices, async (device) => {
                const features = await client.getFeatures(device.id);
                return features['android.hardware.nfc'];
            });
            console.log('The following devices support NFC:', supportedDevices);
        } catch (err) {
            console.error('Something went wrong:', err.stack);
        }
    };
  9. Install an APK on devices

    master

    Use client.install(deviceId, apkPath) to install an application package on a specific device.

    import Bluebird from 'bluebird';
    import Adb from '@devicefarmer/adbkit';
    
    const client = Adb.createClient();
    const apk = 'vendor/app.apk';
    
    const test = async () => {
        try {
            const devices = await client.listDevices();
            await Bluebird.map(devices, (device) => client.install(device.id, apk));
            console.log(`Installed ${apk} on all connected devices`);
        } catch (err) {
            console.error('Something went wrong:', err.stack);
        }
    };
  10. Push a file to a device

    master

    Use client.push(deviceId, localPath, remotePath) to push a file to a device. The returned object is a stream that emits progress, end, and error events.

    import Bluebird from 'bluebird';
    import Adb from '@devicefarmer/adbkit';
    const client = Adb.createClient();
    
    const test = async () => {
        try {
            const devices = await client.listDevices();
            await Bluebird.map(devices, async (device) => {
                const transfer = await client.push(device.id, 'temp/foo.txt', '/data/local/tmp/foo.txt');
                await new Bluebird(function (resolve, reject) {
                    transfer.on('progress', (stats) =>
                        console.log(`[${device.id}] Pushed ${stats.bytesTransferred} bytes so far`),
                    );
                    transfer.on('end', () => {
                        console.log('[${device.id}] Push complete');
                        resolve();
                    });
                    transfer.on('error', reject);
                });
            });
            console.log('Done pushing foo.txt to all connected devices');
        } catch (err) {
            console.error('Something went wrong:', err.stack);
        }
    };
  11. Monitor transfer progress with `PushTransfer`

    master

    When using sync.push() or sync.pushFile(), a PushTransfer instance is returned. This is an EventEmitter used to track progress.

    PushTransfer Events:

    • progress(stats): Emitted when a chunk is flushed. stats contains { bytesTransferred }.
    • error(err): Emitted on error.
    • end: Emitted when the transfer completes successfully.

    Methods:

    • pushTransfer.cancel(): Cancels the ongoing transfer.