120 lines
3.9 KiB
TypeScript
120 lines
3.9 KiB
TypeScript
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<AdbPacketData, Consumable<AdbPacketInit>> = 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]
|
|
})
|
|
}
|