- 增加了wrapper处理原生app的交互

This commit is contained in:
Yudi Xiao
2026-04-08 17:04:29 +08:00
parent 90dcd9605c
commit f9dfde0ace
19 changed files with 1501 additions and 32 deletions

View File

@@ -57,6 +57,7 @@ class BleSDK {
// 批量设备发现缓冲
private pendingDevices: Array<{ id: string; name: string | null; rssi: number | null }> = []
private discoveredDevices: Array<{ id: string; name: string | null; rssi: number | null }> = []
private flushTimer: ReturnType<typeof setTimeout> | null = null
initialize() {
@@ -146,59 +147,84 @@ class BleSDK {
// ====== 接收原生 App 的消息指令 ======
private setupMessageHandler() {
addMessageListener((event: Record<string, any>) => {
const { action, ...params } = event
console.log(`[BleSDK] Received action: ${action}`, params)
void this.handleAction(event)
})
}
private async handleAction(event: Record<string, any>) {
const { action, requestId, ...params } = event
console.log(`[BleSDK] Received action: ${action}`, params)
try {
let data: any = null
switch (action) {
case 'scan':
this.startScan()
await this.startScan()
data = { started: true }
break
case 'stopScan':
this.stopScan()
data = { stopped: true }
break
case 'connect':
this.connect(params.deviceId)
data = await this.connect(params.deviceId)
break
case 'disconnect':
this.disconnect()
data = await this.disconnect()
break
case 'getDeviceInfo':
this.getDeviceInfo()
data = await this.getDeviceInfo()
break
case 'getVersion':
this.getVersion()
data = await this.getVersion()
break
case 'bind':
this.bind(params.userId)
data = await this.bind(params.userId)
break
case 'unbind':
this.unbind(params.userId)
data = await this.unbind(params.userId)
break
case 'deleteFile':
this.deleteFile(params.key)
data = await this.deleteFile(params.key)
break
case 'transferFile':
this.transferFile(params.fileUri, params.commandType)
data = await this.transferFile(params.fileUri, params.commandType)
break
default:
sendMessage({ event: 'error', type: 'unknownAction', message: `Unknown action: ${action}` })
throw new Error(`Unknown action: ${action}`)
}
})
if (requestId) {
this.sendResponse(requestId, action, data)
}
} catch (e: any) {
const message = e instanceof Error ? e.message : String(e)
if (requestId) {
this.sendErrorResponse(requestId, action, message)
return
}
sendMessage({ event: 'error', type: action ?? 'unknownAction', message })
}
}
// ====== 蓝牙操作实现 ======
private async startScan() {
try {
const hasPerms = await this.requestPermissions()
if (!hasPerms) {
sendMessage({ event: 'error', type: 'permission', message: 'Bluetooth permission denied' })
return
}
const hasPerms = await this.requestPermissions()
if (!hasPerms) {
const error = new Error('Bluetooth permission denied')
this.updateState(STATE_KEYS.ERROR, error.message)
sendMessage({ event: 'error', type: 'permission', message: error.message })
throw error
}
try {
this.updateState(STATE_KEYS.BT_STATE, 'scanning')
this.updateState(STATE_KEYS.DISCOVERED_DEVICES, [])
this.pendingDevices = []
this.discoveredDevices = []
await this.bleClient.startScan(
[BLE_UUIDS.SERVICE],
@@ -223,6 +249,7 @@ class BleSDK {
this.updateState(STATE_KEYS.BT_STATE, 'idle')
this.updateState(STATE_KEYS.ERROR, e.message)
sendMessage({ event: 'error', type: 'scan', message: e.message })
throw e
}
}
@@ -235,8 +262,7 @@ class BleSDK {
private async connect(deviceId: string) {
if (!deviceId) {
sendMessage({ event: 'error', type: 'connect', message: 'deviceId is required' })
return
throw new Error('deviceId is required')
}
this.stopScan()
@@ -258,10 +284,15 @@ class BleSDK {
// 自动获取设备信息
this.getDeviceInfo().catch(() => {})
return {
id: device.id,
name: device.name || device.localName || null,
}
} catch (e: any) {
this.updateState(STATE_KEYS.BT_STATE, 'idle')
this.updateState(STATE_KEYS.ERROR, e.message)
sendMessage({ event: 'error', type: 'connect', message: e.message })
throw e
}
}
@@ -278,68 +309,75 @@ class BleSDK {
this.updateState(STATE_KEYS.BT_STATE, 'disconnected')
sendMessage({ event: 'disconnected' })
return { success: true }
} catch (e: any) {
sendMessage({ event: 'error', type: 'disconnect', message: e.message })
throw e
}
}
private async getDeviceInfo() {
if (!this.connectedDeviceId) return
if (!this.connectedDeviceId) throw new Error('No device connected')
try {
const promise = this.createWaitable<DeviceInfo>('deviceInfo')
await this.deviceInfoService.getDeviceInfo(this.connectedDeviceId)
return await promise
} catch (e: any) {
sendMessage({ event: 'error', type: 'deviceInfo', message: e.message })
throw e
}
}
private async getVersion() {
if (!this.connectedDeviceId) return
if (!this.connectedDeviceId) throw new Error('No device connected')
try {
const promise = this.createWaitable<VersionInfo>('versionInfo')
await this.deviceInfoService.getDeviceVersion(this.connectedDeviceId)
return await promise
} catch (e: any) {
sendMessage({ event: 'error', type: 'version', message: e.message })
throw e
}
}
private async bind(userId: string) {
if (!this.connectedDeviceId) return
if (!this.connectedDeviceId) throw new Error('No device connected')
try {
const promise = this.createWaitable<BindingResponse>('bindDevice')
await this.deviceInfoService.bindDevice(this.connectedDeviceId, userId)
return await promise
} catch (e: any) {
sendMessage({ event: 'error', type: 'bind', message: e.message })
throw e
}
}
private async unbind(userId: string) {
if (!this.connectedDeviceId) return
if (!this.connectedDeviceId) throw new Error('No device connected')
try {
const promise = this.createWaitable<UnBindResponse>('unbindDevice')
await this.deviceInfoService.unbindDevice(this.connectedDeviceId, userId)
return await promise
} catch (e: any) {
sendMessage({ event: 'error', type: 'unbind', message: e.message })
throw e
}
}
private async deleteFile(key: string) {
if (!this.connectedDeviceId) return
if (!this.connectedDeviceId) throw new Error('No device connected')
try {
const promise = this.createWaitable<DeleteFileResponse>('deleteFile')
await this.deviceInfoService.deleteFile(this.connectedDeviceId, key)
return await promise
} catch (e: any) {
sendMessage({ event: 'error', type: 'deleteFile', message: e.message })
throw e
}
}
private async transferFile(fileUri: string, commandType?: number) {
if (!this.connectedDeviceId) return
if (!this.connectedDeviceId) throw new Error('No device connected')
try {
this.updateState(STATE_KEYS.TRANSFER_PROGRESS, 0)
const type = commandType ?? COMMAND_TYPES.TRANSFER_ANI_VIDEO
@@ -356,9 +394,11 @@ class BleSDK {
)
sendMessage({ event: 'transferComplete' })
return { success: true }
} catch (e: any) {
this.updateState(STATE_KEYS.ERROR, e.message)
sendMessage({ event: 'error', type: 'transfer', message: e.message })
throw e
}
}
@@ -373,7 +413,9 @@ class BleSDK {
}
private queueDevice(device: { id: string; name: string | null; rssi: number | null }) {
if (this.pendingDevices.some((d) => d.id === device.id)) return
if (this.discoveredDevices.some((d) => d.id === device.id)) return
this.discoveredDevices.push(device)
this.pendingDevices.push(device)
if (this.flushTimer) clearTimeout(this.flushTimer)
@@ -382,12 +424,38 @@ class BleSDK {
private flushDevices() {
if (this.pendingDevices.length === 0) return
const devices = [...this.pendingDevices]
const devices = [...this.discoveredDevices]
this.pendingDevices = []
this.updateState(STATE_KEYS.DISCOVERED_DEVICES, devices)
sendMessage({ event: 'devicesFound', devices })
}
private sendResponse(requestId: string, action: string, data: any) {
sendMessage({ event: 'response', requestId, action, success: true, data: this.serializeForNative(data) })
}
private sendErrorResponse(requestId: string, action: string | undefined, message: string) {
sendMessage({ event: 'response', requestId, action, success: false, error: message })
}
private serializeForNative(value: any): any {
if (typeof value === 'bigint') {
return value.toString()
}
if (Array.isArray(value)) {
return value.map((item) => this.serializeForNative(item))
}
if (value && typeof value === 'object') {
return Object.fromEntries(
Object.entries(value).map(([key, nestedValue]) => [key, this.serializeForNative(nestedValue)])
)
}
return value
}
private createWaitable<T>(opId: string, timeout = 10000): Promise<T> {
return new Promise<T>((resolve, reject) => {
const timeoutId = setTimeout(() => {