185 lines
4.4 KiB
TypeScript
185 lines
4.4 KiB
TypeScript
|
|
import { Adb, AdbDaemonTransport, encodeUtf8 } from '@yume-chan/adb';
|
||
|
|
import AdbWebCredentialStore from '@yume-chan/adb-credential-web';
|
||
|
|
import {
|
||
|
|
AdbDaemonWebUsbDeviceManager,
|
||
|
|
AdbDaemonWebUsbDeviceObserver,
|
||
|
|
type AdbDaemonWebUsbDevice
|
||
|
|
} from '@yume-chan/adb-daemon-webusb';
|
||
|
|
import { AdbInstance } from '../../../routes/state.svelte';
|
||
|
|
import { deviceCredentialManager } from './deviceCredManager';
|
||
|
|
import { Consumable, MaybeConsumable, ReadableStream } from '@yume-chan/stream-extra';
|
||
|
|
import { AdbScrcpyClient } from '@yume-chan/adb-scrcpy';
|
||
|
|
|
||
|
|
export async function connnectViaWebUSB() {
|
||
|
|
const device = await AdbDaemonWebUsbDeviceManager.BROWSER?.requestDevice();
|
||
|
|
console.log('usb ok', globalThis.navigator.usb);
|
||
|
|
if (device) {
|
||
|
|
console.log('connect ', device.name);
|
||
|
|
|
||
|
|
try {
|
||
|
|
const credentialStore = new AdbWebCredentialStore();
|
||
|
|
const connection = await device.connect();
|
||
|
|
|
||
|
|
const transport = await AdbDaemonTransport.authenticate({
|
||
|
|
connection: connection,
|
||
|
|
serial: device.serial,
|
||
|
|
credentialStore: credentialStore
|
||
|
|
});
|
||
|
|
|
||
|
|
const adb = new Adb(transport);
|
||
|
|
saveAdbInstance(adb);
|
||
|
|
|
||
|
|
// save device info
|
||
|
|
await deviceCredentialManager.saveDeviceInfo(device);
|
||
|
|
} catch (e: any) {
|
||
|
|
console.error('error on connect', e);
|
||
|
|
throw new Error(e.toString());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function connectDeviceByCred(
|
||
|
|
device: AdbDaemonWebUsbDevice,
|
||
|
|
credStore: AdbWebCredentialStore
|
||
|
|
) {
|
||
|
|
try {
|
||
|
|
const connection = await device.connect();
|
||
|
|
const transport = await AdbDaemonTransport.authenticate({
|
||
|
|
connection: connection,
|
||
|
|
serial: device.serial,
|
||
|
|
credentialStore: credStore
|
||
|
|
});
|
||
|
|
|
||
|
|
const adb = new Adb(transport);
|
||
|
|
saveAdbInstance(adb);
|
||
|
|
|
||
|
|
return true;
|
||
|
|
} catch (error) {
|
||
|
|
throw error;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export function saveAdbInstance(adb: Adb | undefined) {
|
||
|
|
AdbInstance.instance = adb;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function getAdbInstance() {
|
||
|
|
return AdbInstance.instance;
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function executeCmd(command: string) {
|
||
|
|
let instance = getAdbInstance();
|
||
|
|
|
||
|
|
if (!instance) {
|
||
|
|
console.error('instance not found');
|
||
|
|
return {};
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
if (instance?.subprocess.shellProtocol?.isSupported) {
|
||
|
|
const result = await instance.subprocess.shellProtocol.spawnWaitText(command);
|
||
|
|
return {
|
||
|
|
output: result.stdout,
|
||
|
|
error: result.stderr,
|
||
|
|
exitCode: result.exitCode
|
||
|
|
};
|
||
|
|
} else {
|
||
|
|
const process = await instance.subprocess.noneProtocol.spawn(command);
|
||
|
|
const reader = process.output.getReader();
|
||
|
|
const chunks = [];
|
||
|
|
const decoder = new TextDecoder();
|
||
|
|
|
||
|
|
while (true) {
|
||
|
|
const { done, value } = await reader.read();
|
||
|
|
if (done) break;
|
||
|
|
chunks.push(decoder.decode(value, { stream: true }));
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
output: chunks.join('')
|
||
|
|
};
|
||
|
|
}
|
||
|
|
} catch (e: any) {
|
||
|
|
console.log(e.message);
|
||
|
|
//ExactReadable ended
|
||
|
|
if (e.message.includes('ExactReadable ended')) {
|
||
|
|
console.error('connection cut off');
|
||
|
|
return {
|
||
|
|
error: 'ExactReadableEndedError'
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
console.error('error while execute command', e);
|
||
|
|
return {};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function disconnect() {
|
||
|
|
let instance = getAdbInstance();
|
||
|
|
if (instance) {
|
||
|
|
await instance.close();
|
||
|
|
console.log('close instance');
|
||
|
|
saveAdbInstance(undefined);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function pull(filename: string) {
|
||
|
|
let instance = getAdbInstance();
|
||
|
|
if (instance) {
|
||
|
|
let chunkList: Uint8Array<ArrayBufferLike>[] = [];
|
||
|
|
let sync = await instance.sync();
|
||
|
|
const content = sync.read(filename);
|
||
|
|
let result = content.values();
|
||
|
|
let res;
|
||
|
|
|
||
|
|
let result_string = '';
|
||
|
|
|
||
|
|
while ((res = await result.next()) != null) {
|
||
|
|
// console.log(res.value);
|
||
|
|
if (res.value != undefined) {
|
||
|
|
result_string += new TextDecoder().decode(res.value);
|
||
|
|
}
|
||
|
|
if (res.done) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return result_string;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function push(path: string, obj: string) {
|
||
|
|
let instance = getAdbInstance();
|
||
|
|
if (instance) {
|
||
|
|
let sync = await instance.sync();
|
||
|
|
const encoder = new TextEncoder();
|
||
|
|
|
||
|
|
const file: ReadableStream<MaybeConsumable<Uint8Array>> = new ReadableStream({
|
||
|
|
start(controller) {
|
||
|
|
controller.enqueue(new Uint8Array(encoder.encode(obj)));
|
||
|
|
controller.close();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
try {
|
||
|
|
console.log('support push v2', sync.supportsSendReceiveV2);
|
||
|
|
|
||
|
|
await sync.write({
|
||
|
|
filename: path,
|
||
|
|
file
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
console.log('error while trying to write to machine', error);
|
||
|
|
} finally {
|
||
|
|
await sync.dispose();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// logcat stream
|
||
|
|
|
||
|
|
// TODO: screen mirror
|
||
|
|
export function getScrcpyBinaryFromSource() {
|
||
|
|
//https://github.com/Genymobile/scrcpy/releases
|
||
|
|
}
|