mirror of
https://github.com/viarotel-org/escrcpy.git
synced 2025-02-15 10:02:35 +01:00
247 lines
5.2 KiB
JavaScript
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()
|