import { Adb, AdbDaemonTransport, encodeUtf8 } from '@yume-chan/adb'; import AdbWebCredentialStore from '@yume-chan/adb-credential-web'; import { AdbDaemonWebUsbDevice, AdbDaemonWebUsbDeviceManager, AdbDaemonWebUsbDeviceObserver } 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'; import { addNotification } from '../stores/noti'; import { handleAdbPayload } from '../handlers/adbPayloadHandler'; import { adbWriter } from '../stores/adbWriter'; import { WritableStream } from '@yume-chan/stream-extra'; import { env } from '$env/dynamic/public'; let syncConnection: any = null; 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); await saveAdbInstance(adb); await connectToAndroidServer(); // save device info await deviceCredentialManager.saveDeviceInfo(device); } catch (e: any) { console.error('error on connect', e); if (e instanceof AdbDaemonWebUsbDevice.DeviceBusyError) { addNotification( 'ERR:Device is already in use by another program, please close the program and try again' ); } throw e; } } } 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); await saveAdbInstance(adb); await connectToAndroidServer(); return true; } catch (error) { throw error; } } export async function saveAdbInstance(adb: Adb | undefined) { await cleanupSync(); 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')) { return { output: '', exitCode: 1, error: 'ExactReadableEndedError' }; } console.error('error while execute command', e); return {}; } } export async function disconnect() { let instance = getAdbInstance(); if (instance) { try { await instance.close(); console.log('close instance'); } finally { await saveAdbInstance(undefined); } } } export async function cleanupSync() { if (syncConnection) { try { await syncConnection.dispose(); } catch (e) { console.error('error on dispose sync', e); } } syncConnection = null; } export async function pull(filename: string, timeoutMs: number = 5000) { let instance = getAdbInstance(); await cleanupSync(); try { if (instance) { let chunkList: Uint8Array[] = []; const syncProm = instance.sync(); const timeoutProm = new Promise((_, reject) => { setTimeout(() => reject(new Error('sync timeout')), timeoutMs); }); syncConnection = await Promise.race([syncProm, timeoutProm]); const content = syncConnection.read(filename); let result_string = ''; for await (const chunk of content) { result_string += new TextDecoder().decode(chunk); } return result_string; } } catch (pull_error: any) { console.log('pulling error', pull_error); } finally { await cleanupSync(); } } 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> = 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(); } } } // NOTE: adb reverse is not work by unavailable features support async function connectToAndroidServer() { try { let inst = getAdbInstance(); if (!inst) { console.warn('adb instance not found'); return; } // await push('/sdcard/coffeevending/enable_adb_block_watch', '1'); const stream = await inst.transport.connect(env.PUBLIC_BREW_CONN_PORT); const writer = stream.writable.getWriter(); const reader = stream.readable.getReader(); console.log('checking on writer ', writer); adbWriter.set(writer); if (writer) { addNotification('INFO:Enable Brewing Mode T on machine'); } else { addNotification('WARN:Brewing Mode T unavailable'); } (async () => { try { while (true) { const { value, done } = await reader.read(); if (done) break; handleAdbPayload(new TextDecoder().decode(value)); } } catch (e) { console.error('read error', e); } finally { adbWriter.set(null); addNotification('WARN:Brewing Mode T Offline ...'); } })(); } catch (err) { console.error('Connection failed. Suspect java running or not', err); addNotification('ERR:Fail to enable brewing mode T'); } } // logcat stream // TODO: screen mirror export function getScrcpyBinaryFromSource() { //https://github.com/Genymobile/scrcpy/releases }