All files / services/ble permissions.ts

0% Statements 0/54
0% Branches 0/47
0% Functions 0/8
0% Lines 0/54

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
// BLE Permissions Helper
// Handles Bluetooth permission requests with graceful fallback
 
import { PermissionsAndroid, Platform, Linking, Alert } from 'react-native';
import { BleManager, State } from 'react-native-ble-plx';
 
export interface PermissionStatus {
  granted: boolean;
  canRequest: boolean; // false if user previously denied with "Don't ask again"
  error?: string;
}
 
export interface BluetoothStatus {
  enabled: boolean;
  canEnable: boolean;
  error?: string;
}
 
/**
 * Check and request BLE permissions based on platform
 * Returns permission status with graceful fallback info
 */
export async function requestBLEPermissions(): Promise<PermissionStatus> {
  if (Platform.OS === 'ios') {
    // iOS handles permissions automatically via Info.plist
    // BLE permission dialog shows on first BLE operation
    return { granted: true, canRequest: true };
  }
 
  if (Platform.OS === 'android') {
    try {
      if (Platform.Version >= 31) {
        // Android 12+ requires BLUETOOTH_SCAN and BLUETOOTH_CONNECT
        const results = await PermissionsAndroid.requestMultiple([
          PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN!,
          PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT!,
          PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION!,
        ]);
 
        const allGranted = Object.values(results).every(
          status => status === PermissionsAndroid.RESULTS.GRANTED
        );
 
        const anyNeverAskAgain = Object.values(results).some(
          status => status === PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN
        );
 
        if (allGranted) {
          return { granted: true, canRequest: true };
        }
 
        if (anyNeverAskAgain) {
          return {
            granted: false,
            canRequest: false,
            error: 'Bluetooth permissions were previously denied. Please enable them in Settings.',
          };
        }
 
        return {
          granted: false,
          canRequest: true,
          error: 'Bluetooth permissions are required to scan for sensors.',
        };
      } else {
        // Android < 12 requires ACCESS_FINE_LOCATION
        const result = await PermissionsAndroid.request(
          PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION!
        );
 
        if (result === PermissionsAndroid.RESULTS.GRANTED) {
          return { granted: true, canRequest: true };
        }
 
        if (result === PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN) {
          return {
            granted: false,
            canRequest: false,
            error: 'Location permission was previously denied. Please enable it in Settings.',
          };
        }
 
        return {
          granted: false,
          canRequest: true,
          error: 'Location permission is required to scan for Bluetooth devices.',
        };
      }
    } catch (error: any) {
      return {
        granted: false,
        canRequest: false,
        error: `Failed to request permissions: ${error.message}`,
      };
    }
  }
 
  // Unknown platform
  return {
    granted: false,
    canRequest: false,
    error: 'Bluetooth permissions not supported on this platform',
  };
}
 
/**
 * Check Bluetooth state (enabled/disabled)
 * Returns status with helpful error messages
 */
export async function checkBluetoothEnabled(manager: BleManager): Promise<BluetoothStatus> {
  try {
    const state = await manager.state();
 
    switch (state) {
      case State.PoweredOn:
        return { enabled: true, canEnable: true };
 
      case State.PoweredOff:
        return {
          enabled: false,
          canEnable: true,
          error: 'Bluetooth is turned off. Please enable it in your device settings.',
        };
 
      case State.Unauthorized:
        return {
          enabled: false,
          canEnable: false,
          error: 'Bluetooth access is not authorized. Please enable permissions in Settings.',
        };
 
      case State.Unsupported:
        return {
          enabled: false,
          canEnable: false,
          error: 'Bluetooth is not supported on this device.',
        };
 
      case State.Resetting:
        return {
          enabled: false,
          canEnable: true,
          error: 'Bluetooth is resetting. Please wait a moment and try again.',
        };
 
      case State.Unknown:
      default:
        return {
          enabled: false,
          canEnable: true,
          error: 'Bluetooth state is unknown. Please check your device settings.',
        };
    }
  } catch (error: any) {
    return {
      enabled: false,
      canEnable: false,
      error: `Failed to check Bluetooth state: ${error.message}`,
    };
  }
}
 
/**
 * Show a user-friendly alert for permission/Bluetooth issues
 * Offers to open Settings if needed
 */
export function showPermissionAlert(
  permissionStatus: PermissionStatus,
  bluetoothStatus: BluetoothStatus
): void {
  // Bluetooth disabled (can be fixed easily)
  if (!bluetoothStatus.enabled && bluetoothStatus.canEnable) {
    Alert.alert(
      'Bluetooth Required',
      bluetoothStatus.error || 'Please enable Bluetooth to scan for sensors.',
      [
        { text: 'Cancel', style: 'cancel' },
        {
          text: 'Open Settings',
          onPress: () => {
            if (Platform.OS === 'ios') {
              Linking.openURL('App-Prefs:Bluetooth');
            } else {
              Linking.sendIntent('android.settings.BLUETOOTH_SETTINGS');
            }
          },
        },
      ]
    );
    return;
  }
 
  // Permission denied with "Never ask again"
  if (!permissionStatus.granted && !permissionStatus.canRequest) {
    Alert.alert(
      'Permissions Required',
      permissionStatus.error || 'Bluetooth permissions are required to scan for sensors. Please enable them in Settings.',
      [
        { text: 'Cancel', style: 'cancel' },
        {
          text: 'Open Settings',
          onPress: () => {
            Linking.openSettings();
          },
        },
      ]
    );
    return;
  }
 
  // Permission denied (can retry)
  if (!permissionStatus.granted) {
    Alert.alert(
      'Permissions Required',
      permissionStatus.error || 'Bluetooth permissions are required to scan for sensors.',
      [{ text: 'OK' }]
    );
    return;
  }
 
  // Bluetooth not supported or other unrecoverable error
  if (!bluetoothStatus.canEnable) {
    Alert.alert(
      'Bluetooth Unavailable',
      bluetoothStatus.error || 'Bluetooth is not available on this device.',
      [{ text: 'OK' }]
    );
  }
}
 
/**
 * Comprehensive pre-scan check
 * Returns true if ready to scan, false otherwise (with user alert shown)
 */
export async function checkBLEReadiness(manager: BleManager): Promise<boolean> {
  // Step 1: Check permissions
  const permissionStatus = await requestBLEPermissions();
 
  // Step 2: Check Bluetooth state
  const bluetoothStatus = await checkBluetoothEnabled(manager);
 
  // Step 3: If not ready, show appropriate alert
  if (!permissionStatus.granted || !bluetoothStatus.enabled) {
    showPermissionAlert(permissionStatus, bluetoothStatus);
    return false;
  }
 
  return true;
}