2023-10-12 11:35:27 +02:00
|
|
|
import util from 'node:util'
|
2023-11-14 03:53:58 +01:00
|
|
|
import { exec as _exec, spawn } from 'node:child_process'
|
2023-10-13 11:01:59 +02:00
|
|
|
import path from 'node:path'
|
|
|
|
import fs from 'node:fs'
|
|
|
|
import dayjs from 'dayjs'
|
2023-09-18 09:07:28 +02:00
|
|
|
import { Adb } from '@devicefarmer/adbkit'
|
2023-10-25 12:17:19 +02:00
|
|
|
import { uniq } from 'lodash-es'
|
2024-05-15 05:16:02 +02:00
|
|
|
import appStore from '$electron/helpers/store.js'
|
|
|
|
import { adbPath } from '$electron/configs/index.js'
|
2024-09-07 13:20:23 +02:00
|
|
|
import { formatFileSize } from '$renderer/utils/index'
|
2023-10-17 09:27:08 +02:00
|
|
|
|
2023-11-14 03:53:58 +01:00
|
|
|
const exec = util.promisify(_exec)
|
2023-10-07 07:22:40 +02:00
|
|
|
|
2023-09-18 09:07:28 +02:00
|
|
|
let client = null
|
|
|
|
|
|
|
|
window.addEventListener('beforeunload', () => {
|
|
|
|
if (client) {
|
|
|
|
client.kill()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2023-10-26 12:11:52 +02:00
|
|
|
appStore.onDidChange('common.adbPath', async (value, oldValue) => {
|
2023-10-20 08:23:48 +02:00
|
|
|
if (value === oldValue) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if (value === client?.options?.bin) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-10-19 05:44:17 +02:00
|
|
|
if (client) {
|
|
|
|
await client.kill().catch(e => console.warn(e))
|
|
|
|
client = null
|
|
|
|
}
|
2023-10-20 08:23:48 +02:00
|
|
|
|
2023-10-24 10:46:51 +02:00
|
|
|
client = Adb.createClient({ bin: value || adbPath })
|
2023-10-19 05:44:17 +02:00
|
|
|
})
|
|
|
|
|
2023-11-14 03:53:58 +01:00
|
|
|
const shell = async (command) => {
|
|
|
|
const execPath = appStore.get('common.adbPath') || adbPath
|
2024-05-04 17:09:56 +02:00
|
|
|
return exec(`"${execPath}" ${command}`, {
|
2023-11-14 03:53:58 +01:00
|
|
|
env: { ...process.env },
|
|
|
|
shell: true,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
const spawnShell = async (command, { stdout, stderr } = {}) => {
|
|
|
|
const spawnPath = appStore.get('common.adbPath') || adbPath
|
|
|
|
const args = command.split(' ')
|
|
|
|
|
|
|
|
const spawnProcess = spawn(`"${spawnPath}"`, args, {
|
|
|
|
env: { ...process.env },
|
|
|
|
shell: true,
|
|
|
|
encoding: 'utf8',
|
|
|
|
})
|
|
|
|
|
|
|
|
spawnProcess.stdout.on('data', (data) => {
|
|
|
|
const stringData = data.toString()
|
|
|
|
|
|
|
|
if (stdout) {
|
|
|
|
stdout(stringData, spawnProcess)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2023-11-14 04:06:36 +01:00
|
|
|
const stderrList = []
|
2023-11-14 03:53:58 +01:00
|
|
|
spawnProcess.stderr.on('data', (data) => {
|
|
|
|
const stringData = data.toString()
|
|
|
|
|
2023-11-14 04:06:36 +01:00
|
|
|
stderrList.push(stringData)
|
2023-11-14 03:53:58 +01:00
|
|
|
|
|
|
|
console.error('spawnProcess.stderr.data:', stringData)
|
|
|
|
|
|
|
|
if (stderr) {
|
|
|
|
stderr(stringData, spawnProcess)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
spawnProcess.on('close', (code) => {
|
|
|
|
if (code === 0) {
|
|
|
|
resolve()
|
|
|
|
}
|
|
|
|
else {
|
2023-11-14 04:06:36 +01:00
|
|
|
reject(
|
|
|
|
new Error(stderrList.join(',') || `Command failed with code ${code}`),
|
|
|
|
)
|
2023-11-14 03:53:58 +01:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
spawnProcess.on('error', (err) => {
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-10-17 11:54:29 +02:00
|
|
|
const getDevices = async () => client.listDevicesWithPaths()
|
2023-11-14 03:56:00 +01:00
|
|
|
|
2023-10-25 12:17:19 +02:00
|
|
|
const deviceShell = async (id, command) => {
|
|
|
|
const res = await client.getDevice(id).shell(command).then(Adb.util.readAll)
|
|
|
|
return res.toString()
|
|
|
|
}
|
2023-11-14 03:56:00 +01:00
|
|
|
|
2023-10-17 11:54:29 +02:00
|
|
|
const kill = async (...params) => client.kill(...params)
|
2023-11-14 03:56:00 +01:00
|
|
|
|
2023-10-17 11:54:29 +02:00
|
|
|
const connect = async (...params) => client.connect(...params)
|
2023-11-14 03:56:00 +01:00
|
|
|
|
2023-10-17 11:54:29 +02:00
|
|
|
const disconnect = async (...params) => client.disconnect(...params)
|
2023-09-18 09:07:28 +02:00
|
|
|
|
2023-10-11 10:42:47 +02:00
|
|
|
const getDeviceIP = async (id) => {
|
|
|
|
try {
|
|
|
|
const { stdout } = await shell(`-s ${id} shell ip -f inet addr show wlan0`)
|
|
|
|
const reg = /inet ([0-9.]+)\/\d+/
|
|
|
|
const match = stdout.match(reg)
|
|
|
|
const value = match[1]
|
2023-10-30 09:36:16 +01:00
|
|
|
|
2023-10-11 10:42:47 +02:00
|
|
|
return value
|
|
|
|
}
|
|
|
|
catch (error) {
|
2024-03-13 04:03:50 +01:00
|
|
|
console.warn('adbkit.getDeviceIP.error', error.message)
|
2023-10-11 10:42:47 +02:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-17 11:54:29 +02:00
|
|
|
const tcpip = async (id, port = 5555) => client.getDevice(id).tcpip(port)
|
2023-10-11 10:42:47 +02:00
|
|
|
|
2023-10-13 11:01:59 +02:00
|
|
|
const screencap = async (deviceId, options = {}) => {
|
|
|
|
let fileStream = null
|
|
|
|
try {
|
|
|
|
const device = client.getDevice(deviceId)
|
|
|
|
fileStream = await device.screencap()
|
|
|
|
}
|
|
|
|
catch (error) {
|
|
|
|
console.warn(error?.message || error)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!fileStream) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
const fileName = `Screencap-${dayjs().format('YYYY-MM-DD-HH-mm-ss')}.png`
|
|
|
|
const savePath = options.savePath || path.resolve('../', fileName)
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
fileStream
|
|
|
|
.pipe(fs.createWriteStream(savePath))
|
|
|
|
.on('finish', () => {
|
|
|
|
resolve(true)
|
|
|
|
})
|
|
|
|
.on('error', (error) => {
|
|
|
|
console.warn(error?.message || error)
|
|
|
|
reject(false)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-10-17 11:54:29 +02:00
|
|
|
const install = async (id, path) => client.getDevice(id).install(path)
|
|
|
|
|
2023-10-30 09:36:16 +01:00
|
|
|
const isInstalled = async (id, pkg) => client.getDevice(id).isInstalled(pkg)
|
|
|
|
|
2023-10-20 12:17:58 +02:00
|
|
|
const version = async () => client.version()
|
|
|
|
|
2023-10-25 12:17:19 +02:00
|
|
|
const display = async (deviceId) => {
|
|
|
|
let value = []
|
|
|
|
try {
|
|
|
|
const res = await deviceShell(deviceId, 'dumpsys display')
|
|
|
|
|
|
|
|
const regex = /Display Id=(\d+)/g
|
|
|
|
|
|
|
|
const match = res.match(regex) || []
|
|
|
|
|
|
|
|
const mapValue = match.map(item => item.split('=')[1])
|
|
|
|
|
|
|
|
value = uniq(mapValue)
|
|
|
|
}
|
|
|
|
catch (error) {
|
2023-10-27 13:18:09 +02:00
|
|
|
console.warn(error?.message || error)
|
2023-10-25 12:17:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
|
2023-11-07 07:09:09 +01:00
|
|
|
const clearOverlayDisplayDevices = async (deviceId) => {
|
|
|
|
return deviceShell(
|
|
|
|
deviceId,
|
|
|
|
'settings put global overlay_display_devices none',
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-11-09 09:17:56 +01:00
|
|
|
const push = async (
|
|
|
|
id,
|
|
|
|
filePath,
|
|
|
|
{ progress, savePath = `/sdcard/Download/${path.basename(filePath)}` } = {},
|
|
|
|
) => {
|
|
|
|
const res = await client.getDevice(id).push(filePath, savePath)
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
res.on('progress', (stats) => {
|
|
|
|
progress?.(stats)
|
|
|
|
})
|
|
|
|
|
2024-07-12 14:11:44 +02:00
|
|
|
res.on('end', () => {
|
|
|
|
resolve(savePath)
|
2023-11-09 09:17:56 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
res.on('error', (err) => {
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-09-18 09:07:28 +02:00
|
|
|
const watch = async (callback) => {
|
|
|
|
const tracker = await client.trackDevices()
|
2023-10-11 10:42:47 +02:00
|
|
|
tracker.on('add', async (ret) => {
|
|
|
|
const host = await getDeviceIP(ret.id)
|
|
|
|
callback('add', { ...ret, $host: host })
|
2023-09-18 09:07:28 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
tracker.on('remove', (device) => {
|
2023-10-07 07:22:40 +02:00
|
|
|
callback('remove', device)
|
2023-09-18 09:07:28 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
tracker.on('end', (ret) => {
|
2023-10-07 07:22:40 +02:00
|
|
|
callback('end', ret)
|
2023-09-18 09:07:28 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
tracker.on('error', (err) => {
|
2023-10-07 07:22:40 +02:00
|
|
|
callback('error', err)
|
2023-09-18 09:07:28 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
const close = () => tracker.end()
|
|
|
|
|
|
|
|
return close
|
|
|
|
}
|
|
|
|
|
2024-09-07 13:20:23 +02:00
|
|
|
async function getFiles(id, path) {
|
|
|
|
const value = await client.getDevice(id).readdir(path)
|
|
|
|
|
|
|
|
return value.map(item => ({
|
|
|
|
...item,
|
|
|
|
type: item.isFile() ? 'file' : 'directory',
|
|
|
|
name: item.name,
|
|
|
|
size: formatFileSize(item.size),
|
|
|
|
updateTime: dayjs(item.mtime).format('YYYY-MM-DD HH:mm:ss'),
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
2023-09-18 09:07:28 +02:00
|
|
|
export default () => {
|
2023-10-26 12:11:52 +02:00
|
|
|
const binPath = appStore.get('common.adbPath') || adbPath
|
2023-10-20 08:23:48 +02:00
|
|
|
|
|
|
|
client = Adb.createClient({
|
|
|
|
bin: binPath,
|
|
|
|
})
|
|
|
|
|
2023-09-18 09:07:28 +02:00
|
|
|
return {
|
|
|
|
shell,
|
2023-11-14 03:53:58 +01:00
|
|
|
spawnShell,
|
2023-10-11 10:42:47 +02:00
|
|
|
getDevices,
|
|
|
|
deviceShell,
|
2023-09-18 09:07:28 +02:00
|
|
|
kill,
|
|
|
|
connect,
|
|
|
|
disconnect,
|
2023-10-11 10:42:47 +02:00
|
|
|
getDeviceIP,
|
|
|
|
tcpip,
|
2023-10-13 11:01:59 +02:00
|
|
|
screencap,
|
2023-10-17 11:54:29 +02:00
|
|
|
install,
|
2023-10-30 09:36:16 +01:00
|
|
|
isInstalled,
|
2023-10-20 12:17:58 +02:00
|
|
|
version,
|
2023-10-25 12:17:19 +02:00
|
|
|
display,
|
2023-11-07 07:09:09 +01:00
|
|
|
clearOverlayDisplayDevices,
|
2023-11-09 09:17:56 +01:00
|
|
|
push,
|
2023-10-13 11:01:59 +02:00
|
|
|
watch,
|
2024-09-07 13:20:23 +02:00
|
|
|
getFiles,
|
2023-09-18 09:07:28 +02:00
|
|
|
}
|
|
|
|
}
|