Taobin-Recipe-Manager/client-electron/electron/adb-native.ts

166 lines
4.6 KiB
TypeScript
Raw Normal View History

import { Adb, AdbDaemonTransport } from '@yume-chan/adb'
import { type AdbDaemonWebUsbDevice, AdbDaemonWebUsbDeviceManager } from '@yume-chan/adb-daemon-webusb'
import { type BrowserWindow } from 'electron'
import { WebUSB } from 'usb'
import { type AdbCredentialStore, adbGeneratePublicKey } from '@yume-chan/adb'
import { webcrypto } from 'node:crypto'
import { readFile, writeFile } from 'node:fs/promises'
import { homedir, hostname, userInfo } from 'node:os'
import { join } from 'node:path'
class AdbNodeJsCredentialStore implements AdbCredentialStore {
#name: string
constructor(name: string) {
this.#name = name
}
#privateKeyPath() {
return join(homedir(), '.android', 'adbkey')
}
#publicKeyPath() {
return join(homedir(), '.android', 'adbkey.pub')
}
async generateKey() {
const { privateKey: cryptoKey } = await webcrypto.subtle.generateKey(
{
name: 'RSASSA-PKCS1-v1_5',
modulusLength: 2048,
// 65537
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: 'SHA-1'
},
true,
['sign', 'verify']
)
const privateKey = new Uint8Array(await crypto.subtle.exportKey('pkcs8', cryptoKey))
await writeFile(this.#privateKeyPath(), Buffer.from(privateKey).toString('utf8'))
await writeFile(
this.#publicKeyPath(),
`${Buffer.from(adbGeneratePublicKey(privateKey)).toString('base64')} ${this.#name}\n`
)
return {
buffer: privateKey,
name: this.#name
}
}
async #readPubKeyName() {
const content = await readFile(this.#publicKeyPath(), 'utf8')
const pubKeyName = content.split(' ')[1]
return pubKeyName || `${userInfo().username}@${hostname()}`
}
async *iterateKeys() {
const content = await readFile(this.#privateKeyPath(), 'utf8')
const privateKey = Buffer.from(content.split('\n').slice(1, -2).join(''), 'base64')
yield {
buffer: privateKey,
name: await this.#readPubKeyName()
}
}
}
type ConnectedDevice = {
[serial: string]: {
adb: Adb
metadata: {
venderId?: number
productId?: number
manufacturerNam?: string
productName?: string
serialNumber?: string
}
}
}
export class AdbUsbNative {
private _manager: AdbDaemonWebUsbDeviceManager
private _win: BrowserWindow
private _connectedDevices: ConnectedDevice = {}
constructor(win: BrowserWindow) {
const webUsb: WebUSB = new WebUSB({ allowedDevices: [{ serialNumber: 'd' }] })
this._manager = new AdbDaemonWebUsbDeviceManager(webUsb)
this._win = win
}
getAvailableDevices(responseChannel: string) {
this._manager.getDevices().then(devices => {
this._win.webContents.send(
responseChannel,
devices.map(d => ({
vendorId: d.raw.vendorId,
productId: d.raw.productId,
manufacturerNam: d.raw.manufacturerName,
productName: d.raw.productName,
serialNumber: d.raw.serialNumber
}))
)
})
}
getConnectedDevices(responseChannel: string) {
this._win.webContents.send(
responseChannel,
Object.entries(this._connectedDevices).map(([serial, { metadata }]) => ({
serial: serial,
productId: metadata.productId,
venderId: metadata.venderId,
manufacturerNam: metadata.manufacturerNam,
productName: metadata.productName
}))
)
}
connectDevice(responseChanel: string, serial: string) {
this._manager.getDevices().then(devices => {
let device: AdbDaemonWebUsbDevice | null = null
for (const d of devices) {
if (d.serial === serial) {
device = d
break
}
}
if (!device) {
this._win.webContents.send(responseChanel, {
error: 'Device with serialNumber: ' + serial + ' not found'
})
return
}
device.connect().then(connection => {
const credentialStore = new AdbNodeJsCredentialStore(`${userInfo().username}@${hostname()}`)
AdbDaemonTransport.authenticate({
serial: device!.serial,
connection,
credentialStore
}).then(transport => {
this._connectedDevices[device!.serial] = {
adb: new Adb(transport),
metadata: {
venderId: device!.raw.vendorId,
productId: device!.raw.productId,
manufacturerNam: device!.raw.manufacturerName,
productName: device!.raw.productName,
serialNumber: device!.raw.serialNumber
}
}
this._win.webContents.send(responseChanel, {
serial: device!.serial
})
})
})
})
}
}