feat(adb): add pair device and connect device commands to server client mode

This commit is contained in:
Simon Chan 2024-05-16 01:07:04 +08:00
parent f5e1a2b701
commit a6e3f0e5d1
No known key found for this signature in database
GPG key ID: A8B69F750B9BCEDD
2 changed files with 75 additions and 11 deletions

View file

@ -176,7 +176,7 @@ export class AdbServerClient {
throw new Error(`Unexpected response: ${decodeUtf8(response)}`); throw new Error(`Unexpected response: ${decodeUtf8(response)}`);
} }
async connect( async createConnection(
request: string, request: string,
options?: AdbServerConnectionOptions, options?: AdbServerConnectionOptions,
): Promise<AdbServerConnection> { ): Promise<AdbServerConnection> {
@ -219,7 +219,7 @@ export class AdbServerClient {
} }
async getVersion(): Promise<number> { async getVersion(): Promise<number> {
const connection = await this.connect("host:version"); const connection = await this.createConnection("host:version");
const readable = new BufferedReadableStream(connection.readable); const readable = new BufferedReadableStream(connection.readable);
try { try {
const length = hexToNumber(await readable.readExactly(4)); const length = hexToNumber(await readable.readExactly(4));
@ -241,13 +241,13 @@ export class AdbServerClient {
} }
async killServer(): Promise<void> { async killServer(): Promise<void> {
const connection = await this.connect("host:kill"); const connection = await this.createConnection("host:kill");
connection.writable.close().catch(NOOP); connection.writable.close().catch(NOOP);
connection.readable.cancel().catch(NOOP); connection.readable.cancel().catch(NOOP);
} }
async getServerFeatures(): Promise<AdbFeature[]> { async getServerFeatures(): Promise<AdbFeature[]> {
const connection = await this.connect("host:host-features"); const connection = await this.createConnection("host:host-features");
const readable = new BufferedReadableStream(connection.readable); const readable = new BufferedReadableStream(connection.readable);
try { try {
const response = await AdbServerClient.readString(readable); const response = await AdbServerClient.readString(readable);
@ -258,6 +258,44 @@ export class AdbServerClient {
} }
} }
async pairDevice(address: string, code: string): Promise<void> {
const connection = await this.createConnection(
`host:pair:${code}:${address}`,
);
const readable = new BufferedReadableStream(connection.readable);
try {
const response = await AdbServerClient.readString(readable);
if (!response.startsWith("Successfully paired to")) {
throw new AdbServerClient.UnauthorizedError(response);
}
} finally {
connection.writable.close().catch(NOOP);
readable.cancel().catch(NOOP);
}
}
async connectDevice(address: string): Promise<void> {
const connection = await this.createConnection(
`host:connect:${address}`,
);
const readable = new BufferedReadableStream(connection.readable);
try {
const response = await AdbServerClient.readString(readable);
if (response === `already connected to ${address}`) {
throw new AdbServerClient.AlreadyConnectedError(response);
}
if (response === `failed to connect to ${address}`) {
throw new AdbServerClient.UnauthorizedError(response);
}
if (response !== `connected to ${address}`) {
throw new AdbServerClient.NetworkError(response);
}
} finally {
connection.writable.close().catch(NOOP);
readable.cancel().catch(NOOP);
}
}
parseDeviceList(value: string): AdbServerDevice[] { parseDeviceList(value: string): AdbServerDevice[] {
const devices: AdbServerDevice[] = []; const devices: AdbServerDevice[] = [];
for (const line of value.split("\n")) { for (const line of value.split("\n")) {
@ -309,7 +347,7 @@ export class AdbServerClient {
} }
async getDevices(): Promise<AdbServerDevice[]> { async getDevices(): Promise<AdbServerDevice[]> {
const connection = await this.connect("host:devices-l"); const connection = await this.createConnection("host:devices-l");
const readable = new BufferedReadableStream(connection.readable); const readable = new BufferedReadableStream(connection.readable);
try { try {
const response = await AdbServerClient.readString(readable); const response = await AdbServerClient.readString(readable);
@ -323,7 +361,7 @@ export class AdbServerClient {
async trackDevices( async trackDevices(
callback: (devices: AdbServerDevice[]) => void, callback: (devices: AdbServerDevice[]) => void,
): Promise<() => void> { ): Promise<() => void> {
const connection = await this.connect("host:track-devices-l"); const connection = await this.createConnection("host:track-devices-l");
const readable = new BufferedReadableStream(connection.readable); const readable = new BufferedReadableStream(connection.readable);
let running = true; let running = true;
(async () => { (async () => {
@ -380,7 +418,10 @@ export class AdbServerClient {
// Also, if the command is about a device, but didn't specify a selector, // Also, if the command is about a device, but didn't specify a selector,
// it will be executed against the device selected previously by `connectDevice`. // it will be executed against the device selected previously by `connectDevice`.
// Using this method, we can get the transport ID and device features in one connection. // Using this method, we can get the transport ID and device features in one connection.
const socket = await this.connectDevice(device, "host:features"); const socket = await this.createDeviceConnection(
device,
"host:features",
);
try { try {
const readable = new BufferedReadableStream(socket.readable); const readable = new BufferedReadableStream(socket.readable);
const featuresString = await AdbServerClient.readString(readable); const featuresString = await AdbServerClient.readString(readable);
@ -397,7 +438,7 @@ export class AdbServerClient {
* @param service The service to forward * @param service The service to forward
* @returns An `AdbServerSocket` that can be used to communicate with the service * @returns An `AdbServerSocket` that can be used to communicate with the service
*/ */
async connectDevice( async createDeviceConnection(
device: AdbServerDeviceSelector, device: AdbServerDeviceSelector,
service: string, service: string,
): Promise<AdbServerSocket> { ): Promise<AdbServerSocket> {
@ -420,7 +461,7 @@ export class AdbServerClient {
throw new Error("Invalid device selector"); throw new Error("Invalid device selector");
} }
const connection = await this.connect(switchService); const connection = await this.createConnection(switchService);
try { try {
const writer = connection.writable.getWriter(); const writer = connection.writable.getWriter();
@ -496,7 +537,7 @@ export class AdbServerClient {
`wait-for-${type}-${state}`, `wait-for-${type}-${state}`,
); );
const socket = await this.connect(service, options); const socket = await this.createConnection(service, options);
const readable = new BufferedReadableStream(socket.readable); const readable = new BufferedReadableStream(socket.readable);
await AdbServerClient.readOkay(readable); await AdbServerClient.readOkay(readable);
@ -560,3 +601,26 @@ export async function raceSignal<T>(
} }
} }
} }
export namespace AdbServerClient {
export class NetworkError extends Error {
constructor(message: string) {
super(message);
this.name = "ConnectionFailedError";
}
}
export class UnauthorizedError extends Error {
constructor(message: string) {
super(message);
this.name = "UnauthorizedError";
}
}
export class AlreadyConnectedError extends Error {
constructor(message: string) {
super(message);
this.name = "AlreadyConnectedError";
}
}
}

View file

@ -75,7 +75,7 @@ export class AdbServerTransport implements AdbTransport {
} }
async connect(service: string): Promise<AdbSocket> { async connect(service: string): Promise<AdbSocket> {
return await this.#client.connectDevice( return await this.#client.createDeviceConnection(
{ {
transportId: this.transportId, transportId: this.transportId,
}, },