| | |
| | | |
| | | type BluetoothAccessStatus = 'system_off' | 'unauthorized' | 'authorized'; |
| | | type BluetoothErrorType = 'system_off' | 'permission_deny' | 'env_unsupported' | 'unknown'; |
| | | type MiniappBluetoothAuthState = 'authorized' | 'denied' | 'unknown'; |
| | | type BluetoothOpenResult = { |
| | | opened: boolean; |
| | | errorType: BluetoothErrorType; |
| | |
| | | // 车端钥匙下发失败时的描述文案与文字颜色 |
| | | const UNLOCK_TOKEN_FAILED_TEXT = '蓝牙解锁失败, 请点击右下方按键解锁车门'; |
| | | const UNLOCK_TOKEN_FAILED_COLOR = '#FF4D4F'; |
| | | |
| | | /** |
| | | * 兼容读取小程序蓝牙权限状态。 |
| | | * 优先使用新版 App 级权限接口,旧环境再回退到 getSetting。 |
| | | */ |
| | | async function readMiniappBluetoothAuthState() : Promise<MiniappBluetoothAuthState> { |
| | | try { |
| | | const appAuthorizeSetting = await Promise.resolve((Taro as any).getAppAuthorizeSetting?.()); |
| | | const bluetoothAuthorized = appAuthorizeSetting?.bluetoothAuthorized; |
| | | if (typeof bluetoothAuthorized === 'string') { |
| | | if (bluetoothAuthorized === 'authorized') { |
| | | return 'authorized'; |
| | | } |
| | | if (bluetoothAuthorized === 'denied' || bluetoothAuthorized === 'not determined') { |
| | | return 'denied'; |
| | | } |
| | | } |
| | | } catch (error) { |
| | | } |
| | | |
| | | try { |
| | | const setting = await Taro.getSetting(); |
| | | const authSetting = setting?.authSetting || {}; |
| | | if (Object.prototype.hasOwnProperty.call(authSetting, BLUETOOTH_SCOPE)) { |
| | | return authSetting[BLUETOOTH_SCOPE] === true ? 'authorized' : 'denied'; |
| | | } |
| | | } catch (error) { |
| | | } |
| | | |
| | | return 'unknown'; |
| | | } |
| | | |
| | | /** |
| | | * 输出蓝牙权限链路日志,便于排查授权、系统蓝牙和环境问题。 |
| | |
| | | */ |
| | | const getMiniappBluetoothAuthState = useCallback(async () => { |
| | | try { |
| | | const appAuthorizeSetting = await Promise.resolve((Taro as any).getAppAuthorizeSetting?.()); |
| | | const bluetoothAuthorized = appAuthorizeSetting?.bluetoothAuthorized; |
| | | logBluetoothAccess('读取小程序权限设置', { |
| | | bluetoothAuthorized, |
| | | appAuthorizeSetting |
| | | }); |
| | | if (typeof bluetoothAuthorized === 'string') { |
| | | if (bluetoothAuthorized === 'authorized') { |
| | | return 'authorized'; |
| | | } |
| | | if (bluetoothAuthorized === 'denied' || bluetoothAuthorized === 'not determined') { |
| | | return 'denied'; |
| | | } |
| | | } |
| | | } catch (error) { |
| | | logBluetoothAccess('读取 App 级蓝牙权限设置失败', { |
| | | error |
| | | }); |
| | | } |
| | | |
| | | try { |
| | | const setting = await Taro.getSetting(); |
| | | const authSetting = setting?.authSetting || {}; |
| | | logBluetoothAccess('读取小程序权限设置', { |
| | | logBluetoothAccess('回退读取小程序权限设置', { |
| | | bluetoothScope: authSetting?.[BLUETOOTH_SCOPE], |
| | | authSetting |
| | | }); |
| | |
| | | return authSetting[BLUETOOTH_SCOPE] === true ? 'authorized' : 'denied'; |
| | | } |
| | | } catch (error) { |
| | | logBluetoothAccess('读取小程序权限设置失败', { |
| | | logBluetoothAccess('回退读取小程序权限设置失败', { |
| | | error |
| | | }); |
| | | } |
| | | |
| | | logBluetoothAccess('小程序蓝牙权限状态未知'); |
| | | return 'unknown'; |
| | | }, []); |
| | |
| | | isAuthorizeProtectionActive |
| | | }); |
| | | try { |
| | | const setting = await Taro.getSetting(); |
| | | const appAuthorizeSetting = await Promise.resolve((Taro as any).getAppAuthorizeSetting?.()); |
| | | const bluetoothAuthorized = appAuthorizeSetting?.bluetoothAuthorized; |
| | | logBluetoothAccess('刷新蓝牙状态时读取设置', { |
| | | bluetoothScope: setting?.authSetting?.['scope.bluetooth'], |
| | | authSetting: setting?.authSetting || {} |
| | | bluetoothAuthorized, |
| | | appAuthorizeSetting |
| | | }); |
| | | if (setting?.authSetting?.['scope.bluetooth']) { |
| | | if (bluetoothAuthorized === 'authorized') { |
| | | hasMiniappAuth = true; |
| | | recentAuthorizedAtRef.current = 0; |
| | | } |
| | | } catch (error) { |
| | | logBluetoothAccess('刷新蓝牙状态时读取设置失败', { |
| | | logBluetoothAccess('刷新蓝牙状态时读取 App 级权限失败', { |
| | | error |
| | | }); |
| | | } |
| | | |
| | | if (!hasMiniappAuth) { |
| | | const initialMiniappAuthState = await readMiniappBluetoothAuthState(); |
| | | logBluetoothAccess('刷新蓝牙状态时统一确认权限结果', { |
| | | initialMiniappAuthState |
| | | }); |
| | | if (initialMiniappAuthState === 'authorized') { |
| | | hasMiniappAuth = true; |
| | | Taro.setStorageSync(BLUETOOTH_AUTH_KEY, true); |
| | | recentAuthorizedAtRef.current = 0; |
| | | } |
| | | } |
| | | |
| | | const syncMiniappAuthAfterSystemPrompt = async (scene: string) => { |
| | | const latestMiniappAuthState = await getMiniappBluetoothAuthState(); |
| | | logBluetoothAccess(`${scene}后再次确认小程序蓝牙权限`, { |
| | | latestMiniappAuthState |
| | | }); |
| | | if (latestMiniappAuthState === 'authorized') { |
| | | hasMiniappAuth = true; |
| | | Taro.setStorageSync(BLUETOOTH_AUTH_KEY, true); |
| | | recentAuthorizedAtRef.current = 0; |
| | | return true; |
| | | } |
| | | return false; |
| | | }; |
| | | |
| | | let systemBluetoothEnabled = false; |
| | | const openResult = await openBluetoothAdapterSafe(); |
| | | if (openResult.opened) { |
| | | systemBluetoothEnabled = true; |
| | | if (!hasMiniappAuth) { |
| | | const latestMiniappAuthState = await getMiniappBluetoothAuthState(); |
| | | logBluetoothAccess('蓝牙适配器打开后再次确认小程序蓝牙权限', { |
| | | latestMiniappAuthState |
| | | }); |
| | | if (latestMiniappAuthState === 'authorized') { |
| | | hasMiniappAuth = true; |
| | | Taro.setStorageSync(BLUETOOTH_AUTH_KEY, true); |
| | | recentAuthorizedAtRef.current = 0; |
| | | } |
| | | await syncMiniappAuthAfterSystemPrompt('蓝牙适配器打开'); |
| | | } |
| | | } else if (openResult.errorType === 'system_off') { |
| | | systemBluetoothEnabled = false; |
| | | recentAuthorizedAtRef.current = 0; |
| | | } else if (openResult.errorType === 'permission_deny') { |
| | | systemBluetoothEnabled = true; |
| | | if (!isAuthorizeProtectionActive) { |
| | | const authGrantedAfterPrompt = await syncMiniappAuthAfterSystemPrompt('蓝牙权限弹窗处理'); |
| | | if (!authGrantedAfterPrompt && !isAuthorizeProtectionActive) { |
| | | hasMiniappAuth = false; |
| | | } else { |
| | | } else if (!authGrantedAfterPrompt) { |
| | | logBluetoothAccess('授权保护窗口内忽略瞬时 permission_deny', { |
| | | errCode: openResult.error?.errCode, |
| | | errMsg: openResult.error?.errMsg |