diff --git a/README.md b/README.md index 1f62a7f..637e724 100644 --- a/README.md +++ b/README.md @@ -70,19 +70,19 @@ async function scanForBleDevices(androidNeverForLocation: Boolean = true, timeou With the signalize method you can localize EVVA components. On a successful signalization the component will emit a melody indicating its location. ```typescript -await AbrevvaBle.signalize({ deviceId: 'deviceId' }); +const success = await AbrevvaBle.signalize('deviceId'); ``` ### Perform disengage on EVVA components For the component disengage you have to provide access credentials to the EVVA component. Those are generally acquired in the form of access media metadata from the Xesar software. ```typescript -const status = await AbrevvaBle.disengage({ - deviceId: 'deviceId', - mobileId: 'mobileId', - mobileDeviceKey: 'mobileDeviceKey', - mobileGroupId: 'mobileGroupId', - mobileAccessData: 'mobileAccessData', - isPermanentRelease: false, -}); +const status = await AbrevvaBle.disengage( + 'deviceId', + 'mobileId', + 'mobileDeviceKey', + 'mobileGroupId', + 'mobileAccessData', + false, +); ``` diff --git a/android/src/main/java/com/evva/xesar/abrevva/reactnative/AbrevvaBleModule.kt b/android/src/main/java/com/evva/xesar/abrevva/reactnative/AbrevvaBleModule.kt index d785a75..30f0087 100644 --- a/android/src/main/java/com/evva/xesar/abrevva/reactnative/AbrevvaBleModule.kt +++ b/android/src/main/java/com/evva/xesar/abrevva/reactnative/AbrevvaBleModule.kt @@ -24,28 +24,13 @@ import java.util.UUID class AbrevvaBleModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { - private lateinit var manager: BleManager - private lateinit var aliases: Array + private var manager: BleManager = BleManager(reactContext) + private var aliases: Array init { - manager = BleManager(reactContext) aliases = arrayOf() } - @SuppressLint("MissingPermission") - @ReactMethod - fun signalize(options: ReadableMap, promise: Promise) { - val deviceId = options.getString("deviceId") ?: "" - - manager.signalize(deviceId) { success: Boolean -> - if (success) { - promise.resolve("success") - } else { - promise.reject(Exception("signalize() failed")) - } - } - } - @ReactMethod fun initialize(options: ReadableMap, promise: Promise) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { @@ -255,7 +240,7 @@ class AbrevvaBleModule(reactContext: ReactApplicationContext) : ret.putString("value", bytesToString(data!!)) promise.resolve(ret) } else { - promise.reject("read(): failed to read from device") + promise.reject(Exception("read(): failed to read from device")) } }, timeout @@ -274,7 +259,7 @@ class AbrevvaBleModule(reactContext: ReactApplicationContext) : val characteristic = getCharacteristic(options, promise) - ?: return promise.reject("read(): bad characteristic") + ?: return promise.reject(Exception("read(): bad characteristic")) val value = options.getString("value") ?: return promise.reject(Exception("write(): missing value for write")) @@ -295,6 +280,20 @@ class AbrevvaBleModule(reactContext: ReactApplicationContext) : ) } + @SuppressLint("MissingPermission") + @ReactMethod + fun signalize(options: ReadableMap, promise: Promise) { + val deviceId = options.getString("deviceId") ?: "" + + manager.signalize(deviceId) { success: Boolean -> + if (success) { + promise.resolve("success") + } else { + promise.reject(Exception("signalize() failed")) + } + } + } + @ReactMethod @RequiresPermission(value = "android.permission.BLUETOOTH_CONNECT") fun disengage(options: ReadableMap, promise: Promise) { @@ -449,7 +448,7 @@ class AbrevvaBleModule(reactContext: ReactApplicationContext) : if (scanRecordBytes != null) { try { // Extract EVVA manufacturer-id - var arr = byteArrayOf(0x01) + val arr = byteArrayOf(0x01) arr.toHexString() val keyHex = byteArrayOf(scanRecordBytes.getByte(6)!!).toHexString() + byteArrayOf( scanRecordBytes.getByte(5)!! @@ -508,4 +507,4 @@ class AbrevvaBleModule(reactContext: ReactApplicationContext) : fun removeListeners(type: Int?) { // Keep: Required for RN built in Event Emitter Calls. } -} \ No newline at end of file +} diff --git a/android/src/main/java/com/evva/xesar/abrevva/reactnative/AbrevvaCryptoModule.kt b/android/src/main/java/com/evva/xesar/abrevva/reactnative/AbrevvaCryptoModule.kt index 54856d3..fc07ae8 100644 --- a/android/src/main/java/com/evva/xesar/abrevva/reactnative/AbrevvaCryptoModule.kt +++ b/android/src/main/java/com/evva/xesar/abrevva/reactnative/AbrevvaCryptoModule.kt @@ -11,13 +11,9 @@ import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContextBaseJavaModule import com.facebook.react.bridge.ReactMethod import com.facebook.react.bridge.ReadableMap -import com.hivemq.client.mqtt.mqtt3.Mqtt3AsyncClient.Mqtt3SubscribeAndCallbackBuilder.Call.Ex -import org.bouncycastle.util.encoders.Base64 import org.bouncycastle.util.encoders.Hex import java.io.BufferedInputStream import java.io.FileOutputStream -import java.io.IOException -import java.io.InputStream import java.net.URL import java.nio.file.Paths @@ -88,17 +84,17 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : try { val privateKey = options.getString("privateKey") if (privateKey == null || privateKey == "") { - promise.reject("computeSharedSecret(): invalid private key") + promise.reject(Exception("computeSharedSecret(): invalid private key")) return } val peerPublicKey = options.getString("peerPublicKey") if (peerPublicKey == null || peerPublicKey == "") { - promise.reject("computeSharedSecret(): invalid peer public key") + promise.reject(Exception("computeSharedSecret(): invalid peer public key")) return } val sharedSecret: ByteArray = X25519Wrapper.computeSharedSecret( - Hex.decode(privateKey), - Hex.decode(peerPublicKey) + Hex.decode(privateKey), + Hex.decode(peerPublicKey) ) val ret = Arguments.createMap() @@ -168,8 +164,8 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : promise.reject(e) } } - fun writeToFile(ctPath: String, url: String) { + fun writeToFile(ctPath: String, url: String) { BufferedInputStream(URL(url).openStream()).use { `in` -> FileOutputStream(ctPath).use { fileOutputStream -> val dataBuffer = ByteArray(4096) @@ -258,4 +254,4 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : const val NAME = "AbrevvaCrypto" } -} \ No newline at end of file +} diff --git a/android/src/main/java/com/evva/xesar/abrevva/reactnative/AbrevvaNfcModule.kt b/android/src/main/java/com/evva/xesar/abrevva/reactnative/AbrevvaNfcModule.kt index 26e9d08..52641b5 100644 --- a/android/src/main/java/com/evva/xesar/abrevva/reactnative/AbrevvaNfcModule.kt +++ b/android/src/main/java/com/evva/xesar/abrevva/reactnative/AbrevvaNfcModule.kt @@ -160,4 +160,4 @@ class AbrevvaNfcModule(reactContext: ReactApplicationContext) : companion object { const val NAME = "AbrevvaNfc" } -} \ No newline at end of file +} diff --git a/android/src/main/java/com/evva/xesar/abrevva/reactnative/ExampleAppPackage.kt b/android/src/main/java/com/evva/xesar/abrevva/reactnative/ExampleAppPackage.kt index 7810076..8670457 100644 --- a/android/src/main/java/com/evva/xesar/abrevva/reactnative/ExampleAppPackage.kt +++ b/android/src/main/java/com/evva/xesar/abrevva/reactnative/ExampleAppPackage.kt @@ -11,8 +11,8 @@ class ExampleAppPackage : ReactPackage { AbrevvaBleModule(reactContext) ) } - + override fun createViewManagers(reactContext: ReactApplicationContext): List> { return emptyList() } -} \ No newline at end of file +} diff --git a/android/src/test/java/com/evva/xesar/abrevva/reactnative/AbrevvaBleModuleTest.kt b/android/src/test/java/com/evva/xesar/abrevva/reactnative/AbrevvaBleModuleTest.kt index 6690799..ae4232e 100644 --- a/android/src/test/java/com/evva/xesar/abrevva/reactnative/AbrevvaBleModuleTest.kt +++ b/android/src/test/java/com/evva/xesar/abrevva/reactnative/AbrevvaBleModuleTest.kt @@ -8,7 +8,6 @@ import com.evva.xesar.abrevva.ble.BleManager import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.WritableArray import io.mockk.MockKAnnotations import io.mockk.every @@ -39,9 +38,6 @@ class AbrevvaBleModuleTest { @MockK(relaxed = true) private lateinit var promiseMock: Promise - @MockK(relaxed = true) - private lateinit var readableMapMock: ReadableMap - @MockK(relaxed = true) private lateinit var writeableArrayMock: WritableArray @@ -63,7 +59,6 @@ class AbrevvaBleModuleTest { unmockkAll() } - /* https://github.com/mockk/mockk/issues/586#issuecomment-1404973825 */ @SuppressLint("MissingPermission") @Test @@ -164,6 +159,7 @@ class AbrevvaBleModuleTest { val scanResult = WritableMapTestImplementation() val manufacturerData = WritableMapTestImplementation() val serviceDataMap = WritableMapTestImplementation() + every { result.data } returns null andThen data every { result.device } returns device every { result.device.hasName } returns true @@ -190,6 +186,5 @@ class AbrevvaBleModuleTest { ) ) assert(ref == scanResult) - } } \ No newline at end of file diff --git a/example/src/BleScreenComponents.tsx b/example/src/BleScreenComponents.tsx index ef823e3..16ed849 100644 --- a/example/src/BleScreenComponents.tsx +++ b/example/src/BleScreenComponents.tsx @@ -5,8 +5,8 @@ import { dataViewToNumbers, hexStringToDataView, numbersToDataView, - type ReadResult, type ScanResult, + type StringResult, } from '@evva-sfw/abrevva-react-native'; import { hex } from '@scure/base'; import { Parser } from 'binary-parser-encoder'; @@ -186,26 +186,23 @@ async function mobileIdentificationMediumService(data: ScanResult, setStatus: an try { await AbrevvaBle.stopLEScan(); - await AbrevvaBle.connect({ - deviceId: data.device.deviceId, - timeout: 10000, - }); + await AbrevvaBle.connect(data.device.deviceId, 10000); await AbrevvaBle.startNotifications( data.device.deviceId, SERVICE, CHARACTERISTICS.ACCESS_STATUS, - (event: ReadResult) => { + (event: StringResult) => { newStatus = event.value!; }, ); - const challenge = await AbrevvaBle.read({ - deviceId: data.device.deviceId, - service: SERVICE, - characteristic: CHARACTERISTICS.CHALLENGE, - timeout: 10000, - }); + const challenge = await AbrevvaBle.read( + data.device.deviceId, + SERVICE, + CHARACTERISTICS.CHALLENGE, + 10000, + ); const challengeDataView = hexStringToDataView(challenge.value!); const challengeBuffer = Buffer.from(dataViewToNumbers(challengeDataView)); @@ -255,15 +252,15 @@ async function mobileIdentificationMediumService(data: ScanResult, setStatus: an authTag: authTagBuffer, }); - void AbrevvaBle.write({ - deviceId: data.device.deviceId, - service: SERVICE, - characteristic: CHARACTERISTICS.MOBILE_ACCESS_DATA, - value: dataViewToHexString(numbersToDataView(mdf)), - timeout: 1000, - }); + void AbrevvaBle.write( + data.device.deviceId, + SERVICE, + CHARACTERISTICS.MOBILE_ACCESS_DATA, + dataViewToHexString(numbersToDataView(mdf)), + 1000, + ); } catch (error: any) { - void AbrevvaBle.disconnect({ deviceId: data.device.deviceId }); + void AbrevvaBle.disconnect(data.device.deviceId); serviceIsActive = false; Alert.alert('Error', error.code, [ { @@ -284,7 +281,7 @@ async function mobileIdentificationMediumService(data: ScanResult, setStatus: an setStatus(() => { return newStatus; }); - AbrevvaBle.disconnect({ deviceId: data.device.deviceId }); + AbrevvaBle.disconnect(data.device.deviceId); serviceIsActive = false; resolve(); } @@ -300,7 +297,7 @@ async function mobileIdentificationMediumService(data: ScanResult, setStatus: an text: 'Ok', }, ]); - AbrevvaBle.disconnect({ deviceId: data.device.deviceId }); + AbrevvaBle.disconnect(data.device.deviceId); serviceIsActive = false; resolve(); }, 20000); diff --git a/ios/ble/AbrevvaBle.swift b/ios/ble/AbrevvaBle.swift index c5eb673..5729930 100644 --- a/ios/ble/AbrevvaBle.swift +++ b/ios/ble/AbrevvaBle.swift @@ -153,7 +153,7 @@ public class AbrevvaBle: RCTEventEmitter { return } - let timeout = optionsSwift["timeout"] as? Int ?? nil + let timeout = optionsSwift["timeout"] as? Int ?? 10000 Task { let success = await self.bleManager!.connect(device, timeout) diff --git a/src/index.test.tsx b/src/index.test.tsx index 3f13d95..aa7f6d7 100644 --- a/src/index.test.tsx +++ b/src/index.test.tsx @@ -111,44 +111,35 @@ describe('AbrevvaBleModule', () => { expect(AbrevvaBleMock.stopLEScan).toHaveBeenCalledTimes(1); }); it('should run connect()', async () => { - await AbrevvaBle.connect({ deviceId: 'deviceId' }); + await AbrevvaBle.connect('deviceId'); expect(AbrevvaBleMock.connect).toHaveBeenCalledTimes(1); }); it('should run disconnect()', async () => { - await AbrevvaBle.disconnect({ deviceId: 'deviceId' }); + await AbrevvaBle.disconnect('deviceId'); expect(AbrevvaBleMock.disconnect).toHaveBeenCalledTimes(1); expect(AbrevvaBleMock.setSupportedEvents).toHaveBeenCalledTimes(1); }); it('should run read()', async () => { - await AbrevvaBle.read({ - deviceId: 'deviceId', - service: 'service', - characteristic: 'characteristic', - }); + await AbrevvaBle.read('deviceId', 'service', 'characteristic'); expect(AbrevvaBleMock.read).toHaveBeenCalledTimes(1); }); it('should run write()', async () => { - await AbrevvaBle.write({ - deviceId: 'deviceId', - service: 'service', - characteristic: 'characteristic', - value: 'value', - }); + await AbrevvaBle.write('deviceId', 'service', 'characteristic', 'value'); expect(AbrevvaBleMock.write).toHaveBeenCalledTimes(1); }); it('should run signalize()', async () => { - await AbrevvaBle.signalize({ deviceId: 'deviceId' }); + await AbrevvaBle.signalize('deviceId'); expect(AbrevvaBleMock.signalize).toHaveBeenCalledTimes(1); }); it('should run disengage()', async () => { - await AbrevvaBle.disengage({ - deviceId: 'deviceId', - mobileId: 'mobileId', - mobileDeviceKey: 'mobileDeviceKey', - mobileGroupId: 'mobileGroupId', - mobileAccessData: 'mobileAccessData', - isPermanentRelease: false, - }); + await AbrevvaBle.disengage( + 'deviceId', + 'mobileId', + 'mobileDeviceKey', + 'mobileGroupId', + 'mobileAccessData', + false, + ); expect(AbrevvaBleMock.disengage).toHaveBeenCalledTimes(1); }); describe('startNotifications()', () => {}); diff --git a/src/index.tsx b/src/index.tsx index 6161977..73b5617 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -15,17 +15,10 @@ import type { AbrevvaBLEInterface, AbrevvaCryptoInterface, AbrevvaNfcInterface, - DeviceIdOptions, - DisengageOptions, - ReadOptions, - ReadResult, ScanMode, ScanResult, ScanResultInternal, - SignalizeOptions, StringResult, - TimeoutOptions, - WriteOptions, } from './interfaces'; const NativeModuleNfc = NativeModules.AbrevvaNfc @@ -198,11 +191,14 @@ export class AbrevvaBleModule implements AbrevvaBLEInterface { return await NativeModuleBle.stopLEScan(); } - async connect(options: DeviceIdOptions & TimeoutOptions): Promise { - return await NativeModuleBle.connect(options); + async connect(deviceId: string, timeout?: number): Promise { + return await NativeModuleBle.connect({ + deviceId: deviceId, + timeout: timeout, + }); } - async disconnect(options: DeviceIdOptions): Promise { + async disconnect(deviceId: string): Promise { this.listeners.forEach((listener) => listener?.remove); this.listeners = new Map([ ['onEnabledChanged', undefined], @@ -211,30 +207,71 @@ export class AbrevvaBleModule implements AbrevvaBLEInterface { ['onDisconnect', undefined], ]); NativeModuleBle.setSupportedEvents({ events: [...this.listeners.keys()] }); - return NativeModuleBle.disconnect(options); + return NativeModuleBle.disconnect({ deviceId: deviceId }); } - async read(options: ReadOptions & TimeoutOptions): Promise { - return NativeModuleBle.read(options); + async read( + deviceId: string, + service: string, + characteristic: string, + timeout?: number, + ): Promise { + return NativeModuleBle.read({ + deviceId: deviceId, + service: service, + characteristic: characteristic, + timeout: timeout, + }); } - async write(options: WriteOptions & TimeoutOptions): Promise { - return NativeModuleBle.write(options); + async write( + deviceId: string, + service: string, + characteristic: string, + value: string, + timeout?: number, + ): Promise { + return NativeModuleBle.write({ + deviceId: deviceId, + service: service, + characteristic: characteristic, + value: value, + timeout: timeout, + }); } - async signalize(options: SignalizeOptions): Promise { - return NativeModuleBle.signalize(options); + async signalize(deviceId: string): Promise { + try { + NativeModuleBle.signalize({ deviceId: deviceId }); + } catch (err) { + return false; + } + return true; } - async disengage(options: DisengageOptions): Promise { - return NativeModuleBle.disengage(options); + async disengage( + deviceId: string, + mobileId: string, + mobileDeviceKey: string, + mobileGroupId: string, + mobileAccessData: string, + isPermanentRelease: boolean, + ): Promise { + return NativeModuleBle.disengage({ + deviceId: deviceId, + mobileId: mobileId, + mobileDeviceKey: mobileDeviceKey, + mobileGroupId: mobileGroupId, + mobileAccessData: mobileAccessData, + isPermanentRelease: isPermanentRelease, + }); } async startNotifications( deviceId: string, service: string, characteristic: string, - callback: (event: ReadResult) => void, + callback: (event: StringResult) => void, ): Promise { const key = `notification|${deviceId}|${service}|${characteristic}`.toLowerCase(); const listener = this.eventEmitter!.addListener(key, callback); diff --git a/src/interfaces.tsx b/src/interfaces.tsx index ad29331..80552f5 100644 --- a/src/interfaces.tsx +++ b/src/interfaces.tsx @@ -15,22 +15,6 @@ export interface RequestBleDeviceOptions { export type Data = DataView | string; -export interface BooleanResult { - value: boolean; -} - -export interface StringResult { - value: string; -} - -export interface DeviceIdOptions { - deviceId: string; -} - -export interface TimeoutOptions { - timeout?: number; -} - export interface BleDevice { deviceId: string; name?: string; @@ -58,37 +42,10 @@ export interface ScanResultInternal { uuids?: string[]; rawAdvertisement?: T; } - -export interface ReadOptions { - deviceId: string; - service: string; - characteristic: string; -} - -export interface ReadResult { +export interface StringResult { value?: string; } -export interface WriteOptions { - deviceId: string; - service: string; - characteristic: string; - value: string; -} - -export interface SignalizeOptions { - deviceId: string; -} - -export interface DisengageOptions { - deviceId: string; - mobileId: string; - mobileDeviceKey: string; - mobileGroupId: string; - mobileAccessData: string; - isPermanentRelease: boolean; -} - export interface AbrevvaNfcInterface { connect: () => Promise; disconnect: () => Promise; @@ -139,17 +96,35 @@ export interface AbrevvaBLEInterface { scanMode?: ScanMode, ): Promise; stopLEScan(): Promise; - connect(options: DeviceIdOptions & TimeoutOptions): Promise; - disconnect(options: DeviceIdOptions): Promise; - read(options: ReadOptions & TimeoutOptions): Promise; - write(options: WriteOptions & TimeoutOptions): Promise; - signalize(options: SignalizeOptions): Promise; - disengage(options: DisengageOptions): Promise; + connect(deviceId: string, timeout?: number): Promise; + disconnect(deviceId: string): Promise; + read( + deviceId: string, + service: string, + characteristic: string, + timeout?: number, + ): Promise; + write( + deviceId: string, + service: string, + characteristic: string, + value: string, + timeout?: number, + ): Promise; + signalize(deviceId: string): Promise; + disengage( + deviceId: string, + mobileId: string, + mobileDeviceKey: string, + mobileGroupId: string, + mobileAccessData: string, + isPermanentRelease: boolean, + ): Promise; startNotifications( deviceId: string, service: string, characteristic: string, - callback: (event: ReadResult) => void, + callback: (event: StringResult) => void, ): Promise; stopNotifications(deviceId: string, service: string, characteristic: string): Promise; }