import { type BrowserWindow } from 'electron/main' import type { AdbPacketData, AdbPacketInit } from '@yume-chan/adb' import { Adb, AdbDaemonTransport } from '@yume-chan/adb' import type { Consumable, ReadableWritablePair } from '@yume-chan/stream-extra' import { WritableStream } from '@yume-chan/stream-extra' import { AdbDaemonDirectSocketsDevice } from './adbaemonDirectSocketsDevice' import AdbWebCredentialStore from '@yume-chan/adb-credential-web' export function AdbTcpSocket(win: BrowserWindow | null, ipcMain: Electron.IpcMain) { if (!win) return const adbConnectionList: { [key: string]: Adb } = {} ipcMain.handle('adb:tcp:socket:getprop', async (_event, serial: string, key: string) => { if (!adbConnectionList[serial]) return Promise.reject('adb is not connected') return adbConnectionList[serial].getProp(key) }) ipcMain.handle('adb:tcp:socket:getDevices', () => { return Object.keys(adbConnectionList).map(serial => { return { name: adbConnectionList[serial].banner.product, serial: serial } }) }) ipcMain.handle('adb:tcp:socket:connect', async (_event, { host, port }: { host: string; port: number }) => { const device: AdbDaemonDirectSocketsDevice = new AdbDaemonDirectSocketsDevice({ host, port }) if (Object.keys(adbConnectionList).includes(device.serial)) { return Promise.resolve({ name: device.name, serial: device.serial }) } try { const connection: ReadableWritablePair> = await device.connect() const credentialStore: AdbWebCredentialStore = new AdbWebCredentialStore() const transport = await AdbDaemonTransport.authenticate({ serial: device.serial, connection: connection, credentialStore: credentialStore }) adbConnectionList[device.serial] = new Adb(transport) const productName = await adbConnectionList[device.serial].getProp('ro.product.name') return { name: productName, serial: device.serial } } catch (e) { return Promise.reject(e) } }) ipcMain.handle( 'adb:tcp:socket:shell', async (_event, { serial, command, callbackId }: { serial: string; command: string; callbackId: string }) => { if (!adbConnectionList[serial]) return Promise.reject('adb is not connected') const process = await adbConnectionList[serial].subprocess.shell(command) await process.stdout .pipeTo( new WritableStream({ write(chunk) { win.webContents.send(callbackId, chunk) } }) ) .then(() => { process.stdin.close() process.stderr.cancel() process.stdout.cancel() process.kill() }) } ) ipcMain.handle( 'adb:tcp:socket:spawn', async (_event, { serial, command, callbackId }: { serial: string; command: string; callbackId: string }) => { if (!adbConnectionList[serial]) return Promise.reject('adb is not connected') const process = await adbConnectionList[serial].subprocess.spawn(command) let buffer: Uint8Array = new Uint8Array() const reader = process.stdout.getReader() // eslint-disable-next-line no-constant-condition while (true) { const { value, done } = await reader.read() if (done) break const oldBuffer = buffer buffer = new Uint8Array(value.length + buffer.length) buffer.set(oldBuffer) buffer.set(value, oldBuffer.length) } reader.releaseLock() await reader.cancel() await process.stdin.close() await process.stderr.cancel() await process.stdout.cancel() await process.kill() win.webContents.send(callbackId, buffer) } ) ipcMain.handle('adb:tcp:socket:disconnect', async (_event, serial: string) => { await adbConnectionList[serial].close() delete adbConnectionList[serial] }) }