247 lines
5.2 KiB
JavaScript

import { Bonjour } from 'bonjour-service'
import net from 'node:net'
import appStore from '$electron/helpers/store.js'
export const MDNS_CONFIG = {
PAIRING_TYPE: 'adb-tls-pairing',
CONNECT_TYPE: 'adb-tls-connect',
DEFAULT_TIMEOUT: 60 * 1000,
CONNECT_TIMEOUT: 30 * 1000,
}
export const ERROR_CODES = {
TIMEOUT: 'TIMEOUT',
PAIRING_FAILED: 'PAIRING_FAILED',
CONNECTION_FAILED: 'CONNECTION_FAILED',
INVALID_PARAMS: 'INVALID_PARAMS',
}
export class DeviceData {
constructor(name, address, port) {
this.name = name
this.address = address
this.port = port
}
static fromMdnsService(service) {
const ipv4Address = service.addresses?.find(addr => net.isIP(addr) === 4)
if (!ipv4Address)
return null
return new DeviceData(
service.name,
ipv4Address,
service.port,
)
}
}
export class MonitorError extends Error {
constructor(code, message) {
super(message)
this.code = code
}
}
export class DeviceScanner {
constructor() {
this.bonjour = null
this.scanner = null
}
async startScanning(type, callback) {
this.bonjour = new Bonjour()
return new Promise((resolve, reject) => {
this.scanner = this.bonjour.find({ type }, (service) => {
const device = DeviceData.fromMdnsService(service)
if (device) {
callback(device)
}
})
})
}
dispose() {
if (this.scanner) {
this.scanner.stop()
this.scanner = null
}
if (this.bonjour) {
this.bonjour.destroy()
this.bonjour = null
}
}
}
export class AdbConnectionMonitor {
constructor() {
this.deviceScanner = new DeviceScanner()
this.isActive = false
this.adb = null
this.onStatus = () => {}
}
async startQrCodeScanning(options) {
this.validateOptions(options)
const {
adb,
password,
onStatus = () => {},
} = options
this.adb = adb
this.isActive = true
this.onStatus = onStatus
try {
this.onStatus('pairing')
const device = await this.scanForDevice()
await this.pairWithDevice(device, password)
this.onStatus('connecting')
try {
const connectDevice = await this.waitForDeviceConnect(device)
await this.connectToDevice(connectDevice)
}
catch (error) {
if (error.code === ERROR_CODES.TIMEOUT) {
this.onStatus('connecting-fallback')
// 使用回退端口尝试连接
const fallbackPort = this.getBackPort(device)
await this.connectToDevice({
...device,
port: fallbackPort,
})
}
else {
throw error
}
}
this.onStatus('connected')
return {
success: true,
device,
}
}
catch (error) {
this.onStatus('error', error.message)
return {
success: false,
error: error.message,
}
}
finally {
this.dispose()
}
}
validateOptions(options) {
if (!options?.adb) {
throw new MonitorError(
ERROR_CODES.INVALID_PARAMS,
'Adb is required',
)
}
if (!options?.password) {
throw new MonitorError(
ERROR_CODES.INVALID_PARAMS,
'Password is required',
)
}
}
async scanForDevice() {
return new Promise((resolve, reject) => {
const timeoutHandle = setTimeout(() => {
this.dispose()
reject(new MonitorError(
ERROR_CODES.TIMEOUT,
'Connection attempt timed out',
))
}, MDNS_CONFIG.DEFAULT_TIMEOUT)
this.deviceScanner.startScanning(
MDNS_CONFIG.PAIRING_TYPE,
(device) => {
clearTimeout(timeoutHandle)
resolve(device)
},
)
})
}
async pairWithDevice(device, password) {
try {
await this.adb.pair(device.address, device.port, password)
}
catch (error) {
throw new MonitorError(
ERROR_CODES.PAIRING_FAILED,
'Unable to pair with device',
)
}
}
async waitForDeviceConnect(device) {
return new Promise((resolve, reject) => {
const scanner = new DeviceScanner()
const timeoutHandle = setTimeout(() => {
scanner.dispose()
reject(new MonitorError(
ERROR_CODES.TIMEOUT,
'Device connect timeout',
))
}, MDNS_CONFIG.CONNECT_TIMEOUT)
scanner.startScanning(
MDNS_CONFIG.CONNECT_TYPE,
(connectDevice) => {
if (connectDevice.address === device.address) {
clearTimeout(timeoutHandle)
scanner.dispose()
resolve(connectDevice)
}
},
)
})
}
async connectToDevice(device) {
try {
await this.adb.connect(device.address, device.port)
}
catch (error) {
throw new MonitorError(
ERROR_CODES.CONNECTION_FAILED,
`Failed to connect to device: ${error.message}`,
)
}
}
getBackPort(device) {
const devices = appStore.get('device')
const value = Object.entries(devices).reduce((port, [key, value]) => {
if (key.includes(device.address)) {
port = key.split(':')[1]
}
return port
}, 5555)
return value
}
dispose() {
this.deviceScanner.dispose()
this.isActive = false
}
}
export default new AdbConnectionMonitor()