Custom credential manager
To create a custom credential manager, you need to implement the AdbCredentialManager interface:
import type { MaybePromiseLike } from "@yume-chan/async";
import type { AdbPrivateKey } from "@yume-chan/adb";
export type MaybeError<T> = T | Error;
export type AdbKeyIterable =
| Iterable<MaybeError<AdbPrivateKey>>
| AsyncIterable<MaybeError<AdbPrivateKey>>;
export interface AdbCredentialManager {
/**
* Generates and stores a RSA private key with modulus length `2048` and public exponent `65537`.
*/
generateKey(): MaybePromiseLike<AdbPrivateKey>;
/**
* Synchronously or asynchronously iterates through all stored RSA private keys.
*
* Each call to `iterateKeys` must return a different iterator that iterate through all stored keys.
*/
iterateKeys(): AdbKeyIterable;
}
Each private key is represented as an AdbPrivateKey object with the following structure:
interface AdbPrivateKey {
readonly buffer: Uint8Array;
readonly name?: string;
}
Where:
buffer: AUint8Arraycontaining the private key in PKCS#8 formatname: An optionalstringfor the key name. On Android 11 and above, it will appear in "Settings -> Developer options -> Wireless debugging -> Paired devices"
Error handling behavior
The iterateKeys method can yield Error objects to indicate non-fatal errors when loading keys. This allows the authentication processor to report the error and continue trying other keys:
// Example error handling in iterateKeys
*iterateKeys(): Generator<MaybeError<AdbPrivateKey>, void, void> {
try {
// Attempt to load a key
const key = this.loadKey();
yield key;
} catch (error) {
// Yield the error instead of throwing it
// This allows the auth processor to continue with other keys
yield error as Error;
}
}
Authentication flow
The authentication processor follows this sequence:
- Calls
iterateKeys()and attempts signature authentication with each key until successful or exhausted - If signature authentication fails but keys were available, tries public key authentication with the first key
- If no keys were available or all authentication attempts failed, calls
generateKey()and uses the new key for public key authentication
Example implementation
Here's an example of a credential manager using local storage (similar to TangoLocalStorage):
- JavaScript
- TypeScript
import { decodeBase64, decodeUtf8, encodeBase64 } from "@yume-chan/adb";
export class CustomLocalStorageCredentialManager {
#storageKey;
constructor(storageKey) {
this.#storageKey = storageKey;
}
async generateKey() {
// In a real implementation, generate a new RSA key pair
// This is a placeholder implementation
const privateKey = new Uint8Array([
/* generated key bytes */
]);
// Store the key
const storedKey = {
privateKey: decodeUtf8(encodeBase64(privateKey)),
name: "custom-generated-key",
};
localStorage.setItem(this.#storageKey, JSON.stringify(storedKey));
return { buffer: privateKey, name: storedKey.name };
}
*iterateKeys() {
const json = localStorage.getItem(this.#storageKey);
if (json) {
try {
const storedKey = JSON.parse(json);
const privateKey = decodeBase64(storedKey.privateKey);
yield {
buffer: privateKey,
name: storedKey.name,
};
} catch (error) {
// Yield error instead of throwing to allow continuing with other keys
yield error;
}
}
}
}
import type { AdbCredentialManager, AdbPrivateKey, MaybeError } from "@yume-chan/adb";
import { decodeBase64, decodeUtf8, encodeBase64 } from "@yume-chan/adb";
interface StoredKey {
privateKey: string; // base64 encoded
name?: string;
}
export class CustomLocalStorageCredentialManager implements AdbCredentialManager {
readonly #storageKey: string;
constructor(storageKey: string) {
this.#storageKey = storageKey;
}
async generateKey(): Promise<AdbPrivateKey> {
// In a real implementation, generate a new RSA key pair
// This is a placeholder implementation
const privateKey = new Uint8Array([
/* generated key bytes */
]);
// Store the key
const storedKey: StoredKey = {
privateKey: decodeUtf8(encodeBase64(privateKey)),
name: "custom-generated-key",
};
localStorage.setItem(this.#storageKey, JSON.stringify(storedKey));
return { buffer: privateKey, name: storedKey.name };
}
*iterateKeys(): Generator<MaybeError<AdbPrivateKey>, void, void> {
const json = localStorage.getItem(this.#storageKey);
if (json) {
try {
const storedKey = JSON.parse(json) as StoredKey;
const privateKey = decodeBase64(storedKey.privateKey);
yield {
buffer: privateKey,
name: storedKey.name,
};
} catch (error) {
// Yield error instead of throwing to allow continuing with other keys
yield error as Error;
}
}
}
}