Upgrade from 2.0.0
- 💥 Breaking change
- 🔄 Renamed/Refactored
- ✨ New feature
- 🐛 Bug fix
- 🗑️ Removed
This page lists changes from version 2.0.0 in core packages.
For changes in Scrcpy-related packages, see this page.
@yume-chan/adb
💥🔄 Replace AdbDaemonTransport.authenticate with adbDaemonAuthenticate function
The authentication process for daemon transport has been refactored from a static method on AdbDaemonTransport class to a standalone adbDaemonAuthenticate function. This change provides better flexibility and separates authentication logic from the transport class.
Migration guide:
- Replace
AdbDaemonTransport.authenticate()calls withadbDaemonAuthenticate()function - Replace
credentialStoreoption withcredentialManager(part of new credential system) - The
authenticatorsoption is now handled internally through the authentication processor system
Before:
import { AdbDaemonTransport } from "@yume-chan/adb";
const transport: AdbDaemonTransport = await AdbDaemonTransport.authenticate({
serial: device.serial,
connection,
credentialStore: CredentialStore,
});
After:
- JavaScript
- TypeScript
import { adbDaemonAuthenticate } from "@yume-chan/adb";
const transport = await adbDaemonAuthenticate({
serial: device.serial,
connection,
credentialManager: CredentialManager,
});
import type { AdbPacketData, AdbPacketInit } from "@yume-chan/adb";
import type { Consumable, ReadableWritablePair } from "@yume-chan/stream-extra";
import { adbDaemonAuthenticate, AdbWebCryptoCredentialManager } from "@yume-chan/adb";
import { AdbDaemonWebUsbDevice } from "@yume-chan/adb-daemon-webusb";
declare const device: AdbDaemonWebUsbDevice;
declare const connection: ReadableWritablePair<AdbPacketData, Consumable<AdbPacketInit>>;
declare const CredentialManager: AdbWebCryptoCredentialManager;
const transport: AdbDaemonTransport = await adbDaemonAuthenticate({
serial: device.serial,
connection,
credentialManager: CredentialManager,
});
The new authentication system also supports custom authentication processors for advanced use cases:
- JavaScript
- TypeScript
import { adbDaemonAuthenticate } from "@yume-chan/adb";
const transport = await adbDaemonAuthenticate({
serial: "device-serial",
connection,
processor: customAuthProcessor,
});
import type { AdbPacketData, AdbPacketInit } from "@yume-chan/adb";
import type { Consumable, ReadableWritablePair } from "@yume-chan/stream-extra";
import { adbDaemonAuthenticate, AdbDaemonAuthProcessor } from "@yume-chan/adb";
declare const connection: ReadableWritablePair<AdbPacketData, Consumable<AdbPacketInit>>;
declare const customAuthProcessor: AdbDaemonAuthProcessor;
const transport: AdbDaemonTransport = await adbDaemonAuthenticate({
serial: "device-serial",
connection,
processor: customAuthProcessor,
});
See documentation page Handshake and authenticate for more details about authentication options and usage.
✨ Add state filtering to server client device methods
AdbServerClient.prototype.getDevices and AdbServerClient.prototype.trackDevices now accept an optional includeStates parameter.
It can be an array of AdbServerClient.ConnectionState, i.e., "unauthorized" | "offline" | "device"
The default value is ["unauthorized", "device"] for backward compatibility. In a future major version, the default value will be changed to ["unauthorized", "offline", "device"].
- JavaScript
- TypeScript
const devices = await client.getDevices(["unauthorized", "offline", "device"]);
const observer = await client.trackDevices({
includeStates: ["unauthorized", "offline", "device"],
});
export {};
import type { AdbServerClient, DeviceObserver } from "@yume-chan/adb";
declare const client: AdbServerClient;
const devices: AdbServerClient.Device[] = await client.getDevices([
"unauthorized",
"offline",
"device",
]);
const observer: DeviceObserver<AdbServerClient.Device> = await client.trackDevices({
includeStates: ["unauthorized", "offline", "device"],
});
See documentation pages Get devices and Watch devices for more details and usages.
✨ Add state property to AdbServerClient.Device
To comply with the change in getDevices and trackDevices, AdbServerClient.Device now also has a state property.
The old authenticating property is deprecated, and will be removed in a future major version. It now only returns true when state is "unauthorized".
- JavaScript
- TypeScript
console.log(device.state); // "unauthorized" | "offline" | "device"
console.log(device.authenticating); // `true` when `device.state === "unauthorized"`
export {};
import type { AdbServerClient } from "@yume-chan/adb";
declare const device: AdbServerClient.Device;
console.log(device.state); // "unauthorized" | "offline" | "device"
console.log(device.authenticating); // `true` when `device.state === "unauthorized"`
See documentation page Get devices for more details about the state property and device states.
💥🔄 Move subprocess spawnWait and spawnWaitText to method chaining
Before:
import type { Adb } from "@yume-chan/adb";
declare const adb: Adb;
const output: Uint8Array =
await adb.subprocess.noneProtocol.spawnWait("ls -al /");
const output: string =
await adb.subprocess.noneProtocol.spawnWaitText("ls -al /");
After:
- JavaScript
- TypeScript
const output = await adb.subprocess.noneProtocol.spawn("ls -al /").wait();
const output = await adb.subprocess.noneProtocol.spawn("ls -al /").wait().toString();
export {};
import type { Adb } from "@yume-chan/adb";
declare const adb: Adb;
const output: Uint8Array = await adb.subprocess.noneProtocol.spawn("ls -al /").wait();
const output: string = await adb.subprocess.noneProtocol.spawn("ls -al /").wait().toString();
The wait method now accepts a ReadableStream for stdin:
- JavaScript
- TypeScript
import { encodeUtf8 } from "@yume-chan/struct";
const result = await adb.subprocess.shellProtocol.spawn("cat").wait({
stdin: new ReadableStream({
start(controller) {
controller.enqueue(encodeUtf8("Hello World!"));
controller.close();
},
}),
});
const output = result.stdout;
const exitCode = result.exitCode;
import type { Adb } from "@yume-chan/adb";
import type { ReadableStream } from "@yume-chan/stream-extra";
import { encodeUtf8 } from "@yume-chan/struct";
declare const adb: Adb;
const result = await adb.subprocess.shellProtocol!.spawn("cat").wait({
stdin: new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(encodeUtf8("Hello World!"));
controller.close();
},
}),
});
const output: Uint8Array = result.stdout;
const exitCode: number = result.exitCode;
See documentation pages None protocol and Shell protocol for more details about subprocess protocols and their APIs.
✨ Add stdin closing support to shell protocol raw mode
Shell protocol in raw mode is the only combination that supports closing stdin. Some programs (like cat) might rely on this behavior to properly function.
When using spawn, stdin can be closed by closing the WritableStream:
- JavaScript
- TypeScript
import { encodeUtf8 } from "@yume-chan/struct";
const process = await adb.subprocess.shellProtocol.spawn("cat");
const writer = process.stdin.getWriter();
await writer.write(encodeUtf8("Hello World!"));
await writer.close();
import type { Adb, AdbShellProtocolProcess } from "@yume-chan/adb";
import { encodeUtf8 } from "@yume-chan/struct";
import type { WritableStreamDefaultWriter } from "@yume-chan/stream-extra";
declare const adb: Adb;
const process: AdbShellProtocolProcess = await adb.subprocess.shellProtocol!.spawn("cat");
const writer: WritableStreamDefaultWriter<Uint8Array> = process.stdin.getWriter();
await writer.write(encodeUtf8("Hello World!"));
await writer.close();
When using spawn.wait, stdin can be closed by closing the ReadableStream:
- JavaScript
- TypeScript
import { encodeUtf8 } from "@yume-chan/struct";
import { ReadableStream } from "@yume-chan/stream-extra";
const output = await adb.subprocess.shellProtocol.spawn("cat").wait({
stdin: new ReadableStream({
start(controller) {
controller.enqueue(encodeUtf8("Hello World!"));
controller.close();
},
}),
});
import type { Adb } from "@yume-chan/adb";
import { encodeUtf8 } from "@yume-chan/struct";
import { ReadableStream } from "@yume-chan/stream-extra";
declare const adb: Adb;
const output: Uint8Array = await adb.subprocess.shellProtocol!.spawn("cat").wait({
stdin: new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(encodeUtf8("Hello World!"));
controller.close();
},
}),
});
See documentation page Shell protocol for more details about closing stdin and shell protocol usage.
💥🔄 Reorganize sync API with namespace structure
The sync API has been completely reorganized with a new namespace structure and automatic socket pooling. The public API is now simpler and no longer requires manual socket management.
Socket Pool Architecture
The key change is the introduction of SocketPool, which manages sync socket connections automatically:
- Connection reuse: Sockets are pooled and reused across operations instead of creating new connections for each operation
- Automatic lifecycle management: No need to call
dispose()- sockets are managed by the pool - Configurable pooling: Pool size and idle timeout can be configured via
ServiceOptions - Idle timeout: Idle sockets are automatically closed after a configurable timeout (default: 60 seconds)
The AdbSync.Service class now uses SocketPool internally, which enables the simplified public API.
Public API Changes
The adb.sync() method has been changed to adb.sync property, following the same pattern as other services like subprocess, power, reverse, and tcpip.
Migration guide:
- Replace all calls to
await adb.sync()withadb.sync - No need to call
dispose()anymore - socket pooling is handled automatically - The
AdbSyncclass is now accessed asAdbSync.Service
Before:
import type { Adb } from "@yume-chan/adb";
import { AdbSync } from "@yume-chan/adb";
declare const adb: Adb;
const sync: AdbSync.Service = await adb.sync();
try {
await sync.write({
filename,
file,
});
} finally {
await sync.dispose();
}
After:
import type { Adb } from "@yume-chan/adb";
import { AdbSync } from "@yume-chan/adb";
declare const adb: Adb;
await adb.sync.write({
filename,
file,
});
Internal API Changes
Note: These are internal APIs that are usually not needed directly. Most users should use the public API (adb.sync.read, adb.sync.write, etc.) instead.
Low-level sync functions have been moved into organized namespaces under AdbSync, and now use SocketPool instead of raw AdbSyncSocket:
Before:
import type { AdbSyncSocket } from "@yume-chan/adb";
import {
adbSyncPull,
adbSyncPush,
adbSyncOpenDir,
adbSyncLstat,
} from "@yume-chan/adb";
import type { ReadableStream } from "@yume-chan/stream-extra";
declare const socket: AdbSyncSocket;
declare const path: string;
declare const v2: boolean;
const stream: ReadableStream<Uint8Array> = adbSyncPull(socket, path);
await adbSyncPush({
v2: true,
socket,
filename,
file,
});
for await (const entry of adbSyncOpenDir(socket, path, v2)) {
console.log(entry.name);
}
const stat = await adbSyncLstat(socket, path, v2);
After:
import type { SocketPool } from "@yume-chan/adb";
import { AdbSync } from "@yume-chan/adb";
import type { ReadableStream } from "@yume-chan/stream-extra";
declare const pool: SocketPool;
declare const path: string;
declare const file: ReadableStream<Uint8Array>;
// Pull: AdbSync.Receive namespace
const stream: ReadableStream<Uint8Array> = AdbSync.Receive.stream(pool, path);
// Push: AdbSync.Send namespace
await AdbSync.Send.send({
pool,
filename: path,
file,
version: 2,
});
// Directory listing: AdbSync.OpenDir namespace
for await (const entry of AdbSync.OpenDir.opendir(pool, path, {
version: 2,
})) {
console.log(entry.name);
}
// Stat: AdbSync.Stat namespace
const stat = await AdbSync.Stat.lstat(pool, path, {
version: 2,
});
Socket Pool Usage
For advanced use cases requiring direct socket access, the pool provides acquire() and release() methods:
import type { Adb } from "@yume-chan/adb";
import { AdbSync } from "@yume-chan/adb";
declare const adb: Adb;
const socket = await adb.sync.acquireSocket();
try {
// Use socket directly for custom operations
} finally {
await adb.sync.releaseSocket(socket);
}
The pool also provides a withSocket() helper for automatic resource management:
import type { Adb } from "@yume-chan/adb";
import { AdbSync } from "@yume-chan/adb";
declare const adb: Adb;
await adb.sync.withSocket(async (socket) => {
// Socket is automatically released after use
});
Public API Usage Examples
For most use cases, use the high-level public API:
Reading files:
import type { Adb } from "@yume-chan/adb";
import type { ReadableStream } from "@yume-chan/stream-extra";
declare const adb: Adb;
const stream: ReadableStream<Uint8Array> = adb.sync.read(
"/sdcard/Download/file.txt",
);
Writing files:
import type { Adb } from "@yume-chan/adb";
import type { ReadableStream } from "@yume-chan/stream-extra";
declare const adb: Adb;
declare const readableStream: ReadableStream<Uint8Array>;
await adb.sync.write({
filename: "/sdcard/Download/file.txt",
file: readableStream,
});
Listing directories:
import type { Adb } from "@yume-chan/adb";
import type { AdbSync } from "@yume-chan/adb";
declare const adb: Adb;
for await (const entry: AdbSync.OpenDir.Entry of adb.sync.opendir("/sdcard/Download")) {
console.log(entry.name);
}
// Or get all entries as array
const entries: AdbSync.OpenDir.Entry[] = await adb.sync.readdir("/sdcard/Download");
Getting file stats:
import type { Adb } from "@yume-chan/adb";
import type { AdbSync } from "@yume-chan/adb";
declare const adb: Adb;
const stat: AdbSync.Stat.Stat = await adb.sync.lstat(
"/sdcard/Download/file.txt",
);
// For stat (follows symlinks, requires stat2 feature)
const stat: AdbSync.Stat.Stat = await adb.sync.stat(
"/sdcard/Download/file.txt",
);
Additional Improvements
- Compression support: Runtime-selectable compression (Brotli/LZ4/Zstd) for push operations via
compressionoption - Better error handling: Improved error signaling with dedicated
AdbSyncErrortypes - Abort signal support: Pool can be disposed via abort signal in
ServiceOptions
See the Sync API documentation for detailed usage examples.
💥🔄 Type renames
Several types and interfaces have been renamed for consistency and clarity. These are pure renaming changes without functional modifications.
| Type | Before | After | Related Doc |
|---|---|---|---|
| Credential Store | AdbCredentialStore | AdbCredentialManager | Credential manager |
| PTY Process | AdbPtyProcess | AdbPty | Shell protocol |
| PTY Process (None) | AdbNoneProtocolPtyProcess | AdbNoneProtocolPty | None protocol |
| PTY Process (Shell) | AdbShellProtocolPtyProcess | AdbShellProtocolPty | Shell protocol |
| Socket | AdbSocket | Adb.Socket | Socket |
| Incoming Socket Handler | AdbIncomingSocketHandler | Adb.IncomingSocketHandler | Socket |
| Power Service | AdbPower | AdbPowerService | Power |
| Device Observer Method | stop() | close() | Watch devices |
Migration guide:
Update all imports and type references to use the new names. The APIs remain the same, only the names have changed.
Example 1: Credential Manager
import type {
AdbCredentialManager,
TangoKeyStorage,
} from "@yume-chan/adb-credential-web";
import {
AdbWebCryptoCredentialManager,
TangoIndexedDbStorage,
} from "@yume-chan/adb-credential-web";
const storage: TangoKeyStorage = new TangoIndexedDbStorage();
const credentialManager: AdbCredentialManager =
new AdbWebCryptoCredentialManager(storage);
Example 2: PTY and Socket Types
import type { AdbPty, Adb } from "@yume-chan/adb";
declare const adb: Adb;
const pty: AdbPty = await adb.subprocess.shellProtocol.pty({
command: ["bash"],
});
declare const socket: Adb.Socket;
declare const handler: Adb.IncomingSocketHandler;
@yume-chan/adb-credential-web
💥🔄 Replace AdbWebCredentialStore with AdbWebCryptoCredentialManager
The old AdbWebCredentialStore class can only store private keys in IndexedDB.
It has been replaced with the new AdbWebCryptoCredentialManager and TangoKeyStorage types, which support multiple plaintext and encrypted key storage implementations.
Migration guide:
- Replace
new AdbWebCredentialStore()withnew AdbWebCryptoCredentialManager(storage) - Choose an appropriate storage implementation:
TangoIndexedDbStorage: Uses IndexedDB for storing multiple keys with unlimited capacity. It can read keys stored by previous versions of Tango.TangoLocalStorage: Uses browser localStorage for simple single-key storageTangoPasswordProtectedStorage: Provides password-based encryption for stored keys (requires an underlying storage)TangoPrfStorage: Uses WebAuthn PRF extension for multi-factor authentication (requires an underlying storage)
Before:
import AdbWebCredentialStore from "@yume-chan/adb-credential-web";
const store: AdbWebCredentialStore = new AdbWebCredentialStore();
After:
import type { TangoKeyStorage } from "@yume-chan/adb-credential-web";
import {
AdbWebCryptoCredentialManager,
TangoIndexedDbStorage,
} from "@yume-chan/adb-credential-web";
const storage: TangoKeyStorage = new TangoIndexedDbStorage();
const credentialManager: AdbWebCryptoCredentialManager =
new AdbWebCryptoCredentialManager(storage);
See documentation page Credential manager for more details about credential managers and available storage implementations.
@yume-chan/adb-credential-nodejs
✨ Add TangoNodeStorage class
Added TangoNodeStorage class which is a Node.js ADB key storage manager that implements the TangoKeyStorage interface. It supports all Google ADB key storage conventions including:
- File-based storage compatible with Google ADB format (
~/.android/adbkey) - Secure file permissions (0o600 for private keys, 0o644 for public keys)
- Automatic directory creation and username generation using
username@hostnameformat - ADB_VENDOR_KEYS environment variable support for additional vendor keys
- Proper PEM-format handling with both strict and lax format parsing
- Custom error types for robust key management (
KeyError,InvalidKeyError,VendorKeyError)
See documentation page TangoNodeStorage for more details and usages.
@yume-chan/adb-daemon-webusb
🐛 Fix WebUSB SharedArrayBuffer transfer issue
Fixed an issue where WebUSB transfers would fail when using SharedArrayBuffer. The problem occurred because WebUSB doesn't support SharedArrayBuffer directly, which could cause connection failures.
Fix: Added conversion to local Uint8Array before WebUSB transfer using toLocalUint8Array() function.
🐛 Fix WebUSB connection event handling
Fixed an issue where device observers would lose track of manually selected devices. When a device was selected via navigator.usb.requestDevice(), Chrome wouldn't fire a "connect" event, causing device observers to miss the device.
Fix: Now manually dispatches a connect event after successful device selection to notify observers.
@yume-chan/adb-server-node-tcp
✨ Add new AdbServerNodeTcpConnector constructor overloads
The AdbServerNodeTcpConnector class has been completely rewritten with support for Google ADB-style socket specs and environment variable support.
import { AdbServerNodeTcpConnector } from "@yume-chan/adb-server-node-tcp";
// Using a Node.js `SocketConnectOpts` interface is still supported
const connector: AdbServerNodeTcpConnector = new AdbServerNodeTcpConnector({
host: "localhost",
port: 5037,
});
// 1. ✨ No parameters - uses environment variables or defaults
const connector1: AdbServerNodeTcpConnector = new AdbServerNodeTcpConnector();
// 2. ✨ String specification
const connector2: AdbServerNodeTcpConnector = new AdbServerNodeTcpConnector(
"tcp:localhost:5037",
);
// 3. ✨ TCP socket options with optional port
const connector3: AdbServerNodeTcpConnector = new AdbServerNodeTcpConnector({
host: "localhost",
});
// 4. ✨ IPC socket options (Unix domain sockets)
const connector4: AdbServerNodeTcpConnector = new AdbServerNodeTcpConnector({
path: "/path/to/socket",
});
✨ Add environment variable support
Added support for ADB environment variables that automatically reads ADB_SERVER_SOCKET, ANDROID_ADB_SERVER_ADDRESS, and ANDROID_ADB_SERVER_PORT environment variables.
Usage:
import { AdbServerNodeTcpConnector } from "@yume-chan/adb-server-node-tcp";
// Automatically uses environment variables if set:
// ADB_SERVER_SOCKET - full socket specification
// ANDROID_ADB_SERVER_ADDRESS - server address
// ANDROID_ADB_SERVER_PORT - server port
process.env.ADB_SERVER_SOCKET = "tcp:192.168.1.100:5037";
const connector: AdbServerNodeTcpConnector = new AdbServerNodeTcpConnector();
See documentation page Server client transport for more details about connector usage and transport configuration.