@mentra/bluetooth-sdk 3.2.0-dev.177 → 3.2.0-dev.181
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +6 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/ConnectedDeviceMatcher.kt +69 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedReleaseMetadata.kt +5 -5
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +57 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/OtaManifest.kt +6 -5
- package/android/src/main/java/com/mentra/bluetoothsdk/connection/ConnectionModels.kt +38 -1
- package/android/src/test/java/com/mentra/bluetoothsdk/ConnectedDeviceMatcherTest.kt +161 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/OtaManifestDowngradeTest.kt +8 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/ScanCompletionTest.kt +87 -0
- package/build/BluetoothSdk.types.d.ts +7 -0
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +2 -1
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +28 -49
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/_private/scanSession.d.ts +21 -0
- package/build/_private/scanSession.d.ts.map +1 -0
- package/build/_private/scanSession.js +75 -0
- package/build/_private/scanSession.js.map +1 -0
- package/build/generated/releaseMetadata.js +5 -5
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js.map +1 -1
- package/build/react/useBluetoothScan.d.ts +2 -1
- package/build/react/useBluetoothScan.d.ts.map +1 -1
- package/build/react/useBluetoothScan.js +13 -0
- package/build/react/useBluetoothScan.js.map +1 -1
- package/build/react/useMentraBluetooth.d.ts +2 -1
- package/build/react/useMentraBluetooth.d.ts.map +1 -1
- package/build/react/useMentraBluetooth.js +1 -0
- package/build/react/useMentraBluetooth.js.map +1 -1
- package/ios/BluetoothSdkModule.swift +7 -0
- package/ios/Source/BluetoothSdkDefaults.swift +1 -1
- package/ios/Source/GeneratedReleaseMetadata.swift +5 -5
- package/ios/Source/MentraBluetoothSDK.swift +43 -2
- package/ios/Source/OtaManifest.swift +6 -5
- package/ios/Source/connection/ScanSession.swift +11 -0
- package/ios/Source/internal/BluetoothAvailability.swift +7 -0
- package/ios/Source/internal/ConnectedDeviceMatcher.swift +45 -0
- package/ios/Tests/ConnectedDeviceMatcherTests.swift +27 -0
- package/ios/Tests/OtaManifestDowngradeTests.swift +20 -0
- package/package.json +1 -1
- package/src/BluetoothSdk.types.ts +8 -0
- package/src/_private/BluetoothSdkModule.ts +39 -57
- package/src/_private/scanSession.ts +88 -0
- package/src/generated/releaseMetadata.ts +5 -5
- package/src/index.ts +1 -0
- package/src/react/useBluetoothScan.ts +12 -1
- package/src/react/useMentraBluetooth.ts +2 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMentraBluetooth.d.ts","sourceRoot":"","sources":["../../src/react/useMentraBluetooth.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,cAAc,EACd,MAAM,EACN,WAAW,EACX,uBAAuB,EACvB,aAAa,EAGb,OAAO,EACP,uBAAuB,EACvB,gBAAgB,EAChB,UAAU,EACX,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EAEL,KAAK,oBAAoB,
|
|
1
|
+
{"version":3,"file":"useMentraBluetooth.d.ts","sourceRoot":"","sources":["../../src/react/useMentraBluetooth.ts"],"names":[],"mappings":"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"}
|
|
@@ -99,6 +99,7 @@ function scanController(connection) {
|
|
|
99
99
|
active: connection.scan.scanning,
|
|
100
100
|
clear: connection.scan.clearResults,
|
|
101
101
|
devices: connection.scan.devices,
|
|
102
|
+
diagnostic: connection.scan.diagnostic,
|
|
102
103
|
error: connection.scan.error,
|
|
103
104
|
model: connection.scan.model,
|
|
104
105
|
selectedDevice: connection.scan.selectedDevice,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMentraBluetooth.js","sourceRoot":"","sources":["../../src/react/useMentraBluetooth.ts"],"names":[],"mappings":"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","sourcesContent":["import {useState} from \"react\"\n\nimport BluetoothSdk from \"../index\"\nimport {\n DEFAULT_VOICE_ACTIVITY_DETECTION_ENABLED,\n DeviceModels,\n createDisconnectedGlassesStatus,\n isConnectedGlassesConnectionStatus,\n isReadyGlassesConnectionStatus,\n} from \"../BluetoothSdk.types\"\nimport type {\n ConnectOptions,\n Device,\n DeviceModel,\n GlassesConnectionStatus,\n HotspotStatus,\n PublicBluetoothStatus,\n PublicGlassesStatus,\n MicMode,\n SettingsAckSuccessEvent,\n WifiSearchResult,\n WifiStatus,\n} from \"../BluetoothSdk.types\"\n\nimport {\n useGlassesConnection,\n type DefaultDeviceStorage,\n type GlassesConnectionHookResult,\n} from \"./useGlassesConnection\"\n\nexport type BatteryState = {\n charging: boolean\n level: number | null\n}\n\nexport type ConnectedGlassesInfo = {\n appVersion?: string\n bluetoothName?: string\n buildNumber?: string\n color?: string\n deviceModel?: string\n firmwareVersion?: string\n serialNumber?: string\n style?: string\n}\n\nexport type FirmwareInfo = {\n appVersion?: string\n buildNumber?: string\n source:\n | \"app\"\n | \"bes\"\n | \"device\"\n | \"firmware\"\n | \"left\"\n | \"mtk\"\n | \"right\"\n | \"unknown\"\n version: string | null\n}\n\nexport type SignalState = {\n strengthDbm: number | null\n updatedAt: number | null\n}\n\nexport type GlassesRuntimeState =\n | {\n connected: false\n connection: Exclude<GlassesConnectionStatus, {state: \"connected\"}>\n ready: false\n }\n | {\n battery: BatteryState\n connected: true\n connection: Extract<GlassesConnectionStatus, {state: \"connected\"}>\n device: ConnectedGlassesInfo\n firmware: FirmwareInfo\n hotspot: HotspotStatus\n ready: boolean\n signal: SignalState\n voiceActivityDetectionEnabled: boolean\n wifi: WifiStatus\n }\n\nexport type GalleryModeState = {\n applying: boolean\n enabled: boolean\n error: unknown | null\n}\n\nexport type PhoneSdkRuntimeState = {\n currentMic: MicMode | null\n defaultDevice: Device | null\n galleryMode: GalleryModeState\n lastLog: string[]\n micRanking: MicMode[]\n otherBluetoothConnected: boolean\n searching: boolean\n searchingController: boolean\n systemMicUnavailable: boolean\n wifiScanResults: WifiSearchResult[]\n}\n\nexport type ScanController = {\n active: boolean\n clear: () => void\n devices: Device[]\n error: unknown | null\n model: DeviceModel\n selectedDevice: Device | null\n selectDevice: (device: Device | null) => void\n setModel: (model: DeviceModel) => void\n start: (model?: DeviceModel) => Promise<Device[]>\n stop: () => Promise<void>\n}\n\nexport type UseMentraBluetoothOptions = {\n autoConnectDefault?: boolean\n defaultDeviceStorage?: DefaultDeviceStorage\n defaultModel?: DeviceModel\n onError?: (error: unknown) => void\n scanTimeoutMs?: number\n}\n\nexport type MentraBluetoothSession = {\n busy: boolean\n clearDefaultDevice: () => Promise<void>\n connect: (device?: Device, options?: ConnectOptions) => Promise<void>\n connectDefault: (options?: ConnectOptions) => Promise<void>\n defaultDevice: Device | null\n disconnect: () => Promise<void>\n error: unknown | null\n glasses: GlassesRuntimeState\n refresh: () => Promise<void>\n scan: ScanController\n sdk: PhoneSdkRuntimeState\n setDefaultDevice: (device: Device | null) => Promise<void>\n setGalleryModeEnabled: (enabled: boolean) => Promise<SettingsAckSuccessEvent>\n setVoiceActivityDetectionEnabled: (enabled: boolean) => Promise<void>\n}\n\nfunction stringValue(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined\n}\n\nfunction numberValue(value: unknown): number | null {\n return typeof value === \"number\" && Number.isFinite(value) ? value : null\n}\n\nfunction firmwareInfo(status: Partial<PublicGlassesStatus>): FirmwareInfo {\n const firmwareSources = [\n [\"firmwareVersion\", \"firmware\"],\n [\"deviceFirmwareVersion\", \"device\"],\n [\"rightFirmwareVersion\", \"right\"],\n [\"leftFirmwareVersion\", \"left\"],\n [\"besFirmwareVersion\", \"bes\"],\n [\"mtkFirmwareVersion\", \"mtk\"],\n [\"appVersion\", \"app\"],\n ] as const\n\n for (const [key, source] of firmwareSources) {\n const value = stringValue((status as Record<string, unknown>)[key])\n if (value) {\n return {\n appVersion: stringValue(status.appVersion),\n buildNumber: stringValue(status.buildNumber),\n source,\n version: value,\n }\n }\n }\n\n return {\n appVersion: stringValue(status.appVersion),\n buildNumber: stringValue(status.buildNumber),\n source: \"unknown\",\n version: null,\n }\n}\n\nfunction batteryState(status: Partial<PublicGlassesStatus>): BatteryState {\n const level = typeof status.batteryLevel === \"number\" && status.batteryLevel >= 0 ? status.batteryLevel : null\n return {\n charging: status.charging ?? false,\n level,\n }\n}\n\nfunction connectedGlassesInfo(status: Partial<PublicGlassesStatus>): ConnectedGlassesInfo {\n return {\n appVersion: stringValue(status.appVersion),\n bluetoothName: stringValue(status.bluetoothName),\n buildNumber: stringValue(status.buildNumber),\n color: stringValue(status.color),\n deviceModel: stringValue(status.deviceModel),\n firmwareVersion: stringValue(status.firmwareVersion),\n serialNumber: stringValue(status.serialNumber),\n style: stringValue(status.style),\n }\n}\n\nfunction runtimeGlassesState(status: Partial<PublicGlassesStatus>): GlassesRuntimeState {\n const connection = status.connection ?? createDisconnectedGlassesStatus().connection ?? {state: \"disconnected\"}\n\n if (!isConnectedGlassesConnectionStatus(connection)) {\n return {\n connected: false,\n connection,\n ready: false,\n }\n }\n\n return {\n battery: batteryState(status),\n connected: true,\n connection,\n device: connectedGlassesInfo(status),\n firmware: firmwareInfo(status),\n hotspot: status.hotspot ?? {state: \"disabled\"},\n ready: isReadyGlassesConnectionStatus(connection),\n signal: {\n strengthDbm: numberValue((status as Record<string, unknown>).signalStrength),\n updatedAt: numberValue((status as Record<string, unknown>).signalStrengthUpdatedAt),\n },\n voiceActivityDetectionEnabled: status.voiceActivityDetectionEnabled ?? DEFAULT_VOICE_ACTIVITY_DETECTION_ENABLED,\n wifi: status.wifi ?? {state: \"disconnected\"},\n }\n}\n\nfunction phoneSdkState(\n status: Partial<PublicBluetoothStatus>,\n defaultDevice: Device | null,\n galleryMode: GalleryModeState,\n): PhoneSdkRuntimeState {\n return {\n currentMic: status.currentMic || null,\n defaultDevice,\n galleryMode,\n lastLog: status.lastLog ?? [],\n micRanking: status.micRanking ?? [],\n otherBluetoothConnected: status.otherBtConnected ?? false,\n searching: status.searching ?? false,\n searchingController: status.searchingController ?? false,\n systemMicUnavailable: status.systemMicUnavailable ?? false,\n wifiScanResults: status.wifiScanResults ?? [],\n }\n}\n\nfunction scanController(connection: GlassesConnectionHookResult): ScanController {\n return {\n active: connection.scan.scanning,\n clear: connection.scan.clearResults,\n devices: connection.scan.devices,\n error: connection.scan.error,\n model: connection.scan.model,\n selectedDevice: connection.scan.selectedDevice,\n selectDevice: connection.scan.selectDevice,\n setModel: connection.scan.setModel,\n start: connection.scan.startScan,\n stop: connection.scan.stopScan,\n }\n}\n\nexport function useMentraBluetooth(options: UseMentraBluetoothOptions = {}): MentraBluetoothSession {\n const connection = useGlassesConnection({\n autoConnectDefault: options.autoConnectDefault,\n defaultDeviceStorage: options.defaultDeviceStorage,\n onError: options.onError,\n scanModel: options.defaultModel ?? DeviceModels.MentraLive,\n scanTimeoutMs: options.scanTimeoutMs,\n })\n const [galleryModeApplying, setGalleryModeApplying] = useState(false)\n const [galleryModeError, setGalleryModeError] = useState<unknown | null>(null)\n\n async function setGalleryModeEnabled(enabled: boolean): Promise<SettingsAckSuccessEvent> {\n setGalleryModeApplying(true)\n setGalleryModeError(null)\n try {\n return await BluetoothSdk.setGalleryModeEnabled(enabled)\n } catch (error) {\n setGalleryModeError(error)\n options.onError?.(error)\n throw error\n } finally {\n setGalleryModeApplying(false)\n }\n }\n\n async function setVoiceActivityDetectionEnabled(enabled: boolean) {\n try {\n await BluetoothSdk.setVoiceActivityDetectionEnabled(enabled)\n } catch (error) {\n options.onError?.(error)\n throw error\n }\n }\n\n const galleryMode: GalleryModeState = {\n applying: galleryModeApplying,\n enabled: connection.bluetoothStatus.galleryModeEnabled !== false,\n error: galleryModeError,\n }\n\n return {\n busy: connection.busy || galleryModeApplying,\n clearDefaultDevice: connection.clearDefaultDevice,\n connect: connection.connect,\n connectDefault: connection.connectDefault,\n defaultDevice: connection.defaultDevice,\n disconnect: connection.disconnect,\n error: galleryModeError ?? connection.error,\n glasses: runtimeGlassesState(connection.glassesStatus),\n refresh: connection.refresh,\n scan: scanController(connection),\n sdk: phoneSdkState(connection.bluetoothStatus, connection.defaultDevice, galleryMode),\n setDefaultDevice: connection.setDefaultDevice,\n setGalleryModeEnabled,\n setVoiceActivityDetectionEnabled,\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"useMentraBluetooth.js","sourceRoot":"","sources":["../../src/react/useMentraBluetooth.ts"],"names":[],"mappings":"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","sourcesContent":["import {useState} from \"react\"\n\nimport BluetoothSdk from \"../index\"\nimport {\n DEFAULT_VOICE_ACTIVITY_DETECTION_ENABLED,\n DeviceModels,\n createDisconnectedGlassesStatus,\n isConnectedGlassesConnectionStatus,\n isReadyGlassesConnectionStatus,\n} from \"../BluetoothSdk.types\"\nimport type {\n ConnectOptions,\n Device,\n DeviceModel,\n GlassesConnectionStatus,\n HotspotStatus,\n PublicBluetoothStatus,\n PublicGlassesStatus,\n MicMode,\n SettingsAckSuccessEvent,\n WifiSearchResult,\n WifiStatus,\n} from \"../BluetoothSdk.types\"\n\nimport {\n useGlassesConnection,\n type DefaultDeviceStorage,\n type GlassesConnectionHookResult,\n} from \"./useGlassesConnection\"\n\nexport type BatteryState = {\n charging: boolean\n level: number | null\n}\n\nexport type ConnectedGlassesInfo = {\n appVersion?: string\n bluetoothName?: string\n buildNumber?: string\n color?: string\n deviceModel?: string\n firmwareVersion?: string\n serialNumber?: string\n style?: string\n}\n\nexport type FirmwareInfo = {\n appVersion?: string\n buildNumber?: string\n source:\n | \"app\"\n | \"bes\"\n | \"device\"\n | \"firmware\"\n | \"left\"\n | \"mtk\"\n | \"right\"\n | \"unknown\"\n version: string | null\n}\n\nexport type SignalState = {\n strengthDbm: number | null\n updatedAt: number | null\n}\n\nexport type GlassesRuntimeState =\n | {\n connected: false\n connection: Exclude<GlassesConnectionStatus, {state: \"connected\"}>\n ready: false\n }\n | {\n battery: BatteryState\n connected: true\n connection: Extract<GlassesConnectionStatus, {state: \"connected\"}>\n device: ConnectedGlassesInfo\n firmware: FirmwareInfo\n hotspot: HotspotStatus\n ready: boolean\n signal: SignalState\n voiceActivityDetectionEnabled: boolean\n wifi: WifiStatus\n }\n\nexport type GalleryModeState = {\n applying: boolean\n enabled: boolean\n error: unknown | null\n}\n\nexport type PhoneSdkRuntimeState = {\n currentMic: MicMode | null\n defaultDevice: Device | null\n galleryMode: GalleryModeState\n lastLog: string[]\n micRanking: MicMode[]\n otherBluetoothConnected: boolean\n searching: boolean\n searchingController: boolean\n systemMicUnavailable: boolean\n wifiScanResults: WifiSearchResult[]\n}\n\nexport type ScanController = {\n active: boolean\n clear: () => void\n devices: Device[]\n diagnostic: GlassesConnectionHookResult[\"scan\"][\"diagnostic\"]\n error: unknown | null\n model: DeviceModel\n selectedDevice: Device | null\n selectDevice: (device: Device | null) => void\n setModel: (model: DeviceModel) => void\n start: (model?: DeviceModel) => Promise<Device[]>\n stop: () => Promise<void>\n}\n\nexport type UseMentraBluetoothOptions = {\n autoConnectDefault?: boolean\n defaultDeviceStorage?: DefaultDeviceStorage\n defaultModel?: DeviceModel\n onError?: (error: unknown) => void\n scanTimeoutMs?: number\n}\n\nexport type MentraBluetoothSession = {\n busy: boolean\n clearDefaultDevice: () => Promise<void>\n connect: (device?: Device, options?: ConnectOptions) => Promise<void>\n connectDefault: (options?: ConnectOptions) => Promise<void>\n defaultDevice: Device | null\n disconnect: () => Promise<void>\n error: unknown | null\n glasses: GlassesRuntimeState\n refresh: () => Promise<void>\n scan: ScanController\n sdk: PhoneSdkRuntimeState\n setDefaultDevice: (device: Device | null) => Promise<void>\n setGalleryModeEnabled: (enabled: boolean) => Promise<SettingsAckSuccessEvent>\n setVoiceActivityDetectionEnabled: (enabled: boolean) => Promise<void>\n}\n\nfunction stringValue(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined\n}\n\nfunction numberValue(value: unknown): number | null {\n return typeof value === \"number\" && Number.isFinite(value) ? value : null\n}\n\nfunction firmwareInfo(status: Partial<PublicGlassesStatus>): FirmwareInfo {\n const firmwareSources = [\n [\"firmwareVersion\", \"firmware\"],\n [\"deviceFirmwareVersion\", \"device\"],\n [\"rightFirmwareVersion\", \"right\"],\n [\"leftFirmwareVersion\", \"left\"],\n [\"besFirmwareVersion\", \"bes\"],\n [\"mtkFirmwareVersion\", \"mtk\"],\n [\"appVersion\", \"app\"],\n ] as const\n\n for (const [key, source] of firmwareSources) {\n const value = stringValue((status as Record<string, unknown>)[key])\n if (value) {\n return {\n appVersion: stringValue(status.appVersion),\n buildNumber: stringValue(status.buildNumber),\n source,\n version: value,\n }\n }\n }\n\n return {\n appVersion: stringValue(status.appVersion),\n buildNumber: stringValue(status.buildNumber),\n source: \"unknown\",\n version: null,\n }\n}\n\nfunction batteryState(status: Partial<PublicGlassesStatus>): BatteryState {\n const level = typeof status.batteryLevel === \"number\" && status.batteryLevel >= 0 ? status.batteryLevel : null\n return {\n charging: status.charging ?? false,\n level,\n }\n}\n\nfunction connectedGlassesInfo(status: Partial<PublicGlassesStatus>): ConnectedGlassesInfo {\n return {\n appVersion: stringValue(status.appVersion),\n bluetoothName: stringValue(status.bluetoothName),\n buildNumber: stringValue(status.buildNumber),\n color: stringValue(status.color),\n deviceModel: stringValue(status.deviceModel),\n firmwareVersion: stringValue(status.firmwareVersion),\n serialNumber: stringValue(status.serialNumber),\n style: stringValue(status.style),\n }\n}\n\nfunction runtimeGlassesState(status: Partial<PublicGlassesStatus>): GlassesRuntimeState {\n const connection = status.connection ?? createDisconnectedGlassesStatus().connection ?? {state: \"disconnected\"}\n\n if (!isConnectedGlassesConnectionStatus(connection)) {\n return {\n connected: false,\n connection,\n ready: false,\n }\n }\n\n return {\n battery: batteryState(status),\n connected: true,\n connection,\n device: connectedGlassesInfo(status),\n firmware: firmwareInfo(status),\n hotspot: status.hotspot ?? {state: \"disabled\"},\n ready: isReadyGlassesConnectionStatus(connection),\n signal: {\n strengthDbm: numberValue((status as Record<string, unknown>).signalStrength),\n updatedAt: numberValue((status as Record<string, unknown>).signalStrengthUpdatedAt),\n },\n voiceActivityDetectionEnabled: status.voiceActivityDetectionEnabled ?? DEFAULT_VOICE_ACTIVITY_DETECTION_ENABLED,\n wifi: status.wifi ?? {state: \"disconnected\"},\n }\n}\n\nfunction phoneSdkState(\n status: Partial<PublicBluetoothStatus>,\n defaultDevice: Device | null,\n galleryMode: GalleryModeState,\n): PhoneSdkRuntimeState {\n return {\n currentMic: status.currentMic || null,\n defaultDevice,\n galleryMode,\n lastLog: status.lastLog ?? [],\n micRanking: status.micRanking ?? [],\n otherBluetoothConnected: status.otherBtConnected ?? false,\n searching: status.searching ?? false,\n searchingController: status.searchingController ?? false,\n systemMicUnavailable: status.systemMicUnavailable ?? false,\n wifiScanResults: status.wifiScanResults ?? [],\n }\n}\n\nfunction scanController(connection: GlassesConnectionHookResult): ScanController {\n return {\n active: connection.scan.scanning,\n clear: connection.scan.clearResults,\n devices: connection.scan.devices,\n diagnostic: connection.scan.diagnostic,\n error: connection.scan.error,\n model: connection.scan.model,\n selectedDevice: connection.scan.selectedDevice,\n selectDevice: connection.scan.selectDevice,\n setModel: connection.scan.setModel,\n start: connection.scan.startScan,\n stop: connection.scan.stopScan,\n }\n}\n\nexport function useMentraBluetooth(options: UseMentraBluetoothOptions = {}): MentraBluetoothSession {\n const connection = useGlassesConnection({\n autoConnectDefault: options.autoConnectDefault,\n defaultDeviceStorage: options.defaultDeviceStorage,\n onError: options.onError,\n scanModel: options.defaultModel ?? DeviceModels.MentraLive,\n scanTimeoutMs: options.scanTimeoutMs,\n })\n const [galleryModeApplying, setGalleryModeApplying] = useState(false)\n const [galleryModeError, setGalleryModeError] = useState<unknown | null>(null)\n\n async function setGalleryModeEnabled(enabled: boolean): Promise<SettingsAckSuccessEvent> {\n setGalleryModeApplying(true)\n setGalleryModeError(null)\n try {\n return await BluetoothSdk.setGalleryModeEnabled(enabled)\n } catch (error) {\n setGalleryModeError(error)\n options.onError?.(error)\n throw error\n } finally {\n setGalleryModeApplying(false)\n }\n }\n\n async function setVoiceActivityDetectionEnabled(enabled: boolean) {\n try {\n await BluetoothSdk.setVoiceActivityDetectionEnabled(enabled)\n } catch (error) {\n options.onError?.(error)\n throw error\n }\n }\n\n const galleryMode: GalleryModeState = {\n applying: galleryModeApplying,\n enabled: connection.bluetoothStatus.galleryModeEnabled !== false,\n error: galleryModeError,\n }\n\n return {\n busy: connection.busy || galleryModeApplying,\n clearDefaultDevice: connection.clearDefaultDevice,\n connect: connection.connect,\n connectDefault: connection.connectDefault,\n defaultDevice: connection.defaultDevice,\n disconnect: connection.disconnect,\n error: galleryModeError ?? connection.error,\n glasses: runtimeGlassesState(connection.glassesStatus),\n refresh: connection.refresh,\n scan: scanController(connection),\n sdk: phoneSdkState(connection.bluetoothStatus, connection.defaultDevice, galleryMode),\n setDefaultDevice: connection.setDefaultDevice,\n setGalleryModeEnabled,\n setVoiceActivityDetectionEnabled,\n }\n}\n"]}
|
|
@@ -220,6 +220,13 @@ public class BluetoothSdkModule: Module, MentraBluetoothSDKDelegate {
|
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
+
AsyncFunction("getScanDiagnostic") { (model: String) -> [String: String]? in
|
|
224
|
+
await MainActor.run {
|
|
225
|
+
guard let diagnostic = self.bluetoothSdk().scanDiagnostic(for: DeviceModel.fromDeviceType(model)) else { return nil }
|
|
226
|
+
return ["code": diagnostic.code, "message": diagnostic.message]
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
223
230
|
AsyncFunction("stopScan") {
|
|
224
231
|
await MainActor.run {
|
|
225
232
|
self.bluetoothSdk().stopScan()
|
|
@@ -11,7 +11,7 @@ enum BluetoothSdkDefaults {
|
|
|
11
11
|
static let voiceActivityDetectionEnabled = false
|
|
12
12
|
static let loudnessGateEnabled = false
|
|
13
13
|
private static let infoSdkVersionKey = "MentraBluetoothSdkVersion"
|
|
14
|
-
private static let swiftPackageSdkVersion = "3.2.0-dev.
|
|
14
|
+
private static let swiftPackageSdkVersion = "3.2.0-dev.181"
|
|
15
15
|
private static let swiftPackageSdkVersionPlaceholder = "__MENTRA" + "_BLUETOOTH_SDK_VERSION__"
|
|
16
16
|
|
|
17
17
|
private static func normalizedSdkVersion(_ value: String?) -> String? {
|
|
@@ -3,9 +3,9 @@ import Foundation
|
|
|
3
3
|
/// Generated by release CI. Do not edit in a release checkout.
|
|
4
4
|
enum GeneratedReleaseMetadata {
|
|
5
5
|
static let familyBaseVersion = "3.2.0"
|
|
6
|
-
static let releaseIdentity = "3.2.0-dev.
|
|
7
|
-
static let releaseSetId = "mentra-3.2.0-dev.
|
|
8
|
-
static let sourceCommit = "
|
|
9
|
-
static let otaManifestUrl = "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.
|
|
10
|
-
static let otaManifestSha256 = "
|
|
6
|
+
static let releaseIdentity = "3.2.0-dev.181"
|
|
7
|
+
static let releaseSetId = "mentra-3.2.0-dev.181"
|
|
8
|
+
static let sourceCommit = "89a4792321ce95bd16aadd721bc3dbc20e338861"
|
|
9
|
+
static let otaManifestUrl = "https://github.com/Mentra-Community/MentraOS/releases/download/mentra-builds-v3.2.0/mentra-live-ota-3.2.0-dev.181.json"
|
|
10
|
+
static let otaManifestSha256 = "d9a100cc81733d02848a86c7617eff154f4cd9d714d1d36407789aeebbf71251"
|
|
11
11
|
}
|
|
@@ -24,6 +24,7 @@ private final class ActiveScanSession {
|
|
|
24
24
|
let model: DeviceModel
|
|
25
25
|
let onResults: ([Device]) -> Void
|
|
26
26
|
let onComplete: ([Device]) -> Void
|
|
27
|
+
let onDiagnostic: ((ScanDiagnostic) -> Void)?
|
|
27
28
|
var latestResults: [Device] = []
|
|
28
29
|
var timeoutTask: Task<Void, Never>?
|
|
29
30
|
weak var publicSession: ScanSession?
|
|
@@ -31,11 +32,13 @@ private final class ActiveScanSession {
|
|
|
31
32
|
init(
|
|
32
33
|
model: DeviceModel,
|
|
33
34
|
onResults: @escaping ([Device]) -> Void,
|
|
34
|
-
onComplete: @escaping ([Device]) -> Void
|
|
35
|
+
onComplete: @escaping ([Device]) -> Void,
|
|
36
|
+
onDiagnostic: ((ScanDiagnostic) -> Void)?
|
|
35
37
|
) {
|
|
36
38
|
self.model = model
|
|
37
39
|
self.onResults = onResults
|
|
38
40
|
self.onComplete = onComplete
|
|
41
|
+
self.onDiagnostic = onDiagnostic
|
|
39
42
|
}
|
|
40
43
|
}
|
|
41
44
|
|
|
@@ -354,13 +357,27 @@ public final class MentraBluetoothSDK {
|
|
|
354
357
|
timeout: TimeInterval = 15,
|
|
355
358
|
onResults: @escaping ([Device]) -> Void,
|
|
356
359
|
onComplete: @escaping ([Device]) -> Void = { _ in }
|
|
360
|
+
) throws -> ScanSession {
|
|
361
|
+
try scan(model: model, timeout: timeout, onResults: onResults, onDiagnostic: nil, onComplete: onComplete)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/// Optional advisory before an empty completed scan. Existing scan overloads
|
|
365
|
+
/// retain their signatures and behavior; cancellation does not produce hints.
|
|
366
|
+
@discardableResult
|
|
367
|
+
public func scan(
|
|
368
|
+
model: DeviceModel,
|
|
369
|
+
timeout: TimeInterval = 15,
|
|
370
|
+
onResults: @escaping ([Device]) -> Void,
|
|
371
|
+
onDiagnostic: ((ScanDiagnostic) -> Void)?,
|
|
372
|
+
onComplete: @escaping ([Device]) -> Void = { _ in }
|
|
357
373
|
) throws -> ScanSession {
|
|
358
374
|
let normalizedTimeout = timeout > 0 && timeout.isFinite ? timeout : 15
|
|
359
375
|
let id = UUID()
|
|
360
376
|
let activeSession = ActiveScanSession(
|
|
361
377
|
model: model,
|
|
362
378
|
onResults: onResults,
|
|
363
|
-
onComplete: onComplete
|
|
379
|
+
onComplete: onComplete,
|
|
380
|
+
onDiagnostic: onDiagnostic
|
|
364
381
|
)
|
|
365
382
|
let publicSession = ScanSession { [weak self] in
|
|
366
383
|
self?.finishScanSession(id, reason: .cancelled, shouldStopScan: true)
|
|
@@ -2107,6 +2124,25 @@ private func dispatchDiscoveredDevices(_ rawSearchResults: Any?) {
|
|
|
2107
2124
|
activeSession.onResults(devices)
|
|
2108
2125
|
}
|
|
2109
2126
|
|
|
2127
|
+
/// Used by both the native scan callback and the React Native scan wrapper.
|
|
2128
|
+
func scanDiagnostic(for model: DeviceModel) -> ScanDiagnostic? {
|
|
2129
|
+
let status = glassesStatus
|
|
2130
|
+
guard model != .simulated, !status.connected,
|
|
2131
|
+
status.connectionState != .connected, status.connectionState != .connecting,
|
|
2132
|
+
status.connectionState != .bonding else { return nil }
|
|
2133
|
+
let services = ConnectedDeviceMatcher.serviceUUIDs(for: model).map { CBUUID(string: $0) }
|
|
2134
|
+
let saved = currentDefaultDevice()
|
|
2135
|
+
guard let device = BluetoothAvailability.shared.connectedPeripherals(withServices: services).first(where: {
|
|
2136
|
+
ConnectedDeviceMatcher.matches(model: model, defaultDevice: saved, name: $0.name, identifier: $0.identifier.uuidString)
|
|
2137
|
+
}) else { return nil }
|
|
2138
|
+
let name = device.name.flatMap { $0.isEmpty ? nil : $0 } ?? device.identifier.uuidString
|
|
2139
|
+
return ScanDiagnostic(
|
|
2140
|
+
code: "device_connected_on_phone",
|
|
2141
|
+
message: "Scan found no glasses, but a matching device \"\(name)\" is already connected to this phone. " +
|
|
2142
|
+
"If another app is using it, disconnect it there and scan again."
|
|
2143
|
+
)
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2110
2146
|
private func finishScanSession(_ id: UUID, reason: ScanStopReason, shouldStopScan: Bool) {
|
|
2111
2147
|
guard let activeSession = activeScanSessions.removeValue(forKey: id) else { return }
|
|
2112
2148
|
activeSession.timeoutTask?.cancel()
|
|
@@ -2114,6 +2150,11 @@ private func dispatchDiscoveredDevices(_ rawSearchResults: Any?) {
|
|
|
2114
2150
|
if shouldStopScan {
|
|
2115
2151
|
stopScan(reason: reason)
|
|
2116
2152
|
}
|
|
2153
|
+
if reason == .completed, activeSession.latestResults.isEmpty,
|
|
2154
|
+
let onDiagnostic = activeSession.onDiagnostic, let diagnostic = scanDiagnostic(for: activeSession.model)
|
|
2155
|
+
{
|
|
2156
|
+
onDiagnostic(diagnostic)
|
|
2157
|
+
}
|
|
2117
2158
|
activeSession.onComplete(activeSession.latestResults)
|
|
2118
2159
|
}
|
|
2119
2160
|
|
|
@@ -115,11 +115,12 @@ enum OtaManifestChecker {
|
|
|
115
115
|
currentMtkVersion: String,
|
|
116
116
|
currentBesVersion: String,
|
|
117
117
|
manifest: OtaManifest,
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
|
|
118
|
+
// Mentra 3.0 is the oldest downgrade-safe target, matching Engine, ASG, and recovery.
|
|
119
|
+
// Coordinated releases guarantee higher supported ASG builds can downgrade; no source gate is needed.
|
|
120
|
+
// The shipped floor may increase, never decrease, and must stay aligned across all checkers.
|
|
121
|
+
// See asg_client/docs/mentra-live-spec.md#ota-and-updates.
|
|
122
|
+
// An explicit non-positive override still disables downgrades.
|
|
123
|
+
downgradeFloorVersionCode: Int = 51518114
|
|
123
124
|
) throws -> Bool {
|
|
124
125
|
try hasApkUpdate(
|
|
125
126
|
currentBuildNumber: currentBuildNumber,
|
|
@@ -6,6 +6,17 @@ public enum ScanStopReason {
|
|
|
6
6
|
case error
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
/// A non-fatal explanation for an empty scan; it does not establish app ownership.
|
|
10
|
+
public struct ScanDiagnostic: Equatable {
|
|
11
|
+
public let code: String
|
|
12
|
+
public let message: String
|
|
13
|
+
|
|
14
|
+
public init(code: String, message: String) {
|
|
15
|
+
self.code = code
|
|
16
|
+
self.message = message
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
9
20
|
@MainActor
|
|
10
21
|
public final class ScanSession {
|
|
11
22
|
private let stopAction: () -> Void
|
|
@@ -41,6 +41,13 @@ final class BluetoothAvailability: NSObject, CBCentralManagerDelegate {
|
|
|
41
41
|
listeners[id] = nil
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/// Read-only system lookup. It neither connects nor claims ownership.
|
|
45
|
+
func connectedPeripherals(withServices services: [CBUUID]) -> [CBPeripheral] {
|
|
46
|
+
dispatchPrecondition(condition: .onQueue(.main))
|
|
47
|
+
guard let centralManager, centralManager.state == .poweredOn, !services.isEmpty else { return [] }
|
|
48
|
+
return centralManager.retrieveConnectedPeripherals(withServices: services)
|
|
49
|
+
}
|
|
50
|
+
|
|
44
51
|
func requirePoweredOn(operation: String) throws {
|
|
45
52
|
if let current = centralManager?.state {
|
|
46
53
|
state = current
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Model-level identity heuristic for system-connected peripherals, not app ownership.
|
|
4
|
+
enum ConnectedDeviceMatcher {
|
|
5
|
+
static func matches(model: DeviceModel, defaultDevice: Device?, name: String?, identifier: String?) -> Bool {
|
|
6
|
+
guard model != .simulated else { return false }
|
|
7
|
+
if let saved = defaultDevice, saved.model == model {
|
|
8
|
+
let savedID = saved.identifier.flatMap { $0.isEmpty ? nil : $0 }
|
|
9
|
+
let candidateID = identifier.flatMap { $0.isEmpty ? nil : $0 }
|
|
10
|
+
if let savedID, let candidateID {
|
|
11
|
+
if savedID.caseInsensitiveCompare(candidateID) == .orderedSame { return true }
|
|
12
|
+
} else if let name, !name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty, name == saved.name {
|
|
13
|
+
return true
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
guard let name else { return false }
|
|
17
|
+
switch model {
|
|
18
|
+
case .mentraLive:
|
|
19
|
+
return name == "Xy_A" || name.hasPrefix("XyBLE_") || name.lowercased().hasPrefix("mentra_live")
|
|
20
|
+
case .mentraNex:
|
|
21
|
+
return name.hasPrefix("Nex1-") || name.hasPrefix("MENTRA_DISPLAY_")
|
|
22
|
+
default:
|
|
23
|
+
return false
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// Service IDs used by the corresponding SGCs. CoreBluetooth cannot enumerate
|
|
28
|
+
/// every system connection without service filters; unsupported models stay silent.
|
|
29
|
+
static func serviceUUIDs(for model: DeviceModel) -> [String] {
|
|
30
|
+
switch model {
|
|
31
|
+
case .mentraLive, .mentraNex:
|
|
32
|
+
return ["00004860-0000-1000-8000-00805F9B34FB"]
|
|
33
|
+
case .g1:
|
|
34
|
+
return ["6E400001-B5A3-F393-E0A9-E50E24DCCA9E"]
|
|
35
|
+
case .g2:
|
|
36
|
+
return ["00002760-08C2-11E1-9073-0E8AC72E0000"]
|
|
37
|
+
case .nimo:
|
|
38
|
+
return ["00007033-0000-1000-8000-00805F9B34FB"]
|
|
39
|
+
case .ar99:
|
|
40
|
+
return ["A72EC9A8-CF72-8544-AD96-D1481A02CE98"]
|
|
41
|
+
default:
|
|
42
|
+
return []
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
@testable import MentraBluetoothSDK
|
|
3
|
+
import XCTest
|
|
4
|
+
|
|
5
|
+
final class ConnectedDeviceMatcherTests: XCTestCase {
|
|
6
|
+
func testSavedIdentityTakesPrecedenceOverName() {
|
|
7
|
+
let saved = Device(model: .g1, name: "Saved glasses", identifier: "ABC")
|
|
8
|
+
XCTAssertTrue(ConnectedDeviceMatcher.matches(model: .g1, defaultDevice: saved, name: nil, identifier: "abc"))
|
|
9
|
+
XCTAssertFalse(ConnectedDeviceMatcher.matches(model: .g1, defaultDevice: saved, name: saved.name, identifier: "OTHER"))
|
|
10
|
+
XCTAssertTrue(ConnectedDeviceMatcher.matches(model: .g1, defaultDevice: saved, name: saved.name, identifier: nil))
|
|
11
|
+
XCTAssertFalse(ConnectedDeviceMatcher.matches(model: .g2, defaultDevice: saved, name: saved.name, identifier: "ABC"))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
func testKnownNamesAreOnlyHintsForTheirModel() {
|
|
15
|
+
XCTAssertTrue(ConnectedDeviceMatcher.matches(model: .mentraLive, defaultDevice: nil, name: "MENTRA_LIVE_BLE_123", identifier: nil))
|
|
16
|
+
XCTAssertTrue(ConnectedDeviceMatcher.matches(model: .mentraNex, defaultDevice: nil, name: "Nex1-123", identifier: nil))
|
|
17
|
+
XCTAssertFalse(ConnectedDeviceMatcher.matches(model: .g1, defaultDevice: nil, name: "MENTRA_LIVE_BLE_123", identifier: nil))
|
|
18
|
+
XCTAssertFalse(ConnectedDeviceMatcher.matches(model: .simulated, defaultDevice: nil, name: "Nex1-123", identifier: nil))
|
|
19
|
+
XCTAssertFalse(ConnectedDeviceMatcher.matches(model: .mentraLive, defaultDevice: nil, name: "Headphones", identifier: nil))
|
|
20
|
+
XCTAssertFalse(ConnectedDeviceMatcher.matches(model: .g1, defaultDevice: Device(model: .g1, name: ""), name: "", identifier: ""))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
func testUnsupportedModelsDoNotQueryCoreBluetooth() {
|
|
24
|
+
XCTAssertTrue(ConnectedDeviceMatcher.serviceUUIDs(for: .simulated).isEmpty)
|
|
25
|
+
XCTAssertFalse(ConnectedDeviceMatcher.serviceUUIDs(for: .mentraLive).isEmpty)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
@testable import MentraBluetoothSDK
|
|
2
|
+
import XCTest
|
|
3
|
+
|
|
4
|
+
final class OtaManifestDowngradeTests: XCTestCase {
|
|
5
|
+
func testShippedFloorAllowsMentra30ButRejectsOlderTargets() throws {
|
|
6
|
+
for (target, expected) in [(51_518_113, false), (51_518_114, true), (51_518_115, true), (100_000_095, false)] {
|
|
7
|
+
let manifest = OtaManifest(apps: ["com.mentra.asg_client": OtaManifestApp(versionCode: target)],
|
|
8
|
+
mtkPatches: nil, mtkFullOta: nil, besFirmware: nil, versionCode: nil)
|
|
9
|
+
XCTAssertEqual(try OtaManifestChecker.hasUpdate(currentBuildNumber: "100000095",
|
|
10
|
+
currentMtkVersion: "", currentBesVersion: "", manifest: manifest), expected)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
func testExplicitDisabledFloorStillRejectsDowngrades() throws {
|
|
15
|
+
let manifest = OtaManifest(apps: ["com.mentra.asg_client": OtaManifestApp(versionCode: 51_518_114)],
|
|
16
|
+
mtkPatches: nil, mtkFullOta: nil, besFirmware: nil, versionCode: nil)
|
|
17
|
+
XCTAssertFalse(try OtaManifestChecker.hasUpdate(currentBuildNumber: "100000095",
|
|
18
|
+
currentMtkVersion: "", currentBesVersion: "", manifest: manifest, downgradeFloorVersionCode: 0))
|
|
19
|
+
}
|
|
20
|
+
}
|
package/package.json
CHANGED
|
@@ -1435,6 +1435,12 @@ export interface ConnectOptions {
|
|
|
1435
1435
|
|
|
1436
1436
|
export type ScanResultsCallback = (devices: Device[]) => void
|
|
1437
1437
|
|
|
1438
|
+
/** Advisory for an empty scan. System connection state does not identify an owning app. */
|
|
1439
|
+
export interface ScanDiagnostic {
|
|
1440
|
+
code: string
|
|
1441
|
+
message: string
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1438
1444
|
export interface ScanOptions {
|
|
1439
1445
|
model: DeviceModel
|
|
1440
1446
|
/** Defaults to 15000. */
|
|
@@ -1443,6 +1449,8 @@ export interface ScanOptions {
|
|
|
1443
1449
|
timeout?: number
|
|
1444
1450
|
/** Called every time the discovered device list changes during the scan. */
|
|
1445
1451
|
onResults?: ScanResultsCallback
|
|
1452
|
+
/** Optional non-fatal hint before an empty scan resolves, on Android and iOS. */
|
|
1453
|
+
onDiagnostic?: (diagnostic: ScanDiagnostic) => void
|
|
1446
1454
|
}
|
|
1447
1455
|
|
|
1448
1456
|
export type ScanModelOptions = Omit<ScanOptions, "model">
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {createScanSession} from "./scanSession"
|
|
1
2
|
import {NativeModule, requireNativeModule} from "expo"
|
|
2
3
|
|
|
3
4
|
import {
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
RgbLedAction,
|
|
36
37
|
RgbLedColor,
|
|
37
38
|
RgbLedControlSuccessResponseEvent,
|
|
39
|
+
ScanDiagnostic,
|
|
38
40
|
ScanModelOptions,
|
|
39
41
|
ScanOptions,
|
|
40
42
|
SettingsAckSuccessEvent,
|
|
@@ -85,6 +87,7 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
|
|
|
85
87
|
connectDefaultWithOptions(options: Required<ConnectOptions>): Promise<void>
|
|
86
88
|
setDefaultDevice(device: Device | null): Promise<void>
|
|
87
89
|
clearDefaultDevice(): Promise<void>
|
|
90
|
+
getScanDiagnostic?: (model: DeviceModel) => Promise<ScanDiagnostic | null>
|
|
88
91
|
startScan(model: DeviceModel): Promise<void>
|
|
89
92
|
stopScan(): Promise<void>
|
|
90
93
|
scan(options: ScanOptions): Promise<Device[]>
|
|
@@ -166,7 +169,13 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
|
|
|
166
169
|
startAr99OtaFromFile(path: string): Promise<boolean>
|
|
167
170
|
cancelAr99Ota(): Promise<void>
|
|
168
171
|
sendAr99FactoryReset(): Promise<void>
|
|
169
|
-
buildAr99OtaSignature(
|
|
172
|
+
buildAr99OtaSignature(
|
|
173
|
+
secret: string,
|
|
174
|
+
appName: string,
|
|
175
|
+
currentVersion: string,
|
|
176
|
+
serialNumber: string,
|
|
177
|
+
nonce: string,
|
|
178
|
+
): string
|
|
170
179
|
|
|
171
180
|
// Version Info Commands
|
|
172
181
|
requestVersionInfo(): Promise<VersionInfoResult>
|
|
@@ -581,65 +590,38 @@ NativeBluetoothSdkModule.connect = function (device: Device, options?: ConnectOp
|
|
|
581
590
|
return this.connectWithOptions(device, {...DEFAULT_CONNECT_OPTIONS, ...options})
|
|
582
591
|
}
|
|
583
592
|
|
|
593
|
+
const nativeStopScan = NativeBluetoothSdkModule.stopScan.bind(NativeBluetoothSdkModule)
|
|
594
|
+
const activeScanCancellations = new Set<() => Promise<void>>()
|
|
595
|
+
NativeBluetoothSdkModule.stopScan = async function () {
|
|
596
|
+
if (activeScanCancellations.size === 0) return nativeStopScan()
|
|
597
|
+
await Promise.all([...activeScanCancellations].map(cancel => cancel()))
|
|
598
|
+
}
|
|
599
|
+
|
|
584
600
|
NativeBluetoothSdkModule.scan = async function (modelOrOptions: DeviceModel | ScanOptions, options?: ScanModelOptions) {
|
|
601
|
+
if (activeScanCancellations.size > 0) await this.stopScan()
|
|
585
602
|
const scanOptions = normalizeScanArgs(modelOrOptions, options)
|
|
586
603
|
const timeoutMs = normalizeTimeoutMs(scanOptions.timeoutMs ?? scanOptions.timeout, DEFAULT_SCAN_TIMEOUT_MS)
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
const settle = (error?: Error) => {
|
|
611
|
-
if (settled) {
|
|
612
|
-
return
|
|
613
|
-
}
|
|
614
|
-
settled = true
|
|
615
|
-
cleanup()
|
|
616
|
-
if (error) {
|
|
617
|
-
reject(error)
|
|
618
|
-
} else {
|
|
619
|
-
resolve([...latestResults])
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
const handleBluetoothStatus = (status: Partial<BluetoothStatus>) => {
|
|
624
|
-
if (!Array.isArray(status.searchResults)) {
|
|
625
|
-
return
|
|
626
|
-
}
|
|
627
|
-
emitResults(searchResultsForModel(status, scanOptions.model))
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
removeBluetoothListener = this.onBluetoothStatus(handleBluetoothStatus)
|
|
631
|
-
emitResults([])
|
|
632
|
-
|
|
633
|
-
timeout = setTimeout(() => settle(), timeoutMs)
|
|
634
|
-
|
|
635
|
-
Promise.resolve(this.startScan(scanOptions.model))
|
|
636
|
-
.then(() => {
|
|
637
|
-
scanStarted = true
|
|
638
|
-
return this.getBluetoothStatus()
|
|
639
|
-
})
|
|
640
|
-
.then(handleBluetoothStatus)
|
|
641
|
-
.catch((error) => settle(error instanceof Error ? error : new Error(String(error))))
|
|
642
|
-
})
|
|
604
|
+
const session = createScanSession(
|
|
605
|
+
{
|
|
606
|
+
start: (model) => this.startScan(model),
|
|
607
|
+
stop: nativeStopScan,
|
|
608
|
+
subscribe: (onResults) =>
|
|
609
|
+
this.onBluetoothStatus((status) => {
|
|
610
|
+
if (Array.isArray(status.searchResults)) onResults(searchResultsForModel(status, scanOptions.model))
|
|
611
|
+
}),
|
|
612
|
+
getResults: async () => searchResultsForModel(await this.getBluetoothStatus(), scanOptions.model),
|
|
613
|
+
// Older native binaries simply omit diagnostics until the host is rebuilt.
|
|
614
|
+
getDiagnostic:
|
|
615
|
+
typeof this.getScanDiagnostic === "function" ? () => this.getScanDiagnostic!(scanOptions.model) : undefined,
|
|
616
|
+
},
|
|
617
|
+
{...scanOptions, timeoutMs},
|
|
618
|
+
)
|
|
619
|
+
activeScanCancellations.add(session.cancel)
|
|
620
|
+
try {
|
|
621
|
+
return await session.result
|
|
622
|
+
} finally {
|
|
623
|
+
activeScanCancellations.delete(session.cancel)
|
|
624
|
+
}
|
|
643
625
|
}
|
|
644
626
|
|
|
645
627
|
const nativeRequestPhoto = NativeBluetoothSdkModule.requestPhoto.bind(NativeBluetoothSdkModule)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type {Device, DeviceModel, ScanDiagnostic} from "../BluetoothSdk.types"
|
|
2
|
+
|
|
3
|
+
interface ScanTransport {
|
|
4
|
+
start: (model: DeviceModel) => Promise<void>
|
|
5
|
+
stop: () => Promise<void>
|
|
6
|
+
subscribe: (onResults: (devices: Device[]) => void) => () => void
|
|
7
|
+
getResults: () => Promise<Device[]>
|
|
8
|
+
getDiagnostic?: () => Promise<ScanDiagnostic | null>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface ScanSessionOptions {
|
|
12
|
+
model: DeviceModel
|
|
13
|
+
timeoutMs: number
|
|
14
|
+
onResults?: (devices: Device[]) => void
|
|
15
|
+
onDiagnostic?: (diagnostic: ScanDiagnostic) => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** A scan has one completion; optional diagnostics cannot turn it into a failure. */
|
|
19
|
+
export function createScanSession(transport: ScanTransport, options: ScanSessionOptions) {
|
|
20
|
+
let cancel: () => Promise<void> = async () => {}
|
|
21
|
+
const result = new Promise<Device[]>((resolve, reject) => {
|
|
22
|
+
let latestResults: Device[] = []
|
|
23
|
+
let ending = false
|
|
24
|
+
let cancelled = false
|
|
25
|
+
let startTask: Promise<void> | undefined
|
|
26
|
+
let stopTask: Promise<void> = Promise.resolve()
|
|
27
|
+
let removeListener = () => {}
|
|
28
|
+
let timeout: ReturnType<typeof setTimeout> | undefined
|
|
29
|
+
|
|
30
|
+
const emit = (devices: Device[]) => {
|
|
31
|
+
if (ending) return
|
|
32
|
+
latestResults = devices
|
|
33
|
+
options.onResults?.([...devices])
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const finish = async (error?: unknown) => {
|
|
37
|
+
if (ending) return
|
|
38
|
+
ending = true
|
|
39
|
+
clearTimeout(timeout)
|
|
40
|
+
removeListener()
|
|
41
|
+
stopTask = (async () => {
|
|
42
|
+
try {
|
|
43
|
+
await startTask
|
|
44
|
+
await transport.stop()
|
|
45
|
+
} catch {
|
|
46
|
+
// Failed starts and stops do not change the completion contract.
|
|
47
|
+
}
|
|
48
|
+
})()
|
|
49
|
+
await stopTask
|
|
50
|
+
if (error !== undefined) {
|
|
51
|
+
reject(error)
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
// Query only at natural, empty completion. A cancellation during the
|
|
55
|
+
// native lookup must also suppress the pending advisory.
|
|
56
|
+
if (!cancelled && latestResults.length === 0 && options.onDiagnostic && transport.getDiagnostic) {
|
|
57
|
+
try {
|
|
58
|
+
const diagnostic = await transport.getDiagnostic()
|
|
59
|
+
if (!cancelled && diagnostic) options.onDiagnostic(diagnostic)
|
|
60
|
+
} catch {
|
|
61
|
+
// Optional platform access or advisory handling cannot fail a scan.
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
resolve([...latestResults])
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
cancel = () => {
|
|
68
|
+
cancelled = true
|
|
69
|
+
void finish()
|
|
70
|
+
return stopTask
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
void (async () => {
|
|
74
|
+
try {
|
|
75
|
+
removeListener = transport.subscribe(emit)
|
|
76
|
+
emit([])
|
|
77
|
+
timeout = setTimeout(() => void finish(), options.timeoutMs)
|
|
78
|
+
startTask = Promise.resolve().then(() => transport.start(options.model))
|
|
79
|
+
await startTask
|
|
80
|
+
if (ending) return
|
|
81
|
+
emit(await transport.getResults())
|
|
82
|
+
} catch (error) {
|
|
83
|
+
void finish(error)
|
|
84
|
+
}
|
|
85
|
+
})()
|
|
86
|
+
})
|
|
87
|
+
return {result, cancel: () => cancel()}
|
|
88
|
+
}
|