From 54ea0eb7ba5aa0a66d23761ace062177020b0ce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hochst=C3=B6ger=20Matthias?= <116495532+mhochsto@users.noreply.github.com> Date: Fri, 2 Aug 2024 08:29:40 +0200 Subject: [PATCH 01/36] chore: initial test setup --- ios/ble/AbrevvaBle.swift | 1 + package.json | 3 + src/__tests__/index.test.tsx | 165 ++++++++++++++++++++++++++++++++++- src/index.tsx | 46 +++++----- src/setup.tsx | 56 ++++++++++++ 5 files changed, 244 insertions(+), 27 deletions(-) create mode 100644 src/setup.tsx diff --git a/ios/ble/AbrevvaBle.swift b/ios/ble/AbrevvaBle.swift index 206e08c..445573b 100644 --- a/ios/ble/AbrevvaBle.swift +++ b/ios/ble/AbrevvaBle.swift @@ -44,6 +44,7 @@ public class AbrevvaBle: RCTEventEmitter { bleManager.registerStateReceiver { enabled in self.sendEvent(withName: "onEnabledChanged", body: ["value": enabled]) } + resolve(nil) } @objc diff --git a/package.json b/package.json index 1864058..90f8c43 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,9 @@ "modulePathIgnorePatterns": [ "/example/node_modules", "/lib/" + ], + "setupFilesAfterEnv": [ + "/src/setup.tsx" ] }, "commitlint": { diff --git a/src/__tests__/index.test.tsx b/src/__tests__/index.test.tsx index bf84291..0443075 100644 --- a/src/__tests__/index.test.tsx +++ b/src/__tests__/index.test.tsx @@ -1 +1,164 @@ -it.todo('write a test'); +import { NativeEventEmitter, NativeModules, Platform } from 'react-native'; + +import { AbrevvaCrypto, AbrevvaNfc, createAbrevvaBleInstance } from '../index'; + +describe('AbrevvaBleModule', () => { + const AbrevvaBleMock = NativeModules.AbrevvaBle; + var mockEmitter; + var AbrevvaBle; + + beforeEach(() => { + jest.clearAllMocks(); + + Platform.OS = 'ios'; + Platform.select.mockImplementation(() => { + return mockEmitter; + }); + + mockEmitter = new NativeEventEmitter(); + AbrevvaBle = createAbrevvaBleInstance(); + }); + + it('constructor should throw if Platform is not Supported', () => { + Platform.select.mockImplementation(() => { + return undefined; + }); + expect(createAbrevvaBleInstance).toThrow(); + }); + it('should run initialize()', async () => { + await AbrevvaBle.initialize(); + expect(AbrevvaBleMock.initialize).toHaveBeenCalledTimes(1); + }); + it('should run isEnabled()', async () => { + await AbrevvaBle.isEnabled(); + expect(AbrevvaBleMock.isEnabled).toHaveBeenCalledTimes(1); + }); + it('should run isLocationEnabled()', async () => { + await AbrevvaBle.isLocationEnabled(); + expect(AbrevvaBleMock.isLocationEnabled).toHaveBeenCalledTimes(1); + }); + + describe('startEnableNotification', () => { + it('should add the correct eventlistener and call startEnableNotification', async () => { + const spy = jest.spyOn(mockEmitter, 'addListener'); + const spyNativeModule = jest.spyOn(AbrevvaBle, 'startEnabledNotifications'); + const mockfn = jest.fn(); + await AbrevvaBle.startEnabledNotifications(mockfn); + + expect(spy).toHaveBeenCalledWith('onEnabledChanged', expect.any(Function)); + expect(spy).toHaveBeenCalledTimes(1); + expect(spyNativeModule).toHaveBeenCalledTimes(1); + }); + }); + + it('should run stopEnabledNotifications()', async () => { + await AbrevvaBle.stopEnabledNotifications(); + expect(AbrevvaBleMock.stopEnabledNotifications).toHaveBeenCalledTimes(1); + }); + it('should run openLocationSettings()', async () => { + await AbrevvaBle.openLocationSettings(); + expect(AbrevvaBleMock.openLocationSettings).toHaveBeenCalledTimes(1); + }); + it('should run openBluetoothSettings()', async () => { + await AbrevvaBle.openBluetoothSettings(); + expect(AbrevvaBleMock.openBluetoothSettings).toHaveBeenCalledTimes(1); + }); + it('should run openAppSettings()', async () => { + await AbrevvaBle.openAppSettings(); + expect(AbrevvaBleMock.openAppSettings).toHaveBeenCalledTimes(1); + }); + + it('should run requestLEScan()', async () => {}); + it('should run stopLEScan()', async () => { + await AbrevvaBle.stopLEScan(); + expect(AbrevvaBleMock.stopLEScan).toHaveBeenCalledTimes(1); + }); + it('should run connect()', async () => { + await AbrevvaBle.connect(); + expect(AbrevvaBleMock.connect).toHaveBeenCalledTimes(1); + }); + it('should run disconnect()', async () => {}); + it('should run read()', async () => { + await AbrevvaBle.read(); + expect(AbrevvaBleMock.read).toHaveBeenCalledTimes(1); + }); + it('should run write()', async () => { + await AbrevvaBle.write(); + expect(AbrevvaBleMock.write).toHaveBeenCalledTimes(1); + }); + it('should run signalize()', async () => { + await AbrevvaBle.signalize(); + expect(AbrevvaBleMock.signalize).toHaveBeenCalledTimes(1); + }); + it('should run disengage()', async () => { + await AbrevvaBle.disengage(); + expect(AbrevvaBleMock.disengage).toHaveBeenCalledTimes(1); + }); + it('should run stopNotifications()', async () => {}); +}); + +describe('AbrevvaNfcModule', () => { + const AbrevvaNfcMock = NativeModules.AbrevvaNfc; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should run connect()', async () => { + await AbrevvaNfc.connect(); + expect(AbrevvaNfcMock.connect).toHaveBeenCalledTimes(1); + }); + it('should run disconnect()', async () => { + await AbrevvaNfc.disconnect(); + expect(AbrevvaNfcMock.disconnect).toHaveBeenCalledTimes(1); + }); + it('should run read()', async () => { + await AbrevvaNfc.read(); + expect(AbrevvaNfcMock.read).toHaveBeenCalledTimes(1); + }); +}); + +describe('AbrevvaCryptoModule', () => { + const AbrevvaCryptoMock = NativeModules.AbrevvaCrypto; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should run encrypt()', async () => { + await AbrevvaCrypto.encrypt('', '', '', ''); + expect(AbrevvaCryptoMock.encrypt).toHaveBeenCalledTimes(1); + }); + it('should run decrypt()', async () => { + await AbrevvaCrypto.decrypt('', '', '', ''); + expect(AbrevvaCryptoMock.decrypt).toHaveBeenCalledTimes(1); + }); + it('should run generateKeyPair()', async () => { + await AbrevvaCrypto.generateKeyPair(); + expect(AbrevvaCryptoMock.generateKeyPair).toHaveBeenCalledTimes(1); + }); + it('should run computeSharedSecret()', async () => { + await AbrevvaCrypto.computeSharedSecret('', ''); + expect(AbrevvaCryptoMock.computeSharedSecret).toHaveBeenCalledTimes(1); + }); + it('should run encryptFile()', async () => { + await AbrevvaCrypto.encryptFile('', '', ''); + expect(AbrevvaCryptoMock.encryptFile).toHaveBeenCalledTimes(1); + }); + it('should run decryptFile()', async () => { + await AbrevvaCrypto.decryptFile('', '', ''); + expect(AbrevvaCryptoMock.decryptFile).toHaveBeenCalledTimes(1); + }); + it('should run decryptFileFromURL()', async () => { + await AbrevvaCrypto.decryptFileFromURL('', '', ''); + expect(AbrevvaCryptoMock.decryptFileFromURL).toHaveBeenCalledTimes(1); + }); + it('should run random()', async () => { + await AbrevvaCrypto.random(0); + expect(AbrevvaCryptoMock.random).toHaveBeenCalledTimes(1); + }); + it('should run derive()', async () => { + await AbrevvaCrypto.derive('', '', '', 0); + expect(AbrevvaCryptoMock.derive).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/index.tsx b/src/index.tsx index aff6f56..18a5ed0 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,5 +1,3 @@ -// https://stackoverflow.com/a/73382191 -// TODO: Verify that this works as intended import { DeviceEventEmitter, type EmitterSubscription, @@ -30,7 +28,7 @@ import type { WriteOptions, } from './interfaces'; -const NativeModuleNfc: AbrevvaNfcInterface = NativeModules.AbrevvaNfc +const NativeModuleNfc = NativeModules.AbrevvaNfc ? NativeModules.AbrevvaNfc : new Proxy( {}, @@ -47,7 +45,7 @@ const NativeModuleCrypto = NativeModules.AbrevvaCrypto {}, { get() { - throw new Error('Linking Error AbrevvaNfc'); + throw new Error('Linking Error AbrevvaCrypto'); }, }, ); @@ -72,6 +70,9 @@ class AbrevvaBleModule implements AbrevvaBLEInterface { ios: new NativeEventEmitter(NativeModuleBle), android: DeviceEventEmitter, }); + if (this.eventEmitter === undefined) { + throw new Error('this platform is not supported'); + } this.listeners = new Map([ ['onEnabledChanged', undefined], @@ -80,7 +81,7 @@ class AbrevvaBleModule implements AbrevvaBLEInterface { ['onDisconnect', undefined], ]); - NativeModuleBle.setSupportedEvents({ events: [...this.listeners.keys()] }); + NativeModuleBle?.setSupportedEvents({ events: [...this.listeners.keys()] }); } async initialize(androidNeverForLocation?: boolean): Promise { @@ -98,13 +99,9 @@ class AbrevvaBleModule implements AbrevvaBLEInterface { } async startEnabledNotifications(callback: (result: boolean) => void): Promise { - if (this.eventEmitter === undefined) { - return Promise.reject('unsupported platform'); - } - this.listeners.set( 'onEnabledChanged', - this.eventEmitter.addListener('onEnabledChanged', (event: any) => { + this.eventEmitter!.addListener('onEnabledChanged', (event: any) => { callback(event.value); }), ); @@ -147,9 +144,6 @@ class AbrevvaBleModule implements AbrevvaBLEInterface { scanMode?: ScanMode, timeout?: number, ): Promise { - if (this.eventEmitter === undefined) { - return Promise.reject('unsupported platform'); - } if (this.listeners.get('onScanResult') !== undefined) { return Promise.reject('scan already in progress'); } @@ -218,23 +212,23 @@ class AbrevvaBleModule implements AbrevvaBLEInterface { ['onDisconnect', undefined], ]); NativeModuleBle.setSupportedEvents({ events: [...this.listeners.keys()] }); - return await NativeModuleBle.disconnect(options); + return NativeModuleBle.disconnect(options); } async read(options: ReadOptions & TimeoutOptions): Promise { - return await NativeModuleBle.read(options); + return NativeModuleBle.read(options); } async write(options: WriteOptions & TimeoutOptions): Promise { - return await NativeModuleBle.write(options); + return NativeModuleBle.write(options); } async signalize(options: SignalizeOptions): Promise { - return await NativeModuleBle.signalize(options); + return NativeModuleBle.signalize(options); } async disengage(options: DisengageOptions): Promise { - return await NativeModuleBle.disengage(options); + return NativeModuleBle.disengage(options); } async startNotifications( @@ -243,15 +237,11 @@ class AbrevvaBleModule implements AbrevvaBLEInterface { characteristic: string, callback: (event: ReadResult) => void, ): Promise { - if (this.eventEmitter === undefined) { - console.error('unsupported platform'); - return; - } const key = `notification|${deviceId}|${service}|${characteristic}`.toLowerCase(); - const listener = this.eventEmitter.addListener(key, callback); + const listener = this.eventEmitter!.addListener(key, callback); this.listeners.set(key, listener); await NativeModuleBle.setSupportedEvents({ events: [...this.listeners.keys()] }); - return await NativeModuleBle.startNotifications( + return NativeModuleBle.startNotifications( this.removeUndefinedField({ deviceId: deviceId, service: service, @@ -269,7 +259,7 @@ class AbrevvaBleModule implements AbrevvaBLEInterface { events: [...this.listeners.keys()], }); } - return await NativeModuleBle.stopNotifications(options); + return NativeModuleBle.stopNotifications(options); } } @@ -313,7 +303,7 @@ class AbrevvaCryptoModule implements AbrevvaCryptoInterface { } encryptFile(sharedSecret: string, ptPath: string, ctPath: string) { - return NativeModuleCrypto.encrypt({ + return NativeModuleCrypto.encryptFile({ sharedSecret: sharedSecret, ptPath: ptPath, ctPath: ctPath, @@ -348,3 +338,7 @@ class AbrevvaCryptoModule implements AbrevvaCryptoInterface { export const AbrevvaBle = new AbrevvaBleModule(); export const AbrevvaCrypto = new AbrevvaCryptoModule(); export const AbrevvaNfc = new AbrevvaNfcModule(); + +export function createAbrevvaBleInstance() { + return new AbrevvaBleModule(); +} diff --git a/src/setup.tsx b/src/setup.tsx new file mode 100644 index 0000000..ac7a4ff --- /dev/null +++ b/src/setup.tsx @@ -0,0 +1,56 @@ +jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter', () => ({ + __esModule: true, + default: class { + addListener = () => jest.fn(); + removeListener = () => jest.fn(); + removeAllListeners = () => jest.fn(); + }, +})); + +jest.mock('react-native/Libraries/Utilities/Platform', () => ({ + OS: 'ios', + select: jest.fn(() => null), +})); + +jest.mock('react-native', () => { + const originalModule = jest.requireActual('react-native'); + originalModule.NativeModules.AbrevvaNfc = { + connect: jest.fn(), + disconnect: jest.fn(), + read: jest.fn(), + }; + originalModule.NativeModules.AbrevvaBle = { + setSupportedEvents: jest.fn(), + initialize: jest.fn(), + isEnabled: jest.fn(), + isLocationEnabled: jest.fn(), + startEnabledNotifications: jest.fn(), + stopEnabledNotifications: jest.fn(), + openLocationSettings: jest.fn(), + openBluetoothSettings: jest.fn(), + openAppSettings: jest.fn(), + requestLEScan: jest.fn(), + stopLEScan: jest.fn(), + connect: jest.fn(), + disconnect: jest.fn(), + read: jest.fn(), + write: jest.fn(), + signalize: jest.fn(), + disengage: jest.fn(), + stopNotifications: jest.fn(), + addListener: jest.fn(), + removeListeners: jest.fn(), + }; + originalModule.NativeModules.AbrevvaCrypto = { + encrypt: jest.fn(), + decrypt: jest.fn(), + generateKeyPair: jest.fn(), + computeSharedSecret: jest.fn(), + encryptFile: jest.fn(), + decryptFile: jest.fn(), + decryptFileFromURL: jest.fn(), + random: jest.fn(), + derive: jest.fn(), + }; + return originalModule; +}); From a36b67781b83b2f75dd15f76c4f9912f50a6c440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hochst=C3=B6ger=20Matthias?= <116495532+mhochsto@users.noreply.github.com> Date: Wed, 7 Aug 2024 13:14:18 +0200 Subject: [PATCH 02/36] chore: added test cases for web and Kotlin Crypto Module --- android/build.gradle | 14 +- .../java/com/exampleapp/AbrevvaBleModule.kt | 2 +- .../com/exampleapp/AbrevvaCryptoModule.kt | 68 +-- .../test/exampleapp/AbrevvaBleModuleTest.kt | 40 ++ .../exampleapp/AbrevvaCryptoModuleTest.kt | 386 ++++++++++++++++++ .../WriteableMapTestImplementation.kt | 44 ++ example/android/app/build.gradle | 2 +- src/{__tests__ => }/index.test.tsx | 65 ++- src/index.tsx | 35 +- src/interfaces.tsx | 2 +- src/setup.tsx | 1 + 11 files changed, 604 insertions(+), 55 deletions(-) create mode 100644 android/src/main/java/com/test/exampleapp/AbrevvaBleModuleTest.kt create mode 100644 android/src/main/java/com/test/exampleapp/AbrevvaCryptoModuleTest.kt create mode 100644 android/src/main/java/com/test/exampleapp/WriteableMapTestImplementation.kt rename src/{__tests__ => }/index.test.tsx (66%) diff --git a/android/build.gradle b/android/build.gradle index f0df3a4..a04e4b3 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -101,6 +101,16 @@ dependencies { implementation "com.facebook.react:react-native:+" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation group: "com.evva.xesar", name: "abrevva-sdk-android", version: "1.0.19" - + implementation 'junit:junit:4.12' + implementation 'org.junit.jupiter:junit-jupiter:5.8.1' + + testImplementation("androidx.test:core-ktx:1.5.0") + testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0") + testImplementation("org.junit.jupiter:junit-jupiter:5.10.0") + testImplementation("org.junit.platform:junit-platform-suite-engine:1.10.0") + implementation("io.mockk:mockk:1.13.12") + implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.22") + + androidTestImplementation("androidx.test.ext:junit:1.2.1") + androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") } - diff --git a/android/src/main/java/com/exampleapp/AbrevvaBleModule.kt b/android/src/main/java/com/exampleapp/AbrevvaBleModule.kt index 2f32428..44500e8 100644 --- a/android/src/main/java/com/exampleapp/AbrevvaBleModule.kt +++ b/android/src/main/java/com/exampleapp/AbrevvaBleModule.kt @@ -298,7 +298,7 @@ class AbrevvaBleModule(reactContext: ReactApplicationContext) : isPermanentRelease ) { status: Any -> val result = Arguments.createMap() - result.putString("value", status as String) // TODO: Check if this works + result.putString("value", status as String) promise.resolve(result) } diff --git a/android/src/main/java/com/exampleapp/AbrevvaCryptoModule.kt b/android/src/main/java/com/exampleapp/AbrevvaCryptoModule.kt index bbc0ab2..6222853 100644 --- a/android/src/main/java/com/exampleapp/AbrevvaCryptoModule.kt +++ b/android/src/main/java/com/exampleapp/AbrevvaCryptoModule.kt @@ -11,11 +11,13 @@ import com.evva.xesar.abrevva.crypto.X25519Wrapper import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Promise 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 @@ -38,7 +40,7 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : System.arraycopy(ct, pt.size, authTag, 0, tagLength) if (ct.isEmpty()) { - promise.reject("encrypt(): encryption failed") + promise.reject(Exception("encrypt(): encryption failed")) } else { val ret = Arguments.createMap() ret.putString("cipherText", Hex.toHexString(cipherText)) @@ -57,8 +59,8 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : val pt: ByteArray = AesCCM.decrypt(key, iv, adata, ct, tagLength) - if (ct.isEmpty()) { - promise.reject("decrypt(): decryption failed") + if (pt.isEmpty()) { + promise.reject(Exception("decrypt(): decryption failed")) } else { val ret = Arguments.createMap() ret.putString("plainText", Hex.toHexString(pt)) @@ -77,7 +79,7 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : ret.putString("publicKey", Base64.toBase64String(keyPair.publicKey)) promise.resolve(ret) } catch (e: Exception) { - promise.reject("generateKeyPair(): private key creation failed") + promise.reject(Exception("generateKeyPair(): private key creation failed")) } } @@ -103,7 +105,7 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : ret.putString("sharedSecret", Hex.toHexString(sharedSecret)) promise.resolve(ret) } catch (e: Exception) { - promise.reject("computeSharedSecret(): failed to create shared key") + promise.reject(Exception("computeSharedSecret(): failed to create shared key")) } } @@ -112,17 +114,17 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : try { val ptPath = options.getString("ptPath") if (ptPath == null || ptPath == "") { - promise.reject("encryptFile(): invalid ptPath") + promise.reject(Exception("encryptFile(): invalid ptPath")) return } val ctPath = options.getString("ctPath") if (ctPath == null || ctPath == "") { - promise.reject("encryptFile(): invalid ctPath") + promise.reject(Exception("encryptFile(): invalid ctPath")) return } val sharedSecret = options.getString("sharedSecret") if (sharedSecret == null || sharedSecret == "") { - promise.reject("encryptFile(): invalid shared secret") + promise.reject(Exception("encryptFile(): invalid shared secret")) return } @@ -133,7 +135,7 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : ret.putBoolean("opOk", operationOk) promise.resolve(ret) } catch (e: Exception) { - promise.reject("encryptFile(): failed to encrypt file") + promise.reject(e) } } @@ -142,17 +144,17 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : try { val sharedSecret = options.getString("sharedSecret") if (sharedSecret == null || sharedSecret == "") { - promise.reject("decryptFile(): invalid shared secret") + promise.reject(Exception("decryptFile(): invalid shared secret")) return } val ctPath = options.getString("ctPath") if (ctPath == null || ctPath == "") { - promise.reject("decryptFile(): invalid ctPath") + promise.reject(Exception("decryptFile(): invalid ctPath")) return } val ptPath = options.getString("ptPath") if (ptPath == null || ptPath == "") { - promise.reject("decryptFile(): invalid ptPath") + promise.reject(Exception("decryptFile(): invalid ptPath")) return } @@ -163,7 +165,19 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : ret.putBoolean("opOk", operationOk) promise.resolve(ret) } catch (e: Exception) { - promise.reject("decryptFile(): failed to decrypt file") + promise.reject(e) + } + } + fun writeToFile(ctPath: String, url: String) { + + BufferedInputStream(URL(url).openStream()).use { `in` -> + FileOutputStream(ctPath).use { fileOutputStream -> + val dataBuffer = ByteArray(4096) + var bytesRead: Int + while (`in`.read(dataBuffer, 0, 4096).also { bytesRead = it } != -1) { + fileOutputStream.write(dataBuffer, 0, bytesRead) + } + } } } @@ -171,33 +185,25 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : fun decryptFileFromURL(options: ReadableMap, promise: Promise) { val sharedSecret = options.getString("sharedSecret") if (sharedSecret == null || sharedSecret == "") { - promise.reject("decryptFileFromURL(): invalid shared secret") + promise.reject(Exception("decryptFileFromURL(): invalid shared secret")) return } val url = options.getString("url") if (url == null || url == "") { - promise.reject("decryptFileFromURL(): invalid url") + promise.reject(Exception("decryptFileFromURL(): invalid url")) return } val ptPath = options.getString("ptPath") if (ptPath == null || ptPath == "") { - promise.reject("decryptFileFromURL(): invalid ptPath") + promise.reject(Exception("decryptFileFromURL(): invalid ptPath")) return } val ctPath = Paths.get(ptPath).parent.toString() + "/blob" try { - BufferedInputStream(URL(url).openStream()).use { `in` -> - FileOutputStream(ctPath).use { fileOutputStream -> - val dataBuffer = ByteArray(4096) - var bytesRead: Int - while (`in`.read(dataBuffer, 0, 4096).also { bytesRead = it } != -1) { - fileOutputStream.write(dataBuffer, 0, bytesRead) - } - } - } - } catch (e: IOException) { - promise.reject("decryptFileFromURL(): failed to load data from url") + writeToFile(ptPath, url) + } catch (e: Exception) { + promise.reject(e) return } @@ -209,7 +215,7 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : ret.putBoolean("opOk", operationOk) promise.resolve(ret) } catch (e: Exception) { - promise.reject("decryptFileFromURL(): failed to decrypt from file") + promise.reject(e) } } @@ -219,7 +225,7 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : val rnd: ByteArray = SimpleSecureRandom.getSecureRandomBytes(numBytes) if (rnd.isEmpty()) { - promise.reject("random(): random generation failed") + promise.reject(Exception("random(): random generation failed")) } else { val ret = Arguments.createMap() ret.putString("value", Hex.toHexString(rnd)) @@ -232,11 +238,11 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : val key = Hex.decode(options.getString("key") ?: "") val salt = Hex.decode(options.getString("salt") ?: "") val info = Hex.decode(options.getString("info") ?: "") - val length = options.getInt("length") + val length = options.getInt("length") ?: 0 val derived: ByteArray = HKDF.derive(key, salt, info, length) if (derived.isEmpty()) { - promise.reject("derive(): key derivation failed") + promise.reject(Exception("derive(): key derivation failed")) } else { val ret = Arguments.createMap() ret.putString("value", Hex.toHexString(derived)) diff --git a/android/src/main/java/com/test/exampleapp/AbrevvaBleModuleTest.kt b/android/src/main/java/com/test/exampleapp/AbrevvaBleModuleTest.kt new file mode 100644 index 0000000..b40993e --- /dev/null +++ b/android/src/main/java/com/test/exampleapp/AbrevvaBleModuleTest.kt @@ -0,0 +1,40 @@ +package com.test.exampleapp + +import com.exampleapp.AbrevvaBleModule +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReadableMap +import io.mockk.MockKAnnotations +import io.mockk.impl.annotations.MockK +import io.mockk.unmockkAll +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test + +class AbrevvaBleModuleTest { + private lateinit var abrevvaBleModule: AbrevvaBleModule + + private lateinit var testMap: WritableMapTestImplementation + + @MockK(relaxed = true) + private lateinit var contextMock: ReactApplicationContext + + @MockK(relaxed = true) + private lateinit var promiseMock: Promise + + @MockK(relaxed = true) + private lateinit var readableMapMock: ReadableMap + + @BeforeEach + fun beforeEach(){ + MockKAnnotations.init(this) + abrevvaBleModule = AbrevvaBleModule(contextMock) + } + + @AfterEach + fun afterEach(){ + unmockkAll() + } +} \ No newline at end of file diff --git a/android/src/main/java/com/test/exampleapp/AbrevvaCryptoModuleTest.kt b/android/src/main/java/com/test/exampleapp/AbrevvaCryptoModuleTest.kt new file mode 100644 index 0000000..22d410f --- /dev/null +++ b/android/src/main/java/com/test/exampleapp/AbrevvaCryptoModuleTest.kt @@ -0,0 +1,386 @@ +package com.test.exampleapp + +import android.graphics.Color +import com.evva.xesar.abrevva.crypto.AesCCM +import com.evva.xesar.abrevva.crypto.AesGCM +import com.evva.xesar.abrevva.crypto.HKDF +import com.evva.xesar.abrevva.crypto.SimpleSecureRandom +import com.evva.xesar.abrevva.crypto.X25519Wrapper +import com.exampleapp.AbrevvaCryptoModule +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.Dynamic +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.ReadableMapKeySetIterator +import com.facebook.react.bridge.ReadableType +import com.facebook.react.bridge.WritableMap +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.spyk +import io.mockk.unmockkAll +import io.mockk.verify +import org.bouncycastle.util.encoders.Hex +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource +import org.junit.jupiter.params.provider.MethodSource +import java.util.stream.Stream +import org.junit.jupiter.params.provider.Arguments as JunitArguments + +class AbrevvaCryptoModuleTest { + + private lateinit var abrevvaCryptoModule: AbrevvaCryptoModule + + private lateinit var testMap: WritableMapTestImplementation + + @MockK(relaxed = true) + private lateinit var contextMock: ReactApplicationContext + + @MockK(relaxed = true) + private lateinit var promiseMock: Promise + + @MockK(relaxed = true) + private lateinit var readableMapMock: ReadableMap + + @BeforeEach + fun beforeEach() { + MockKAnnotations.init(this) + mockkObject(AesCCM) + mockkObject(AesGCM) + mockkObject(X25519Wrapper) + mockkObject(SimpleSecureRandom) + mockkObject(HKDF) + mockkStatic(Arguments::createMap) + mockkStatic(Hex::class) + testMap = WritableMapTestImplementation() + every { Arguments.createMap() } returns testMap + every { Hex.decode(any()) } returns byteArrayOf(1) + abrevvaCryptoModule = AbrevvaCryptoModule(contextMock) + } + + @AfterEach + fun afterEach(){ + unmockkAll() + } + @Nested + @DisplayName("encrypt()") + inner class EncryptTests { + @Test + fun `should reject if ct is empty`() { + every { Hex.decode(any()) } answers { callOriginal()} + every { AesCCM.encrypt(any(), any(), any(), any(), any()) } returns ByteArray(0) + + abrevvaCryptoModule.encrypt(readableMapMock, promiseMock) + + verify { promiseMock.reject(any()) } + } + + @Test + fun `should resolve if ct is not empty`() { + every { AesCCM.encrypt(any(), any(), any(), any(), any()) } returns ByteArray(10) + + abrevvaCryptoModule.encrypt(readableMapMock, promiseMock) + + verify { promiseMock.resolve(any()) } + } + } + + @Nested + @DisplayName("decrypt()") + inner class DecryptTests { + @Test + fun `should reject if pt is empty`() { + every { AesCCM.decrypt(any(), any(), any(), any(), any()) } returns ByteArray(0) + + abrevvaCryptoModule.decrypt(readableMapMock, promiseMock) + + verify { promiseMock.reject(any()) } + } + + @Test + fun `should resolve if pt is not empty`() { + every { AesCCM.decrypt(any(), any(), any(), any(), any()) } returns ByteArray(10) + + abrevvaCryptoModule.decrypt(readableMapMock, promiseMock) + + verify { promiseMock.resolve(any()) } + } + } + + @Nested + @DisplayName("generateKeyPair()") + inner class GenerateKeyPairTests { + @Test + fun `should resolve if keys where generated successfully`() { + every { X25519Wrapper.generateKeyPair() } returns mockk(relaxed = true) + + abrevvaCryptoModule.generateKeyPair(promiseMock) + + verify { promiseMock.resolve(any()) } + } + + @Test + fun `should reject if keys cannot be generated`() { + every { X25519Wrapper.generateKeyPair() } throws Exception("generateKeyPair() Fail Exception") + + abrevvaCryptoModule.generateKeyPair(promiseMock) + + verify { promiseMock.reject(any()) } + } + } + + @Nested + @DisplayName("encryptFile()") + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class EncryptFileTests { + @ParameterizedTest(name = "encryptFile({0}, {1}, {2}) should reject") + @MethodSource("parameterizedArgs_encrypt") + fun `encryptFile() should reject if any Param is missing`( + ctPath: String?, + ptPath: String?, + sharedSecret: String? + ) { + testMap.putString("ctPath", ctPath) + testMap.putString("ptPath", ptPath) + testMap.putString("sharedSecret", sharedSecret) + + abrevvaCryptoModule.encryptFile(testMap, promiseMock) + + verify { promiseMock.reject(any()) } + } + fun parameterizedArgs_encrypt(): Stream{ + return Stream.of( + JunitArguments.of("", "ptPath", "sharedSecret"), + JunitArguments.of("ctPath", "", "sharedSecret"), + JunitArguments.of("ctPath", "sharedSecret", ""), + JunitArguments.of(null, "ptPath", "sharedSecret"), + JunitArguments.of("ctPath", null, "sharedSecret"), + JunitArguments.of("ctPath", "ptPath", null), + ) + } + + @Test + fun `should resolve if args are valid and file could be encrypted`() { + val mapMock = mockk(relaxed = true) + every { mapMock.getString(any()) } returns "notEmpty" + every { AesGCM.encryptFile(any(), any(), any()) } returns true + + abrevvaCryptoModule.encryptFile(mapMock, promiseMock) + + verify { promiseMock.resolve(any()) } + } + + @Test + fun `should reject if args are valid but encryption fails`() { + val mapMock = mockk(relaxed = true) + every { mapMock.getString(any()) } returns "notEmpty" + every { + AesGCM.encryptFile( + any(), + any(), + any() + ) + } throws Exception("encryptFile() Fail Exception") + + abrevvaCryptoModule.encryptFile(mapMock, promiseMock) + + verify { promiseMock.reject(any()) } + } + } + + @Nested + @DisplayName("decryptFile()") + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class DecryptFileTests { + @ParameterizedTest(name = "empty args should be rejected") + @MethodSource("parameterizedArgs_decrypt") + fun `should reject if any Param is empty`( + ctPath: String?, + ptPath: String?, + sharedSecret: String? + ) { + testMap.putString("ctPath", ctPath) + testMap.putString("ptPath", ptPath) + testMap.putString("sharedSecret", sharedSecret) + + abrevvaCryptoModule.decryptFile(testMap, promiseMock) + + verify { promiseMock.reject(any()) } + } + + fun parameterizedArgs_decrypt(): Stream{ + return Stream.of( + JunitArguments.of("", "ptPath", "sharedSecret"), + JunitArguments.of("ctPath", "", "sharedSecret"), + JunitArguments.of("ctPath", "ptPath", ""), + JunitArguments.of(null, "ptPath", "sharedSecret"), + JunitArguments.of("ctPath", null, "sharedSecret"), + JunitArguments.of("ctPath", "ptPath", null), + ) + } + + @Test + fun `should resolve if args are valid and file could be encrypted`() { + val mapMock = mockk(relaxed = true) + every { mapMock.getString(any()) } returns "notEmpty" + every { AesGCM.decryptFile(any(), any(), any()) } returns true + + abrevvaCryptoModule.decryptFile(mapMock, promiseMock) + + verify { promiseMock.resolve(any()) } + } + + @Test + fun `should reject if encryption fails`() { + val mapMock = mockk(relaxed = true) + every { mapMock.getString(any()) } returns "notEmpty" + every { + AesGCM.decryptFile( + any(), + any(), + any() + ) + } throws Exception("encryptFile() Fail Exception") + + abrevvaCryptoModule.decryptFile(mapMock, promiseMock) + + verify { promiseMock.reject(any()) } + } + } + + @Nested + @DisplayName("decryptFileFromURL()") + inner class DecryptFileFromURLTests { + + @Nested + @DisplayName("should reject if any Param is empty") + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class DecryptFileFromURL_ParameterizedTest { + @ParameterizedTest + @MethodSource("parameterizedArgs_decryptFileFromURL") + fun `should reject if any Param is empty`( + sharedSecret: String?, + url: String?, + ptPath: String? + ) { + testMap.putString("sharedSecret", sharedSecret) + testMap.putString("url", url) + testMap.putString("ptPath", ptPath) + + abrevvaCryptoModule.decryptFileFromURL(testMap, promiseMock) + + verify { promiseMock.reject(any()) } + } + + fun parameterizedArgs_decryptFileFromURL(): Stream { + return Stream.of( + JunitArguments.of("", "url", "ptPath"), + JunitArguments.of("sharedSecret", "", "ptPath"), + JunitArguments.of("sharedSecret", "url", ""), + JunitArguments.of(null, "url", "ptPath"), + JunitArguments.of("sharedSecret", null, "ptPath"), + JunitArguments.of("sharedSecret", "url", null), + ) + } + } + + @Test + fun `decryptFileFromURL() should reject if ctPath-File is not accessible`() { + val mockMap = mockk(relaxed = true) + val moduleSpy = spyk(AbrevvaCryptoModule(contextMock)) + every { mockMap.getString(any()) } returns "notEmpty" + every { moduleSpy.writeToFile(any(), any()) } throws Exception("decryptFileFromURL() Fail Exception") + + moduleSpy.decryptFileFromURL(mockMap, promiseMock) + + verify { promiseMock.reject(any()) } + } + + @Test + fun `decryptFileFromURL() should reject if decode fails`() { + val mockMap = mockk(relaxed = true) + val moduleSpy = spyk(AbrevvaCryptoModule(contextMock)) + every { mockMap.getString(any()) } returns "notEmpty" + every { moduleSpy.writeToFile(any(), any()) } returns Unit + every { Hex.decode(any()) } throws Exception("decryptFileFromURL() Fail Exception") + + moduleSpy.decryptFileFromURL(mockMap, promiseMock) + + verify { promiseMock.reject(any()) } + } + + @Test + fun `decryptFileFromURL() should resolve if everything works as intended`() { + val mockMap = mockk(relaxed = true) + val moduleSpy = spyk(AbrevvaCryptoModule(contextMock)) + every { mockMap.getString(any()) } returns "notEmpty" + every { moduleSpy.writeToFile(any(), any()) } returns Unit + every { AesGCM.decryptFile(any(), any(), any()) } returns true + + moduleSpy.decryptFileFromURL(mockMap, promiseMock) + + verify { promiseMock.resolve(any()) } + } + } + + @Nested + @DisplayName("random()") + inner class RandomTests { + @ParameterizedTest(name = "random(numBytes: {0}) resolved String size should be {1}") + @CsvSource("2,4", "4,8", "7,14") + fun `should return random bytes n number of bytes if successful`( + numBytes: Int, + expectedStrLen: Int + ) { + val mockMap = mockk(relaxed = true) + every { mockMap.getInt("numBytes") } returns numBytes + + abrevvaCryptoModule.random(mockMap, promiseMock) + + assert(testMap.getString("value")!!.length == expectedStrLen) + } + + @Test + fun `should reject if bytes cannot be generated`(){ + every { SimpleSecureRandom.getSecureRandomBytes(any()) } returns ByteArray(0) + testMap.putInt("numBytes", 10) + + abrevvaCryptoModule.random(testMap, promiseMock) + + verify { promiseMock.reject(any()) } + } + } + @Nested + @DisplayName("derive()") + inner class DeriveTests { + + @Test + fun `should resolve if successful`() { + testMap.putInt("length", 0) + every { HKDF.derive(any(), any(), any(), any()) } returns ByteArray(0) + + abrevvaCryptoModule.derive(testMap, promiseMock) + + verify { promiseMock.reject(any()) } + } + @Test + fun `should reject if unsuccessful`() { + testMap.putInt("length", 10) + every { HKDF.derive(any(), any(), any(), any()) } returns ByteArray(10) + abrevvaCryptoModule.derive(testMap, promiseMock) + + verify { promiseMock.resolve(any()) } + } + } +} \ No newline at end of file diff --git a/android/src/main/java/com/test/exampleapp/WriteableMapTestImplementation.kt b/android/src/main/java/com/test/exampleapp/WriteableMapTestImplementation.kt new file mode 100644 index 0000000..26c5dbe --- /dev/null +++ b/android/src/main/java/com/test/exampleapp/WriteableMapTestImplementation.kt @@ -0,0 +1,44 @@ +package com.test.exampleapp + +import com.facebook.react.bridge.Dynamic +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.ReadableMapKeySetIterator +import com.facebook.react.bridge.ReadableType +import com.facebook.react.bridge.WritableMap +import io.mockk.mockk + +class WritableMapTestImplementation : WritableMap { + private val map = mutableMapOf() + + override fun hasKey(p0: String): Boolean { return map.containsKey(p0) } + override fun isNull(p0: String): Boolean { return map[p0] == null } + override fun getBoolean(p0: String): Boolean { return map[p0] as Boolean } + override fun getDouble(p0: String): Double { return map[p0] as Double } + override fun getInt(p0: String): Int { return map[p0] as Int } + override fun getString(p0: String): String? { return map[p0] as String? } + override fun getArray(p0: String): ReadableArray? { return map[p0] as ReadableArray? } + override fun getMap(p0: String): ReadableMap? { return map[p0] as ReadableMap? } + override fun getDynamic(p0: String): Dynamic { return mockk() } + override fun getType(p0: String): ReadableType { return mockk() } + override fun getEntryIterator(): MutableIterator> { + return mockk>>() + } + override fun keySetIterator(): ReadableMapKeySetIterator { + return mockk() + } + override fun toHashMap(): HashMap { return mockk>() } + override fun putNull(p0: String) { map[p0] = null } + override fun putBoolean(p0: String, p1: Boolean) { map[p0] = p1 } + override fun putDouble(p0: String, p1: Double) { map[p0] = p1 } + override fun putInt(p0: String, p1: Int) { map[p0] = p1 } + override fun putString(p0: String, p1: String?) { map[p0] = p1 } + override fun putArray(p0: String, p1: ReadableArray?) { map[p0] = p1 } + override fun putMap(p0: String, p1: ReadableMap?) { map[p0] = p1 } + override fun merge(p0: ReadableMap) { + TODO("Not yet implemented") + } + override fun copy(): WritableMap { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 5a29035..7a39f93 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -139,7 +139,7 @@ repositories { dependencies { // The version of react-native is set by the React Native Gradle Plugin implementation("com.facebook.react:react-android") - implementation group: "com.evva.xesar", name: "abrevva-sdk-android", version: "1.0.15" + implementation group: "com.evva.xesar", name: "abrevva-sdk-android", version: "1.0.19" if (hermesEnabled.toBoolean()) { implementation("com.facebook.react:hermes-android") diff --git a/src/__tests__/index.test.tsx b/src/index.test.tsx similarity index 66% rename from src/__tests__/index.test.tsx rename to src/index.test.tsx index 0443075..362eb58 100644 --- a/src/__tests__/index.test.tsx +++ b/src/index.test.tsx @@ -1,6 +1,6 @@ import { NativeEventEmitter, NativeModules, Platform } from 'react-native'; -import { AbrevvaCrypto, AbrevvaNfc, createAbrevvaBleInstance } from '../index'; +import { AbrevvaCrypto, AbrevvaNfc, createAbrevvaBleInstance } from './index'; describe('AbrevvaBleModule', () => { const AbrevvaBleMock = NativeModules.AbrevvaBle; @@ -8,7 +8,7 @@ describe('AbrevvaBleModule', () => { var AbrevvaBle; beforeEach(() => { - jest.clearAllMocks(); + jest.useFakeTimers(); Platform.OS = 'ios'; Platform.select.mockImplementation(() => { @@ -17,6 +17,7 @@ describe('AbrevvaBleModule', () => { mockEmitter = new NativeEventEmitter(); AbrevvaBle = createAbrevvaBleInstance(); + jest.clearAllMocks(); }); it('constructor should throw if Platform is not Supported', () => { @@ -68,7 +69,30 @@ describe('AbrevvaBleModule', () => { expect(AbrevvaBleMock.openAppSettings).toHaveBeenCalledTimes(1); }); - it('should run requestLEScan()', async () => {}); + describe('requestLEScan()', () => { + it('should reject if a scan is already in progress', async () => { + AbrevvaBle.requestLEScan(); + await expect(AbrevvaBle.requestLEScan).rejects.toThrow(); + }); + it('should add the expected eventlisteners and discard them after the timeout', async () => { + const addListenerSpy = jest.spyOn(mockEmitter, 'addListener'); + + const emitterSubscriptionMock = { remove: jest.fn() }; + mockEmitter.addListener.mockImplementation(() => { + return emitterSubscriptionMock; + }); + AbrevvaBle.requestLEScan(jest.fn(), jest.fn(), jest.fn()); + + jest.advanceTimersByTime(20000); + + expect(addListenerSpy).toHaveBeenCalledWith('onScanResult', expect.any(Function)); + expect(addListenerSpy).toHaveBeenCalledWith('onConnect', expect.any(Function)); + expect(addListenerSpy).toHaveBeenCalledWith('onDisconnect', expect.any(Function)); + expect(addListenerSpy).toHaveBeenCalledTimes(3); + expect(AbrevvaBleMock.requestLEScan).toHaveBeenCalledTimes(1); + expect(emitterSubscriptionMock.remove).toHaveBeenCalledTimes(3); + }); + }); it('should run stopLEScan()', async () => { await AbrevvaBle.stopLEScan(); expect(AbrevvaBleMock.stopLEScan).toHaveBeenCalledTimes(1); @@ -77,7 +101,11 @@ describe('AbrevvaBleModule', () => { await AbrevvaBle.connect(); expect(AbrevvaBleMock.connect).toHaveBeenCalledTimes(1); }); - it('should run disconnect()', async () => {}); + it('should run disconnect()', async () => { + await AbrevvaBle.disconnect(); + expect(AbrevvaBleMock.disconnect).toHaveBeenCalledTimes(1); + expect(AbrevvaBleMock.setSupportedEvents).toHaveBeenCalledTimes(1); + }); it('should run read()', async () => { await AbrevvaBle.read(); expect(AbrevvaBleMock.read).toHaveBeenCalledTimes(1); @@ -94,7 +122,34 @@ describe('AbrevvaBleModule', () => { await AbrevvaBle.disengage(); expect(AbrevvaBleMock.disengage).toHaveBeenCalledTimes(1); }); - it('should run stopNotifications()', async () => {}); + describe('startNotifications()', () => {}); + describe('stopNotifications()', () => { + const deviceId = 'deviceId'; + const service = 'service'; + const characteristic = 'characteristic'; + + var emitterSubscriptionMock; + beforeEach(() => { + emitterSubscriptionMock = { remove: jest.fn() }; + void jest.spyOn(mockEmitter, 'addListener').mockImplementation(() => { + return emitterSubscriptionMock; + }); + }); + it('should delete the Eventlistener if it was previously added', async () => { + await AbrevvaBle.startNotifications(deviceId, service, characteristic); + expect(emitterSubscriptionMock.remove).toHaveBeenCalledTimes(0); + expect(AbrevvaBleMock.stopNotifications).toHaveBeenCalledTimes(0); + expect(AbrevvaBleMock.startNotifications).toHaveBeenCalledTimes(1); + await AbrevvaBle.stopNotifications(deviceId, service, characteristic); + expect(emitterSubscriptionMock.remove).toHaveBeenCalledTimes(1); + expect(AbrevvaBleMock.stopNotifications).toHaveBeenCalledTimes(1); + }); + it("shouldn't remove any key if it wasn't previously added", async () => { + await AbrevvaBle.stopNotifications(deviceId, service, characteristic); + expect(emitterSubscriptionMock.remove).toHaveBeenCalledTimes(0); + expect(AbrevvaBleMock.stopNotifications).toHaveBeenCalledTimes(1); + }); + }); }); describe('AbrevvaNfcModule', () => { diff --git a/src/index.tsx b/src/index.tsx index 18a5ed0..7becf87 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -161,14 +161,14 @@ class AbrevvaBleModule implements AbrevvaBLEInterface { }; const listeners = new Map([ - ['onScanResult', { callback: onScanResultHelper, listener: undefined }], - ['onConnect', { callback: onConnectCallback, listener: undefined }], - ['onDisconnect', { callback: onDisconnectCallback, listener: undefined }], + ['onScanResult', onScanResultHelper], + ['onConnect', onConnectCallback], + ['onDisconnect', onDisconnectCallback], ]); - listeners.forEach((callbackObj: any, listenerName: string) => { - callbackObj.listener = this.eventEmitter!.addListener(listenerName, callbackObj.callback); - this.listeners.set(listenerName, callbackObj.listener); + listeners.forEach((callback: any, listenerName: string) => { + const listener = this.eventEmitter!.addListener(listenerName, callback); + this.listeners.set(listenerName, listener); }); NativeModuleBle.requestLEScan( @@ -182,12 +182,11 @@ class AbrevvaBleModule implements AbrevvaBLEInterface { timeout: timeout, }), ); - setTimeout( () => { - listeners.forEach((callbackObj: any, listenerName: string) => { + this.listeners.forEach((listener, listenerName) => { + listener?.remove(); this.listeners.set(listenerName, undefined); - callbackObj.listener.remove(); }); Promise.resolve(); }, @@ -250,16 +249,24 @@ class AbrevvaBleModule implements AbrevvaBLEInterface { ); } - async stopNotifications(options: ReadOptions): Promise { - const key = - `notification|${options.deviceId}|${options.service}|${options.characteristic}`.toLowerCase(); - if (key in this.listeners) { + async stopNotifications( + deviceId: string, + service: string, + characteristic: string, + ): Promise { + const key = `notification|${deviceId}|${service}|${characteristic}`.toLowerCase(); + if (this.listeners.get(key)) { + this.listeners.get(key)?.remove(); this.listeners.delete(key); NativeModuleBle.setSupportedEvents({ events: [...this.listeners.keys()], }); } - return NativeModuleBle.stopNotifications(options); + return NativeModuleBle.stopNotifications({ + deviceId: deviceId, + service: service, + characteristic: characteristic, + }); } } diff --git a/src/interfaces.tsx b/src/interfaces.tsx index 5b2aec6..cec83ed 100644 --- a/src/interfaces.tsx +++ b/src/interfaces.tsx @@ -151,5 +151,5 @@ export interface AbrevvaBLEInterface { characteristic: string, callback: (event: ReadResult) => void, ): Promise; - stopNotifications(options: ReadOptions): Promise; + stopNotifications(deviceId: string, service: string, characteristic: string): Promise; } diff --git a/src/setup.tsx b/src/setup.tsx index ac7a4ff..0a9702c 100644 --- a/src/setup.tsx +++ b/src/setup.tsx @@ -37,6 +37,7 @@ jest.mock('react-native', () => { write: jest.fn(), signalize: jest.fn(), disengage: jest.fn(), + startNotifications: jest.fn(), stopNotifications: jest.fn(), addListener: jest.fn(), removeListeners: jest.fn(), From 7cba9f124acb8ff3cf89525471f6cadcbe628728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hochst=C3=B6ger=20Matthias?= <116495532+mhochsto@users.noreply.github.com> Date: Fri, 2 Aug 2024 08:29:40 +0200 Subject: [PATCH 03/36] chore: initial test setup --- ios/ble/AbrevvaBle.swift | 1 + package.json | 3 + src/__tests__/index.test.tsx | 165 ++++++++++++++++++++++++++++++++++- src/index.tsx | 46 +++++----- src/setup.tsx | 56 ++++++++++++ 5 files changed, 244 insertions(+), 27 deletions(-) create mode 100644 src/setup.tsx diff --git a/ios/ble/AbrevvaBle.swift b/ios/ble/AbrevvaBle.swift index 206e08c..445573b 100644 --- a/ios/ble/AbrevvaBle.swift +++ b/ios/ble/AbrevvaBle.swift @@ -44,6 +44,7 @@ public class AbrevvaBle: RCTEventEmitter { bleManager.registerStateReceiver { enabled in self.sendEvent(withName: "onEnabledChanged", body: ["value": enabled]) } + resolve(nil) } @objc diff --git a/package.json b/package.json index 85cadc8..565dc0d 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,9 @@ "modulePathIgnorePatterns": [ "/example/node_modules", "/lib/" + ], + "setupFilesAfterEnv": [ + "/src/setup.tsx" ] }, "commitlint": { diff --git a/src/__tests__/index.test.tsx b/src/__tests__/index.test.tsx index bf84291..0443075 100644 --- a/src/__tests__/index.test.tsx +++ b/src/__tests__/index.test.tsx @@ -1 +1,164 @@ -it.todo('write a test'); +import { NativeEventEmitter, NativeModules, Platform } from 'react-native'; + +import { AbrevvaCrypto, AbrevvaNfc, createAbrevvaBleInstance } from '../index'; + +describe('AbrevvaBleModule', () => { + const AbrevvaBleMock = NativeModules.AbrevvaBle; + var mockEmitter; + var AbrevvaBle; + + beforeEach(() => { + jest.clearAllMocks(); + + Platform.OS = 'ios'; + Platform.select.mockImplementation(() => { + return mockEmitter; + }); + + mockEmitter = new NativeEventEmitter(); + AbrevvaBle = createAbrevvaBleInstance(); + }); + + it('constructor should throw if Platform is not Supported', () => { + Platform.select.mockImplementation(() => { + return undefined; + }); + expect(createAbrevvaBleInstance).toThrow(); + }); + it('should run initialize()', async () => { + await AbrevvaBle.initialize(); + expect(AbrevvaBleMock.initialize).toHaveBeenCalledTimes(1); + }); + it('should run isEnabled()', async () => { + await AbrevvaBle.isEnabled(); + expect(AbrevvaBleMock.isEnabled).toHaveBeenCalledTimes(1); + }); + it('should run isLocationEnabled()', async () => { + await AbrevvaBle.isLocationEnabled(); + expect(AbrevvaBleMock.isLocationEnabled).toHaveBeenCalledTimes(1); + }); + + describe('startEnableNotification', () => { + it('should add the correct eventlistener and call startEnableNotification', async () => { + const spy = jest.spyOn(mockEmitter, 'addListener'); + const spyNativeModule = jest.spyOn(AbrevvaBle, 'startEnabledNotifications'); + const mockfn = jest.fn(); + await AbrevvaBle.startEnabledNotifications(mockfn); + + expect(spy).toHaveBeenCalledWith('onEnabledChanged', expect.any(Function)); + expect(spy).toHaveBeenCalledTimes(1); + expect(spyNativeModule).toHaveBeenCalledTimes(1); + }); + }); + + it('should run stopEnabledNotifications()', async () => { + await AbrevvaBle.stopEnabledNotifications(); + expect(AbrevvaBleMock.stopEnabledNotifications).toHaveBeenCalledTimes(1); + }); + it('should run openLocationSettings()', async () => { + await AbrevvaBle.openLocationSettings(); + expect(AbrevvaBleMock.openLocationSettings).toHaveBeenCalledTimes(1); + }); + it('should run openBluetoothSettings()', async () => { + await AbrevvaBle.openBluetoothSettings(); + expect(AbrevvaBleMock.openBluetoothSettings).toHaveBeenCalledTimes(1); + }); + it('should run openAppSettings()', async () => { + await AbrevvaBle.openAppSettings(); + expect(AbrevvaBleMock.openAppSettings).toHaveBeenCalledTimes(1); + }); + + it('should run requestLEScan()', async () => {}); + it('should run stopLEScan()', async () => { + await AbrevvaBle.stopLEScan(); + expect(AbrevvaBleMock.stopLEScan).toHaveBeenCalledTimes(1); + }); + it('should run connect()', async () => { + await AbrevvaBle.connect(); + expect(AbrevvaBleMock.connect).toHaveBeenCalledTimes(1); + }); + it('should run disconnect()', async () => {}); + it('should run read()', async () => { + await AbrevvaBle.read(); + expect(AbrevvaBleMock.read).toHaveBeenCalledTimes(1); + }); + it('should run write()', async () => { + await AbrevvaBle.write(); + expect(AbrevvaBleMock.write).toHaveBeenCalledTimes(1); + }); + it('should run signalize()', async () => { + await AbrevvaBle.signalize(); + expect(AbrevvaBleMock.signalize).toHaveBeenCalledTimes(1); + }); + it('should run disengage()', async () => { + await AbrevvaBle.disengage(); + expect(AbrevvaBleMock.disengage).toHaveBeenCalledTimes(1); + }); + it('should run stopNotifications()', async () => {}); +}); + +describe('AbrevvaNfcModule', () => { + const AbrevvaNfcMock = NativeModules.AbrevvaNfc; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should run connect()', async () => { + await AbrevvaNfc.connect(); + expect(AbrevvaNfcMock.connect).toHaveBeenCalledTimes(1); + }); + it('should run disconnect()', async () => { + await AbrevvaNfc.disconnect(); + expect(AbrevvaNfcMock.disconnect).toHaveBeenCalledTimes(1); + }); + it('should run read()', async () => { + await AbrevvaNfc.read(); + expect(AbrevvaNfcMock.read).toHaveBeenCalledTimes(1); + }); +}); + +describe('AbrevvaCryptoModule', () => { + const AbrevvaCryptoMock = NativeModules.AbrevvaCrypto; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should run encrypt()', async () => { + await AbrevvaCrypto.encrypt('', '', '', ''); + expect(AbrevvaCryptoMock.encrypt).toHaveBeenCalledTimes(1); + }); + it('should run decrypt()', async () => { + await AbrevvaCrypto.decrypt('', '', '', ''); + expect(AbrevvaCryptoMock.decrypt).toHaveBeenCalledTimes(1); + }); + it('should run generateKeyPair()', async () => { + await AbrevvaCrypto.generateKeyPair(); + expect(AbrevvaCryptoMock.generateKeyPair).toHaveBeenCalledTimes(1); + }); + it('should run computeSharedSecret()', async () => { + await AbrevvaCrypto.computeSharedSecret('', ''); + expect(AbrevvaCryptoMock.computeSharedSecret).toHaveBeenCalledTimes(1); + }); + it('should run encryptFile()', async () => { + await AbrevvaCrypto.encryptFile('', '', ''); + expect(AbrevvaCryptoMock.encryptFile).toHaveBeenCalledTimes(1); + }); + it('should run decryptFile()', async () => { + await AbrevvaCrypto.decryptFile('', '', ''); + expect(AbrevvaCryptoMock.decryptFile).toHaveBeenCalledTimes(1); + }); + it('should run decryptFileFromURL()', async () => { + await AbrevvaCrypto.decryptFileFromURL('', '', ''); + expect(AbrevvaCryptoMock.decryptFileFromURL).toHaveBeenCalledTimes(1); + }); + it('should run random()', async () => { + await AbrevvaCrypto.random(0); + expect(AbrevvaCryptoMock.random).toHaveBeenCalledTimes(1); + }); + it('should run derive()', async () => { + await AbrevvaCrypto.derive('', '', '', 0); + expect(AbrevvaCryptoMock.derive).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/index.tsx b/src/index.tsx index aff6f56..18a5ed0 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,5 +1,3 @@ -// https://stackoverflow.com/a/73382191 -// TODO: Verify that this works as intended import { DeviceEventEmitter, type EmitterSubscription, @@ -30,7 +28,7 @@ import type { WriteOptions, } from './interfaces'; -const NativeModuleNfc: AbrevvaNfcInterface = NativeModules.AbrevvaNfc +const NativeModuleNfc = NativeModules.AbrevvaNfc ? NativeModules.AbrevvaNfc : new Proxy( {}, @@ -47,7 +45,7 @@ const NativeModuleCrypto = NativeModules.AbrevvaCrypto {}, { get() { - throw new Error('Linking Error AbrevvaNfc'); + throw new Error('Linking Error AbrevvaCrypto'); }, }, ); @@ -72,6 +70,9 @@ class AbrevvaBleModule implements AbrevvaBLEInterface { ios: new NativeEventEmitter(NativeModuleBle), android: DeviceEventEmitter, }); + if (this.eventEmitter === undefined) { + throw new Error('this platform is not supported'); + } this.listeners = new Map([ ['onEnabledChanged', undefined], @@ -80,7 +81,7 @@ class AbrevvaBleModule implements AbrevvaBLEInterface { ['onDisconnect', undefined], ]); - NativeModuleBle.setSupportedEvents({ events: [...this.listeners.keys()] }); + NativeModuleBle?.setSupportedEvents({ events: [...this.listeners.keys()] }); } async initialize(androidNeverForLocation?: boolean): Promise { @@ -98,13 +99,9 @@ class AbrevvaBleModule implements AbrevvaBLEInterface { } async startEnabledNotifications(callback: (result: boolean) => void): Promise { - if (this.eventEmitter === undefined) { - return Promise.reject('unsupported platform'); - } - this.listeners.set( 'onEnabledChanged', - this.eventEmitter.addListener('onEnabledChanged', (event: any) => { + this.eventEmitter!.addListener('onEnabledChanged', (event: any) => { callback(event.value); }), ); @@ -147,9 +144,6 @@ class AbrevvaBleModule implements AbrevvaBLEInterface { scanMode?: ScanMode, timeout?: number, ): Promise { - if (this.eventEmitter === undefined) { - return Promise.reject('unsupported platform'); - } if (this.listeners.get('onScanResult') !== undefined) { return Promise.reject('scan already in progress'); } @@ -218,23 +212,23 @@ class AbrevvaBleModule implements AbrevvaBLEInterface { ['onDisconnect', undefined], ]); NativeModuleBle.setSupportedEvents({ events: [...this.listeners.keys()] }); - return await NativeModuleBle.disconnect(options); + return NativeModuleBle.disconnect(options); } async read(options: ReadOptions & TimeoutOptions): Promise { - return await NativeModuleBle.read(options); + return NativeModuleBle.read(options); } async write(options: WriteOptions & TimeoutOptions): Promise { - return await NativeModuleBle.write(options); + return NativeModuleBle.write(options); } async signalize(options: SignalizeOptions): Promise { - return await NativeModuleBle.signalize(options); + return NativeModuleBle.signalize(options); } async disengage(options: DisengageOptions): Promise { - return await NativeModuleBle.disengage(options); + return NativeModuleBle.disengage(options); } async startNotifications( @@ -243,15 +237,11 @@ class AbrevvaBleModule implements AbrevvaBLEInterface { characteristic: string, callback: (event: ReadResult) => void, ): Promise { - if (this.eventEmitter === undefined) { - console.error('unsupported platform'); - return; - } const key = `notification|${deviceId}|${service}|${characteristic}`.toLowerCase(); - const listener = this.eventEmitter.addListener(key, callback); + const listener = this.eventEmitter!.addListener(key, callback); this.listeners.set(key, listener); await NativeModuleBle.setSupportedEvents({ events: [...this.listeners.keys()] }); - return await NativeModuleBle.startNotifications( + return NativeModuleBle.startNotifications( this.removeUndefinedField({ deviceId: deviceId, service: service, @@ -269,7 +259,7 @@ class AbrevvaBleModule implements AbrevvaBLEInterface { events: [...this.listeners.keys()], }); } - return await NativeModuleBle.stopNotifications(options); + return NativeModuleBle.stopNotifications(options); } } @@ -313,7 +303,7 @@ class AbrevvaCryptoModule implements AbrevvaCryptoInterface { } encryptFile(sharedSecret: string, ptPath: string, ctPath: string) { - return NativeModuleCrypto.encrypt({ + return NativeModuleCrypto.encryptFile({ sharedSecret: sharedSecret, ptPath: ptPath, ctPath: ctPath, @@ -348,3 +338,7 @@ class AbrevvaCryptoModule implements AbrevvaCryptoInterface { export const AbrevvaBle = new AbrevvaBleModule(); export const AbrevvaCrypto = new AbrevvaCryptoModule(); export const AbrevvaNfc = new AbrevvaNfcModule(); + +export function createAbrevvaBleInstance() { + return new AbrevvaBleModule(); +} diff --git a/src/setup.tsx b/src/setup.tsx new file mode 100644 index 0000000..ac7a4ff --- /dev/null +++ b/src/setup.tsx @@ -0,0 +1,56 @@ +jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter', () => ({ + __esModule: true, + default: class { + addListener = () => jest.fn(); + removeListener = () => jest.fn(); + removeAllListeners = () => jest.fn(); + }, +})); + +jest.mock('react-native/Libraries/Utilities/Platform', () => ({ + OS: 'ios', + select: jest.fn(() => null), +})); + +jest.mock('react-native', () => { + const originalModule = jest.requireActual('react-native'); + originalModule.NativeModules.AbrevvaNfc = { + connect: jest.fn(), + disconnect: jest.fn(), + read: jest.fn(), + }; + originalModule.NativeModules.AbrevvaBle = { + setSupportedEvents: jest.fn(), + initialize: jest.fn(), + isEnabled: jest.fn(), + isLocationEnabled: jest.fn(), + startEnabledNotifications: jest.fn(), + stopEnabledNotifications: jest.fn(), + openLocationSettings: jest.fn(), + openBluetoothSettings: jest.fn(), + openAppSettings: jest.fn(), + requestLEScan: jest.fn(), + stopLEScan: jest.fn(), + connect: jest.fn(), + disconnect: jest.fn(), + read: jest.fn(), + write: jest.fn(), + signalize: jest.fn(), + disengage: jest.fn(), + stopNotifications: jest.fn(), + addListener: jest.fn(), + removeListeners: jest.fn(), + }; + originalModule.NativeModules.AbrevvaCrypto = { + encrypt: jest.fn(), + decrypt: jest.fn(), + generateKeyPair: jest.fn(), + computeSharedSecret: jest.fn(), + encryptFile: jest.fn(), + decryptFile: jest.fn(), + decryptFileFromURL: jest.fn(), + random: jest.fn(), + derive: jest.fn(), + }; + return originalModule; +}); From a62e0013e830aa1f25d8103cfd1d61b3dc70988c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hochst=C3=B6ger=20Matthias?= <116495532+mhochsto@users.noreply.github.com> Date: Wed, 7 Aug 2024 13:14:18 +0200 Subject: [PATCH 04/36] chore: added test cases for web and Kotlin Crypto Module --- android/build.gradle | 14 +- .../java/com/exampleapp/AbrevvaBleModule.kt | 2 +- .../com/exampleapp/AbrevvaCryptoModule.kt | 68 +-- .../test/exampleapp/AbrevvaBleModuleTest.kt | 40 ++ .../exampleapp/AbrevvaCryptoModuleTest.kt | 386 ++++++++++++++++++ .../WriteableMapTestImplementation.kt | 44 ++ example/android/app/build.gradle | 2 +- src/{__tests__ => }/index.test.tsx | 65 ++- src/index.tsx | 35 +- src/interfaces.tsx | 2 +- src/setup.tsx | 1 + 11 files changed, 604 insertions(+), 55 deletions(-) create mode 100644 android/src/main/java/com/test/exampleapp/AbrevvaBleModuleTest.kt create mode 100644 android/src/main/java/com/test/exampleapp/AbrevvaCryptoModuleTest.kt create mode 100644 android/src/main/java/com/test/exampleapp/WriteableMapTestImplementation.kt rename src/{__tests__ => }/index.test.tsx (66%) diff --git a/android/build.gradle b/android/build.gradle index f0df3a4..a04e4b3 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -101,6 +101,16 @@ dependencies { implementation "com.facebook.react:react-native:+" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation group: "com.evva.xesar", name: "abrevva-sdk-android", version: "1.0.19" - + implementation 'junit:junit:4.12' + implementation 'org.junit.jupiter:junit-jupiter:5.8.1' + + testImplementation("androidx.test:core-ktx:1.5.0") + testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0") + testImplementation("org.junit.jupiter:junit-jupiter:5.10.0") + testImplementation("org.junit.platform:junit-platform-suite-engine:1.10.0") + implementation("io.mockk:mockk:1.13.12") + implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.22") + + androidTestImplementation("androidx.test.ext:junit:1.2.1") + androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") } - diff --git a/android/src/main/java/com/exampleapp/AbrevvaBleModule.kt b/android/src/main/java/com/exampleapp/AbrevvaBleModule.kt index 2f32428..44500e8 100644 --- a/android/src/main/java/com/exampleapp/AbrevvaBleModule.kt +++ b/android/src/main/java/com/exampleapp/AbrevvaBleModule.kt @@ -298,7 +298,7 @@ class AbrevvaBleModule(reactContext: ReactApplicationContext) : isPermanentRelease ) { status: Any -> val result = Arguments.createMap() - result.putString("value", status as String) // TODO: Check if this works + result.putString("value", status as String) promise.resolve(result) } diff --git a/android/src/main/java/com/exampleapp/AbrevvaCryptoModule.kt b/android/src/main/java/com/exampleapp/AbrevvaCryptoModule.kt index bbc0ab2..6222853 100644 --- a/android/src/main/java/com/exampleapp/AbrevvaCryptoModule.kt +++ b/android/src/main/java/com/exampleapp/AbrevvaCryptoModule.kt @@ -11,11 +11,13 @@ import com.evva.xesar.abrevva.crypto.X25519Wrapper import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Promise 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 @@ -38,7 +40,7 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : System.arraycopy(ct, pt.size, authTag, 0, tagLength) if (ct.isEmpty()) { - promise.reject("encrypt(): encryption failed") + promise.reject(Exception("encrypt(): encryption failed")) } else { val ret = Arguments.createMap() ret.putString("cipherText", Hex.toHexString(cipherText)) @@ -57,8 +59,8 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : val pt: ByteArray = AesCCM.decrypt(key, iv, adata, ct, tagLength) - if (ct.isEmpty()) { - promise.reject("decrypt(): decryption failed") + if (pt.isEmpty()) { + promise.reject(Exception("decrypt(): decryption failed")) } else { val ret = Arguments.createMap() ret.putString("plainText", Hex.toHexString(pt)) @@ -77,7 +79,7 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : ret.putString("publicKey", Base64.toBase64String(keyPair.publicKey)) promise.resolve(ret) } catch (e: Exception) { - promise.reject("generateKeyPair(): private key creation failed") + promise.reject(Exception("generateKeyPair(): private key creation failed")) } } @@ -103,7 +105,7 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : ret.putString("sharedSecret", Hex.toHexString(sharedSecret)) promise.resolve(ret) } catch (e: Exception) { - promise.reject("computeSharedSecret(): failed to create shared key") + promise.reject(Exception("computeSharedSecret(): failed to create shared key")) } } @@ -112,17 +114,17 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : try { val ptPath = options.getString("ptPath") if (ptPath == null || ptPath == "") { - promise.reject("encryptFile(): invalid ptPath") + promise.reject(Exception("encryptFile(): invalid ptPath")) return } val ctPath = options.getString("ctPath") if (ctPath == null || ctPath == "") { - promise.reject("encryptFile(): invalid ctPath") + promise.reject(Exception("encryptFile(): invalid ctPath")) return } val sharedSecret = options.getString("sharedSecret") if (sharedSecret == null || sharedSecret == "") { - promise.reject("encryptFile(): invalid shared secret") + promise.reject(Exception("encryptFile(): invalid shared secret")) return } @@ -133,7 +135,7 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : ret.putBoolean("opOk", operationOk) promise.resolve(ret) } catch (e: Exception) { - promise.reject("encryptFile(): failed to encrypt file") + promise.reject(e) } } @@ -142,17 +144,17 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : try { val sharedSecret = options.getString("sharedSecret") if (sharedSecret == null || sharedSecret == "") { - promise.reject("decryptFile(): invalid shared secret") + promise.reject(Exception("decryptFile(): invalid shared secret")) return } val ctPath = options.getString("ctPath") if (ctPath == null || ctPath == "") { - promise.reject("decryptFile(): invalid ctPath") + promise.reject(Exception("decryptFile(): invalid ctPath")) return } val ptPath = options.getString("ptPath") if (ptPath == null || ptPath == "") { - promise.reject("decryptFile(): invalid ptPath") + promise.reject(Exception("decryptFile(): invalid ptPath")) return } @@ -163,7 +165,19 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : ret.putBoolean("opOk", operationOk) promise.resolve(ret) } catch (e: Exception) { - promise.reject("decryptFile(): failed to decrypt file") + promise.reject(e) + } + } + fun writeToFile(ctPath: String, url: String) { + + BufferedInputStream(URL(url).openStream()).use { `in` -> + FileOutputStream(ctPath).use { fileOutputStream -> + val dataBuffer = ByteArray(4096) + var bytesRead: Int + while (`in`.read(dataBuffer, 0, 4096).also { bytesRead = it } != -1) { + fileOutputStream.write(dataBuffer, 0, bytesRead) + } + } } } @@ -171,33 +185,25 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : fun decryptFileFromURL(options: ReadableMap, promise: Promise) { val sharedSecret = options.getString("sharedSecret") if (sharedSecret == null || sharedSecret == "") { - promise.reject("decryptFileFromURL(): invalid shared secret") + promise.reject(Exception("decryptFileFromURL(): invalid shared secret")) return } val url = options.getString("url") if (url == null || url == "") { - promise.reject("decryptFileFromURL(): invalid url") + promise.reject(Exception("decryptFileFromURL(): invalid url")) return } val ptPath = options.getString("ptPath") if (ptPath == null || ptPath == "") { - promise.reject("decryptFileFromURL(): invalid ptPath") + promise.reject(Exception("decryptFileFromURL(): invalid ptPath")) return } val ctPath = Paths.get(ptPath).parent.toString() + "/blob" try { - BufferedInputStream(URL(url).openStream()).use { `in` -> - FileOutputStream(ctPath).use { fileOutputStream -> - val dataBuffer = ByteArray(4096) - var bytesRead: Int - while (`in`.read(dataBuffer, 0, 4096).also { bytesRead = it } != -1) { - fileOutputStream.write(dataBuffer, 0, bytesRead) - } - } - } - } catch (e: IOException) { - promise.reject("decryptFileFromURL(): failed to load data from url") + writeToFile(ptPath, url) + } catch (e: Exception) { + promise.reject(e) return } @@ -209,7 +215,7 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : ret.putBoolean("opOk", operationOk) promise.resolve(ret) } catch (e: Exception) { - promise.reject("decryptFileFromURL(): failed to decrypt from file") + promise.reject(e) } } @@ -219,7 +225,7 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : val rnd: ByteArray = SimpleSecureRandom.getSecureRandomBytes(numBytes) if (rnd.isEmpty()) { - promise.reject("random(): random generation failed") + promise.reject(Exception("random(): random generation failed")) } else { val ret = Arguments.createMap() ret.putString("value", Hex.toHexString(rnd)) @@ -232,11 +238,11 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : val key = Hex.decode(options.getString("key") ?: "") val salt = Hex.decode(options.getString("salt") ?: "") val info = Hex.decode(options.getString("info") ?: "") - val length = options.getInt("length") + val length = options.getInt("length") ?: 0 val derived: ByteArray = HKDF.derive(key, salt, info, length) if (derived.isEmpty()) { - promise.reject("derive(): key derivation failed") + promise.reject(Exception("derive(): key derivation failed")) } else { val ret = Arguments.createMap() ret.putString("value", Hex.toHexString(derived)) diff --git a/android/src/main/java/com/test/exampleapp/AbrevvaBleModuleTest.kt b/android/src/main/java/com/test/exampleapp/AbrevvaBleModuleTest.kt new file mode 100644 index 0000000..b40993e --- /dev/null +++ b/android/src/main/java/com/test/exampleapp/AbrevvaBleModuleTest.kt @@ -0,0 +1,40 @@ +package com.test.exampleapp + +import com.exampleapp.AbrevvaBleModule +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReadableMap +import io.mockk.MockKAnnotations +import io.mockk.impl.annotations.MockK +import io.mockk.unmockkAll +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test + +class AbrevvaBleModuleTest { + private lateinit var abrevvaBleModule: AbrevvaBleModule + + private lateinit var testMap: WritableMapTestImplementation + + @MockK(relaxed = true) + private lateinit var contextMock: ReactApplicationContext + + @MockK(relaxed = true) + private lateinit var promiseMock: Promise + + @MockK(relaxed = true) + private lateinit var readableMapMock: ReadableMap + + @BeforeEach + fun beforeEach(){ + MockKAnnotations.init(this) + abrevvaBleModule = AbrevvaBleModule(contextMock) + } + + @AfterEach + fun afterEach(){ + unmockkAll() + } +} \ No newline at end of file diff --git a/android/src/main/java/com/test/exampleapp/AbrevvaCryptoModuleTest.kt b/android/src/main/java/com/test/exampleapp/AbrevvaCryptoModuleTest.kt new file mode 100644 index 0000000..22d410f --- /dev/null +++ b/android/src/main/java/com/test/exampleapp/AbrevvaCryptoModuleTest.kt @@ -0,0 +1,386 @@ +package com.test.exampleapp + +import android.graphics.Color +import com.evva.xesar.abrevva.crypto.AesCCM +import com.evva.xesar.abrevva.crypto.AesGCM +import com.evva.xesar.abrevva.crypto.HKDF +import com.evva.xesar.abrevva.crypto.SimpleSecureRandom +import com.evva.xesar.abrevva.crypto.X25519Wrapper +import com.exampleapp.AbrevvaCryptoModule +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.Dynamic +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.ReadableMapKeySetIterator +import com.facebook.react.bridge.ReadableType +import com.facebook.react.bridge.WritableMap +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.spyk +import io.mockk.unmockkAll +import io.mockk.verify +import org.bouncycastle.util.encoders.Hex +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource +import org.junit.jupiter.params.provider.MethodSource +import java.util.stream.Stream +import org.junit.jupiter.params.provider.Arguments as JunitArguments + +class AbrevvaCryptoModuleTest { + + private lateinit var abrevvaCryptoModule: AbrevvaCryptoModule + + private lateinit var testMap: WritableMapTestImplementation + + @MockK(relaxed = true) + private lateinit var contextMock: ReactApplicationContext + + @MockK(relaxed = true) + private lateinit var promiseMock: Promise + + @MockK(relaxed = true) + private lateinit var readableMapMock: ReadableMap + + @BeforeEach + fun beforeEach() { + MockKAnnotations.init(this) + mockkObject(AesCCM) + mockkObject(AesGCM) + mockkObject(X25519Wrapper) + mockkObject(SimpleSecureRandom) + mockkObject(HKDF) + mockkStatic(Arguments::createMap) + mockkStatic(Hex::class) + testMap = WritableMapTestImplementation() + every { Arguments.createMap() } returns testMap + every { Hex.decode(any()) } returns byteArrayOf(1) + abrevvaCryptoModule = AbrevvaCryptoModule(contextMock) + } + + @AfterEach + fun afterEach(){ + unmockkAll() + } + @Nested + @DisplayName("encrypt()") + inner class EncryptTests { + @Test + fun `should reject if ct is empty`() { + every { Hex.decode(any()) } answers { callOriginal()} + every { AesCCM.encrypt(any(), any(), any(), any(), any()) } returns ByteArray(0) + + abrevvaCryptoModule.encrypt(readableMapMock, promiseMock) + + verify { promiseMock.reject(any()) } + } + + @Test + fun `should resolve if ct is not empty`() { + every { AesCCM.encrypt(any(), any(), any(), any(), any()) } returns ByteArray(10) + + abrevvaCryptoModule.encrypt(readableMapMock, promiseMock) + + verify { promiseMock.resolve(any()) } + } + } + + @Nested + @DisplayName("decrypt()") + inner class DecryptTests { + @Test + fun `should reject if pt is empty`() { + every { AesCCM.decrypt(any(), any(), any(), any(), any()) } returns ByteArray(0) + + abrevvaCryptoModule.decrypt(readableMapMock, promiseMock) + + verify { promiseMock.reject(any()) } + } + + @Test + fun `should resolve if pt is not empty`() { + every { AesCCM.decrypt(any(), any(), any(), any(), any()) } returns ByteArray(10) + + abrevvaCryptoModule.decrypt(readableMapMock, promiseMock) + + verify { promiseMock.resolve(any()) } + } + } + + @Nested + @DisplayName("generateKeyPair()") + inner class GenerateKeyPairTests { + @Test + fun `should resolve if keys where generated successfully`() { + every { X25519Wrapper.generateKeyPair() } returns mockk(relaxed = true) + + abrevvaCryptoModule.generateKeyPair(promiseMock) + + verify { promiseMock.resolve(any()) } + } + + @Test + fun `should reject if keys cannot be generated`() { + every { X25519Wrapper.generateKeyPair() } throws Exception("generateKeyPair() Fail Exception") + + abrevvaCryptoModule.generateKeyPair(promiseMock) + + verify { promiseMock.reject(any()) } + } + } + + @Nested + @DisplayName("encryptFile()") + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class EncryptFileTests { + @ParameterizedTest(name = "encryptFile({0}, {1}, {2}) should reject") + @MethodSource("parameterizedArgs_encrypt") + fun `encryptFile() should reject if any Param is missing`( + ctPath: String?, + ptPath: String?, + sharedSecret: String? + ) { + testMap.putString("ctPath", ctPath) + testMap.putString("ptPath", ptPath) + testMap.putString("sharedSecret", sharedSecret) + + abrevvaCryptoModule.encryptFile(testMap, promiseMock) + + verify { promiseMock.reject(any()) } + } + fun parameterizedArgs_encrypt(): Stream{ + return Stream.of( + JunitArguments.of("", "ptPath", "sharedSecret"), + JunitArguments.of("ctPath", "", "sharedSecret"), + JunitArguments.of("ctPath", "sharedSecret", ""), + JunitArguments.of(null, "ptPath", "sharedSecret"), + JunitArguments.of("ctPath", null, "sharedSecret"), + JunitArguments.of("ctPath", "ptPath", null), + ) + } + + @Test + fun `should resolve if args are valid and file could be encrypted`() { + val mapMock = mockk(relaxed = true) + every { mapMock.getString(any()) } returns "notEmpty" + every { AesGCM.encryptFile(any(), any(), any()) } returns true + + abrevvaCryptoModule.encryptFile(mapMock, promiseMock) + + verify { promiseMock.resolve(any()) } + } + + @Test + fun `should reject if args are valid but encryption fails`() { + val mapMock = mockk(relaxed = true) + every { mapMock.getString(any()) } returns "notEmpty" + every { + AesGCM.encryptFile( + any(), + any(), + any() + ) + } throws Exception("encryptFile() Fail Exception") + + abrevvaCryptoModule.encryptFile(mapMock, promiseMock) + + verify { promiseMock.reject(any()) } + } + } + + @Nested + @DisplayName("decryptFile()") + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class DecryptFileTests { + @ParameterizedTest(name = "empty args should be rejected") + @MethodSource("parameterizedArgs_decrypt") + fun `should reject if any Param is empty`( + ctPath: String?, + ptPath: String?, + sharedSecret: String? + ) { + testMap.putString("ctPath", ctPath) + testMap.putString("ptPath", ptPath) + testMap.putString("sharedSecret", sharedSecret) + + abrevvaCryptoModule.decryptFile(testMap, promiseMock) + + verify { promiseMock.reject(any()) } + } + + fun parameterizedArgs_decrypt(): Stream{ + return Stream.of( + JunitArguments.of("", "ptPath", "sharedSecret"), + JunitArguments.of("ctPath", "", "sharedSecret"), + JunitArguments.of("ctPath", "ptPath", ""), + JunitArguments.of(null, "ptPath", "sharedSecret"), + JunitArguments.of("ctPath", null, "sharedSecret"), + JunitArguments.of("ctPath", "ptPath", null), + ) + } + + @Test + fun `should resolve if args are valid and file could be encrypted`() { + val mapMock = mockk(relaxed = true) + every { mapMock.getString(any()) } returns "notEmpty" + every { AesGCM.decryptFile(any(), any(), any()) } returns true + + abrevvaCryptoModule.decryptFile(mapMock, promiseMock) + + verify { promiseMock.resolve(any()) } + } + + @Test + fun `should reject if encryption fails`() { + val mapMock = mockk(relaxed = true) + every { mapMock.getString(any()) } returns "notEmpty" + every { + AesGCM.decryptFile( + any(), + any(), + any() + ) + } throws Exception("encryptFile() Fail Exception") + + abrevvaCryptoModule.decryptFile(mapMock, promiseMock) + + verify { promiseMock.reject(any()) } + } + } + + @Nested + @DisplayName("decryptFileFromURL()") + inner class DecryptFileFromURLTests { + + @Nested + @DisplayName("should reject if any Param is empty") + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + inner class DecryptFileFromURL_ParameterizedTest { + @ParameterizedTest + @MethodSource("parameterizedArgs_decryptFileFromURL") + fun `should reject if any Param is empty`( + sharedSecret: String?, + url: String?, + ptPath: String? + ) { + testMap.putString("sharedSecret", sharedSecret) + testMap.putString("url", url) + testMap.putString("ptPath", ptPath) + + abrevvaCryptoModule.decryptFileFromURL(testMap, promiseMock) + + verify { promiseMock.reject(any()) } + } + + fun parameterizedArgs_decryptFileFromURL(): Stream { + return Stream.of( + JunitArguments.of("", "url", "ptPath"), + JunitArguments.of("sharedSecret", "", "ptPath"), + JunitArguments.of("sharedSecret", "url", ""), + JunitArguments.of(null, "url", "ptPath"), + JunitArguments.of("sharedSecret", null, "ptPath"), + JunitArguments.of("sharedSecret", "url", null), + ) + } + } + + @Test + fun `decryptFileFromURL() should reject if ctPath-File is not accessible`() { + val mockMap = mockk(relaxed = true) + val moduleSpy = spyk(AbrevvaCryptoModule(contextMock)) + every { mockMap.getString(any()) } returns "notEmpty" + every { moduleSpy.writeToFile(any(), any()) } throws Exception("decryptFileFromURL() Fail Exception") + + moduleSpy.decryptFileFromURL(mockMap, promiseMock) + + verify { promiseMock.reject(any()) } + } + + @Test + fun `decryptFileFromURL() should reject if decode fails`() { + val mockMap = mockk(relaxed = true) + val moduleSpy = spyk(AbrevvaCryptoModule(contextMock)) + every { mockMap.getString(any()) } returns "notEmpty" + every { moduleSpy.writeToFile(any(), any()) } returns Unit + every { Hex.decode(any()) } throws Exception("decryptFileFromURL() Fail Exception") + + moduleSpy.decryptFileFromURL(mockMap, promiseMock) + + verify { promiseMock.reject(any()) } + } + + @Test + fun `decryptFileFromURL() should resolve if everything works as intended`() { + val mockMap = mockk(relaxed = true) + val moduleSpy = spyk(AbrevvaCryptoModule(contextMock)) + every { mockMap.getString(any()) } returns "notEmpty" + every { moduleSpy.writeToFile(any(), any()) } returns Unit + every { AesGCM.decryptFile(any(), any(), any()) } returns true + + moduleSpy.decryptFileFromURL(mockMap, promiseMock) + + verify { promiseMock.resolve(any()) } + } + } + + @Nested + @DisplayName("random()") + inner class RandomTests { + @ParameterizedTest(name = "random(numBytes: {0}) resolved String size should be {1}") + @CsvSource("2,4", "4,8", "7,14") + fun `should return random bytes n number of bytes if successful`( + numBytes: Int, + expectedStrLen: Int + ) { + val mockMap = mockk(relaxed = true) + every { mockMap.getInt("numBytes") } returns numBytes + + abrevvaCryptoModule.random(mockMap, promiseMock) + + assert(testMap.getString("value")!!.length == expectedStrLen) + } + + @Test + fun `should reject if bytes cannot be generated`(){ + every { SimpleSecureRandom.getSecureRandomBytes(any()) } returns ByteArray(0) + testMap.putInt("numBytes", 10) + + abrevvaCryptoModule.random(testMap, promiseMock) + + verify { promiseMock.reject(any()) } + } + } + @Nested + @DisplayName("derive()") + inner class DeriveTests { + + @Test + fun `should resolve if successful`() { + testMap.putInt("length", 0) + every { HKDF.derive(any(), any(), any(), any()) } returns ByteArray(0) + + abrevvaCryptoModule.derive(testMap, promiseMock) + + verify { promiseMock.reject(any()) } + } + @Test + fun `should reject if unsuccessful`() { + testMap.putInt("length", 10) + every { HKDF.derive(any(), any(), any(), any()) } returns ByteArray(10) + abrevvaCryptoModule.derive(testMap, promiseMock) + + verify { promiseMock.resolve(any()) } + } + } +} \ No newline at end of file diff --git a/android/src/main/java/com/test/exampleapp/WriteableMapTestImplementation.kt b/android/src/main/java/com/test/exampleapp/WriteableMapTestImplementation.kt new file mode 100644 index 0000000..26c5dbe --- /dev/null +++ b/android/src/main/java/com/test/exampleapp/WriteableMapTestImplementation.kt @@ -0,0 +1,44 @@ +package com.test.exampleapp + +import com.facebook.react.bridge.Dynamic +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.ReadableMapKeySetIterator +import com.facebook.react.bridge.ReadableType +import com.facebook.react.bridge.WritableMap +import io.mockk.mockk + +class WritableMapTestImplementation : WritableMap { + private val map = mutableMapOf() + + override fun hasKey(p0: String): Boolean { return map.containsKey(p0) } + override fun isNull(p0: String): Boolean { return map[p0] == null } + override fun getBoolean(p0: String): Boolean { return map[p0] as Boolean } + override fun getDouble(p0: String): Double { return map[p0] as Double } + override fun getInt(p0: String): Int { return map[p0] as Int } + override fun getString(p0: String): String? { return map[p0] as String? } + override fun getArray(p0: String): ReadableArray? { return map[p0] as ReadableArray? } + override fun getMap(p0: String): ReadableMap? { return map[p0] as ReadableMap? } + override fun getDynamic(p0: String): Dynamic { return mockk() } + override fun getType(p0: String): ReadableType { return mockk() } + override fun getEntryIterator(): MutableIterator> { + return mockk>>() + } + override fun keySetIterator(): ReadableMapKeySetIterator { + return mockk() + } + override fun toHashMap(): HashMap { return mockk>() } + override fun putNull(p0: String) { map[p0] = null } + override fun putBoolean(p0: String, p1: Boolean) { map[p0] = p1 } + override fun putDouble(p0: String, p1: Double) { map[p0] = p1 } + override fun putInt(p0: String, p1: Int) { map[p0] = p1 } + override fun putString(p0: String, p1: String?) { map[p0] = p1 } + override fun putArray(p0: String, p1: ReadableArray?) { map[p0] = p1 } + override fun putMap(p0: String, p1: ReadableMap?) { map[p0] = p1 } + override fun merge(p0: ReadableMap) { + TODO("Not yet implemented") + } + override fun copy(): WritableMap { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 5a29035..7a39f93 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -139,7 +139,7 @@ repositories { dependencies { // The version of react-native is set by the React Native Gradle Plugin implementation("com.facebook.react:react-android") - implementation group: "com.evva.xesar", name: "abrevva-sdk-android", version: "1.0.15" + implementation group: "com.evva.xesar", name: "abrevva-sdk-android", version: "1.0.19" if (hermesEnabled.toBoolean()) { implementation("com.facebook.react:hermes-android") diff --git a/src/__tests__/index.test.tsx b/src/index.test.tsx similarity index 66% rename from src/__tests__/index.test.tsx rename to src/index.test.tsx index 0443075..362eb58 100644 --- a/src/__tests__/index.test.tsx +++ b/src/index.test.tsx @@ -1,6 +1,6 @@ import { NativeEventEmitter, NativeModules, Platform } from 'react-native'; -import { AbrevvaCrypto, AbrevvaNfc, createAbrevvaBleInstance } from '../index'; +import { AbrevvaCrypto, AbrevvaNfc, createAbrevvaBleInstance } from './index'; describe('AbrevvaBleModule', () => { const AbrevvaBleMock = NativeModules.AbrevvaBle; @@ -8,7 +8,7 @@ describe('AbrevvaBleModule', () => { var AbrevvaBle; beforeEach(() => { - jest.clearAllMocks(); + jest.useFakeTimers(); Platform.OS = 'ios'; Platform.select.mockImplementation(() => { @@ -17,6 +17,7 @@ describe('AbrevvaBleModule', () => { mockEmitter = new NativeEventEmitter(); AbrevvaBle = createAbrevvaBleInstance(); + jest.clearAllMocks(); }); it('constructor should throw if Platform is not Supported', () => { @@ -68,7 +69,30 @@ describe('AbrevvaBleModule', () => { expect(AbrevvaBleMock.openAppSettings).toHaveBeenCalledTimes(1); }); - it('should run requestLEScan()', async () => {}); + describe('requestLEScan()', () => { + it('should reject if a scan is already in progress', async () => { + AbrevvaBle.requestLEScan(); + await expect(AbrevvaBle.requestLEScan).rejects.toThrow(); + }); + it('should add the expected eventlisteners and discard them after the timeout', async () => { + const addListenerSpy = jest.spyOn(mockEmitter, 'addListener'); + + const emitterSubscriptionMock = { remove: jest.fn() }; + mockEmitter.addListener.mockImplementation(() => { + return emitterSubscriptionMock; + }); + AbrevvaBle.requestLEScan(jest.fn(), jest.fn(), jest.fn()); + + jest.advanceTimersByTime(20000); + + expect(addListenerSpy).toHaveBeenCalledWith('onScanResult', expect.any(Function)); + expect(addListenerSpy).toHaveBeenCalledWith('onConnect', expect.any(Function)); + expect(addListenerSpy).toHaveBeenCalledWith('onDisconnect', expect.any(Function)); + expect(addListenerSpy).toHaveBeenCalledTimes(3); + expect(AbrevvaBleMock.requestLEScan).toHaveBeenCalledTimes(1); + expect(emitterSubscriptionMock.remove).toHaveBeenCalledTimes(3); + }); + }); it('should run stopLEScan()', async () => { await AbrevvaBle.stopLEScan(); expect(AbrevvaBleMock.stopLEScan).toHaveBeenCalledTimes(1); @@ -77,7 +101,11 @@ describe('AbrevvaBleModule', () => { await AbrevvaBle.connect(); expect(AbrevvaBleMock.connect).toHaveBeenCalledTimes(1); }); - it('should run disconnect()', async () => {}); + it('should run disconnect()', async () => { + await AbrevvaBle.disconnect(); + expect(AbrevvaBleMock.disconnect).toHaveBeenCalledTimes(1); + expect(AbrevvaBleMock.setSupportedEvents).toHaveBeenCalledTimes(1); + }); it('should run read()', async () => { await AbrevvaBle.read(); expect(AbrevvaBleMock.read).toHaveBeenCalledTimes(1); @@ -94,7 +122,34 @@ describe('AbrevvaBleModule', () => { await AbrevvaBle.disengage(); expect(AbrevvaBleMock.disengage).toHaveBeenCalledTimes(1); }); - it('should run stopNotifications()', async () => {}); + describe('startNotifications()', () => {}); + describe('stopNotifications()', () => { + const deviceId = 'deviceId'; + const service = 'service'; + const characteristic = 'characteristic'; + + var emitterSubscriptionMock; + beforeEach(() => { + emitterSubscriptionMock = { remove: jest.fn() }; + void jest.spyOn(mockEmitter, 'addListener').mockImplementation(() => { + return emitterSubscriptionMock; + }); + }); + it('should delete the Eventlistener if it was previously added', async () => { + await AbrevvaBle.startNotifications(deviceId, service, characteristic); + expect(emitterSubscriptionMock.remove).toHaveBeenCalledTimes(0); + expect(AbrevvaBleMock.stopNotifications).toHaveBeenCalledTimes(0); + expect(AbrevvaBleMock.startNotifications).toHaveBeenCalledTimes(1); + await AbrevvaBle.stopNotifications(deviceId, service, characteristic); + expect(emitterSubscriptionMock.remove).toHaveBeenCalledTimes(1); + expect(AbrevvaBleMock.stopNotifications).toHaveBeenCalledTimes(1); + }); + it("shouldn't remove any key if it wasn't previously added", async () => { + await AbrevvaBle.stopNotifications(deviceId, service, characteristic); + expect(emitterSubscriptionMock.remove).toHaveBeenCalledTimes(0); + expect(AbrevvaBleMock.stopNotifications).toHaveBeenCalledTimes(1); + }); + }); }); describe('AbrevvaNfcModule', () => { diff --git a/src/index.tsx b/src/index.tsx index 18a5ed0..7becf87 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -161,14 +161,14 @@ class AbrevvaBleModule implements AbrevvaBLEInterface { }; const listeners = new Map([ - ['onScanResult', { callback: onScanResultHelper, listener: undefined }], - ['onConnect', { callback: onConnectCallback, listener: undefined }], - ['onDisconnect', { callback: onDisconnectCallback, listener: undefined }], + ['onScanResult', onScanResultHelper], + ['onConnect', onConnectCallback], + ['onDisconnect', onDisconnectCallback], ]); - listeners.forEach((callbackObj: any, listenerName: string) => { - callbackObj.listener = this.eventEmitter!.addListener(listenerName, callbackObj.callback); - this.listeners.set(listenerName, callbackObj.listener); + listeners.forEach((callback: any, listenerName: string) => { + const listener = this.eventEmitter!.addListener(listenerName, callback); + this.listeners.set(listenerName, listener); }); NativeModuleBle.requestLEScan( @@ -182,12 +182,11 @@ class AbrevvaBleModule implements AbrevvaBLEInterface { timeout: timeout, }), ); - setTimeout( () => { - listeners.forEach((callbackObj: any, listenerName: string) => { + this.listeners.forEach((listener, listenerName) => { + listener?.remove(); this.listeners.set(listenerName, undefined); - callbackObj.listener.remove(); }); Promise.resolve(); }, @@ -250,16 +249,24 @@ class AbrevvaBleModule implements AbrevvaBLEInterface { ); } - async stopNotifications(options: ReadOptions): Promise { - const key = - `notification|${options.deviceId}|${options.service}|${options.characteristic}`.toLowerCase(); - if (key in this.listeners) { + async stopNotifications( + deviceId: string, + service: string, + characteristic: string, + ): Promise { + const key = `notification|${deviceId}|${service}|${characteristic}`.toLowerCase(); + if (this.listeners.get(key)) { + this.listeners.get(key)?.remove(); this.listeners.delete(key); NativeModuleBle.setSupportedEvents({ events: [...this.listeners.keys()], }); } - return NativeModuleBle.stopNotifications(options); + return NativeModuleBle.stopNotifications({ + deviceId: deviceId, + service: service, + characteristic: characteristic, + }); } } diff --git a/src/interfaces.tsx b/src/interfaces.tsx index 5b2aec6..cec83ed 100644 --- a/src/interfaces.tsx +++ b/src/interfaces.tsx @@ -151,5 +151,5 @@ export interface AbrevvaBLEInterface { characteristic: string, callback: (event: ReadResult) => void, ): Promise; - stopNotifications(options: ReadOptions): Promise; + stopNotifications(deviceId: string, service: string, characteristic: string): Promise; } diff --git a/src/setup.tsx b/src/setup.tsx index ac7a4ff..0a9702c 100644 --- a/src/setup.tsx +++ b/src/setup.tsx @@ -37,6 +37,7 @@ jest.mock('react-native', () => { write: jest.fn(), signalize: jest.fn(), disengage: jest.fn(), + startNotifications: jest.fn(), stopNotifications: jest.fn(), addListener: jest.fn(), removeListeners: jest.fn(), From 8eb07d0cf90585685778a9ff600766a286ad9b34 Mon Sep 17 00:00:00 2001 From: Aleksandar Palic <1425202+codepushr@users.noreply.github.com> Date: Thu, 8 Aug 2024 13:26:51 +0200 Subject: [PATCH 05/36] chore: fix linting, type errors --- src/index.test.tsx | 61 ++++++++++++++++++++++++++++++++++------------ src/index.tsx | 4 +-- 2 files changed, 47 insertions(+), 18 deletions(-) diff --git a/src/index.test.tsx b/src/index.test.tsx index 362eb58..3f13d95 100644 --- a/src/index.test.tsx +++ b/src/index.test.tsx @@ -1,16 +1,22 @@ import { NativeEventEmitter, NativeModules, Platform } from 'react-native'; -import { AbrevvaCrypto, AbrevvaNfc, createAbrevvaBleInstance } from './index'; +import { + type AbrevvaBLEInterface, + AbrevvaCrypto, + AbrevvaNfc, + createAbrevvaBleInstance, +} from './index'; describe('AbrevvaBleModule', () => { - const AbrevvaBleMock = NativeModules.AbrevvaBle; - var mockEmitter; - var AbrevvaBle; + let AbrevvaBleMock = NativeModules.AbrevvaBle; + let AbrevvaBle: AbrevvaBLEInterface; + let mockEmitter: NativeEventEmitter; beforeEach(() => { jest.useFakeTimers(); Platform.OS = 'ios'; + // @ts-ignore Platform.select.mockImplementation(() => { return mockEmitter; }); @@ -21,6 +27,7 @@ describe('AbrevvaBleModule', () => { }); it('constructor should throw if Platform is not Supported', () => { + // @ts-ignore Platform.select.mockImplementation(() => { return undefined; }); @@ -43,8 +50,8 @@ describe('AbrevvaBleModule', () => { it('should add the correct eventlistener and call startEnableNotification', async () => { const spy = jest.spyOn(mockEmitter, 'addListener'); const spyNativeModule = jest.spyOn(AbrevvaBle, 'startEnabledNotifications'); - const mockfn = jest.fn(); - await AbrevvaBle.startEnabledNotifications(mockfn); + const mockFn = jest.fn(); + await AbrevvaBle.startEnabledNotifications(mockFn); expect(spy).toHaveBeenCalledWith('onEnabledChanged', expect.any(Function)); expect(spy).toHaveBeenCalledTimes(1); @@ -71,17 +78,23 @@ describe('AbrevvaBleModule', () => { describe('requestLEScan()', () => { it('should reject if a scan is already in progress', async () => { - AbrevvaBle.requestLEScan(); + void AbrevvaBle.requestLEScan( + () => {}, + () => {}, + () => {}, + ); await expect(AbrevvaBle.requestLEScan).rejects.toThrow(); }); it('should add the expected eventlisteners and discard them after the timeout', async () => { const addListenerSpy = jest.spyOn(mockEmitter, 'addListener'); const emitterSubscriptionMock = { remove: jest.fn() }; + // @ts-ignore mockEmitter.addListener.mockImplementation(() => { return emitterSubscriptionMock; }); - AbrevvaBle.requestLEScan(jest.fn(), jest.fn(), jest.fn()); + + void AbrevvaBle.requestLEScan(jest.fn(), jest.fn(), jest.fn()); jest.advanceTimersByTime(20000); @@ -98,28 +111,44 @@ describe('AbrevvaBleModule', () => { expect(AbrevvaBleMock.stopLEScan).toHaveBeenCalledTimes(1); }); it('should run connect()', async () => { - await AbrevvaBle.connect(); + await AbrevvaBle.connect({ deviceId: 'deviceId' }); expect(AbrevvaBleMock.connect).toHaveBeenCalledTimes(1); }); it('should run disconnect()', async () => { - await AbrevvaBle.disconnect(); + await AbrevvaBle.disconnect({ deviceId: 'deviceId' }); expect(AbrevvaBleMock.disconnect).toHaveBeenCalledTimes(1); expect(AbrevvaBleMock.setSupportedEvents).toHaveBeenCalledTimes(1); }); it('should run read()', async () => { - await AbrevvaBle.read(); + await AbrevvaBle.read({ + deviceId: 'deviceId', + service: 'service', + characteristic: 'characteristic', + }); expect(AbrevvaBleMock.read).toHaveBeenCalledTimes(1); }); it('should run write()', async () => { - await AbrevvaBle.write(); + await AbrevvaBle.write({ + deviceId: 'deviceId', + service: 'service', + characteristic: 'characteristic', + value: 'value', + }); expect(AbrevvaBleMock.write).toHaveBeenCalledTimes(1); }); it('should run signalize()', async () => { - await AbrevvaBle.signalize(); + await AbrevvaBle.signalize({ deviceId: 'deviceId' }); expect(AbrevvaBleMock.signalize).toHaveBeenCalledTimes(1); }); it('should run disengage()', async () => { - await AbrevvaBle.disengage(); + await AbrevvaBle.disengage({ + deviceId: 'deviceId', + mobileId: 'mobileId', + mobileDeviceKey: 'mobileDeviceKey', + mobileGroupId: 'mobileGroupId', + mobileAccessData: 'mobileAccessData', + isPermanentRelease: false, + }); expect(AbrevvaBleMock.disengage).toHaveBeenCalledTimes(1); }); describe('startNotifications()', () => {}); @@ -128,7 +157,7 @@ describe('AbrevvaBleModule', () => { const service = 'service'; const characteristic = 'characteristic'; - var emitterSubscriptionMock; + let emitterSubscriptionMock: any; beforeEach(() => { emitterSubscriptionMock = { remove: jest.fn() }; void jest.spyOn(mockEmitter, 'addListener').mockImplementation(() => { @@ -136,7 +165,7 @@ describe('AbrevvaBleModule', () => { }); }); it('should delete the Eventlistener if it was previously added', async () => { - await AbrevvaBle.startNotifications(deviceId, service, characteristic); + await AbrevvaBle.startNotifications(deviceId, service, characteristic, () => {}); expect(emitterSubscriptionMock.remove).toHaveBeenCalledTimes(0); expect(AbrevvaBleMock.stopNotifications).toHaveBeenCalledTimes(0); expect(AbrevvaBleMock.startNotifications).toHaveBeenCalledTimes(1); diff --git a/src/index.tsx b/src/index.tsx index 7becf87..63257b9 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -61,9 +61,9 @@ const NativeModuleBle = NativeModules.AbrevvaBle }, ); -class AbrevvaBleModule implements AbrevvaBLEInterface { +export class AbrevvaBleModule implements AbrevvaBLEInterface { private listeners: Map; - private eventEmitter: NativeEventEmitter | undefined; + private readonly eventEmitter: NativeEventEmitter | undefined; constructor() { this.eventEmitter = Platform.select({ From 4446c12c3fb6666ae84cd1efdcea2177ee347063 Mon Sep 17 00:00:00 2001 From: axi92 Date: Fri, 9 Aug 2024 08:19:16 +0200 Subject: [PATCH 06/36] build: add test execution in workflows --- .github/workflows/test.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..30d14d5 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,24 @@ +# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs +name: Test +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [lts/*] + # See supported Node.js release schedule at https://nodejs.org/en/about/previous-releases + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + - run: yarn + - run: yarn test From 1192749997ebf79f8ccae3a661f9811baf0a462a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hochst=C3=B6ger=20Matthias?= <116495532+mhochsto@users.noreply.github.com> Date: Fri, 9 Aug 2024 08:48:38 +0200 Subject: [PATCH 07/36] chore: added crypto ios tests --- .../com/exampleapp/AbrevvaCryptoModule.kt | 8 +- .../project.pbxproj | 330 +++++++++--------- .../xcschemes/ExampleAppExample.xcscheme | 2 +- .../UserInterfaceState.xcuserstate | Bin 68124 -> 107997 bytes .../xcdebugger/Breakpoints_v2.xcbkptlist | 6 - .../UserInterfaceState.xcuserstate | Bin 14355 -> 0 bytes .../AbrevvaCryptoTests.swift | 225 ++++++++++++ .../ExampleAppExampleTests.m | 66 ---- example/ios/ExampleAppExampleTests/Info.plist | 24 -- example/ios/Podfile | 4 +- example/ios/Podfile.lock | 50 ++- ios/crypto/AbrevvaCrypto.swift | 17 +- ios/nfc/AbrevvaNfc.swift | 4 +- react-native-example-app.podspec | 2 +- 14 files changed, 446 insertions(+), 292 deletions(-) delete mode 100644 example/ios/ExampleAppExample.xcworkspace/xcuserdata/matthias.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist delete mode 100644 example/ios/ExampleAppExample.xcworkspace/xcuserdata/palic.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 example/ios/ExampleAppExampleTests/AbrevvaCryptoTests.swift delete mode 100644 example/ios/ExampleAppExampleTests/ExampleAppExampleTests.m delete mode 100644 example/ios/ExampleAppExampleTests/Info.plist diff --git a/android/src/main/java/com/exampleapp/AbrevvaCryptoModule.kt b/android/src/main/java/com/exampleapp/AbrevvaCryptoModule.kt index 6222853..018e4df 100644 --- a/android/src/main/java/com/exampleapp/AbrevvaCryptoModule.kt +++ b/android/src/main/java/com/exampleapp/AbrevvaCryptoModule.kt @@ -75,8 +75,8 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : val keyPair: X25519Wrapper.KeyPair = X25519Wrapper.generateKeyPair() val ret = Arguments.createMap() - ret.putString("privateKey", Base64.toBase64String(keyPair.privateKey)) - ret.putString("publicKey", Base64.toBase64String(keyPair.publicKey)) + ret.putString("privateKey", Hex.toHexString(keyPair.privateKey)) + ret.putString("publicKey", Hex.toHexString(keyPair.publicKey)) promise.resolve(ret) } catch (e: Exception) { promise.reject(Exception("generateKeyPair(): private key creation failed")) @@ -97,8 +97,8 @@ class AbrevvaCryptoModule(reactContext: ReactApplicationContext) : return } val sharedSecret: ByteArray = X25519Wrapper.computeSharedSecret( - Base64.decode(privateKey), - Base64.decode(peerPublicKey) + Hex.decode(privateKey), + Hex.decode(peerPublicKey) ) val ret = Arguments.createMap() diff --git a/example/ios/ExampleAppExample.xcodeproj/project.pbxproj b/example/ios/ExampleAppExample.xcodeproj/project.pbxproj index 07d8ebd..fa2b377 100644 --- a/example/ios/ExampleAppExample.xcodeproj/project.pbxproj +++ b/example/ios/ExampleAppExample.xcodeproj/project.pbxproj @@ -7,18 +7,18 @@ objects = { /* Begin PBXBuildFile section */ - 00E356F31AD99517003FC87E /* ExampleAppExampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* ExampleAppExampleTests.m */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 39FBC3E02C64DE9800BEE979 /* AbrevvaCryptoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39FBC3DF2C64DE9800BEE979 /* AbrevvaCryptoTests.swift */; }; + 40DC940EDA1FC456936AAD80 /* Pods_ExampleAppExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 054ABA2C59F9A4523AE0B026 /* Pods_ExampleAppExample.framework */; }; + 4B22F003BE70442FE2D4C58D /* Pods_ExampleAppExample_ExampleAppExampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F5FB403992171CD0BC089A5D /* Pods_ExampleAppExample_ExampleAppExampleTests.framework */; }; 7C2CCBD275D987DDA9C659B6 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 61DF2990903C4888EBB0D63C /* PrivacyInfo.xcprivacy */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; - C31ABF8F57C71FA25518C699 /* Pods_ExampleAppExample_ExampleAppExampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FFFCB0400B47BFEE01056C /* Pods_ExampleAppExample_ExampleAppExampleTests.framework */; }; - EF82AAB8D7CDA185B168B4CE /* Pods_ExampleAppExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 75262DEC733135018D6B2C7C /* Pods_ExampleAppExample.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = { + 39FBC3E12C64DE9800BEE979 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; proxyType = 1; @@ -28,11 +28,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 00E356EE1AD99517003FC87E /* ExampleAppExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleAppExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 00E356F21AD99517003FC87E /* ExampleAppExampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExampleAppExampleTests.m; sourceTree = ""; }; - 05868A8BF1FC288BEBB212D0 /* Pods-ExampleAppExample-ExampleAppExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleAppExample-ExampleAppExampleTests.release.xcconfig"; path = "Target Support Files/Pods-ExampleAppExample-ExampleAppExampleTests/Pods-ExampleAppExample-ExampleAppExampleTests.release.xcconfig"; sourceTree = ""; }; - 125DBE2C52B81A08A4D3D09B /* Pods-ExampleAppExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleAppExample.debug.xcconfig"; path = "Target Support Files/Pods-ExampleAppExample/Pods-ExampleAppExample.debug.xcconfig"; sourceTree = ""; }; + 054ABA2C59F9A4523AE0B026 /* Pods_ExampleAppExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ExampleAppExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07F961A680F5B00A75B9A /* ExampleAppExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ExampleAppExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = ExampleAppExample/AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = ExampleAppExample/AppDelegate.mm; sourceTree = ""; }; @@ -40,52 +36,38 @@ 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ExampleAppExample/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = ExampleAppExample/main.m; sourceTree = ""; }; 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = ExampleAppExample/PrivacyInfo.xcprivacy; sourceTree = ""; }; - 17EB8284C14F4E99984AC2F2 /* Pods-ExampleAppExample-ExampleAppExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleAppExample-ExampleAppExampleTests.debug.xcconfig"; path = "Target Support Files/Pods-ExampleAppExample-ExampleAppExampleTests/Pods-ExampleAppExample-ExampleAppExampleTests.debug.xcconfig"; sourceTree = ""; }; - 43FFFCB0400B47BFEE01056C /* Pods_ExampleAppExample_ExampleAppExampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ExampleAppExample_ExampleAppExampleTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 39FBC3DD2C64DE9800BEE979 /* ExampleAppExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleAppExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 39FBC3DF2C64DE9800BEE979 /* AbrevvaCryptoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbrevvaCryptoTests.swift; sourceTree = ""; }; + 51FB5F9190B0CE40B53B6AF3 /* Pods-ExampleAppExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleAppExample.debug.xcconfig"; path = "Target Support Files/Pods-ExampleAppExample/Pods-ExampleAppExample.debug.xcconfig"; sourceTree = ""; }; 61DF2990903C4888EBB0D63C /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = ExampleAppExample/PrivacyInfo.xcprivacy; sourceTree = ""; }; - 75262DEC733135018D6B2C7C /* Pods_ExampleAppExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ExampleAppExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = ExampleAppExample/LaunchScreen.storyboard; sourceTree = ""; }; - EC04F70477194A5A6AC4C19B /* Pods-ExampleAppExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleAppExample.release.xcconfig"; path = "Target Support Files/Pods-ExampleAppExample/Pods-ExampleAppExample.release.xcconfig"; sourceTree = ""; }; + A7F75B2DA7ADD5AEF3E64FA3 /* Pods-ExampleAppExample-ExampleAppExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleAppExample-ExampleAppExampleTests.debug.xcconfig"; path = "Target Support Files/Pods-ExampleAppExample-ExampleAppExampleTests/Pods-ExampleAppExample-ExampleAppExampleTests.debug.xcconfig"; sourceTree = ""; }; + C091A222493664870FD85204 /* Pods-ExampleAppExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleAppExample.release.xcconfig"; path = "Target Support Files/Pods-ExampleAppExample/Pods-ExampleAppExample.release.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; + F5FB403992171CD0BC089A5D /* Pods_ExampleAppExample_ExampleAppExampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ExampleAppExample_ExampleAppExampleTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F8CC9DCE2BD2F85429FEF3A8 /* Pods-ExampleAppExample-ExampleAppExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleAppExample-ExampleAppExampleTests.release.xcconfig"; path = "Target Support Files/Pods-ExampleAppExample-ExampleAppExampleTests/Pods-ExampleAppExample-ExampleAppExampleTests.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 00E356EB1AD99517003FC87E /* Frameworks */ = { + 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - C31ABF8F57C71FA25518C699 /* Pods_ExampleAppExample_ExampleAppExampleTests.framework in Frameworks */, + 40DC940EDA1FC456936AAD80 /* Pods_ExampleAppExample.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { + 39FBC3DA2C64DE9800BEE979 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - EF82AAB8D7CDA185B168B4CE /* Pods_ExampleAppExample.framework in Frameworks */, + 4B22F003BE70442FE2D4C58D /* Pods_ExampleAppExample_ExampleAppExampleTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 00E356EF1AD99517003FC87E /* ExampleAppExampleTests */ = { - isa = PBXGroup; - children = ( - 00E356F21AD99517003FC87E /* ExampleAppExampleTests.m */, - 00E356F01AD99517003FC87E /* Supporting Files */, - ); - path = ExampleAppExampleTests; - sourceTree = ""; - }; - 00E356F01AD99517003FC87E /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 00E356F11AD99517003FC87E /* Info.plist */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; 13B07FAE1A68108700A75B9A /* ExampleAppExample */ = { isa = PBXGroup; children = ( @@ -105,12 +87,20 @@ isa = PBXGroup; children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - 75262DEC733135018D6B2C7C /* Pods_ExampleAppExample.framework */, - 43FFFCB0400B47BFEE01056C /* Pods_ExampleAppExample_ExampleAppExampleTests.framework */, + 054ABA2C59F9A4523AE0B026 /* Pods_ExampleAppExample.framework */, + F5FB403992171CD0BC089A5D /* Pods_ExampleAppExample_ExampleAppExampleTests.framework */, ); name = Frameworks; sourceTree = ""; }; + 39FBC3DE2C64DE9800BEE979 /* ExampleAppExampleTests */ = { + isa = PBXGroup; + children = ( + 39FBC3DF2C64DE9800BEE979 /* AbrevvaCryptoTests.swift */, + ); + path = ExampleAppExampleTests; + sourceTree = ""; + }; 832341AE1AAA6A7D00B99B32 /* Libraries */ = { isa = PBXGroup; children = ( @@ -123,7 +113,7 @@ children = ( 13B07FAE1A68108700A75B9A /* ExampleAppExample */, 832341AE1AAA6A7D00B99B32 /* Libraries */, - 00E356EF1AD99517003FC87E /* ExampleAppExampleTests */, + 39FBC3DE2C64DE9800BEE979 /* ExampleAppExampleTests */, 83CBBA001A601CBA00E9B192 /* Products */, 2D16E6871FA4F8E400B85C8A /* Frameworks */, BBD78D7AC51CEA395F1C20DB /* Pods */, @@ -137,7 +127,7 @@ isa = PBXGroup; children = ( 13B07F961A680F5B00A75B9A /* ExampleAppExample.app */, - 00E356EE1AD99517003FC87E /* ExampleAppExampleTests.xctest */, + 39FBC3DD2C64DE9800BEE979 /* ExampleAppExampleTests.xctest */, ); name = Products; sourceTree = ""; @@ -145,10 +135,10 @@ BBD78D7AC51CEA395F1C20DB /* Pods */ = { isa = PBXGroup; children = ( - 125DBE2C52B81A08A4D3D09B /* Pods-ExampleAppExample.debug.xcconfig */, - EC04F70477194A5A6AC4C19B /* Pods-ExampleAppExample.release.xcconfig */, - 17EB8284C14F4E99984AC2F2 /* Pods-ExampleAppExample-ExampleAppExampleTests.debug.xcconfig */, - 05868A8BF1FC288BEBB212D0 /* Pods-ExampleAppExample-ExampleAppExampleTests.release.xcconfig */, + 51FB5F9190B0CE40B53B6AF3 /* Pods-ExampleAppExample.debug.xcconfig */, + C091A222493664870FD85204 /* Pods-ExampleAppExample.release.xcconfig */, + A7F75B2DA7ADD5AEF3E64FA3 /* Pods-ExampleAppExample-ExampleAppExampleTests.debug.xcconfig */, + F8CC9DCE2BD2F85429FEF3A8 /* Pods-ExampleAppExample-ExampleAppExampleTests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -156,36 +146,16 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 00E356ED1AD99517003FC87E /* ExampleAppExampleTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "ExampleAppExampleTests" */; - buildPhases = ( - DB3AFB7B250174089CEA8555 /* [CP] Check Pods Manifest.lock */, - 00E356EA1AD99517003FC87E /* Sources */, - 00E356EB1AD99517003FC87E /* Frameworks */, - 00E356EC1AD99517003FC87E /* Resources */, - CD69EC139D5FC9C37AA3D2FC /* [CP] Embed Pods Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - 00E356F51AD99517003FC87E /* PBXTargetDependency */, - ); - name = ExampleAppExampleTests; - productName = ExampleAppExampleTests; - productReference = 00E356EE1AD99517003FC87E /* ExampleAppExampleTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; 13B07F861A680F5B00A75B9A /* ExampleAppExample */ = { isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "ExampleAppExample" */; buildPhases = ( - 702D2827E8C928554D0EAA0F /* [CP] Check Pods Manifest.lock */, + 77B7F422842E0B98DF130554 /* [CP] Check Pods Manifest.lock */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - 05336DA818B8D9AF17EF01E7 /* [CP] Embed Pods Frameworks */, + 274419FB47E6F003B3C0EFB2 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -196,21 +166,41 @@ productReference = 13B07F961A680F5B00A75B9A /* ExampleAppExample.app */; productType = "com.apple.product-type.application"; }; + 39FBC3DC2C64DE9800BEE979 /* ExampleAppExampleTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 39FBC3E32C64DE9800BEE979 /* Build configuration list for PBXNativeTarget "ExampleAppExampleTests" */; + buildPhases = ( + 52DAE33E8CC4311A722DE218 /* [CP] Check Pods Manifest.lock */, + 39FBC3D92C64DE9800BEE979 /* Sources */, + 39FBC3DA2C64DE9800BEE979 /* Frameworks */, + 39FBC3DB2C64DE9800BEE979 /* Resources */, + 839F032038E9AA227127E1EC /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 39FBC3E22C64DE9800BEE979 /* PBXTargetDependency */, + ); + name = ExampleAppExampleTests; + productName = ExampleAppExampleTests; + productReference = 39FBC3DD2C64DE9800BEE979 /* ExampleAppExampleTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 83CBB9F71A601CBA00E9B192 /* Project object */ = { isa = PBXProject; attributes = { + LastSwiftUpdateCheck = 1540; LastUpgradeCheck = 1210; TargetAttributes = { - 00E356ED1AD99517003FC87E = { - CreatedOnToolsVersion = 6.2; - TestTargetID = 13B07F861A680F5B00A75B9A; - }; 13B07F861A680F5B00A75B9A = { LastSwiftMigration = 1120; }; + 39FBC3DC2C64DE9800BEE979 = { + CreatedOnToolsVersion = 15.4; + }; }; }; buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "ExampleAppExample" */; @@ -227,26 +217,26 @@ projectRoot = ""; targets = ( 13B07F861A680F5B00A75B9A /* ExampleAppExample */, - 00E356ED1AD99517003FC87E /* ExampleAppExampleTests */, + 39FBC3DC2C64DE9800BEE979 /* ExampleAppExampleTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - 00E356EC1AD99517003FC87E /* Resources */ = { + 13B07F8E1A680F5B00A75B9A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */, + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, + 7C2CCBD275D987DDA9C659B6 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; - 13B07F8E1A680F5B00A75B9A /* Resources */ = { + 39FBC3DB2C64DE9800BEE979 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */, - 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, - 7C2CCBD275D987DDA9C659B6 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -269,7 +259,7 @@ shellPath = /bin/sh; shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; }; - 05336DA818B8D9AF17EF01E7 /* [CP] Embed Pods Frameworks */ = { + 274419FB47E6F003B3C0EFB2 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -286,7 +276,7 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ExampleAppExample/Pods-ExampleAppExample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 702D2827E8C928554D0EAA0F /* [CP] Check Pods Manifest.lock */ = { + 52DAE33E8CC4311A722DE218 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -301,137 +291,86 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-ExampleAppExample-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-ExampleAppExample-ExampleAppExampleTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - CD69EC139D5FC9C37AA3D2FC /* [CP] Embed Pods Frameworks */ = { + 77B7F422842E0B98DF130554 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-ExampleAppExample-ExampleAppExampleTests/Pods-ExampleAppExample-ExampleAppExampleTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-ExampleAppExample-ExampleAppExampleTests/Pods-ExampleAppExample-ExampleAppExampleTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ExampleAppExample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ExampleAppExample-ExampleAppExampleTests/Pods-ExampleAppExample-ExampleAppExampleTests-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - DB3AFB7B250174089CEA8555 /* [CP] Check Pods Manifest.lock */ = { + 839F032038E9AA227127E1EC /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ExampleAppExample-ExampleAppExampleTests/Pods-ExampleAppExample-ExampleAppExampleTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; + name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-ExampleAppExample-ExampleAppExampleTests-checkManifestLockResult.txt", + "${PODS_ROOT}/Target Support Files/Pods-ExampleAppExample-ExampleAppExampleTests/Pods-ExampleAppExample-ExampleAppExampleTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ExampleAppExample-ExampleAppExampleTests/Pods-ExampleAppExample-ExampleAppExampleTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 00E356EA1AD99517003FC87E /* Sources */ = { + 13B07F871A680F5B00A75B9A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 00E356F31AD99517003FC87E /* ExampleAppExampleTests.m in Sources */, + 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */, + 13B07FC11A68108700A75B9A /* main.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - 13B07F871A680F5B00A75B9A /* Sources */ = { + 39FBC3D92C64DE9800BEE979 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */, - 13B07FC11A68108700A75B9A /* main.m in Sources */, + 39FBC3E02C64DE9800BEE979 /* AbrevvaCryptoTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 00E356F51AD99517003FC87E /* PBXTargetDependency */ = { + 39FBC3E22C64DE9800BEE979 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 13B07F861A680F5B00A75B9A /* ExampleAppExample */; - targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */; + targetProxy = 39FBC3E12C64DE9800BEE979 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - 00E356F61AD99517003FC87E /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 17EB8284C14F4E99984AC2F2 /* Pods-ExampleAppExample-ExampleAppExampleTests.debug.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - INFOPLIST_FILE = ExampleAppExampleTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - OTHER_LDFLAGS = ( - "-ObjC", - "-lc++", - "$(inherited)", - ); - PRODUCT_BUNDLE_IDENTIFIER = exampleapp.example; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ExampleAppExample.app/ExampleAppExample"; - }; - name = Debug; - }; - 00E356F71AD99517003FC87E /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 05868A8BF1FC288BEBB212D0 /* Pods-ExampleAppExample-ExampleAppExampleTests.release.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - COPY_PHASE_STRIP = NO; - INFOPLIST_FILE = ExampleAppExampleTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - OTHER_LDFLAGS = ( - "-ObjC", - "-lc++", - "$(inherited)", - ); - PRODUCT_BUNDLE_IDENTIFIER = exampleapp.example; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ExampleAppExample.app/ExampleAppExample"; - }; - name = Release; - }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 125DBE2C52B81A08A4D3D09B /* Pods-ExampleAppExample.debug.xcconfig */; + baseConfigurationReference = 51FB5F9190B0CE40B53B6AF3 /* Pods-ExampleAppExample.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -459,7 +398,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = EC04F70477194A5A6AC4C19B /* Pods-ExampleAppExample.release.xcconfig */; + baseConfigurationReference = C091A222493664870FD85204 /* Pods-ExampleAppExample.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -483,6 +422,71 @@ }; name = Release; }; + 39FBC3E42C64DE9800BEE979 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A7F75B2DA7ADD5AEF3E64FA3 /* Pods-ExampleAppExample-ExampleAppExampleTests.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 47Z5RC7U2C; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.evva.xesasr.mcs.ExampleAppExampleTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 39FBC3E52C64DE9800BEE979 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F8CC9DCE2BD2F85429FEF3A8 /* Pods-ExampleAppExample-ExampleAppExampleTests.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 47Z5RC7U2C; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.evva.xesasr.mcs.ExampleAppExampleTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; 83CBBA201A601CBA00E9B192 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -567,10 +571,7 @@ "-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1", ); - OTHER_LDFLAGS = ( - "$(inherited)", - " ", - ); + OTHER_LDFLAGS = "$(inherited) "; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; @@ -653,10 +654,7 @@ "-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1", ); - OTHER_LDFLAGS = ( - "$(inherited)", - " ", - ); + OTHER_LDFLAGS = "$(inherited) "; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; @@ -667,20 +665,20 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "ExampleAppExampleTests" */ = { + 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "ExampleAppExample" */ = { isa = XCConfigurationList; buildConfigurations = ( - 00E356F61AD99517003FC87E /* Debug */, - 00E356F71AD99517003FC87E /* Release */, + 13B07F941A680F5B00A75B9A /* Debug */, + 13B07F951A680F5B00A75B9A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "ExampleAppExample" */ = { + 39FBC3E32C64DE9800BEE979 /* Build configuration list for PBXNativeTarget "ExampleAppExampleTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - 13B07F941A680F5B00A75B9A /* Debug */, - 13B07F951A680F5B00A75B9A /* Release */, + 39FBC3E42C64DE9800BEE979 /* Debug */, + 39FBC3E52C64DE9800BEE979 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/example/ios/ExampleAppExample.xcodeproj/xcshareddata/xcschemes/ExampleAppExample.xcscheme b/example/ios/ExampleAppExample.xcodeproj/xcshareddata/xcschemes/ExampleAppExample.xcscheme index 188722b..325192f 100644 --- a/example/ios/ExampleAppExample.xcodeproj/xcshareddata/xcschemes/ExampleAppExample.xcscheme +++ b/example/ios/ExampleAppExample.xcodeproj/xcshareddata/xcschemes/ExampleAppExample.xcscheme @@ -32,7 +32,7 @@ skipped = "NO"> diff --git a/example/ios/ExampleAppExample.xcworkspace/xcuserdata/matthias.xcuserdatad/UserInterfaceState.xcuserstate b/example/ios/ExampleAppExample.xcworkspace/xcuserdata/matthias.xcuserdatad/UserInterfaceState.xcuserstate index 6e713f12493c1a44d6c9bba678576c06fdcae3e4..3a0d415576cfedf175c1090dc7e248a3c6851e63 100644 GIT binary patch literal 107997 zcmeEP1z=NG_rLP;Brh4kZ9t*G*hVT)ifl*~3RGx|!?H9<+l)5plC)4}@6O$rE40j* zySw{V<}Tmd-M9a_Pb#E9w}1Hv`#Sd0`-jBW@-qQP)z>1c1nR~>|3<)dTax+SB-6$b=-v1lKJk6!O_^(rcx7ibLlv&=-# zBN-_R%VOSGK*QJe7HmjG4m1!ALW9v{GzCpX)6hO>Uo;)fKr_)Sl!dZU4$4If&|gkDCkqj%7U=p*zo`W$_W?O4SQ?8Lj_Uf6|u z<36}A?uQ5A5jY8t#G`OB-W!j{Zaf)J!Bg=xJOgLpJe-f`;1aw5m*RzZ5nha!;Qg@| zS70Bm#RuXDZosSYLHJ;NI6eX&gHOR{;&btZcmuu^Uxly3x8U3HUHAd~2!0BGh(E=j z;ji%5_w3r;>BXx#T>ukz7J9 zC6|#a$+hHGavQmw+(GUn50Zz-8{|#$7I~YzL*6Ctk@v}e$Oq&@@+tX-d`o^Mzmeam zNF^#$g*s_(I+zZjL+S2xG#x{y(;0Lookg=~HqD{AG>_)f*>oN)rDe37uAnRFezckf z=>c>V4bvz+h#pK2p@-7L=rQzIx}I*JO|+SAq?gc3>1FhCdIjA?uc0^7Tj;IyUV0zB zpFT;SqEFLjXbXLbzD3`r@6dPYd-PNKnLq_WunD3d39_IFc0m;!f>Y=%3=#$lBZW~y zvaq)>UT_POg(<>RVVW>Q$QE*hdBS|bF9d{2p-QM0g2Dm9DxpTG6+%KpXcX28hYLpt zM+zqkrwFGCrwL~Y=L#EyCZSolQn*TZNO)LyM0iwqOn6*)LU>YmN_bj$MraXU66h0EZ5xy0^6TTOI7Jj!0Hk(bfNjBN$vh}v@Y1_-Tw{5)5ZA-DG+R|+4 zwh6Y0w#l{`wwbnBwmG%}+gw|zt<1L4wx7*o+u!E5RojBL18ou8@wO9eC)!T3ooqYB zcB<_(+v&D-wli$!*e*#0BDVafP^2+)wn20Wm1nh!HU=t`-jxj}VU(j}ng+PY|2Mjp8NZ zrQ&7c<>D3MChRDtg{69_L0T&vBx%yA z(rMD^(mLr3=}dgObe6PHxmYQx=FfCdPsU$dP-`MHcKx^uSjo5 zZ%Xe8Tvli!y=l0TC_m%o<3mw%Rjk^hwcQU)r6l)=gnWvH^dGE5n+j8Kx4k;)ilypo|z zQZkhp%1k9&nXeQoMM|+!rYu%G%KnO12`C|@UO7Zz%Hhh<$}!3b%8ANJ%4y2!%2~?U z%K6HT%1z46$}P&R%5BQ+${os`%3aFc%Kge?%5%!|N{jNE^1AYY@}csP^0D%{^0o4_ z@{97To!ag8KK23jf%c*H-R;BdBkg1Cd)oK1r`YrDv+Z;21@^i2dG`7CLVJ{jRi~=c)P2-_)huM*TtkQTR9Gj?(jG&9IG4=$6Cjsj>8>CJC1jp>^Q}-&T*yVD#z81YaG`) zu5(=PxWRFw<0i+=j@upgIUaL7?s&rSg5yQUOOBTvuRGp$eB}7p@wMX{$G47O9Dg~f zQ*x?KhtuWU-8sxT+&RLT zpVRLQI4hlX&I6tG&IaefPUbwpd7|?q=h@D47LE?p*VJrAPP7~9gfQJG!A86jfSG# z(J(X|jX+6gBpQX1(P%UVjYWHCHciwdP1Y36uBn8B`)af&+6(QC#v?aM zL8&MWrNj4$C<9H>`e=JH{5r#LX{#81hv9b_{tv@Hzzs$uMr19lWqDkKN&`Nqah^XI z3rDQ_FD#uO^|*%Q1}eSvHRdy2f{NycBmCitEME*HurgnDpf=!f4Kse0M8aGWD+|O5 z{E%m`G8l+}EC#(%S|7>{L}S5_HwF@%E}F$4Wql;DC>RY^1Z#pZNY=|Le{nG653lA` zFur9)BHmCH6cvi)1}f^S3PP1(kE=K2niCGi0&8MMBjAO8Mol`F=Wz|(vVuk4+JIiQ zUS)wAs7b)zT4vgOZ#0$@uC1#H!0i$)bVg%PveDRVZxm!p-g>BVX`n6~fzj@l9SL|> z)rEthSX6%o!lu7PpH+FNWE09qv(X$>faaokXg(@LMW|Tos}0ZwX+yN#wc%QlHcA_< zjn&3&Mx`(l%TPI5h!*j=SOLW?hrf|P4Rl>YKto!8t)Iu$SQ)GdOiN8oT^NN5rq+66 zvFe~Vnrii6G_@)iOZ8Sn0u2pbH`LA-bMww`2)Ox!z#1=aUurNMP0cgDXVul2-^yX6 zqN!QNyE&1@x>#7hl@eVYtc>h&M!aUHV5>)&~^j5f5dQl(y{!5-J5|6i4OS5`omn*Ekt6NoQsm0s4+ZXLdo zus@n=j8YjNpVYE&eZ&{&cyG?U-2C|J)alh3=5ej~|2N&I&l9WlMt$N-tk+8%;cwJdqZrveiJ zXIEM7yqT~ng{s{CKxHr#h)ypl&h_LJ7v&esURat{UQk@*$<51NID5>*-1IRM(%OH| zDb6X*g5+iV*;pf&>VVfDh`8&#vFhmbEqTiPU`50mX-v%xGz4nG(7~xoc%|TX1o(+R z*BkT3GZA;Szs}nbTD!Jxt-mr>?_2F#wdz1$!)kv;bai!VLC7CilM(`k34c?w>w`6Z zxEBjVY6JctFwbb~Gv3W96?y`l^}B<0)nRCGu(lpJVmOl8VUSF|=7xD-zNGUkcNnY% zk)R*)t*-UfL5KXS+NgJd$6)UsvkN@X1@7?bkUQp$R0U#gV}$?5+V8EYafiYoccn#} ziPak*FZt`ias`wGLQR)vc4{!>tEu+~cHT7JPQ^>PxB(JWZ8((L?t4n>Fr?1W`Au}! zZ-K#nQVrTOxOA#79N8tt#$BchfzkG3 zK4YXj#*g?RGACms0IO;QmT4a-F|byGQj*#b46JTzuW!wo1aEJVuB#O4VowxQ6A-L* zPU4Aw5Z@08-nJ?bQ>%kMlkPm$&wp<_ww#w%i`yn-=LGlU`lKP68ps$nJ z&^;Na=4a3Mu5DZd=7Vr#$CXl=Q(hW4us#?G_;+9-x`n_80#h*55MCA7k?qloiqwOF zs5TH-6Y$mV(C(||N=4Ki2vvce)Qufj5e`RV-I!nNqy?o4G@#U_;VN%8W}h1dVhDh_ z%-luo#;hxAW4ne(>TYE`##&n(P*v42eWp@%q5OM^OOD&(iy>Z@U`pxa(uV8cWmBzwdG_s0?`d zMrCDJo@Xx~B5>sEh6Vo)T&FX8%j<`(19wmNU_^t8LG|pGbq&CAP6; zLxbGFQELjKiLslwW4vfgS0B^0416jgwceUkZ=^OeW9PBb|3*{)7usn0fYpO1sp%&4 zZ*&BU=mLS^sL{bm3PkEaggflIxpUzEn1@uI;j-PqJS1aHF)GO$~R_32Cjaq~`Nh2d|wwW45y;qjLhw8@`>#=*}DZ?Nm4?>`}r_Pw2?<#=1b6w=xjx%oLj-ovAzTn6}5n?Mt(kiSCDM8hR4EAKPu+CwNWU?*b)w zRpa-q6THH$w&BFDr#=l5znVpX7>^fA{EC)?3X$No1dn!ItWWT2>N~s%UdQ&%4GCUV z%g~(Q)ihj?6TFfKM1zT6!)>^WCz5;&eHn_@Z7nV!k!o5Kt^y~T1Rr?AXzL4yDuY!C zUR8*WuG}U1w1a<9f_JJJD-s2?5grDg=+jkqb4=_mHN0>VwvL9)t2D7KUt0;#Neu1| zF*yl4x>dj+JE`11b=_-&#OTIMJ4B780&qku5)Q{wt-);t`-rC2hX4d467}xHouln+ zA1ujft!{@c^VE)wEQti%9B3nEZsr(>5xeA0j@g1Bgg-}O?s^^$UDF7lE9Ms1^u|Ec zOk#xW!)8!zVEWc}h|Qz`Ix~F>@R9V%DU-~n2F%8E-6evLQVR_lni$(R*9K3 zu{TzANS2P9gt2hA##ikHnRYtFB&F!BOYz52TJNXmyN4!RSbK1#BXua7F&)ngcK#>_iH%3qIZm(29^HcT6sq)DNJmALKBFuH7wbfH62NwGyt2k7 zbRt@5LA;%U&On(P(W&S(bUIq6?WOIljn~{8(V6HhbT&FiOVMU)b=t8Uk!`ZE6{~+v z1k%+WMH;1xDz%;9E+wS$a z_Sn|T^TSmZd-k~8+e&E`)0VT<0~ezWD02f^ucdB4O zWsMIuw(FortvYQ&*P+Zy(3R*abTzsLU8_ydCTbblBn@s}k8VIWqMOjo+GK5tHdV{f z^7-f(jko&T7!Tl_4Z(1I)Eq41W)24p^ti_Boz;2zavhpyyU8ttI%@>xhmEZ)$i1`L zc#INtP$huk1JJ#ys8NQ;HMW!5nCaVB1_1LqB;X&p(Oc+kv~qjN zpyb>|BoKUyB2cB70^I~i1cOk0)54^E!0MBKwoQf+8%s|{zRFZ(D&#E^dtHS{fvG=zoOsJ@8}P$K%1-0 z)8=c1T9H<)m1qmJ(oN_u4B*`Gk75D2;jaW}lV#cx{ohjU0Bsd~t1;juhqN&QW5iNg zr-H||d;G)d@an=~2>dGRJg$+QKIVImIovS`yjDD}5uK(f2sLn6N$}?CEEjNo3D?K+ zxLXWgDj-dAd@jBSgv98Wmnq@O%4h&k9y_T|yHo(20QKp-Nm=!=>Tm=Kihs;m5=7tl zfl2}HGOix0!IyYG2YAzI4EF~b!vnN(ZJhCYFe=%Ihv1=b4bv8Ci{M(UjdKllxm+IE z9Cth#?|}w1;W2ouwoF^zgva4MwG~=`qn9R?Kxb4%!XP4*2BLuV8J*v;b95BYnwZxZ zye%GD1*G5!Xh1Vg#c4QQTdD1*d7AM=oPj54`)d`Nj~8UF)O_&Fc>wORX36E@#@t|( z|K@0(yx#*6U{!H_(e{Q>*D}D3fG!^%moN%QZx!B1%Ics$R^5X4LA~(4csdYtN_~CM zU(pEoa>Xv!PtPn)p8ssY!KV&9GW@6V(Tkc_r*b?K&sqd9mJ17VoA6AuvIS?u`#JIN z|M>7vxA!`!aOH9SGxkXx@x;xVH+hzSJ>vfv?BX>%yNPoWtD6e&d_E3y@jR_kt7^i9 zxJawkf|`FhpF27vULd-k#TMrSVg=NmgNWiXRI*+h*E=#eSB~r?@9f%Fc9WOij;qXJw_OyYus==H*Py%FfQt%5S&KV*~yX zNNJ0FI2vxwq?|N&UiOq6cgEz*Deft0lk(lU*%LD+O`M#QnU^`aeLe=BV?$g%S(B!w zW##8jcBf68l;zIIpPcT_%FmeOo|K#SQTx_%Psx_)u-l2CQj~oXHN} z>Pl-2!;zf&j?&g@<6eZd?ICWc*HVoP;t zx&&YqH9&QgwTO1mW_&6>4aW3zybhlMAhtzi`pEM%#*phr7_{Ew=!!0)9jqPNg3p52 z&c^3(Tv$Kc&dwd79b(Mc^YHmA+mVR<^%k zq-w&=uyTS7*-qxOB#vgh5nsYrx2-(i=CXz_!&kKS_$G~MSw^p3&3pA4?eNZewI1JW zbX-h3qOId@#b;!MLSI!eIn*UC(4G`IX z6*?CGtQUK##})0KPT;V$or|TGMrmN@>WItj+y94Nk<$|z!&YCB2qG$olLQk&C=rN_ zh(sbXQHWhTLpxJDOFLUTM>|(LPdi_`K)Y}gaTwwxagpAn51jha55&ofOmT99cC~g5 ze809woYWE8Q=%Xtcb7OxMgdbK$=b!HI7!BWI7#+EZhmR&L5}CwIM)LvQzR)Q9b^rX zO476@t+|OzAQQEX+NN%kB*_%AFG!MPDw#(1(Js+0)h=r$)A1xSQ@b4G$SZh3-6a?P zeVMd{0la%v+}l?A3R25y)JOazKq^TUsU|^k09i$9v>UV=wVSk?wOh1XwcE7YwL7#s zH<6G@qY)A%F`&@~pwYWb8of_@N_!fmNL-J{*B zU*lW{xfWVudm1@|&!p4II_-Y#fhKY$IZJy`d%XLo^n7wLr_u|^h2$dbA?;!9k!GOL z4WtQZ^il0GUeI=wvcFG!T_*e%aygLR74f9!x#97Y!QFZ*)LuXElxrsbIhW67FpMB~^E(%t?9{Xq18hg!YP0 z`uIaHU3Bm5a}T=fzTIqR_nJMUl}2eF+8_Fs_Jx)7V^~S)09Z*s>1-u++~CSI2ZRoz zNjhoL5kQ)s18LF$@X9A~gl(lCOVfcg=^k_(-IMM`_om~io2JlInx=iJeWiV^eWQJ= zeW!h|{hu%azt*xo~(-A|o#4p1j8(0(zg zlg)-|`?HBIrOULx7`AmAfl?3k0fEx} zsh3tTj2OlYlV<9t0X&Ie$}p$Yza#JdeG}_;7U>pR1Eg0QPkNXCakD)+FMr927ucWq z?9n%3oJ#8;>4EV{zxnZW`;gCyzqmT@(ZG`f*WJ@br7^mi6WmoLV0auS z-#vTCcL$K~m7IL9Vt9~6zt?j5y^dbbFNOy*JjD2f74|vT2y47=qjz!|zMbB|@a_x` zYod42yBQwNuzqq|x9ok;2k0Z5j31;A(T5ox!Eh48Bb(`?^f6AxqZm%!`DFYo(B^aT zw0TNe@27@eRs8ss$Be6-x<``C$#^p)eIY*S5kFr3+N|8-tP_45yyWY;)n~Vn@yqlz zgN$G0WITqG;TxQc$HtTKh^vxkjW@~oCTo!1rym+*`~fH9y|(eb^~UG)Cr-v+&@bs% z^lSPJ{g!@5zo$RY9~mCcu$$o&hEo|%V>q4R2@FqUID_Fyo9NFb8UIeA^iPZg#K}0* zBIBvrQw-;ED$ehr;_jznVK<;+0XXbrlZu5tK*d5|SdL_GNe`UmM|s!maI1c42SRDrB_4_eX^>crL@!L0}Zd;X;OI zbS5#r>9<}=5%`{`kSe4JFw15!oW*cc@?{TRqnbXEWpO6(8YOl$Y((J!c0gyE1vF;D|nhty>#9o=UzQ*=m`@qi)h~7wmYmY z$Q5Smq$}ie(wz;YD~#i$KO>Ixtv3pVB|y4Dkx(p@2n&Q#p-dY!R{mzhGB4*Gzo_Z8pBH&_H>&FEgU5r$7%3r z;TYjqhLwC%{b|Aa`pXoYSOP6xtT7f)FCsMAJWb7}rv zC;nMp@z7JFhH@H&?OoyQ_@p1bAV2WUTUl#vcY5x5?9HEhwXVIwdBTOflh5abvp;8C z7jeq*cE-5qW!A`S6fQHU_EJu@K2Eh4ajNxhm1?gRZsSyYjc~1Sop8NygK(p8lW?2Avn}pjGnAW?i+@?kg%E4?F+(-!b`%-3?ImFgyAT|F`(RttCuV0a$2+GE#X~0 zwcZxqVYq?e)lI^C!ut%bVc;+U6xA)Vv+%L-Ij7%GginRf7;a>EEyD*j3ts>Qf5q^@ zoP-bA*=y(zK$Jhm6Xnzsd!PC7CE1@}`)tb}m$0#)aQgiPlKvW>^!y{|_J8n$EdSAX z#@wru-ww6W?;keAJNZvezlQ<++AybItuy+~k0j3;WXR6Faes4^Y>LgH)3QzFw0tDc zvJG=u*0x5=wm!CDK+Cqiwtlw$wgI+*wn4VRwjs8mHW09mX80I}fs-D`@bL_v!0?F- zpTzLV44<;eHr$|P+bCNyclxu9<+OaNMayR}d@-y5K+5ZTNV)q-*_HvMY=aIz%_L>p z6d+~WRNFLuF?>41V8q~`K+39Xh&4g7Y`HK&Y}vLPhReD|d;zT6wq>^EoairP_@XYY z+g)6R|Na8sV)LS2wu*RSj*NTs*fDbUic?2cf9g3n<4>S_TL6+)#wTsaYuWtEwMAw3 z+KvtF|32FT`~&a(+8|+Tcn+}D8uVMk>375SdoF4_l#_4FR&Q&tt+uVPHQLtN4ze9= z12L0ZrA_a?r8T*nuXo8uOc-YaKVHM`k%J15^;Y`5BOV;F=$ z_`JQ@c8BdwZt}W=;X8M_$;9+PPk84 zg!{Cm`M%miw>yAtf8=!g6T?qhbo(o(+uwk0;bQnHPPgy@bSs)8EfUcNb3>$}!0CrIf^bWIkt2rIrhEIs@VW>2+*xKP#h$J$@~R| zUu5{DW^t&vJEz;18GdD_)2*0{dWoasiShT-);#guae0$Y=~W%sJn=LY=vLeVl8%c{ z>hE9Z@PA!ga>LORCN$seT;D2gi+hVHI@OA9PPMNA)rv08>{{ZOoz-I*VkR1}L4@9U zV}m%E;Wst^O7M&>f@smQSVRxm=pEJ1Hnt=QB zi&-$OP#=wI-9>9#D+6ASgb48L#WS1gw7*Xrr5#~C(@#qA;u z#Zqw*ALTN!TwKWT2MmA6@JG$!VsVKG)|ZbN{+!{jb?FLPQd%G4F&BXER2uN{gM!Nd zV*vuRAe_kKn$%f>`QB&@PO$L9o4F9k73A0);ZSy?@uL3T*v{VTn4TA4HQgh6q3PoO z41cmggssp|xhsBrx3-9t&~sH{HFtmKQDi(=Z0(5B55_Y5nb9`~h^q{T_dt_)Kx^EE zSSyBk0U?IJ*dT%#>&vYa0NKaHda+?C6aewwFuV+Z1yPj~($X_>GBUH>nOT!3x-%xG zWw^8Q^0VAiC+AE_%gad1%bk$n*&;I7qXz;T)mkG$XZC6xG-Kv8i)+NSXyp>{cGios zdgEXb!V;RqL&QTF{)XXin?y|nE6H~Zf6uEuE)D*hlAfOCPD@Llke-p1mzJKBo{^TG zXVxGbHH!ZK)m!iDqs408#|IamH2gQ-$NJ6V%$qMgymSWKw5olgcs3faNjynBSv*BN zRXj~RU0f%gA)YCM&;$E2z(Rj!_!owMWf=1Mo#8(i{*&RqHla!4xo8qb;sxS`;zi=c z;(Bp|7G?y&dbWfS%m`tGG9oZyV?@;a%YxConqU==?((?$#qv78sqhxVHen1VZfj-sHiMZRm<7fLOoD&QTn9(iRpgVr$)1$ z6bqZhr^KhlXBg?t$UsJhwrl0{;)@UvCANs0#TOXq!$@C7`ZbF$i7$)yFw&oq0nkX^ zHqPEUwyi_iC6Qn_0?qcg_TF~FY_D%sS)k4f*z;ist=eGMJGeT2}H*Qvvm%q%i~~=^;hp=lGoB-hx_DR}+lJ($ZeQ zgbFrMvM(9Ufi0kmmyY)G*$%(TN5{f-OGbw)cu;z@55h;U*I65|^MGF`2}tz3tdw)K z3=LoF8mv0{_S=2fo_nQD+h^b8=~)GH=gluHTfAh2XW{4&EQ*_*yY+JQ?$gQ!7mfDu zxM_&h%ZdSCtf+@rJ&iz!4P9DR@ACx$0A#+o|A5^F4j#(afmMOV=rZ_gR%pW@P1rC* zvuS-{9mt1;?WN%(l17f&EqU~qv3rbzdu7430Vu07pC9JOBYXo!oK`pjxydE%rAe|TY6D?nr|5^#8{zW{3wRmX5N5Y|x4p-^Q&J&r+95y9$smqwx1e<)sXZ|(+ z&@rvibmCXyk0|p};ELaf--<7Y--|!!ym5C%h5>tIWH=)uE){M4_F$wtFnSQl|5 zn<3)-vbwq9E)a3{D-Zj$D~)CXl=F4-kja!5{TH>sE8l3-aG z!^l`h_F!ZjBYQHk7bAN!GX8Qd<4FCb0r2VM-wpA`%}5FtbH?$XyrbOEJm5oxL4dY| zBT00AAI*=1Ym3St=ouCYj#Z$Cr9(?$>h_29iy_D~yt*Ko5B$^!o!hVVu8#cUan0a2 z0Bbf{=k*23y%p^Ot@Cqo)3dTCK3a zJsRttGHF86)X9^RCT3)$C8bZCI3>xrm6m4S%1EDNt%%ZCDGg;_A?+cJllGMMlJ=Iy zOKvGeN@WE8Ph%vVkqL}UWF&);NsMGNGWiNLNtz%{lrp4AQl>N+eof_)=oCgyX5rey2!B&#!Rkm;};&b5PjwxI+j({ZC+ls?(EdlucY(OcfGaeX1 zVRCCpMd28T|53e8`q$R?toARL_6HGGS|P2J_LDq}%wi;qk!(hCHcDQpLh?yoMsgX+ zV2HkbbMshYn4=k z2CQGt)p0HvcgVLa408q~TPtmy6aiy|bRZ+MH%L)N=4|t=7Su~xEv=D24jlp*6P#_7 zaQP0TaezpI^;_558JWvS0Z4z}b(H=VE#7Y_Xg9Tbtzo``Ocu)m@hb|NKWDM~f!G8% z4JF|yOzUuHr2*;@@E5>4(Q94(M2D;2;N6qP?v*lO($sxtssFwMKMO}aInIGZ3l3d=n1&e(8nK&(F z!E3Ael>Vg%u_v3BEN@!6EZGvxlJ7I4$(mNIOr8}h(eEeET34fg+2DbK_g9;}6~4dK zF(Bib@QLlB&UG1gC`v)o(L7Xvs!$DTKx;s@JsGV-m!g}|Js{UU0>~y`qVKSX`-2>t z1n4q*;PE&e=i)NF0(${gq|uaMFUB|E`|%U_S=@p@1nE^E1Ibv3A}3mJR{&29V=w4B@J{ zKE~l#_~L6Qw48OC{JU8?R62~~Uvx3O$5r7ekIl|ml%5H2PiHPROXqO-j+`1d1WANgsR#UEswQ6FzHyn#vd*nAss0lB^@mt!^nI_3K=P4q?nNs zMiyKu9VZ=++#uSV1R*B`miaQ+y#Rrx)(}7WA_S%QR&o0ZtP`Bm>M|%OdhkeJ#C3F& zAh0jJI>5gM=4Q`sUAc5~awujjjCyt;Ar*&e8o>Y@f{JI?0IF-03#g|22!>}cwhiM2 zn<;gWN{|O2wV9jM#IvOf0k2IuM>5mI1&-`L0#`i zhjeqi4FJ;vD;^vk*J$OFQ?k$y$9Q$CgEf9YRyN-mZspxko`LzKuakf~1qpKMBk)FS zzPCQ)t1dITyfjc5fSW!pa_F`zX_ItylXRtY6(h?SS>NE_(*R^hM|Y(gLq1&sUfW-UNUwj~Gr9UYsdxYEkA z?U@FXb$|Y`iV+as4)D0r+oV5T{5Cr^C2vi@2LR0BP#$1_MZzJz&Ke~d?Ro?@@T5nj z$E3%lCl~>#FTh9@Bh|1KBRwrW18sa(dQN(NSzFl)^;$7f!HAC$kktBQB+Z$Zo1fzI z*7)kd<{TKer%N!H$&1oUAOgdvwy_Swo?xQx1S3t-%bbC1GmTY~SCLzKo{>t}WhDM3 z<<)$GCZ$_XuQHx4*PmK9-!pCo^_w2oUi|t@PRvZ3JT)z2GC)2KJm{db@k#FVv_r-x9W*fm zzNb%|kama#=p%h8eaB_@ucWV~Z=`P-0iv#B1jOJ7m)(Dmel%rw5Q$^|4cWa5qWWJl z1mA9uVZPRHkO?CV+Y{AgQCcI*+ZWYW7x%_{cNEn{X}5nas;}22=uyuO^ft+AlVa~g z+;BLPXVuTr*UaQuXRO!1Z0OYjM%#f~2=g2J_Uk|3Z;BsVVN)x?>M?Gzi`QNe{Egk;YS{K@;Mh?|HS*>=(R!EGGv(^lEz*5{LWlBWL0*-9FiTfQ{GMP z#YiI~YZ*C+k%KpazB?Nf<2kyb3kor)#ryCD!$4e&(^PdeG^HRKt=GW?Te(mNh1f`> zFAK23Y%2|L2RMNg_K>!!TpM|DA>C@sP&tV=W_Nj*JX{{Z$f1lJ#)!rU+bEBeN6E?Z zXhsfa-04OcO z@A1Y4!;8iS1D~4K-a;4-Y2fA>!|K>S7G4zqOx!??->^ZO@=k9!F%Z@cLxVTh>*0YA zmj?aW^|6?aqYFBJ+d>i5V3xsVg`kNogPenhwA+|iuhEv>L2K;H7;f3Nn}#Q~*}(@n zMUv0K{1#|%$`{KqA4*i(}?2sF4n2 z7gM;?B^ABJ8$kn1+HNXXq}#G7=RMb!L;N$nSM}VtVmDujd?*CA^TW}3fWP}JdLCd6 zUqmmXR{=(q)XxM07`~R13|8njz zg(J4fx9bc`e=FZ!zXcyPdZ?9s_UhPU=m?AB%#yQtw}8p)gbi{IBPVXRTV_MI6l}j+ zPTE1;Qn1Z#Xde*5MWYDut<2-jHKTsW@p+S_7 z1pGN5Csu*|wiR}^AlJZjmCHfWmO+UD@9pJ)6*HgFv-MS!KO!FRm@S$gsEn0| zOM_L_hOQ`|B^EZxXUpd>vWZb}GP68K_@+I(j88=B>$H?`J!oJLPjB-S27|`BE4;dRzrfS{SxHktH4@3nw!Kkp( zxW`8=ed6BB`Iudac;+DWRS!v)vkl)~x&AJ*X?1@|~!pS-wlYTfT>p zn;ChRQB@oFihRHPfc&8R5Ud;z%a6#9%8$v9%TLHp%1_BpqkOau)=&!oxP#)w9TI@0 zR08$$jXy5Bm_BM-PMBU%Zm?*BE(cB!YOf)0nZ9?2r@Wl@$n5a8Ri8G_)hcB$at9+1 zGV%~3U@CZrk+%U7)!o5dAU`j+$eX46Z(oyNm)~IIc1G@GQPV;aGN!Rz$^I#KuUYn&` z!~CWmEq`oea<`dD{I0@g*x3gRV6;g75|wNH7`az7V4vl0bNUk^4Qa>{QN}qN%msSgbnejiy@Wk!WgFFqZ1Ahy)rMyzUO0 z2>e07P@g!cs;<(@zskQYH$*_)1k)`4F8=`t!0m3qHd_f$QozqmG*_0N zg61I??r69^;tQD6q9BDMeJWTX3RMKfrihB9$cmzXf7ru}Ji^GMj6BB3N-`tQnSSjGNB-;_QNGc zOqr}q<1X5Bm8lSqLSAfA_Mt%HFLeedqH_XemX3^W)iFl_M1p1|SIJZI8F__~R~dP& zS(&30fQLJIoslSq+11JQI;snbq{xC8F;vpx3<-N zi=uEo<+wd{&Y)B%ux->2B#jQMDqr846hG%#oj!|O%#=#`aiv-b%1Xamkq`9c z;cG_TWm!^nq>e9Xva zjC|3`_LWNUXKpNo5=ZJ*T9?W9vuj|bTpg^8nXFwo!btd$k&y2}T2GEuU?pJW6GlGO zv)Jm!tZR_<%E?A%pSPB}<@TQH#v{Fr*aQx?f!@%gfzX?!HVH7?&M-y7WYJK&}iC~zMe z))%Ar@OseZB8EyaKd;Q^4UO|m0|Q&Q2E3u+3wM@=FMGx_%-?`v{-OLSKV?U#7#Jo6 z+w_Bs4r0`yGt3Ea&_G9qX&3CaR)$FjGV0utVcJz-n0ANVY2VG>%kE-yH%8&JFQfe! z?cc^Qf73Y%TY(3G;WL@?__QAjwX>UgR09!PiQY^OCk!4zUJ90%UpRq=P13F=W z$LTC^kM=Cko@$>4EYO~2Pq$C7Pqb&)C)qRYlkHRNQyCr1=nzJSGP*mX!x$aT=m~L3>Ijg2{5<-R&CD#7D$s#76{(0I$Sjaq)y#X(j!`OX}P;F&0PBxxV_fF?z_9*IQkHxd6cqr-?`)WwKCO&CAz_r!M2iZ9q z6P{~71jK5Z3Y?6aBwp$Sx7vE+Nc%B5@3SAxd0+ZA-hiDlb9Xs62-YiK0OCZzjX&`1o*rWYquyoo_VHD8#-fyu3GLn6r9V}0Se1;1|obbsgjLzaXuE}jC z_H*p#S~e5Y3PGncnz9?>4pQk&)+f}SiOF?{V~q+?y=u%zt4WZ{Q>)f_J`~b+aIw%%IFM6 zXEHjA(JV%@8O>odm(e^%^BJAJ$^N*>@}4&IY5Vh><;}5J9z+T6VIY*!g*{BK`=vhn z+nnivf>U5Iz4y1LPuoAWf592wXZFt-oyX|>Ci|E6uNW<4w5SDn`9j63@*#QHWI=Bo*V6ZdD;x6;%gFnyadu@s)2& zAvS4H?W1z^aJ)?I$2s4kZM~u^&+1Thcf-A`1OL)Z(oO0xu03<#v-pQ#Y_m+%9^8@gQe_hf&7LL>EDCw&6)cI{Z`v6A0Tk>qR6eL}>Of6Rzs*BXcj8-t}XB0-diqSTY zE;ZW_JfTEG((T*9B;3I|pnAA9n9+b?4mO>5ExGn@NV{r{QE0FeX}3<7c89f>cGY@S z1Jo%ody+*yB^Rl_>b)5Yj%6VC% zuEEBGtXuWERRz%OX7x7pcJ&TMHAWeuhc~Ntsdw`g;|NBN-1#d8?4GF)$1ek&z|yOa zLDI+Llg7i+yOvr#@U;4zu>d^F7l327)oHLJ=MU(9{(M`$i%P%xvigeps`?tR!Z*}6 z(P@mH%;*`6p2_I(IxD=E(bKiPJM5yV@2Kx`Ua0;@{eaO^89mYDh4chQPw}{nlfc_K ztMQe^+(d)@(E6GHY_GN5(mVR_9RDW~qM5 zSs0_I={(HSJO3u@V9;8Y#`$1+hoz{Tked_4^~8A=RMUl z!UiJ7_9Fa_fL6jIm%Q4>1+O>6S{gttL-)i5gRR{VSR{JZWtE*G>9o>{r z7&ejvYf^Ps0k{X|oZ5gt2pg@@)@MAJC#9kuJiwtmaJtE3OCWVnqH{vT^UL=?C~;gGvB z0vqtFAyPN7dIL_j_k*7*>^=XjnVlL8`D*I@ft@#vw^Q-bfY+ycfrLY;?Y^h94nyi3 zo!>-v{T3MPC)J=mgG;CSzzJ$sXqmfA_XLl&A0v->Z`T~Fzt^HvIQm?NHsa14i9KNM zd~Zc0=rcyjWBiC8B6BiAj1w&8uuS{F6AzGx0J$f%0bJAC+Ur}hCc)cVq%UTMkQLRz zVa&G2KZxaBLFP}EfieJ0&`te^khc5FE>troXU$j%Ay@4IBBa&ggGT~bSQAFEDg zL-%Bynx8%2yS8zWj^4B5N-51L=Z=*Cs^;H;h43ZY4Y79+rP}}j2$3CKRHPojj%ot| zJ+Lsm1BF12AxtLiRn?f+M)i|*O~68nCa+p*2c~&@TrK@dTUbkLr``eEB$XY^?#v_rVkhg)-m0L{*8`cTXm8Gk(wiy z1^jM~1M)xSAypR%^Ek>5?qTM9XsC>Gx49Z`|j}@w*N8B;6Mh1~rAZb}aWHt%pTyx`7yDFnF$7&Nm;Vl<_G4v4$I^o(3+!?c-Eg78?bOH>&^M-yq70wC!!rsCK<>iT6Lmib}pDGDk$1d2U5<>?p zJTL~{lQ862(Z-Ok43ND8ojDpLMrF5UJxrJy=5qKqpMEw+!cI@jbZGt8F;(>#0wJ#II&i zAjV@-62GG5ph6^gEy1H*7wZ$en)(iJg4eOVb3=kx)iN|Ecr^_dnW@Scf1cCt*jo3K(Q3mHVfzdu@;y-I!^I zsIgQ4j)+CzoV`?QaPuudfoN)d2v8RyQSVONIoi(l!IGTT>UP*NPwm)9I8?>Wfi|qo zoJrl%snvx-wI6M+77XqH0ob9 zJy=%_0oBo9EkB_!97&&?GRb^uz-&y{T_QOA2%I$xL9OW%Q!?DG$<3sRy|Jo8vUJ=e zjKPWWzG^sDGCCcc%u@8$rTAkht@jO}9P{*)&8QU4a419NXrcOxhP3{SuIs_F;W#%v zST^B^;Vah3?yHH{vo#{@!LsSWveA7M04%3lVW+mU#FX-_q#mdB#C2LX#^&!KzjPuf zT9Vxk2082cZ2NmH+8&ff0yXb{E*Ls~tMA_vqH0;-%@$cdceSXbt|LSIQ(yOgs@=B2 zg7je7fS&E%;t00R@P3S`*n?%$r8De|4%}Mw{<`=sL$rY+fLKx(}wde%sY(rw7YMKPWko*3Lk^JWz!DMg&+0SgJlD#PXCr^--Bh-<%%`cByKDA*fI1P0Ff| zRfi)`Q2b*)C~$(Q9}u28>#fz`!!{psj<)b&bBsVG8yrcDZqUXVHRCrxTSR{z6|CE^kM2Y5|)P>bAwU-Voo>&xMno~spxSHk9#8;1QU-plCnDJk5#ug zvY_v>@$?l%WhwRbL4QSKED$Yrxqf{3r`vm-RJigu{~7zFj(Fl`&D-M0gQWTKNq>4~ zaq|3U3l2VY;E~}!m5*N3yjJ@Q9P^=*@m$9|t&-8Jnv{KT5hE{YLCwFPKkTco4TNIz z1EH!|we?1+V7m#G#9O7)W^t!jNZlQjf}j*=u6t}n)@Z`cIgi3PSi`fOS;=pDjC}7f?v``Js?F$O z!1HQ=FnTYe z_c3}uqYp3&o%0Z*4>S76CdYb{1#YC1=@ejrmva{QsKo*wXIN%f;Y{#_9wulqec+We|g6}xq=bij6p3CTFt+I(W zD*G@jaXz>;F8GP#bAte z>>R@A`;30fnHT*=%UUu&P#Iek46I&S8my|;zb!Ks;6jiXJg)u)xp`%EH9`GJZlI#R zDhs4=lealXI>++jMmdw6qn%?I{STubF!~{*A8o`4xs`p;Bw$z2aPQEWo`guqhK8@=%3C@Ym4Cf?grgO4$igPNXpD_97iGi=glcV&~qxcVZDZAg?|W@Pnuj4Z}!52br!gc_H3dzb@Z-T<)!B&U>t5(ydmztiB@J7>xyLJEl+F zTGn9o_XVQSA^;NY_{LW2o&$1mRXEbvKDjlVh0bD>xxrb)=(ii3C5(Q@2c&oIqVm@1 zS^!lu1X^dgSlHxT=v>6;_l*7ml8cqgQs+uwsLo~1<<1q1{=n#ujQ-T@?1Lsb_XqOP z!i@f``By@=1%42)f|bEQWCi~PGgue>JuVmgivP_S*JdR_}qy9^F~l$3WS~F#$o@0@nOXVayEaJ9*Y|gF4jWWasIfWv~u1IFkvKXCUwe zt`}sXGYA07=OmCkOS?_a2tH)E2E#Q9uJLfC!!-@AnQ$$DYY|*4;Mxz8*1#2k>oB;E zf$Ma*&V}nDxYonf1lMJ7T>;lsa9sn}!*G2L*DnYu{oon^7qmhd0@v#=*50Tu>h+1+D_P=D}46R|#BYa4m#u30%wJg1RYQxcqQc!F2#!P*)`c*Ku%N z3fGNrZHDVtgzOHu_JnIXT;*^ry;bDC4(#ml8bun)oZU_d-d9@*TLL6*k1>8>d+T^ z?C@I%9gm(R=K9T0rEAVgfF_T{2>ZPw<^7fLq*XNjdFEj5YV=wp8>reg)p|`p8 zHkaN-xV_#ZC}A}2u=g}(@Ek8No4L%R4K@0>r9OJ;<9GVZXAQM%L{EL}rH{V*m|-8i z^!Wuj`pD5ojy`JiHM72U-1js(b0%Hr$^~4=6+9{l^)#X9zUC8k z3~n;211I8*sFN|TsJnO^cOTV*r?87CwWG`>Dw$Ng6IIA0reG#fW)kJiC^LyNlPEKZ zGLxtmSwJOr8dZZ?M9CGki3YaN%rob=l(=)lu!tXw_4|9q(n`kqMKA&z} z#MS5}`W9~E4)hg$5BKo^<`w-gy@@7<{v@KuXgx;jF?uq~kUQG$qBmoA(M`x0?JlF; zR`g!%v)@r%h1vD%OAbJ26m`^XyeF`tGID@$xQ>>&0MF2hV?GZ~2e#d;^!JF(u0RWo)zZX@;;7Eyya z#QukO*~fc)z(<%x|HJqfr*Imb>B^;Cf%*5pnfrN+p7g^``)4tfQIzA|{|p6FCXrOq>jHGQ`OcXZ~^KA16oLb=-&?ab_R)Anq+ro;dT5 zdy**RinE8fIPxh(zPMow=UFBqXPo)ORj?E{8TT41Sj8IFQN?=fCr(dsZZhs|-oY&6 z%rVZs;`A7&$GETgjvx4oe?k}#!Qm9+UI$EK3cm69_UJYKOwQ(9%rbr|)0xThAtd;@ z1V5MH=Mwx}f}cyU+k~yyXM!9F_L*Sb39=-7!G7dP_?GYaF@(f+9L=#DPX|t*6K){U z{1eST(OZe$N;L1p-V9?No6%>I-%YZYq=!f%n_LEC?n&mHqp?n&mG^lJ#o=9VmLviFi@O_nwJR8Hp%&f**%AcZ;DZSqPQX<{e) zFoR?>Nd7y76m?UMnyhKH)U$~Ow(xlf>24_9%+k#&y%(vNQM&s`ABh>HKaX9dtDEiy(wC!l zy4vY#r<+51GuzqAn|#a{?B{EK-~d1KYX}*3n$d|1xtL4Qe}?`u^q-;s4E<;5KSTc+ z`p@d@OGR-H` zd@}WssgF#3WWLQi{10}U`2pYXBR}yAe_-dC?lmjWl{>JjELpR>mnCbKtXcgTKmtjm zFrE#3hWp6+Glc98oPhht?uHp;n?bf4$W}Mo4P?84Y_+r1&Q?3y9J15MB%31aFnbhZ zn7~9PGnE%v&Sn}hr|hlhKf8rCcCm*ycniDC{+91CgKRU%HiPWnIh6aa5C)pTz{5C# zE||qYI~gd$z@?};@aGV6PQpHN^pc~8oG7A+AsK~oav4lMg$zYsImMKrkDTeuWHxh| z&r9ej=OfHCN5&i(b7ai1r(E}!do;(}B{t=!IC+{07EGMEDNIcPW|7)2@WWRU$0 zn!TfGOMxU!9RwO*MU>%%vs2v zcM(@}9XIlCyW zH*jZpa^`z8|4G!#S0mp{@{4ds`TEaagqrzk=Ib|K-F$WP?LEJVZS3G(-s5w===%xBb_*zOSl}f9Oib0UCRyJ#J{o6VfH!9%?`U88HW`k&oH|fW*0^375Tj) zcULqJvnYC=*_cVueB4~oO3bBb9o5ueHbwQANzrcXujp;|@d5fN`ZR>$M{zzf4wr3s zcWy(j;r27!42M68Z)&(4!_9H{Ld*L;^rRQ|HX?;|vdBUH z5%P~HWGL#5D5i|5%;0(EGM@#!g7-$4=ZGKq9l1vQ9m2>6j^;Spa{`^XhXmZkNcBdl zF|rbK8EGCPck(f6j#P8xujqHAx+Q_bIg+C|h0{5Y3%G_GxtUvV4<&Y2Vg@DdqNEq` z6kpas%EL3mD*WpS8k#^zS~lJD%E4@{rGlEALR-3 zTN;VpN;AnOhe70HkEO#H&In4d%hCmGLY}eS9IJ=1J#gb=XJC(GckvE#kNtp;_zLqF zYaU~N$IXrXi+@5G7eNP3LJ#9k=WM!gKHaz*`xuu?0YfNaB%>&094|133e0lcD%{n$ zD(q~WosHWe$Fm z^gaGZe#XAW|AAg7xPb}wHK85Ha6BFH{)7v;81tNPIahHF*Kq?k)16!Ci)<6*nV_c$ zpM+4RUYXx3djvZzbFXDnv6Hgr(LP}L3QWgUlL>{A=#!TG8Bzv1Qj~CJRB>SIa_mj4>mv{MqFZr5p z`JRLP6T)P-Ir#{V!i*<(;$%*xGhMN#$v1N!kD~v{vQCa98kr{#AdzIov5BwvjX%)) zlz{t~awMm79+z+}ZeWV}PVvT+hv`ie{pe3T>Q6D-DJ4uq!6~iS;}rc((btsULzt@m z)MIJS3Fv3)X`I1XJb*b)oy7CZ=4F=gIva2wQ{Uii-sL?$lFc#58w_lyLRNh1^YH^ZAV%xs1?XLxgl z+0B@Vw`aJ)8S`+1Gqz#pGrOYCnOD%AySSGJc$g>g-pt+_L)0oqoI^55kgCWd4 zl{e6D zFMOVr$hlC?h4L*r8Mzk8vgj(VtZz*>vgeS7vDz=$;h-=mc{0`cmk7{$_$pX5qn?!1%HH4VJ{V@aRz5`F22i(ZuG_7 zRk*hb_g0aE`W1Sv&~t^|R7~Rq?7PCADtspu>!@NAAM#lUOO8PPCI7;kOHM}*ORmK1 zmt4n<+=H1cc@Q&M;wF~pWr<#vsJo;Fvt9B-2uqLURAgQ%^HQ0YUd&}&&rNjaHtytZ z^t9Amm&(3$0kxR#Qa7-47khb&|KQG+9t`1CSzkR9b6;jh%c5|<%aTYX1NXa3)@5eD ztQeV>x!+~pS+)$dm;D~X@-wir($+&|RvaDFk9^OQsDG zH+&z$D&Nd1x3p?K=C?|oReo-jJgc_i=T`lKepmg;-yy6%pUctfYQ3($gS)vG`(B+u z4*3kB2s2zg3Vo~|i)^bmvy;z}X|=aj%e49c-dyd?)!tm~%{37m!BLz|7rN4oi@A&| zxrXbxiSD?UHS(<)z!coc8vU-Z!!=)qu(kvGSu6M2NZi0$xz~=TjOUrfYgDpttUi`#Raz9ZP#Uav~QX=eh^!$zZ&{&aBprLxy#enSm_p zWLYv1+*q}m)eBigGuvs!uB*+c z+Kj5rs9JB;AE38ty;c9gUwC7^U9CTyb{xa;xRLdCwf-bdM$hZbbp2rLWPJ@Eg-~-O zZmH&OVo4#LEM%|*4vywH~QH}gH=CDDw4M$@Z z8_Z%uCuG`i8lAZq`8Hh3_1wjSJi_BVNgtjehW-pdmJM~7%LcpN@Jk4_-l_FYt-fo| zMa^0@Yt^i^=UR1Z)vYy)+NbG-+O^5(xwZiP)!K3GtF*9_J-o?(u;W_yT5Hd>_EGyM ze}_)#dlw)*SgENlB>CmJGlq>>iUvIDj8%mh!Oa{>Pi`p zo2grjS=YJ2y7koJZtC7cwz^OF9N$3Q@BD$AsdF>+vekFN?&~k)5-#T|%&h(fZsrzl z$E@njs@|;XqlhLJd#F#u{OZ$?x!xS>%P_xsv#b9xgpKB~u{$vgryQ9#HqgX2cJK!B zZ{2G0#m)SrUscB)=aVY}UKznY%d-}dRqy}g;;oitti?`R_V5)4LufsQQ}A}{nViFUT);(` zf2$jA?apo7iCnF6wLXBEwmw4)a<$6UnobsOsa3D7dC1o4Zd%Q~wVFE2ytR=g?5S0z z)^9PBR(>ABCx3;|CTE+RZF08B*(PV3oNX6!6*9KT*d}9}jBW0`?I9ka4|2B2 z*(PUO0izjsu#gH~rHUHlZIidHjorM#+w5aMU*ksFWN(voXD3ePG|u2` zx^O=7?!1J{k$dO8nB`7ccgni+F?#Scy^wvU>^uA6rgj!FmyLWJ!mf5)#uJ$PF1_rM zeb*W)Syf_Xv){?eF#G-V3-0Z|}W=tGSK@ zG8xDqWY}B67~IX?33z+2e)cZobyl$^M08H4kf9XuY>4Q86OYoDEOHo30e-Ika7HkS zQpPip1uSGSOL&#nSivg1*T0f#YWNSog^0K_xRw5>7w7llwzEG(3}{D3I&m_maxNEh zDVK94S92|Qa4!$=Fi-Flk@QCX0WToufMvXnYy;M^fqFL6NE6?Oi1;qtg?jO7#HW!# z9wj`7n(=DJFGbyWb>sCLuitq6#_Km;Pw{WFkN5eA&(UZ6*L=&t5Rq^mmvROAO1KX5 zNzhxu-NchbDsm)blZ!kF1q@+2awW`SF7vUUgjZNj6*48%u?hVqG_#Gjcn3NCy=O$i zH<&}h0e%S)iHC6{M{^vfVTXxlV^)cBC(4~DcjArQ!h<}_W7tVzGdM41!qCDC0a zj$jn-AaOhsnS$9SR$#t~=9}oQ5?5lziDsN=#)%uyPvS=2<6wwLaw|!8my}2uE7-|* zAtG7!WkU#kCd-yATe57)>oH@0 zCmNA##>r-!yn~-ZL`paAN4*p^QnE3Z6!S>9h!p+$yUmCc z_nM-1$}ZmHV?N~z>@CIKQV!s))WbQEOSqh?@Xe&^FZE`+a}V~EYEP*>c$!ECV1}v5 zq+w^N?ku$keW%(}>J+9ilO?R9iW+L!&R*WaeWmWheWiZM*Vswwj~vSSEB}Ouv}5=e zmmy=CU8dQCza@=GlQT`uv|jWfihjfrhg@l9m$nh_`FqibwBPuHzd}TMz)aHH;hps3 z>Bza9$NAV#`bC&ox(w-VBi-E6uOSvar|T`N84bM6 zhkS9NF{@8^0A-HVHD%L z${f%0yud8xU_O~kSAI2@d`1h{OKx-`o4|$^ZASh^#*U3trqr{r~^~ delta 16400 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%0D%BN03tvlKp{XQ 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 - - diff --git a/example/ios/ExampleAppExample.xcworkspace/xcuserdata/palic.xcuserdatad/UserInterfaceState.xcuserstate b/example/ios/ExampleAppExample.xcworkspace/xcuserdata/palic.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index 64480bdf88b608785b42c70cec34d464b779cb96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14355 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 XGB+t#FPVEnq$mB*4|@EM-0c4Wy6GSR diff --git a/example/ios/ExampleAppExampleTests/AbrevvaCryptoTests.swift b/example/ios/ExampleAppExampleTests/AbrevvaCryptoTests.swift new file mode 100644 index 0000000..c7771f1 --- /dev/null +++ b/example/ios/ExampleAppExampleTests/AbrevvaCryptoTests.swift @@ -0,0 +1,225 @@ +import XCTest +import Quick +import Nimble +import Mockingbird +@testable import react_native_example_app +import AbrevvaSDK + +class RCTPromise{ + var data: Any? + var resolved: Bool? + + func resolve(data: Any){ + self.data = data + resolved = true + } + func reject(_: String?,_: String?,_: Error?){ + resolved = false + } +} +final class AbrevvaCryptoTests: QuickSpec { + override class func spec(){ + var promise: RCTPromise? + var cryptoModule: AbrevvaCrypto? + + beforeEach { + promise = RCTPromise() + cryptoModule = AbrevvaCrypto() + } + + describe("encrypt()"){ + it("should resolve if encryption succeds"){ + let options: NSDictionary = [ + "key": "404142434445464748494a4b4c4d4e4f", + "iv": "10111213141516", + "adata": "0001020304050607", + "pt": "20212223", + "tagLength": 4, + ] + + cryptoModule?.encrypt(options, resolver: promise!.resolve, rejecter: promise!.reject) + + expect(promise!.resolved).to(beTrue()) + } + + it("should reject if encryption fails"){ + cryptoModule?.encrypt([:], resolver: promise!.resolve, rejecter: promise!.reject) + + expect(promise!.resolved).to(beFalse()) + } + } + describe("decrypt()"){ + it("should resolve if decryption succeds"){ + let options: NSDictionary = [ + "key": "404142434445464748494a4b4c4d4e4f", + "iv": "10111213141516", + "adata": "0001020304050607", + "ct": "7162015b4dac255d", + "tagLength": 4, + ] + + cryptoModule?.decrypt(options, resolver: promise!.resolve, rejecter: promise!.reject) + + expect(promise!.resolved).to(beTrue()) + } + + it("should reject if decryption fails"){ + cryptoModule!.decrypt([:], resolver: promise!.resolve, rejecter: promise!.reject) + + expect(promise!.resolved).to(beFalse()) + } + } + describe("generateKeyPair()"){ + it("should resolve with two keys"){ + cryptoModule!.generateKeyPair(promise!.resolve, rejecter: promise!.reject) + + expect(promise!.resolved).to(beTrue()) + let pair = promise!.data as! [String:String] + expect(pair["privateKey"]).toNot(beNil()) + expect(pair["publicKey"]).toNot(beNil()) + } + } + describe("computeSharedSecret"){ + it("should resolve with a valid shared secret"){ + let options: NSDictionary = [ + "key": "0468f4f0ec2f08c558246a866ce477d903fa577373f8622e1aa2e64e2e2c456d", + "peerPublicKey": "f764ef9667497e7bcb4cdbeb0bf86462638cf65637569a65a8b5ed23b9a79621" + ] + let secret = "34b78ecc79b605c85e0d995f8143990ffcee19b276fa55418c5232915c43af2c" + + cryptoModule!.computeSharedSecret(options, resolver: promise!.resolve, rejecter: promise!.reject) + + expect(promise!.resolved).to(beTrue()) + let data = promise!.data as! [String:String] + expect(data["sharedSecret"]).to(equal(secret)) + } + it("should return nil if secret cannot be computed"){ + let options: NSDictionary = [ + "key": "InvalidKey", + "peerPublicKey": "f764ef9667497e7bcb4cdbeb0bf86462638cf65637569a65a8b5ed23b9a79621" + ] + + cryptoModule!.computeSharedSecret(options, resolver: promise!.resolve, rejecter: promise!.reject) + + expect(promise!.resolved).to(beTrue()) + let data = promise!.data as! [String:String?] + expect(data["sharedSecret"]).to(beNil()) + } + } + describe("encryptFile"){ + let directoryPath = "./aes_gcm_test" + + beforeEach { + try? FileManager.default.createDirectory(atPath: directoryPath, withIntermediateDirectories: false) + + debugPrint(FileManager.default.currentDirectoryPath) + } + afterEach { + try? FileManager.default.removeItem(atPath: directoryPath) + + } + it("should resolve if encryption worked"){ + let options: [String : String] = [ + "sharedSecret": "feffe9928665731c6d6a8f9467308308", + "ptPath": "\(directoryPath)/pt", + "ctPath": "\(directoryPath)/ct" + ] + let pt = "feedfacedeadbeeffeedfacedeadbeef" + FileManager.default.createFile(atPath: "\(directoryPath)/pt", contents: Data(hex: pt)) + + cryptoModule!.encryptFile(options as NSDictionary, resolver: promise!.resolve, rejecter: promise!.reject) + + expect(promise!.resolved).to(beTrue()) + } + it("should reject if encryption failed"){ + let options: [String : String] = [ + "sharedSecret": "", + "ptPath": "\(directoryPath)/pt", + "ctPath": "\(directoryPath)/ct" + ] + let pt = "feedfacedeadbeeffeedfacedeadbeef" + FileManager.default.createFile(atPath: "\(directoryPath)/pt", contents: Data(hex: pt)) + + cryptoModule!.encryptFile(options as NSDictionary, resolver: promise!.resolve, rejecter: promise!.reject) + + expect(promise!.resolved).to(beFalse()) + } + } + describe("decryptFile"){ + let directoryPath = "./aes_gcm_test" + + beforeEach { + try? FileManager.default.createDirectory(atPath: directoryPath, withIntermediateDirectories: false) + } + afterEach { + try? FileManager.default.removeItem(atPath: directoryPath) + } + it("should resolve if decryption worked"){ + let options: [String : String] = [ + "sharedSecret": "feffe9928665731c6d6a8f9467308308", + "ptPath": "\(directoryPath)/pt", + "ctPath": "\(directoryPath)/ct" + ] + let ct = "017d4aacf0a0f987d697d09c885aa9513aed2a25a1e87252038f0f7a3955b11dec43d9d7669e9910c527ee4eec719edb387ee63f8e0c2d7dcf7678fe58" + FileManager.default.createFile(atPath: "\(directoryPath)/ct", contents: Data(hex: ct)) + + cryptoModule!.decryptFile(options as NSDictionary, resolver: promise!.resolve, rejecter: promise!.reject) + + expect(promise!.resolved).to(beTrue()) + } + it("should reject if decryption failed"){ + let options: NSDictionary = [ + "sharedSecret": "", + "ptPath": "\(directoryPath)/pt", + "ctPath": "\(directoryPath)/ct" + ] + let ct = "017d4aacf0a0f987d697d09c885aa9513aed2a25a1e87252038f0f7a3955b11dec43d9d7669e9910c527ee4eec719edb387ee63f8e0c2d7dcf7678fe58" + FileManager.default.createFile(atPath: "\(directoryPath)/ct", contents: Data(hex: ct)) + + cryptoModule!.decryptFile(options, resolver: promise!.resolve, rejecter: promise!.reject) + + expect(promise!.resolved).to(beFalse()) + } + } + describe("random()"){ + it("should return n random byte"){ + let options: NSDictionary = ["numBytes" : 4] + + cryptoModule!.random(options, resolver: promise!.resolve, rejecter: promise!.reject) + + expect(promise!.resolved).to(beTrue()) + let data = promise!.data as! [String:String?] + expect((data["value"]!!).count).to(equal(8)) + } + } + describe("derive()"){ + it("should return a correctly derived key"){ + let options: NSDictionary = [ + "key": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", + "salt": "000102030405060708090a0b0c", + "info": "f0f1f2f3f4f5f6f7f8f9", + "length": 42, + ] + + cryptoModule!.derive(options, resolver: promise!.resolve, rejecter: promise!.reject) + + expect(promise!.resolved).to(beTrue()) + let derivedKey = "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865" + let data = promise!.data as! [String:String?] + expect((data["value"]!!)).to(equal(derivedKey)) + } + it("should reject on failed derivation"){ + let options: NSDictionary = [ + "key": "invalidKey", + "salt": "000102030405060708090a0b0c", + "info": "f0f1f2f3f4f5f6f7f8f9", + "length": 42, + ] + + cryptoModule!.derive(options, resolver: promise!.resolve, rejecter: promise!.reject) + + expect(promise!.resolved).to(beFalse()) + } + } + } +} diff --git a/example/ios/ExampleAppExampleTests/ExampleAppExampleTests.m b/example/ios/ExampleAppExampleTests/ExampleAppExampleTests.m deleted file mode 100644 index daa3270..0000000 --- a/example/ios/ExampleAppExampleTests/ExampleAppExampleTests.m +++ /dev/null @@ -1,66 +0,0 @@ -#import -#import - -#import -#import - -#define TIMEOUT_SECONDS 600 -#define TEXT_TO_LOOK_FOR @"Welcome to React" - -@interface ExampleAppExampleTests : XCTestCase - -@end - -@implementation ExampleAppExampleTests - -- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL (^)(UIView *view))test -{ - if (test(view)) { - return YES; - } - for (UIView *subview in [view subviews]) { - if ([self findSubviewInView:subview matching:test]) { - return YES; - } - } - return NO; -} - -- (void)testRendersWelcomeScreen -{ - UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; - NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; - BOOL foundElement = NO; - - __block NSString *redboxError = nil; -#ifdef DEBUG - RCTSetLogFunction( - ^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { - if (level >= RCTLogLevelError) { - redboxError = message; - } - }); -#endif - - while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { - [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - - foundElement = [self findSubviewInView:vc.view - matching:^BOOL(UIView *view) { - if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { - return YES; - } - return NO; - }]; - } - -#ifdef DEBUG - RCTSetLogFunction(RCTDefaultLogFunction); -#endif - - XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); - XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); -} - -@end diff --git a/example/ios/ExampleAppExampleTests/Info.plist b/example/ios/ExampleAppExampleTests/Info.plist deleted file mode 100644 index ba72822..0000000 --- a/example/ios/ExampleAppExampleTests/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - - diff --git a/example/ios/Podfile b/example/ios/Podfile index a4e60ef..9b66a9f 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -44,7 +44,9 @@ target 'ExampleAppExample' do target 'ExampleAppExampleTests' do inherit! :complete - # Pods for testing + pod 'Quick' + pod 'Nimble' + pod 'MockingbirdFramework', '~> 0.20' end pre_install do |installer| diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 23a7b14..6b9e99b 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - AbrevvaSDK (1.0.15): + - AbrevvaSDK (1.0.19): - CocoaMQTT - CryptoSwift - boost (1.83.0) @@ -7,7 +7,16 @@ PODS: - CocoaMQTT/Core (= 2.1.6) - CocoaMQTT/Core (2.1.6): - MqttCocoaAsyncSocket (~> 1.0.8) - - CryptoSwift (1.8.2) + - CryptoSwift (1.8.3) + - CwlCatchException (2.2.0): + - CwlCatchExceptionSupport (~> 2.2.0) + - CwlCatchExceptionSupport (2.2.0) + - CwlMachBadInstructionHandler (2.2.0) + - CwlPosixPreconditionTesting (2.2.0) + - CwlPreconditionTesting (2.2.1): + - CwlCatchException (~> 2.2.0) + - CwlMachBadInstructionHandler (~> 2.2.0) + - CwlPosixPreconditionTesting (~> 2.2.0) - DoubleConversion (1.1.6) - FBLazyVector (0.74.3) - fmt (9.1.0) @@ -15,7 +24,11 @@ PODS: - hermes-engine (0.74.3): - hermes-engine/Pre-built (= 0.74.3) - hermes-engine/Pre-built (0.74.3) + - MockingbirdFramework (0.20.0) - MqttCocoaAsyncSocket (1.0.8) + - Nimble (13.3.0): + - CwlPreconditionTesting (~> 2.2.0) + - Quick (7.6.2) - RCT-Folly (2024.01.01.00): - boost - DoubleConversion @@ -945,7 +958,7 @@ PODS: - glog - React-debug - react-native-example-app (0.1.0): - - AbrevvaSDK (= 1.0.15) + - AbrevvaSDK (= 1.0.19) - CocoaMQTT - CryptoSwift - DoubleConversion @@ -1233,6 +1246,9 @@ DEPENDENCIES: - fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`) - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) + - MockingbirdFramework (~> 0.20) + - Nimble + - Quick - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - RCT-Folly/Fabric (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) @@ -1294,7 +1310,15 @@ SPEC REPOS: trunk: - CocoaMQTT - CryptoSwift + - CwlCatchException + - CwlCatchExceptionSupport + - CwlMachBadInstructionHandler + - CwlPosixPreconditionTesting + - CwlPreconditionTesting + - MockingbirdFramework - MqttCocoaAsyncSocket + - Nimble + - Quick - SocketRocket EXTERNAL SOURCES: @@ -1417,16 +1441,24 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - AbrevvaSDK: 933f1d13612f7984a903a5c8115b34d3e68d1692 + AbrevvaSDK: 4b173dcb919fa9f2a26c2dc3093a8311c5cea7f5 boost: d3f49c53809116a5d38da093a8aa78bf551aed09 CocoaMQTT: 1f206228b29318eabdacad0c2e4e88575922c27a - CryptoSwift: c63a805d8bb5e5538e88af4e44bb537776af11ea + CryptoSwift: 967f37cea5a3294d9cce358f78861652155be483 + CwlCatchException: 51bf8319009a31104ea6f0568730d1ecc25b6454 + CwlCatchExceptionSupport: 1345d6adb01a505933f2bc972dab60dcb9ce3e50 + CwlMachBadInstructionHandler: ea1030428925d9bf340882522af30712fb4bf356 + CwlPosixPreconditionTesting: a125dee731883f2582715f548c6b6c92c7fde145 + CwlPreconditionTesting: ccfd08aca58d14e04062b2a3dd2fd52e09857453 DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 FBLazyVector: 7e977dd099937dc5458851233141583abba49ff2 fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 glog: fdfdfe5479092de0c4bdbebedd9056951f092c4f hermes-engine: 1f547997900dd0752dc0cc0ae6dd16173c49e09b + MockingbirdFramework: 54e35fbbb47b806c1a1fae2cf3ef99f6eceb55e5 MqttCocoaAsyncSocket: 77d3b74f76228dd5a05d1f9526eab101d415b30c + Nimble: 3ac6c6b0b7e9835d1540b6507d8054b12a415536 + Quick: b8bec97cd4b9f21da0472d45580f763b801fc353 RCT-Folly: 02617c592a293bd6d418e0a88ff4ee1f88329b47 RCTDeprecation: 4c7eeb42be0b2e95195563c49be08d0b839d22b4 RCTRequired: d530a0f489699c8500e944fde963102c42dcd0c2 @@ -1451,7 +1483,7 @@ SPEC CHECKSUMS: React-jsitracing: 1aa5681c353b41573b03e0e480a5adf5fa1c56d8 React-logger: fa92ba4d3a5d39ac450f59be2a3cec7b099f0304 React-Mapbuffer: 70da5955150a58732e9336a0c7e71cd49e909f25 - react-native-example-app: 5f4e6cf425690c11a463d60a3122a641b7020d7e + react-native-example-app: a2624924a0673474425c9f6dd1ea09849b0dd742 react-native-safe-area-context: b7daa1a8df36095a032dff095a1ea8963cb48371 React-nativeconfig: 84806b820491db30175afbf027e99e8415dc63f0 React-NativeModulesApple: 7b79212f8cf496ab554e0b7b09acbd4aa4690260 @@ -1479,8 +1511,8 @@ SPEC CHECKSUMS: RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNScreens: fd2722bcc59f36a629205af8cc7b48e4bc0d09f5 SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d - Yoga: eed50599a85bd9f6882a9938d118aed6a397db9c + Yoga: bd92064a0d558be92786820514d74fc4dddd1233 -PODFILE CHECKSUM: 305f8b38479ce74425909f150a0e67cf5d6eabfd +PODFILE CHECKSUM: 3011eddbff6993e358b388201e0c3143f162cafe -COCOAPODS: 1.15.2 +COCOAPODS: 1.14.3 diff --git a/ios/crypto/AbrevvaCrypto.swift b/ios/crypto/AbrevvaCrypto.swift index 476a2eb..1672038 100644 --- a/ios/crypto/AbrevvaCrypto.swift +++ b/ios/crypto/AbrevvaCrypto.swift @@ -2,13 +2,6 @@ import Foundation import CryptoKit import AbrevvaSDK -/** - * Please read the Capacitor iOS Plugin Development Guide - * here: https://capacitorjs.com/docs/plugins/ios - */ - - - @objc(AbrevvaCrypto) public class AbrevvaCrypto: NSObject { private let X25519Impl = X25519() @@ -67,8 +60,8 @@ public class AbrevvaCrypto: NSObject { let keyPair = X25519Impl.generateKeyPair() resolve([ - "privateKey": keyPair[0].base64EncodedString(), - "publicKey": keyPair[1].base64EncodedString() + "privateKey": keyPair[0].toHexString(), + "publicKey": keyPair[1].toHexString() ]) } @@ -78,9 +71,9 @@ public class AbrevvaCrypto: NSObject { return } - let privateKeyData = Data(base64Encoded: optionsSwift["key"] as? String ?? "") - let publicKeyData = Data(base64Encoded: optionsSwift["peerPublicKey"] as? String ?? "") - let sharedSecret = X25519Impl.computeSharedSecret(privateKeyData: privateKeyData!, publicKeyData: publicKeyData!) + let privateKeyData = Data(hex: "0x" + (optionsSwift["key"] as? String ?? "")) + let publicKeyData = Data(hex: "0x" + (optionsSwift["peerPublicKey"] as? String ?? "")) + let sharedSecret = X25519Impl.computeSharedSecret(privateKeyData: privateKeyData, publicKeyData: publicKeyData) resolve([ "sharedSecret": sharedSecret?.toHexString() diff --git a/ios/nfc/AbrevvaNfc.swift b/ios/nfc/AbrevvaNfc.swift index 8516ed9..638bbb9 100644 --- a/ios/nfc/AbrevvaNfc.swift +++ b/ios/nfc/AbrevvaNfc.swift @@ -117,10 +117,10 @@ class AbrevvaNfc: NSObject, NFCSessionDelegate { } func sessionDidReceiveKeyOnEvent(_ tagID: Data, _ historicalBytes: Data) { - mqtt5Client?.publishKyOn(identifier: tagID.toHexString(), historicalBytes: historicalBytes.toHexString()) + mqtt5Client?.publishKyOn(identifier: tagID, historicalBytes: historicalBytes) } func sessionDidReceiveKeyOffEvent(_ tagID: Data, _ historicalBytes: Data) { - mqtt5Client?.publishKyOff(identifier: tagID.toHexString(), historicalBytes: historicalBytes.toHexString()) + mqtt5Client?.publishKyOff(identifier: tagID, historicalBytes: historicalBytes) } } diff --git a/react-native-example-app.podspec b/react-native-example-app.podspec index a515c5c..d457715 100644 --- a/react-native-example-app.podspec +++ b/react-native-example-app.podspec @@ -18,7 +18,7 @@ Pod::Spec.new do |s| s.dependency "CocoaMQTT" s.dependency "CryptoSwift" - s.dependency "AbrevvaSDK", '1.0.15' + s.dependency "AbrevvaSDK", '1.0.19' # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0. # See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79. From 0f412993390977eb34ae8a6a35ace320deace5c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hochst=C3=B6ger=20Matthias?= <116495532+mhochsto@users.noreply.github.com> Date: Fri, 9 Aug 2024 14:29:40 +0200 Subject: [PATCH 08/36] chore: added ble tests for swift --- example/android/app/build.gradle | 10 -- .../AbrevvaBleTests.swift | 159 ++++++++++++++++++ example/ios/Podfile | 3 - example/ios/Podfile.lock | 2 +- ios/ble/AbrevvaBle.swift | 10 +- 5 files changed, 165 insertions(+), 19 deletions(-) create mode 100644 example/ios/ExampleAppExampleTests/AbrevvaBleTests.swift diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 7a39f93..f517ee5 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -125,21 +125,11 @@ try { repositories { google() mavenCentral() - mavenLocal() - maven { - name = 'evva-maven' - url = uri('https://maven.pkg.github.com/evva-sfw/abrevva-sdk-android') - credentials { - username = properties.getProperty('github.username') ?: System.getenv('GH_USERNAME') - password = properties.getProperty('github.token') ?: System.getenv('GH_TOKEN') - } - } } dependencies { // The version of react-native is set by the React Native Gradle Plugin implementation("com.facebook.react:react-android") - implementation group: "com.evva.xesar", name: "abrevva-sdk-android", version: "1.0.19" if (hermesEnabled.toBoolean()) { implementation("com.facebook.react:hermes-android") diff --git a/example/ios/ExampleAppExampleTests/AbrevvaBleTests.swift b/example/ios/ExampleAppExampleTests/AbrevvaBleTests.swift new file mode 100644 index 0000000..eb732ee --- /dev/null +++ b/example/ios/ExampleAppExampleTests/AbrevvaBleTests.swift @@ -0,0 +1,159 @@ +import XCTest +import Quick +import Nimble +import Mockingbird +import CoreBluetooth +@testable import react_native_example_app +import AbrevvaSDK + +final class AbrevvaBleTests: QuickSpec { + override class func spec(){ + var abrevvaBle: AbrevvaBle? + + beforeEach { + abrevvaBle = AbrevvaBle() + } + + describe("getManufacturerDataDict()"){ + it("should return a dictionary containing the manufacturer data"){ + for (input, expectedOutput) in getManufacturerDataDictTestVektor { + XCTContext.runActivity(named: "getManufacturerDataDict"){ _ in + let output = abrevvaBle!.getManufacturerDataDict(data: input) + + expect(output).to(equal((expectedOutput))) + } + } + } + } + + describe("getServiceDataDict()"){ + it("should return a dictionary containing the service data"){ + for (uuids, data, uuidOutput, dataOutput) in getServiceDataDictTestVektor { + XCTContext.runActivity(named: "getServiceDataDict"){ _ in + var input: [CBUUID: Data] = [:] + var expectedOutput: [String: String] = [:] + if (uuids.count > 0){ + for i in 0...uuids.count - 1 { + input[uuids[i]] = data[i] + expectedOutput[uuidOutput[i]] = dataOutput[i] + } + } + + let output = abrevvaBle!.getServiceDataDict(data: input) + + expect(NSDictionary(dictionary: output).isEqual(to: expectedOutput)).to(beTrue()) + } + } + } + } + + describe("getScanResultDict()"){ + it("should return the scan Result as dictionary"){ + let peripheralMock = mock(CBPeripheral.self) + let testUUID = UUID().uuidString + given(peripheralMock.identifier) ~> UUID(uuidString: testUUID) + given(peripheralMock.name) ~> "name" + let bleDevice = BleDevice(peripheralMock) + + for (txPower, uuids, localName, rssi) in getScanResultDictTestVektor { + XCTContext.runActivity(named: "getScanResultDict"){ _ in + var advertismentData: [String: Any] = [ + CBAdvertisementDataTxPowerLevelKey: txPower, + CBAdvertisementDataServiceUUIDsKey: uuids, + ] + if (localName != nil) { + advertismentData[CBAdvertisementDataLocalNameKey] = localName + } + + + let output = abrevvaBle!.getScanResultDict(bleDevice, advertismentData, rssi) + + var expectedOutput: [String: Any] = [ + "device": ["deviceId": testUUID, "name": "name"] , + "rssi": rssi, + "txPower": txPower, + "uuids": CBUUIDsAsStrings(uuids), + ] + if (localName != nil){ + expectedOutput["localName"] = localName + } + expect(NSDictionary(dictionary: output).isEqual(to: expectedOutput)).to(beTrue()) + } + } + } + } + } +} + +private func CBUUIDsAsStrings(_ array: [CBUUID]) -> [String] { + var strArray: [String] = [] + array.forEach { uuid in + strArray.append(uuid.uuidString.lowercased()) + } + return strArray +} + +private let getServiceDataDictTestVektor = [ + ( + uuids: [CBUUID(string: "ad9be5cb-e180-4fef-8253-0a7c7c9481c0"), CBUUID(string: "f2615e93-da11-40ab-8fea-532b2ae28262")], + data: [Data([0x01, 0x02]), Data([0x03, 0x04])], + uuidOutput:["ad9be5cb-e180-4fef-8253-0a7c7c9481c0","f2615e93-da11-40ab-8fea-532b2ae28262"], + dataOutput: ["01 02 ", "03 04 "] + ), + ( + uuids: [CBUUID(string: "ef4af0f9-6e16-470f-bb44-577349ad0b31")], + data: [Data()], + uuidOutput:["ef4af0f9-6e16-470f-bb44-577349ad0b31"], + dataOutput: [""] + ), + ( + uuids: [], + data: [], + uuidOutput:[], + dataOutput: [] + ), +] + +private let getManufacturerDataDictTestVektor = [ + ( + input: Data(), + expectedOutput: ["0":""] + ), + ( + input: Data([0x69, 0x08]), + expectedOutput: ["2153":""] + ), + ( + input: Data([0x69, 0x08, 0x01, 0x02]), + expectedOutput: ["2153":"01 02 "] + ), + ( + input: Data([0x69, 0x08, 0x00, 0xFF]), + expectedOutput: ["2153":"00 ff "] + ), + ( + input: Data([0x69, 0x08, 0x00, 0xFF, 0xAA, 0xA2, 0x01, 0xFF]), + expectedOutput: ["2153":"00 ff aa a2 01 ff "] + ), +] + +private let getScanResultDictTestVektor = [ + ( + txPower : 1, + uuids : [], + localName : "name", + rssi : 1 as NSNumber + ), + ( + txPower : 45, + uuids : [CBUUID(string: UUID().uuidString), CBUUID(string: UUID().uuidString)], + localName : nil, + rssi : 45 + ), + ( + txPower : 69, + uuids : [CBUUID(string: UUID().uuidString)], + localName : "otherName", + rssi : 69 + ) +] diff --git a/example/ios/Podfile b/example/ios/Podfile index 9b66a9f..defcce6 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -8,9 +8,6 @@ require Pod::Executable.execute_command('node', ['-p', platform :ios, '15.0' prepare_react_native_project! -source 'https://github.com/evva-sfw/abrevva-sdk-ios-pod-specs.git' -source 'https://cdn.cocoapods.org/' - # If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set. # because `react-native-flipper` depends on (FlipperKit,...) that will be excluded # diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 6b9e99b..029c7de 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1513,6 +1513,6 @@ SPEC CHECKSUMS: SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d Yoga: bd92064a0d558be92786820514d74fc4dddd1233 -PODFILE CHECKSUM: 3011eddbff6993e358b388201e0c3143f162cafe +PODFILE CHECKSUM: 13f644251c2c60892784dee414097c2cc20e668b COCOAPODS: 1.14.3 diff --git a/ios/ble/AbrevvaBle.swift b/ios/ble/AbrevvaBle.swift index 445573b..bc2a07a 100644 --- a/ios/ble/AbrevvaBle.swift +++ b/ios/ble/AbrevvaBle.swift @@ -323,7 +323,7 @@ public class AbrevvaBle: RCTEventEmitter { reject("Failed to convert NSDictionary to Swift dictionary", nil, nil) return nil } - let services = optionsSwift["services"] as? [String] ?? [] // TODO: Check if works as intended + let services = optionsSwift["services"] as? [String] ?? [] let serviceUUIDs = services.map { service -> CBUUID in return CBUUID(string: service) } @@ -386,7 +386,7 @@ public class AbrevvaBle: RCTEventEmitter { return bleDevice } - private func getScanResultDict( + func getScanResultDict( _ device: BleDevice, _ advertisementData: [String: Any], _ rssi: NSNumber @@ -417,7 +417,7 @@ public class AbrevvaBle: RCTEventEmitter { return data } - private func getManufacturerDataDict(data: Data) -> [String: String] { + func getManufacturerDataDict(data: Data) -> [String: String] { var company = 0 var rest = "" for (index, byte) in data.enumerated() { @@ -432,7 +432,7 @@ public class AbrevvaBle: RCTEventEmitter { return [String(company): rest] } - private func getServiceDataDict(data: [CBUUID: Data]) -> [String: String] { + func getServiceDataDict(data: [CBUUID: Data]) -> [String: String] { var result: [String: String] = [:] for (key, value) in data { result[CBUUIDToString(key)] = dataToString(value) @@ -453,7 +453,7 @@ public class AbrevvaBle: RCTEventEmitter { return reject("setSupportedEvents(): Failed to convert NSDictionary to Swift dictionary", nil, nil) } guard let newEvents = optionsSwift["events"] as? [String] else { - return reject("setSupportedEvents(): characteristic UUID required", nil, nil) + return reject("setSupportedEvents(): provide events as [String]", nil, nil) } self.events = newEvents resolve(nil) From 26f7cf3c3d80b3bb08d4624517274131680b756d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hochst=C3=B6ger=20Matthias?= <116495532+mhochsto@users.noreply.github.com> Date: Mon, 12 Aug 2024 15:05:56 +0200 Subject: [PATCH 09/36] chore: added some testcases for android ble --- .../java/com/exampleapp/AbrevvaBleModule.kt | 43 ++++----- .../test/exampleapp/AbrevvaBleModuleTest.kt | 95 +++++++++++++++++++ .../WriteableMapTestImplementation.kt | 19 +++- .../project.pbxproj | 60 ++++++------ 4 files changed, 165 insertions(+), 52 deletions(-) diff --git a/android/src/main/java/com/exampleapp/AbrevvaBleModule.kt b/android/src/main/java/com/exampleapp/AbrevvaBleModule.kt index 44500e8..a1430cf 100644 --- a/android/src/main/java/com/exampleapp/AbrevvaBleModule.kt +++ b/android/src/main/java/com/exampleapp/AbrevvaBleModule.kt @@ -87,11 +87,11 @@ class AbrevvaBleModule(reactContext: ReactApplicationContext) : private fun runInitialization(options: ReadableMap, promise: Promise) { if (!currentActivity!!.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { - return promise.reject("runInitialization(): BLE is not supported") + return promise.reject(Exception("runInitialization(): BLE is not supported")) } if (!manager.isBleEnabled()) { - return promise.reject("runInitialization(): BLE is not available") + return promise.reject(Exception("runInitialization(): BLE is not available")) } promise.resolve("success") } @@ -121,8 +121,7 @@ class AbrevvaBleModule(reactContext: ReactApplicationContext) : } if (!success) { - promise.reject("startEnabledNotifications(): Failed to set handler") - return + return promise.reject(Exception("startEnabledNotifications(): Failed to set handler")) } promise.resolve("success") } @@ -171,7 +170,7 @@ class AbrevvaBleModule(reactContext: ReactApplicationContext) : if (success) { promise.resolve("success") } else { - promise.reject("requestLEScan(): failed to start") + promise.reject(Exception("requestLEScan(): failed to start")) } }, { result: BleScanResult -> val scanResult = getScanResultFromNordic(result) @@ -204,7 +203,7 @@ class AbrevvaBleModule(reactContext: ReactApplicationContext) : if (success) { promise.resolve("success") } else { - promise.reject("connect(): failed to connect") + promise.reject(Exception("connect(): failed to connect")) } }, timeout) } @@ -218,7 +217,7 @@ class AbrevvaBleModule(reactContext: ReactApplicationContext) : if (success) { promise.resolve("success") } else { - promise.reject("disconnect(): failed to disconnect") + promise.reject(Exception("disconnect(): failed to disconnect")) } } } @@ -233,7 +232,7 @@ class AbrevvaBleModule(reactContext: ReactApplicationContext) : } catch (_:Exception) {} val characteristic = getCharacteristic(options, promise) - ?: return promise.reject("read(): bad characteristic") + ?: return promise.reject(Exception("read(): bad characteristic")) manager.read(deviceId, characteristic.first, characteristic.second, { success: Boolean, data: ByteArray? -> if (success) { @@ -241,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) } @@ -256,9 +255,9 @@ class AbrevvaBleModule(reactContext: ReactApplicationContext) : }catch (_:Exception){} val characteristic = - getCharacteristic(options, promise) ?: return promise.reject("read(): bad characteristic") + getCharacteristic(options, promise) ?: return promise.reject(Exception("read(): bad characteristic")) val value = - options.getString("value") ?: return promise.reject("write(): missing value for write") + options.getString("value") ?: return promise.reject(Exception("write(): missing value for write")) manager.write( deviceId, @@ -269,7 +268,7 @@ class AbrevvaBleModule(reactContext: ReactApplicationContext) : if (success) { promise.resolve("success") } else { - promise.reject("write(): failed to write to device") + promise.reject(Exception("write(): failed to write to device")) } }, timeout @@ -310,7 +309,7 @@ class AbrevvaBleModule(reactContext: ReactApplicationContext) : val deviceId = options.getString("deviceId") ?: "" val characteristic = getCharacteristic(options, promise) - ?: return promise.reject("startNotifications(): bad characteristic") + ?: return promise.reject(Exception("startNotifications(): bad characteristic")) manager.startNotifications( deviceId, @@ -320,7 +319,7 @@ class AbrevvaBleModule(reactContext: ReactApplicationContext) : if (success) { promise.resolve("success") } else { - promise.reject("startNotifications(): failed to set notifications") + promise.reject(Exception("startNotifications(): failed to set notifications")) } }, { data: ByteArray -> val key = @@ -338,13 +337,13 @@ class AbrevvaBleModule(reactContext: ReactApplicationContext) : val deviceId = options.getString("deviceId") ?: "" val characteristic = getCharacteristic(options, promise) - ?: return promise.reject("stopNotifications(): bad characteristic") + ?: return promise.reject(Exception("stopNotifications(): bad characteristic")) manager.stopNotifications(deviceId, characteristic.first, characteristic.second) { success: Boolean -> if (success) { promise.resolve("success") } else { - promise.reject("stopNotifications(): failed to unset notifications") + promise.reject(Exception("stopNotifications(): failed to unset notifications")) } } } @@ -356,12 +355,12 @@ class AbrevvaBleModule(reactContext: ReactApplicationContext) : try { serviceUUID = UUID.fromString(serviceString) } catch (e: IllegalArgumentException) { - promise.reject("getCharacteristic(): invalid service uuid") + promise.reject(Exception("getCharacteristic(): invalid service uuid")) return null } if (serviceUUID == null) { - promise.reject("getCharacteristic(): service uuid required") + promise.reject(Exception("getCharacteristic(): service uuid required")) return null } @@ -371,19 +370,19 @@ class AbrevvaBleModule(reactContext: ReactApplicationContext) : try { characteristicUUID = UUID.fromString(characteristicString) } catch (e: IllegalArgumentException) { - promise.reject("getCharacteristic(): invalid characteristic uuid") + promise.reject(Exception("getCharacteristic(): invalid characteristic uuid")) return null } if (characteristicUUID == null) { - promise.reject("getCharacteristic(): characteristic uuid required") + promise.reject(Exception("getCharacteristic(): characteristic uuid required")) return null } return Pair(serviceUUID, characteristicUUID) } - private fun getBleDeviceFromNordic(result: BleScanResult): ReadableMap { + fun getBleDeviceFromNordic(result: BleScanResult): ReadableMap { val bleDevice = Arguments.createMap() bleDevice.putString("deviceId", result.device.address) @@ -401,7 +400,7 @@ class AbrevvaBleModule(reactContext: ReactApplicationContext) : return bleDevice } - private fun getScanResultFromNordic(result: BleScanResult): ReadableMap { + fun getScanResultFromNordic(result: BleScanResult): ReadableMap { val scanResult = Arguments.createMap() val bleDevice = getBleDeviceFromNordic(result) diff --git a/android/src/main/java/com/test/exampleapp/AbrevvaBleModuleTest.kt b/android/src/main/java/com/test/exampleapp/AbrevvaBleModuleTest.kt index b40993e..24e3408 100644 --- a/android/src/main/java/com/test/exampleapp/AbrevvaBleModuleTest.kt +++ b/android/src/main/java/com/test/exampleapp/AbrevvaBleModuleTest.kt @@ -1,17 +1,34 @@ package com.test.exampleapp +import android.bluetooth.BluetoothManager +import android.content.Context +import android.os.ParcelUuid import com.exampleapp.AbrevvaBleModule +import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.WritableArray +import com.facebook.react.bridge.WritableNativeMap import io.mockk.MockKAnnotations +import io.mockk.every import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.spyk import io.mockk.unmockkAll +import no.nordicsemi.android.common.core.DataByteArray +import no.nordicsemi.android.kotlin.ble.core.ServerDevice +import no.nordicsemi.android.kotlin.ble.core.scanner.BleScanRecord +import no.nordicsemi.android.kotlin.ble.core.scanner.BleScanResult +import no.nordicsemi.android.kotlin.ble.core.scanner.BleScanResultData import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test +import java.util.UUID class AbrevvaBleModuleTest { private lateinit var abrevvaBleModule: AbrevvaBleModule @@ -27,9 +44,16 @@ class AbrevvaBleModuleTest { @MockK(relaxed = true) private lateinit var readableMapMock: ReadableMap + @MockK(relaxed = true) + private lateinit var writeableArrayMock: WritableArray @BeforeEach fun beforeEach(){ MockKAnnotations.init(this) + mockkStatic(Arguments::class) + testMap = WritableMapTestImplementation() + every { Arguments.createMap() } returns testMap + every { Arguments.createArray() } returns writeableArrayMock + every { contextMock.getSystemService(Context.BLUETOOTH_SERVICE) } returns mockk(relaxed = true) abrevvaBleModule = AbrevvaBleModule(contextMock) } @@ -37,4 +61,75 @@ class AbrevvaBleModuleTest { fun afterEach(){ unmockkAll() } + + + @Test + fun `getBleDeviceFromNordic() should save data from BleScanResult in new map`() { + val name = "name" + val address = "deviceAddress" + val bleDevice = WritableMapTestImplementation() + every {Arguments.createMap()} returns bleDevice + val bleScanResult = mockk(relaxed = true) + val device = mockk() + every { bleScanResult.device } returns device + every { device.hasName } returns true + every { device.name } returns name + every { device.address } returns address + every { writeableArrayMock.size() } returns 0 + + abrevvaBleModule.getBleDeviceFromNordic(bleScanResult) + + val ref = WritableMapTestImplementation(mutableMapOf( + "deviceId" to address, + "name" to name, + )) + assert(ref == bleDevice) + } + + @Test + fun `getScanResultFromNordic() should construct ReadableMap from ScanResult`(){ + val name = "name" + val deviceId = "deviceId" + val txPower = 10 + val bleSpy = spyk(AbrevvaBleModule(contextMock)) + val result = mockk() + val data = mockk() + val device = mockk() + val scanRecord = mockk() + val bytes = DataByteArray(byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x07, 0x08, 0x09, 0x10)) + val parcelUuid = mockk(relaxed = true) + val serviceData = mapOf( parcelUuid to DataByteArray(byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x07, 0x08, 0x09, 0x10))) + val bleDevice = WritableMapTestImplementation(mutableMapOf( + "deviceId" to deviceId, + "name" to name + )) + 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 + every { result.device.name } returns "name" + every { data.txPower } returns txPower + every { data.scanRecord } returns scanRecord + every { scanRecord.bytes } returns bytes + every { scanRecord.serviceData } returns serviceData + every { scanRecord.serviceUuids } returns null + every { bleSpy.getBleDeviceFromNordic(any()) } returns bleDevice + every { Arguments.createMap() } returns scanResult andThen manufacturerData andThen serviceDataMap + + bleSpy.getScanResultFromNordic(result) + + val ref = WritableMapTestImplementation(mutableMapOf( + "device" to bleDevice, + "localName" to name, + "txPower" to txPower, + "manufacturerData" to WritableMapTestImplementation(mutableMapOf("2055" to "09 10")), + "rawAdvertisement" to "(0x) 01:02:03:04:05:07:08:09:10", + "uuids" to writeableArrayMock, + "serviceData" to serviceDataMap + )) + assert(ref == scanResult) + + } } \ No newline at end of file diff --git a/android/src/main/java/com/test/exampleapp/WriteableMapTestImplementation.kt b/android/src/main/java/com/test/exampleapp/WriteableMapTestImplementation.kt index 26c5dbe..dca44f4 100644 --- a/android/src/main/java/com/test/exampleapp/WriteableMapTestImplementation.kt +++ b/android/src/main/java/com/test/exampleapp/WriteableMapTestImplementation.kt @@ -7,10 +7,25 @@ import com.facebook.react.bridge.ReadableMapKeySetIterator import com.facebook.react.bridge.ReadableType import com.facebook.react.bridge.WritableMap import io.mockk.mockk +import kotlin.reflect.typeOf -class WritableMapTestImplementation : WritableMap { - private val map = mutableMapOf() +class WritableMapTestImplementation(args: MutableMap = mutableMapOf()) : WritableMap { + private var map = mutableMapOf() + init { + map = args + } + + fun print() { + map.forEach { key, value -> + println("${key}: ${value}") + } + } + override fun equals(other: Any?): Boolean { + return map == (other as WritableMapTestImplementation).getMap() + } + fun getSize(): Int {return map.size} + fun getMap(): MutableMap { return map } override fun hasKey(p0: String): Boolean { return map.containsKey(p0) } override fun isNull(p0: String): Boolean { return map[p0] == null } override fun getBoolean(p0: String): Boolean { return map[p0] as Boolean } diff --git a/example/ios/ExampleAppExample.xcodeproj/project.pbxproj b/example/ios/ExampleAppExample.xcodeproj/project.pbxproj index fa2b377..8776832 100644 --- a/example/ios/ExampleAppExample.xcodeproj/project.pbxproj +++ b/example/ios/ExampleAppExample.xcodeproj/project.pbxproj @@ -11,8 +11,9 @@ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 39FBC3E02C64DE9800BEE979 /* AbrevvaCryptoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39FBC3DF2C64DE9800BEE979 /* AbrevvaCryptoTests.swift */; }; - 40DC940EDA1FC456936AAD80 /* Pods_ExampleAppExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 054ABA2C59F9A4523AE0B026 /* Pods_ExampleAppExample.framework */; }; - 4B22F003BE70442FE2D4C58D /* Pods_ExampleAppExample_ExampleAppExampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F5FB403992171CD0BC089A5D /* Pods_ExampleAppExample_ExampleAppExampleTests.framework */; }; + 39FBC3E72C65FC2000BEE979 /* AbrevvaBleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39FBC3E62C65FC2000BEE979 /* AbrevvaBleTests.swift */; }; + 445E0A07C73265779D71B028 /* Pods_ExampleAppExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 19F4E3BE36375229A55CE8AC /* Pods_ExampleAppExample.framework */; }; + 5139CB327BAC956E69597EC7 /* Pods_ExampleAppExample_ExampleAppExampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 80D61586D51FD1C66C1D58E3 /* Pods_ExampleAppExample_ExampleAppExampleTests.framework */; }; 7C2CCBD275D987DDA9C659B6 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 61DF2990903C4888EBB0D63C /* PrivacyInfo.xcprivacy */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ @@ -28,7 +29,6 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 054ABA2C59F9A4523AE0B026 /* Pods_ExampleAppExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ExampleAppExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07F961A680F5B00A75B9A /* ExampleAppExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ExampleAppExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = ExampleAppExample/AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = ExampleAppExample/AppDelegate.mm; sourceTree = ""; }; @@ -36,16 +36,18 @@ 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ExampleAppExample/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = ExampleAppExample/main.m; sourceTree = ""; }; 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = ExampleAppExample/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 19F4E3BE36375229A55CE8AC /* Pods_ExampleAppExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ExampleAppExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 20389187CDDBF5F10F171A64 /* Pods-ExampleAppExample-ExampleAppExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleAppExample-ExampleAppExampleTests.debug.xcconfig"; path = "Target Support Files/Pods-ExampleAppExample-ExampleAppExampleTests/Pods-ExampleAppExample-ExampleAppExampleTests.debug.xcconfig"; sourceTree = ""; }; 39FBC3DD2C64DE9800BEE979 /* ExampleAppExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleAppExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 39FBC3DF2C64DE9800BEE979 /* AbrevvaCryptoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbrevvaCryptoTests.swift; sourceTree = ""; }; - 51FB5F9190B0CE40B53B6AF3 /* Pods-ExampleAppExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleAppExample.debug.xcconfig"; path = "Target Support Files/Pods-ExampleAppExample/Pods-ExampleAppExample.debug.xcconfig"; sourceTree = ""; }; + 39FBC3E62C65FC2000BEE979 /* AbrevvaBleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbrevvaBleTests.swift; sourceTree = ""; }; 61DF2990903C4888EBB0D63C /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = ExampleAppExample/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 68BC1AF6EDA113C27D65AC10 /* Pods-ExampleAppExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleAppExample.release.xcconfig"; path = "Target Support Files/Pods-ExampleAppExample/Pods-ExampleAppExample.release.xcconfig"; sourceTree = ""; }; + 80D61586D51FD1C66C1D58E3 /* Pods_ExampleAppExample_ExampleAppExampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ExampleAppExample_ExampleAppExampleTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = ExampleAppExample/LaunchScreen.storyboard; sourceTree = ""; }; - A7F75B2DA7ADD5AEF3E64FA3 /* Pods-ExampleAppExample-ExampleAppExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleAppExample-ExampleAppExampleTests.debug.xcconfig"; path = "Target Support Files/Pods-ExampleAppExample-ExampleAppExampleTests/Pods-ExampleAppExample-ExampleAppExampleTests.debug.xcconfig"; sourceTree = ""; }; - C091A222493664870FD85204 /* Pods-ExampleAppExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleAppExample.release.xcconfig"; path = "Target Support Files/Pods-ExampleAppExample/Pods-ExampleAppExample.release.xcconfig"; sourceTree = ""; }; + 88BFDD102C005583A3DDE543 /* Pods-ExampleAppExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleAppExample.debug.xcconfig"; path = "Target Support Files/Pods-ExampleAppExample/Pods-ExampleAppExample.debug.xcconfig"; sourceTree = ""; }; + 9A974A4BFAF3C7FA0946ABAC /* Pods-ExampleAppExample-ExampleAppExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleAppExample-ExampleAppExampleTests.release.xcconfig"; path = "Target Support Files/Pods-ExampleAppExample-ExampleAppExampleTests/Pods-ExampleAppExample-ExampleAppExampleTests.release.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; - F5FB403992171CD0BC089A5D /* Pods_ExampleAppExample_ExampleAppExampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ExampleAppExample_ExampleAppExampleTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F8CC9DCE2BD2F85429FEF3A8 /* Pods-ExampleAppExample-ExampleAppExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleAppExample-ExampleAppExampleTests.release.xcconfig"; path = "Target Support Files/Pods-ExampleAppExample-ExampleAppExampleTests/Pods-ExampleAppExample-ExampleAppExampleTests.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -53,7 +55,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 40DC940EDA1FC456936AAD80 /* Pods_ExampleAppExample.framework in Frameworks */, + 445E0A07C73265779D71B028 /* Pods_ExampleAppExample.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -61,7 +63,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 4B22F003BE70442FE2D4C58D /* Pods_ExampleAppExample_ExampleAppExampleTests.framework in Frameworks */, + 5139CB327BAC956E69597EC7 /* Pods_ExampleAppExample_ExampleAppExampleTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -87,8 +89,8 @@ isa = PBXGroup; children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - 054ABA2C59F9A4523AE0B026 /* Pods_ExampleAppExample.framework */, - F5FB403992171CD0BC089A5D /* Pods_ExampleAppExample_ExampleAppExampleTests.framework */, + 19F4E3BE36375229A55CE8AC /* Pods_ExampleAppExample.framework */, + 80D61586D51FD1C66C1D58E3 /* Pods_ExampleAppExample_ExampleAppExampleTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -97,6 +99,7 @@ isa = PBXGroup; children = ( 39FBC3DF2C64DE9800BEE979 /* AbrevvaCryptoTests.swift */, + 39FBC3E62C65FC2000BEE979 /* AbrevvaBleTests.swift */, ); path = ExampleAppExampleTests; sourceTree = ""; @@ -135,10 +138,10 @@ BBD78D7AC51CEA395F1C20DB /* Pods */ = { isa = PBXGroup; children = ( - 51FB5F9190B0CE40B53B6AF3 /* Pods-ExampleAppExample.debug.xcconfig */, - C091A222493664870FD85204 /* Pods-ExampleAppExample.release.xcconfig */, - A7F75B2DA7ADD5AEF3E64FA3 /* Pods-ExampleAppExample-ExampleAppExampleTests.debug.xcconfig */, - F8CC9DCE2BD2F85429FEF3A8 /* Pods-ExampleAppExample-ExampleAppExampleTests.release.xcconfig */, + 88BFDD102C005583A3DDE543 /* Pods-ExampleAppExample.debug.xcconfig */, + 68BC1AF6EDA113C27D65AC10 /* Pods-ExampleAppExample.release.xcconfig */, + 20389187CDDBF5F10F171A64 /* Pods-ExampleAppExample-ExampleAppExampleTests.debug.xcconfig */, + 9A974A4BFAF3C7FA0946ABAC /* Pods-ExampleAppExample-ExampleAppExampleTests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -150,12 +153,12 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "ExampleAppExample" */; buildPhases = ( - 77B7F422842E0B98DF130554 /* [CP] Check Pods Manifest.lock */, + 88569654A39B7F4943A4ECB5 /* [CP] Check Pods Manifest.lock */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - 274419FB47E6F003B3C0EFB2 /* [CP] Embed Pods Frameworks */, + 238F5AFED192D4D7BBC6F824 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -170,11 +173,11 @@ isa = PBXNativeTarget; buildConfigurationList = 39FBC3E32C64DE9800BEE979 /* Build configuration list for PBXNativeTarget "ExampleAppExampleTests" */; buildPhases = ( - 52DAE33E8CC4311A722DE218 /* [CP] Check Pods Manifest.lock */, + 59D3BAD406D244112EDD8B5C /* [CP] Check Pods Manifest.lock */, 39FBC3D92C64DE9800BEE979 /* Sources */, 39FBC3DA2C64DE9800BEE979 /* Frameworks */, 39FBC3DB2C64DE9800BEE979 /* Resources */, - 839F032038E9AA227127E1EC /* [CP] Embed Pods Frameworks */, + 92BE6BC0CFAA17A0C2386446 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -259,7 +262,7 @@ shellPath = /bin/sh; shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; }; - 274419FB47E6F003B3C0EFB2 /* [CP] Embed Pods Frameworks */ = { + 238F5AFED192D4D7BBC6F824 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -276,7 +279,7 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ExampleAppExample/Pods-ExampleAppExample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 52DAE33E8CC4311A722DE218 /* [CP] Check Pods Manifest.lock */ = { + 59D3BAD406D244112EDD8B5C /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -298,7 +301,7 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 77B7F422842E0B98DF130554 /* [CP] Check Pods Manifest.lock */ = { + 88569654A39B7F4943A4ECB5 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -320,7 +323,7 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 839F032038E9AA227127E1EC /* [CP] Embed Pods Frameworks */ = { + 92BE6BC0CFAA17A0C2386446 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -354,6 +357,7 @@ buildActionMask = 2147483647; files = ( 39FBC3E02C64DE9800BEE979 /* AbrevvaCryptoTests.swift in Sources */, + 39FBC3E72C65FC2000BEE979 /* AbrevvaBleTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -370,7 +374,7 @@ /* Begin XCBuildConfiguration section */ 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 51FB5F9190B0CE40B53B6AF3 /* Pods-ExampleAppExample.debug.xcconfig */; + baseConfigurationReference = 88BFDD102C005583A3DDE543 /* Pods-ExampleAppExample.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -398,7 +402,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C091A222493664870FD85204 /* Pods-ExampleAppExample.release.xcconfig */; + baseConfigurationReference = 68BC1AF6EDA113C27D65AC10 /* Pods-ExampleAppExample.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -424,7 +428,7 @@ }; 39FBC3E42C64DE9800BEE979 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A7F75B2DA7ADD5AEF3E64FA3 /* Pods-ExampleAppExample-ExampleAppExampleTests.debug.xcconfig */; + baseConfigurationReference = 20389187CDDBF5F10F171A64 /* Pods-ExampleAppExample-ExampleAppExampleTests.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; @@ -457,7 +461,7 @@ }; 39FBC3E52C64DE9800BEE979 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = F8CC9DCE2BD2F85429FEF3A8 /* Pods-ExampleAppExample-ExampleAppExampleTests.release.xcconfig */; + baseConfigurationReference = 9A974A4BFAF3C7FA0946ABAC /* Pods-ExampleAppExample-ExampleAppExampleTests.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; From 0ab52df67d193a8deef0bbb7d639a72e5d9f1fa9 Mon Sep 17 00:00:00 2001 From: axi92 Date: Mon, 12 Aug 2024 18:36:14 +0200 Subject: [PATCH 10/36] chore: update README --- README.md | 51 +++++++++++++++++++++------------------------------ 1 file changed, 21 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index ffd5899..3fa7dfb 100644 --- a/README.md +++ b/README.md @@ -18,28 +18,23 @@ The EVVA React-Native Module is a collection of tools to work with electronical ## Requirements - Java 17+ - Android SDK - Xcode 15+ - react-native < 0.74.3 - iOS 15.0+ - Android 10+ (API level 29) +- react-native < 0.74.3 +- Java 17+ (Android) +- Android SDK (Android) +- Android 10+ (API level 29) (Android) +- Xcode 15+ (iOS) +- iOS 15.0+ (iOS) ## Installation ``` yarn add ``` -### Setup Github auth to load package - -Create a copy of [local.properties.template](example/android/local.properties.template) and rename it to local.properties in the same directory. Paste your github username and [classic PAT](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) - ### IOS Add the following to your Podfile: ```ruby -source 'https://github.com/evva-sfw/abrevva-sdk-ios-pod-specs.git' source 'https://cdn.cocoapods.org/' ``` @@ -49,12 +44,10 @@ then execute `pod install` inside of your projects ios/ folder. Add this to your `build.gradle` file: +[![Maven Central Version](https://img.shields.io/maven-central/v/com.evva.xesar/abrevva-sdk-android)](https://central.sonatype.com/artifact/com.evva.xesar/abrevva-sdk-android) + + ```ruby -repositories { - maven { - url = uri("https://maven.pkg.github.com/evva-sfw/abrevva-sdk-android") - } -} ... dependencies { implementation group: "com.evva.xesar", name: "abrevva-sdk-android", version: "1.0.19" <-- change to latest version. @@ -63,7 +56,7 @@ dependencies { Add Permissions to your `Manifest.xml` file as needed. -```ruby +```xml @@ -76,7 +69,7 @@ Add Permissions to your `Manifest.xml` file as needed. To start off first import `AbrevvaBle` from this module ```typescript -import {AbrevvaBle} from 'react-natice-example-app'; +import {AbrevvaBle} from '@evva-sfw/abrevva-react-native'; async function scanForBleDevices(androidNeverForLocation: Boolean, timeout: Number){ const androidNeverForLocation = true; @@ -103,11 +96,9 @@ async function scanForBleDevices(androidNeverForLocation: Boolean, timeout: Numb With the signalize method you can localize EVVA components. On a successful signalization the component will emit a melody indicating its location. ```typescript -AbrevvaBle.signalize( - deviceID, - () => { +AbrevvaBle.signalize({ deviceId: 'deviceId' }() => { console.log(`Signalized /w success=${it}`) - } +} ); ``` ### Perform disengage for EVVA components @@ -115,12 +106,12 @@ AbrevvaBle.signalize( 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 -AbrevvaBle.disengage( - mobileId: '', - mobileDeviceKey:: '', - mobileGroupId: '', - mobileAccessData: '', - isPermanentRelease: '', - timeout: 10_000 -); +AbrevvaBle.disengage({ + deviceId: 'deviceId', + mobileId: 'mobileId', + mobileDeviceKey: 'mobileDeviceKey', + mobileGroupId: 'mobileGroupId', + mobileAccessData: 'mobileAccessData', + isPermanentRelease: false, +}); ``` From b8a552145b47f60764fedff50a346b5584dceb48 Mon Sep 17 00:00:00 2001 From: mhochsto <116495532+mhochsto@users.noreply.github.com> Date: Tue, 13 Aug 2024 08:19:30 +0200 Subject: [PATCH 11/36] Update README.md We don"t need any sources anymore :) --- README.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index 3fa7dfb..2ef166e 100644 --- a/README.md +++ b/README.md @@ -32,13 +32,7 @@ yarn add ### IOS -Add the following to your Podfile: - -```ruby -source 'https://cdn.cocoapods.org/' -``` - -then execute `pod install` inside of your projects ios/ folder. + execute `pod install` inside of your projects ios/ folder. ### Android From b18baa461131a3b41a182472ff9e370d5281b5b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hochst=C3=B6ger=20Matthias?= <116495532+mhochsto@users.noreply.github.com> Date: Tue, 13 Aug 2024 15:20:17 +0200 Subject: [PATCH 12/36] chore: changed function names to non-backtick-names --- .../test/exampleapp/AbrevvaBleModuleTest.kt | 37 ++++++++++++++++- .../exampleapp/AbrevvaCryptoModuleTest.kt | 40 +++++++++---------- 2 files changed, 55 insertions(+), 22 deletions(-) diff --git a/android/src/main/java/com/test/exampleapp/AbrevvaBleModuleTest.kt b/android/src/main/java/com/test/exampleapp/AbrevvaBleModuleTest.kt index 24e3408..a5c78bd 100644 --- a/android/src/main/java/com/test/exampleapp/AbrevvaBleModuleTest.kt +++ b/android/src/main/java/com/test/exampleapp/AbrevvaBleModuleTest.kt @@ -1,8 +1,11 @@ package com.test.exampleapp +import android.annotation.SuppressLint import android.bluetooth.BluetoothManager import android.content.Context import android.os.ParcelUuid +import android.util.Log +import com.evva.xesar.abrevva.ble.BleManager import com.exampleapp.AbrevvaBleModule import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Promise @@ -15,9 +18,12 @@ import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.mockk +import io.mockk.mockkConstructor import io.mockk.mockkStatic +import io.mockk.slot import io.mockk.spyk import io.mockk.unmockkAll +import io.mockk.verify import no.nordicsemi.android.common.core.DataByteArray import no.nordicsemi.android.kotlin.ble.core.ServerDevice import no.nordicsemi.android.kotlin.ble.core.scanner.BleScanRecord @@ -28,6 +34,7 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test +import org.junit.runner.RunWith import java.util.UUID class AbrevvaBleModuleTest { @@ -63,8 +70,34 @@ class AbrevvaBleModuleTest { } + @SuppressLint("MissingPermission") @Test - fun `getBleDeviceFromNordic() should save data from BleScanResult in new map`() { + fun startNotifications_notification_recieved_closure_should_generate_key_correctly(){ + val callbackSlot = slot<(data: ByteArray) -> Unit>() + val keySlot = slot() + val successCallback = slot<(success: Boolean) -> Unit>() + val deviceId = "e7f635ac-27ae-4bc6-a5ca-3f07872f49e9" + val service = "01a660db-5dbd-488a-bd01-b42449817c82" + val characteristic = "d0d71305-05b2-4add-9ea9-bcd1cc82211c" + val options = WritableMapTestImplementation(mutableMapOf( + "deviceId" to deviceId, + "service" to service, + "characteristic" to characteristic + )) + mockkConstructor(BleManager::class) + every {Arguments.createMap()} returns options + every { anyConstructed().startNotifications(any(), any(), any(), capture(successCallback), capture(callbackSlot)) } returns Unit + every { anyConstructed().isBleEnabled() } returns false + every { contextMock.emitDeviceEvent(capture(keySlot), any()) } returns Unit + abrevvaBleModule.startNotifications(options, promiseMock) + + callbackSlot.captured(ByteArray(1)) + + assert(keySlot.captured == "notification|e7f635ac-27ae-4bc6-a5ca-3f07872f49e9|01a660db-5dbd-488a-bd01-b42449817c82|d0d71305-05b2-4add-9ea9-bcd1cc82211c") + } + + @Test + fun getBleDeviceFromNordic_should_save_data_from_BleScanResult_in_new_map() { val name = "name" val address = "deviceAddress" val bleDevice = WritableMapTestImplementation() @@ -87,7 +120,7 @@ class AbrevvaBleModuleTest { } @Test - fun `getScanResultFromNordic() should construct ReadableMap from ScanResult`(){ + fun getScanResultFromNordic_should_construct_ReadableMap_from_ScanResult(){ val name = "name" val deviceId = "deviceId" val txPower = 10 diff --git a/android/src/main/java/com/test/exampleapp/AbrevvaCryptoModuleTest.kt b/android/src/main/java/com/test/exampleapp/AbrevvaCryptoModuleTest.kt index 22d410f..eec2f7a 100644 --- a/android/src/main/java/com/test/exampleapp/AbrevvaCryptoModuleTest.kt +++ b/android/src/main/java/com/test/exampleapp/AbrevvaCryptoModuleTest.kt @@ -77,7 +77,7 @@ class AbrevvaCryptoModuleTest { @DisplayName("encrypt()") inner class EncryptTests { @Test - fun `should reject if ct is empty`() { + fun should_reject_if_ct_is_empty() { every { Hex.decode(any()) } answers { callOriginal()} every { AesCCM.encrypt(any(), any(), any(), any(), any()) } returns ByteArray(0) @@ -87,7 +87,7 @@ class AbrevvaCryptoModuleTest { } @Test - fun `should resolve if ct is not empty`() { + fun should_resolve_if_ct_is_not_empty() { every { AesCCM.encrypt(any(), any(), any(), any(), any()) } returns ByteArray(10) abrevvaCryptoModule.encrypt(readableMapMock, promiseMock) @@ -100,7 +100,7 @@ class AbrevvaCryptoModuleTest { @DisplayName("decrypt()") inner class DecryptTests { @Test - fun `should reject if pt is empty`() { + fun should_reject_if_pt_is_empty() { every { AesCCM.decrypt(any(), any(), any(), any(), any()) } returns ByteArray(0) abrevvaCryptoModule.decrypt(readableMapMock, promiseMock) @@ -109,7 +109,7 @@ class AbrevvaCryptoModuleTest { } @Test - fun `should resolve if pt is not empty`() { + fun should_resolve_if_pt_is_not_empty() { every { AesCCM.decrypt(any(), any(), any(), any(), any()) } returns ByteArray(10) abrevvaCryptoModule.decrypt(readableMapMock, promiseMock) @@ -122,7 +122,7 @@ class AbrevvaCryptoModuleTest { @DisplayName("generateKeyPair()") inner class GenerateKeyPairTests { @Test - fun `should resolve if keys where generated successfully`() { + fun should_resolve_if_keys_where_generated_successfully() { every { X25519Wrapper.generateKeyPair() } returns mockk(relaxed = true) abrevvaCryptoModule.generateKeyPair(promiseMock) @@ -131,7 +131,7 @@ class AbrevvaCryptoModuleTest { } @Test - fun `should reject if keys cannot be generated`() { + fun should_reject_if_keys_cannot_be_generated() { every { X25519Wrapper.generateKeyPair() } throws Exception("generateKeyPair() Fail Exception") abrevvaCryptoModule.generateKeyPair(promiseMock) @@ -146,7 +146,7 @@ class AbrevvaCryptoModuleTest { inner class EncryptFileTests { @ParameterizedTest(name = "encryptFile({0}, {1}, {2}) should reject") @MethodSource("parameterizedArgs_encrypt") - fun `encryptFile() should reject if any Param is missing`( + fun encryptFile_should_reject_if_any_Param_is_missing( ctPath: String?, ptPath: String?, sharedSecret: String? @@ -171,7 +171,7 @@ class AbrevvaCryptoModuleTest { } @Test - fun `should resolve if args are valid and file could be encrypted`() { + fun should_resolve_if_args_are_valid_and_file_could_be_encrypted() { val mapMock = mockk(relaxed = true) every { mapMock.getString(any()) } returns "notEmpty" every { AesGCM.encryptFile(any(), any(), any()) } returns true @@ -182,7 +182,7 @@ class AbrevvaCryptoModuleTest { } @Test - fun `should reject if args are valid but encryption fails`() { + fun should_reject_if_args_are_valid_but_encryption_fails() { val mapMock = mockk(relaxed = true) every { mapMock.getString(any()) } returns "notEmpty" every { @@ -205,7 +205,7 @@ class AbrevvaCryptoModuleTest { inner class DecryptFileTests { @ParameterizedTest(name = "empty args should be rejected") @MethodSource("parameterizedArgs_decrypt") - fun `should reject if any Param is empty`( + fun should_reject_if_any_Param_is_empty( ctPath: String?, ptPath: String?, sharedSecret: String? @@ -231,7 +231,7 @@ class AbrevvaCryptoModuleTest { } @Test - fun `should resolve if args are valid and file could be encrypted`() { + fun should_resolve_if_args_are_valid_and_file_could_be_encrypted() { val mapMock = mockk(relaxed = true) every { mapMock.getString(any()) } returns "notEmpty" every { AesGCM.decryptFile(any(), any(), any()) } returns true @@ -242,7 +242,7 @@ class AbrevvaCryptoModuleTest { } @Test - fun `should reject if encryption fails`() { + fun should_reject_if_encryption_fails() { val mapMock = mockk(relaxed = true) every { mapMock.getString(any()) } returns "notEmpty" every { @@ -269,7 +269,7 @@ class AbrevvaCryptoModuleTest { inner class DecryptFileFromURL_ParameterizedTest { @ParameterizedTest @MethodSource("parameterizedArgs_decryptFileFromURL") - fun `should reject if any Param is empty`( + fun should_reject_if_any_Param_is_empty( sharedSecret: String?, url: String?, ptPath: String? @@ -296,7 +296,7 @@ class AbrevvaCryptoModuleTest { } @Test - fun `decryptFileFromURL() should reject if ctPath-File is not accessible`() { + fun decryptFileFromURL_should_reject_if_ctPathFile_is_not_accessible() { val mockMap = mockk(relaxed = true) val moduleSpy = spyk(AbrevvaCryptoModule(contextMock)) every { mockMap.getString(any()) } returns "notEmpty" @@ -308,7 +308,7 @@ class AbrevvaCryptoModuleTest { } @Test - fun `decryptFileFromURL() should reject if decode fails`() { + fun decryptFileFromURL_should_reject_if_decode_fails() { val mockMap = mockk(relaxed = true) val moduleSpy = spyk(AbrevvaCryptoModule(contextMock)) every { mockMap.getString(any()) } returns "notEmpty" @@ -321,7 +321,7 @@ class AbrevvaCryptoModuleTest { } @Test - fun `decryptFileFromURL() should resolve if everything works as intended`() { + fun decryptFileFromURL_should_resolve_if_everything_works_as_intended() { val mockMap = mockk(relaxed = true) val moduleSpy = spyk(AbrevvaCryptoModule(contextMock)) every { mockMap.getString(any()) } returns "notEmpty" @@ -339,7 +339,7 @@ class AbrevvaCryptoModuleTest { inner class RandomTests { @ParameterizedTest(name = "random(numBytes: {0}) resolved String size should be {1}") @CsvSource("2,4", "4,8", "7,14") - fun `should return random bytes n number of bytes if successful`( + fun should_return_random_bytes_n_number_of_bytes_if_successful( numBytes: Int, expectedStrLen: Int ) { @@ -352,7 +352,7 @@ class AbrevvaCryptoModuleTest { } @Test - fun `should reject if bytes cannot be generated`(){ + fun should_reject_if_bytes_cannot_be_generated(){ every { SimpleSecureRandom.getSecureRandomBytes(any()) } returns ByteArray(0) testMap.putInt("numBytes", 10) @@ -366,7 +366,7 @@ class AbrevvaCryptoModuleTest { inner class DeriveTests { @Test - fun `should resolve if successful`() { + fun should_resolve_if_successful() { testMap.putInt("length", 0) every { HKDF.derive(any(), any(), any(), any()) } returns ByteArray(0) @@ -375,7 +375,7 @@ class AbrevvaCryptoModuleTest { verify { promiseMock.reject(any()) } } @Test - fun `should reject if unsuccessful`() { + fun should_reject_if_unsuccessful() { testMap.putInt("length", 10) every { HKDF.derive(any(), any(), any(), any()) } returns ByteArray(10) abrevvaCryptoModule.derive(testMap, promiseMock) From e0c8495ba1d556bb726b7958d2c72bccdb13c2f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hochst=C3=B6ger=20Matthias?= <116495532+mhochsto@users.noreply.github.com> Date: Tue, 13 Aug 2024 16:19:53 +0200 Subject: [PATCH 13/36] chore: resolved issues from previous merge --- .../test/exampleapp/AbrevvaBleModuleTest.kt | 168 -------- .../exampleapp/AbrevvaCryptoModuleTest.kt | 386 ------------------ .../WriteableMapTestImplementation.kt | 59 --- .../reactnative/AbrevvaBleModuleTest.kt | 162 +++++++- .../WriteableMapTestImplementation.kt | 27 +- example/android/app/build.gradle | 2 +- 6 files changed, 186 insertions(+), 618 deletions(-) delete mode 100644 android/src/main/java/com/test/exampleapp/AbrevvaBleModuleTest.kt delete mode 100644 android/src/main/java/com/test/exampleapp/AbrevvaCryptoModuleTest.kt delete mode 100644 android/src/main/java/com/test/exampleapp/WriteableMapTestImplementation.kt diff --git a/android/src/main/java/com/test/exampleapp/AbrevvaBleModuleTest.kt b/android/src/main/java/com/test/exampleapp/AbrevvaBleModuleTest.kt deleted file mode 100644 index a5c78bd..0000000 --- a/android/src/main/java/com/test/exampleapp/AbrevvaBleModuleTest.kt +++ /dev/null @@ -1,168 +0,0 @@ -package com.test.exampleapp - -import android.annotation.SuppressLint -import android.bluetooth.BluetoothManager -import android.content.Context -import android.os.ParcelUuid -import android.util.Log -import com.evva.xesar.abrevva.ble.BleManager -import com.exampleapp.AbrevvaBleModule -import com.facebook.react.bridge.Arguments -import com.facebook.react.bridge.Promise -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.bridge.ReadableArray -import com.facebook.react.bridge.ReadableMap -import com.facebook.react.bridge.WritableArray -import com.facebook.react.bridge.WritableNativeMap -import io.mockk.MockKAnnotations -import io.mockk.every -import io.mockk.impl.annotations.MockK -import io.mockk.mockk -import io.mockk.mockkConstructor -import io.mockk.mockkStatic -import io.mockk.slot -import io.mockk.spyk -import io.mockk.unmockkAll -import io.mockk.verify -import no.nordicsemi.android.common.core.DataByteArray -import no.nordicsemi.android.kotlin.ble.core.ServerDevice -import no.nordicsemi.android.kotlin.ble.core.scanner.BleScanRecord -import no.nordicsemi.android.kotlin.ble.core.scanner.BleScanResult -import no.nordicsemi.android.kotlin.ble.core.scanner.BleScanResultData -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test -import org.junit.runner.RunWith -import java.util.UUID - -class AbrevvaBleModuleTest { - private lateinit var abrevvaBleModule: AbrevvaBleModule - - private lateinit var testMap: WritableMapTestImplementation - - @MockK(relaxed = true) - private lateinit var contextMock: ReactApplicationContext - - @MockK(relaxed = true) - private lateinit var promiseMock: Promise - - @MockK(relaxed = true) - private lateinit var readableMapMock: ReadableMap - - @MockK(relaxed = true) - private lateinit var writeableArrayMock: WritableArray - @BeforeEach - fun beforeEach(){ - MockKAnnotations.init(this) - mockkStatic(Arguments::class) - testMap = WritableMapTestImplementation() - every { Arguments.createMap() } returns testMap - every { Arguments.createArray() } returns writeableArrayMock - every { contextMock.getSystemService(Context.BLUETOOTH_SERVICE) } returns mockk(relaxed = true) - abrevvaBleModule = AbrevvaBleModule(contextMock) - } - - @AfterEach - fun afterEach(){ - unmockkAll() - } - - - @SuppressLint("MissingPermission") - @Test - fun startNotifications_notification_recieved_closure_should_generate_key_correctly(){ - val callbackSlot = slot<(data: ByteArray) -> Unit>() - val keySlot = slot() - val successCallback = slot<(success: Boolean) -> Unit>() - val deviceId = "e7f635ac-27ae-4bc6-a5ca-3f07872f49e9" - val service = "01a660db-5dbd-488a-bd01-b42449817c82" - val characteristic = "d0d71305-05b2-4add-9ea9-bcd1cc82211c" - val options = WritableMapTestImplementation(mutableMapOf( - "deviceId" to deviceId, - "service" to service, - "characteristic" to characteristic - )) - mockkConstructor(BleManager::class) - every {Arguments.createMap()} returns options - every { anyConstructed().startNotifications(any(), any(), any(), capture(successCallback), capture(callbackSlot)) } returns Unit - every { anyConstructed().isBleEnabled() } returns false - every { contextMock.emitDeviceEvent(capture(keySlot), any()) } returns Unit - abrevvaBleModule.startNotifications(options, promiseMock) - - callbackSlot.captured(ByteArray(1)) - - assert(keySlot.captured == "notification|e7f635ac-27ae-4bc6-a5ca-3f07872f49e9|01a660db-5dbd-488a-bd01-b42449817c82|d0d71305-05b2-4add-9ea9-bcd1cc82211c") - } - - @Test - fun getBleDeviceFromNordic_should_save_data_from_BleScanResult_in_new_map() { - val name = "name" - val address = "deviceAddress" - val bleDevice = WritableMapTestImplementation() - every {Arguments.createMap()} returns bleDevice - val bleScanResult = mockk(relaxed = true) - val device = mockk() - every { bleScanResult.device } returns device - every { device.hasName } returns true - every { device.name } returns name - every { device.address } returns address - every { writeableArrayMock.size() } returns 0 - - abrevvaBleModule.getBleDeviceFromNordic(bleScanResult) - - val ref = WritableMapTestImplementation(mutableMapOf( - "deviceId" to address, - "name" to name, - )) - assert(ref == bleDevice) - } - - @Test - fun getScanResultFromNordic_should_construct_ReadableMap_from_ScanResult(){ - val name = "name" - val deviceId = "deviceId" - val txPower = 10 - val bleSpy = spyk(AbrevvaBleModule(contextMock)) - val result = mockk() - val data = mockk() - val device = mockk() - val scanRecord = mockk() - val bytes = DataByteArray(byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x07, 0x08, 0x09, 0x10)) - val parcelUuid = mockk(relaxed = true) - val serviceData = mapOf( parcelUuid to DataByteArray(byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x07, 0x08, 0x09, 0x10))) - val bleDevice = WritableMapTestImplementation(mutableMapOf( - "deviceId" to deviceId, - "name" to name - )) - 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 - every { result.device.name } returns "name" - every { data.txPower } returns txPower - every { data.scanRecord } returns scanRecord - every { scanRecord.bytes } returns bytes - every { scanRecord.serviceData } returns serviceData - every { scanRecord.serviceUuids } returns null - every { bleSpy.getBleDeviceFromNordic(any()) } returns bleDevice - every { Arguments.createMap() } returns scanResult andThen manufacturerData andThen serviceDataMap - - bleSpy.getScanResultFromNordic(result) - - val ref = WritableMapTestImplementation(mutableMapOf( - "device" to bleDevice, - "localName" to name, - "txPower" to txPower, - "manufacturerData" to WritableMapTestImplementation(mutableMapOf("2055" to "09 10")), - "rawAdvertisement" to "(0x) 01:02:03:04:05:07:08:09:10", - "uuids" to writeableArrayMock, - "serviceData" to serviceDataMap - )) - assert(ref == scanResult) - - } -} \ No newline at end of file diff --git a/android/src/main/java/com/test/exampleapp/AbrevvaCryptoModuleTest.kt b/android/src/main/java/com/test/exampleapp/AbrevvaCryptoModuleTest.kt deleted file mode 100644 index eec2f7a..0000000 --- a/android/src/main/java/com/test/exampleapp/AbrevvaCryptoModuleTest.kt +++ /dev/null @@ -1,386 +0,0 @@ -package com.test.exampleapp - -import android.graphics.Color -import com.evva.xesar.abrevva.crypto.AesCCM -import com.evva.xesar.abrevva.crypto.AesGCM -import com.evva.xesar.abrevva.crypto.HKDF -import com.evva.xesar.abrevva.crypto.SimpleSecureRandom -import com.evva.xesar.abrevva.crypto.X25519Wrapper -import com.exampleapp.AbrevvaCryptoModule -import com.facebook.react.bridge.Arguments -import com.facebook.react.bridge.Dynamic -import com.facebook.react.bridge.Promise -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.bridge.ReadableArray -import com.facebook.react.bridge.ReadableMap -import com.facebook.react.bridge.ReadableMapKeySetIterator -import com.facebook.react.bridge.ReadableType -import com.facebook.react.bridge.WritableMap -import io.mockk.MockKAnnotations -import io.mockk.every -import io.mockk.impl.annotations.MockK -import io.mockk.mockk -import io.mockk.mockkObject -import io.mockk.mockkStatic -import io.mockk.spyk -import io.mockk.unmockkAll -import io.mockk.verify -import org.bouncycastle.util.encoders.Hex -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.TestInstance -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.CsvSource -import org.junit.jupiter.params.provider.MethodSource -import java.util.stream.Stream -import org.junit.jupiter.params.provider.Arguments as JunitArguments - -class AbrevvaCryptoModuleTest { - - private lateinit var abrevvaCryptoModule: AbrevvaCryptoModule - - private lateinit var testMap: WritableMapTestImplementation - - @MockK(relaxed = true) - private lateinit var contextMock: ReactApplicationContext - - @MockK(relaxed = true) - private lateinit var promiseMock: Promise - - @MockK(relaxed = true) - private lateinit var readableMapMock: ReadableMap - - @BeforeEach - fun beforeEach() { - MockKAnnotations.init(this) - mockkObject(AesCCM) - mockkObject(AesGCM) - mockkObject(X25519Wrapper) - mockkObject(SimpleSecureRandom) - mockkObject(HKDF) - mockkStatic(Arguments::createMap) - mockkStatic(Hex::class) - testMap = WritableMapTestImplementation() - every { Arguments.createMap() } returns testMap - every { Hex.decode(any()) } returns byteArrayOf(1) - abrevvaCryptoModule = AbrevvaCryptoModule(contextMock) - } - - @AfterEach - fun afterEach(){ - unmockkAll() - } - @Nested - @DisplayName("encrypt()") - inner class EncryptTests { - @Test - fun should_reject_if_ct_is_empty() { - every { Hex.decode(any()) } answers { callOriginal()} - every { AesCCM.encrypt(any(), any(), any(), any(), any()) } returns ByteArray(0) - - abrevvaCryptoModule.encrypt(readableMapMock, promiseMock) - - verify { promiseMock.reject(any()) } - } - - @Test - fun should_resolve_if_ct_is_not_empty() { - every { AesCCM.encrypt(any(), any(), any(), any(), any()) } returns ByteArray(10) - - abrevvaCryptoModule.encrypt(readableMapMock, promiseMock) - - verify { promiseMock.resolve(any()) } - } - } - - @Nested - @DisplayName("decrypt()") - inner class DecryptTests { - @Test - fun should_reject_if_pt_is_empty() { - every { AesCCM.decrypt(any(), any(), any(), any(), any()) } returns ByteArray(0) - - abrevvaCryptoModule.decrypt(readableMapMock, promiseMock) - - verify { promiseMock.reject(any()) } - } - - @Test - fun should_resolve_if_pt_is_not_empty() { - every { AesCCM.decrypt(any(), any(), any(), any(), any()) } returns ByteArray(10) - - abrevvaCryptoModule.decrypt(readableMapMock, promiseMock) - - verify { promiseMock.resolve(any()) } - } - } - - @Nested - @DisplayName("generateKeyPair()") - inner class GenerateKeyPairTests { - @Test - fun should_resolve_if_keys_where_generated_successfully() { - every { X25519Wrapper.generateKeyPair() } returns mockk(relaxed = true) - - abrevvaCryptoModule.generateKeyPair(promiseMock) - - verify { promiseMock.resolve(any()) } - } - - @Test - fun should_reject_if_keys_cannot_be_generated() { - every { X25519Wrapper.generateKeyPair() } throws Exception("generateKeyPair() Fail Exception") - - abrevvaCryptoModule.generateKeyPair(promiseMock) - - verify { promiseMock.reject(any()) } - } - } - - @Nested - @DisplayName("encryptFile()") - @TestInstance(TestInstance.Lifecycle.PER_CLASS) - inner class EncryptFileTests { - @ParameterizedTest(name = "encryptFile({0}, {1}, {2}) should reject") - @MethodSource("parameterizedArgs_encrypt") - fun encryptFile_should_reject_if_any_Param_is_missing( - ctPath: String?, - ptPath: String?, - sharedSecret: String? - ) { - testMap.putString("ctPath", ctPath) - testMap.putString("ptPath", ptPath) - testMap.putString("sharedSecret", sharedSecret) - - abrevvaCryptoModule.encryptFile(testMap, promiseMock) - - verify { promiseMock.reject(any()) } - } - fun parameterizedArgs_encrypt(): Stream{ - return Stream.of( - JunitArguments.of("", "ptPath", "sharedSecret"), - JunitArguments.of("ctPath", "", "sharedSecret"), - JunitArguments.of("ctPath", "sharedSecret", ""), - JunitArguments.of(null, "ptPath", "sharedSecret"), - JunitArguments.of("ctPath", null, "sharedSecret"), - JunitArguments.of("ctPath", "ptPath", null), - ) - } - - @Test - fun should_resolve_if_args_are_valid_and_file_could_be_encrypted() { - val mapMock = mockk(relaxed = true) - every { mapMock.getString(any()) } returns "notEmpty" - every { AesGCM.encryptFile(any(), any(), any()) } returns true - - abrevvaCryptoModule.encryptFile(mapMock, promiseMock) - - verify { promiseMock.resolve(any()) } - } - - @Test - fun should_reject_if_args_are_valid_but_encryption_fails() { - val mapMock = mockk(relaxed = true) - every { mapMock.getString(any()) } returns "notEmpty" - every { - AesGCM.encryptFile( - any(), - any(), - any() - ) - } throws Exception("encryptFile() Fail Exception") - - abrevvaCryptoModule.encryptFile(mapMock, promiseMock) - - verify { promiseMock.reject(any()) } - } - } - - @Nested - @DisplayName("decryptFile()") - @TestInstance(TestInstance.Lifecycle.PER_CLASS) - inner class DecryptFileTests { - @ParameterizedTest(name = "empty args should be rejected") - @MethodSource("parameterizedArgs_decrypt") - fun should_reject_if_any_Param_is_empty( - ctPath: String?, - ptPath: String?, - sharedSecret: String? - ) { - testMap.putString("ctPath", ctPath) - testMap.putString("ptPath", ptPath) - testMap.putString("sharedSecret", sharedSecret) - - abrevvaCryptoModule.decryptFile(testMap, promiseMock) - - verify { promiseMock.reject(any()) } - } - - fun parameterizedArgs_decrypt(): Stream{ - return Stream.of( - JunitArguments.of("", "ptPath", "sharedSecret"), - JunitArguments.of("ctPath", "", "sharedSecret"), - JunitArguments.of("ctPath", "ptPath", ""), - JunitArguments.of(null, "ptPath", "sharedSecret"), - JunitArguments.of("ctPath", null, "sharedSecret"), - JunitArguments.of("ctPath", "ptPath", null), - ) - } - - @Test - fun should_resolve_if_args_are_valid_and_file_could_be_encrypted() { - val mapMock = mockk(relaxed = true) - every { mapMock.getString(any()) } returns "notEmpty" - every { AesGCM.decryptFile(any(), any(), any()) } returns true - - abrevvaCryptoModule.decryptFile(mapMock, promiseMock) - - verify { promiseMock.resolve(any()) } - } - - @Test - fun should_reject_if_encryption_fails() { - val mapMock = mockk(relaxed = true) - every { mapMock.getString(any()) } returns "notEmpty" - every { - AesGCM.decryptFile( - any(), - any(), - any() - ) - } throws Exception("encryptFile() Fail Exception") - - abrevvaCryptoModule.decryptFile(mapMock, promiseMock) - - verify { promiseMock.reject(any()) } - } - } - - @Nested - @DisplayName("decryptFileFromURL()") - inner class DecryptFileFromURLTests { - - @Nested - @DisplayName("should reject if any Param is empty") - @TestInstance(TestInstance.Lifecycle.PER_CLASS) - inner class DecryptFileFromURL_ParameterizedTest { - @ParameterizedTest - @MethodSource("parameterizedArgs_decryptFileFromURL") - fun should_reject_if_any_Param_is_empty( - sharedSecret: String?, - url: String?, - ptPath: String? - ) { - testMap.putString("sharedSecret", sharedSecret) - testMap.putString("url", url) - testMap.putString("ptPath", ptPath) - - abrevvaCryptoModule.decryptFileFromURL(testMap, promiseMock) - - verify { promiseMock.reject(any()) } - } - - fun parameterizedArgs_decryptFileFromURL(): Stream { - return Stream.of( - JunitArguments.of("", "url", "ptPath"), - JunitArguments.of("sharedSecret", "", "ptPath"), - JunitArguments.of("sharedSecret", "url", ""), - JunitArguments.of(null, "url", "ptPath"), - JunitArguments.of("sharedSecret", null, "ptPath"), - JunitArguments.of("sharedSecret", "url", null), - ) - } - } - - @Test - fun decryptFileFromURL_should_reject_if_ctPathFile_is_not_accessible() { - val mockMap = mockk(relaxed = true) - val moduleSpy = spyk(AbrevvaCryptoModule(contextMock)) - every { mockMap.getString(any()) } returns "notEmpty" - every { moduleSpy.writeToFile(any(), any()) } throws Exception("decryptFileFromURL() Fail Exception") - - moduleSpy.decryptFileFromURL(mockMap, promiseMock) - - verify { promiseMock.reject(any()) } - } - - @Test - fun decryptFileFromURL_should_reject_if_decode_fails() { - val mockMap = mockk(relaxed = true) - val moduleSpy = spyk(AbrevvaCryptoModule(contextMock)) - every { mockMap.getString(any()) } returns "notEmpty" - every { moduleSpy.writeToFile(any(), any()) } returns Unit - every { Hex.decode(any()) } throws Exception("decryptFileFromURL() Fail Exception") - - moduleSpy.decryptFileFromURL(mockMap, promiseMock) - - verify { promiseMock.reject(any()) } - } - - @Test - fun decryptFileFromURL_should_resolve_if_everything_works_as_intended() { - val mockMap = mockk(relaxed = true) - val moduleSpy = spyk(AbrevvaCryptoModule(contextMock)) - every { mockMap.getString(any()) } returns "notEmpty" - every { moduleSpy.writeToFile(any(), any()) } returns Unit - every { AesGCM.decryptFile(any(), any(), any()) } returns true - - moduleSpy.decryptFileFromURL(mockMap, promiseMock) - - verify { promiseMock.resolve(any()) } - } - } - - @Nested - @DisplayName("random()") - inner class RandomTests { - @ParameterizedTest(name = "random(numBytes: {0}) resolved String size should be {1}") - @CsvSource("2,4", "4,8", "7,14") - fun should_return_random_bytes_n_number_of_bytes_if_successful( - numBytes: Int, - expectedStrLen: Int - ) { - val mockMap = mockk(relaxed = true) - every { mockMap.getInt("numBytes") } returns numBytes - - abrevvaCryptoModule.random(mockMap, promiseMock) - - assert(testMap.getString("value")!!.length == expectedStrLen) - } - - @Test - fun should_reject_if_bytes_cannot_be_generated(){ - every { SimpleSecureRandom.getSecureRandomBytes(any()) } returns ByteArray(0) - testMap.putInt("numBytes", 10) - - abrevvaCryptoModule.random(testMap, promiseMock) - - verify { promiseMock.reject(any()) } - } - } - @Nested - @DisplayName("derive()") - inner class DeriveTests { - - @Test - fun should_resolve_if_successful() { - testMap.putInt("length", 0) - every { HKDF.derive(any(), any(), any(), any()) } returns ByteArray(0) - - abrevvaCryptoModule.derive(testMap, promiseMock) - - verify { promiseMock.reject(any()) } - } - @Test - fun should_reject_if_unsuccessful() { - testMap.putInt("length", 10) - every { HKDF.derive(any(), any(), any(), any()) } returns ByteArray(10) - abrevvaCryptoModule.derive(testMap, promiseMock) - - verify { promiseMock.resolve(any()) } - } - } -} \ No newline at end of file diff --git a/android/src/main/java/com/test/exampleapp/WriteableMapTestImplementation.kt b/android/src/main/java/com/test/exampleapp/WriteableMapTestImplementation.kt deleted file mode 100644 index dca44f4..0000000 --- a/android/src/main/java/com/test/exampleapp/WriteableMapTestImplementation.kt +++ /dev/null @@ -1,59 +0,0 @@ -package com.test.exampleapp - -import com.facebook.react.bridge.Dynamic -import com.facebook.react.bridge.ReadableArray -import com.facebook.react.bridge.ReadableMap -import com.facebook.react.bridge.ReadableMapKeySetIterator -import com.facebook.react.bridge.ReadableType -import com.facebook.react.bridge.WritableMap -import io.mockk.mockk -import kotlin.reflect.typeOf - -class WritableMapTestImplementation(args: MutableMap = mutableMapOf()) : WritableMap { - private var map = mutableMapOf() - init { - map = args - } - - fun print() { - map.forEach { key, value -> - println("${key}: ${value}") - } - } - - override fun equals(other: Any?): Boolean { - return map == (other as WritableMapTestImplementation).getMap() - } - fun getSize(): Int {return map.size} - fun getMap(): MutableMap { return map } - override fun hasKey(p0: String): Boolean { return map.containsKey(p0) } - override fun isNull(p0: String): Boolean { return map[p0] == null } - override fun getBoolean(p0: String): Boolean { return map[p0] as Boolean } - override fun getDouble(p0: String): Double { return map[p0] as Double } - override fun getInt(p0: String): Int { return map[p0] as Int } - override fun getString(p0: String): String? { return map[p0] as String? } - override fun getArray(p0: String): ReadableArray? { return map[p0] as ReadableArray? } - override fun getMap(p0: String): ReadableMap? { return map[p0] as ReadableMap? } - override fun getDynamic(p0: String): Dynamic { return mockk() } - override fun getType(p0: String): ReadableType { return mockk() } - override fun getEntryIterator(): MutableIterator> { - return mockk>>() - } - override fun keySetIterator(): ReadableMapKeySetIterator { - return mockk() - } - override fun toHashMap(): HashMap { return mockk>() } - override fun putNull(p0: String) { map[p0] = null } - override fun putBoolean(p0: String, p1: Boolean) { map[p0] = p1 } - override fun putDouble(p0: String, p1: Double) { map[p0] = p1 } - override fun putInt(p0: String, p1: Int) { map[p0] = p1 } - override fun putString(p0: String, p1: String?) { map[p0] = p1 } - override fun putArray(p0: String, p1: ReadableArray?) { map[p0] = p1 } - override fun putMap(p0: String, p1: ReadableMap?) { map[p0] = p1 } - override fun merge(p0: ReadableMap) { - TODO("Not yet implemented") - } - override fun copy(): WritableMap { - TODO("Not yet implemented") - } -} \ 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 9124527..6b89e84 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 @@ -1,16 +1,36 @@ package com.evva.xesar.abrevva.reactnative +import android.annotation.SuppressLint +import android.bluetooth.BluetoothManager +import android.content.Context +import android.os.ParcelUuid +import android.util.Log +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 import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import io.mockk.mockkConstructor +import io.mockk.mockkStatic +import io.mockk.slot +import io.mockk.spyk import io.mockk.unmockkAll +import no.nordicsemi.android.common.core.DataByteArray +import no.nordicsemi.android.kotlin.ble.core.ServerDevice +import no.nordicsemi.android.kotlin.ble.core.scanner.BleScanRecord +import no.nordicsemi.android.kotlin.ble.core.scanner.BleScanResult +import no.nordicsemi.android.kotlin.ble.core.scanner.BleScanResultData import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test class AbrevvaBleModuleTest { - private lateinit var abrevvaBleModule: com.evva.xesar.abrevva.reactnative.AbrevvaBleModule + private lateinit var abrevvaBleModule: AbrevvaBleModule private lateinit var testMap: WritableMapTestImplementation @@ -23,14 +43,152 @@ class AbrevvaBleModuleTest { @MockK(relaxed = true) private lateinit var readableMapMock: ReadableMap + @MockK(relaxed = true) + private lateinit var writeableArrayMock: WritableArray + @BeforeEach fun beforeEach() { MockKAnnotations.init(this) - abrevvaBleModule = com.evva.xesar.abrevva.reactnative.AbrevvaBleModule(contextMock) + mockkStatic(Arguments::class) + testMap = WritableMapTestImplementation() + every { Arguments.createMap() } returns testMap + every { Arguments.createArray() } returns writeableArrayMock + every { contextMock.getSystemService(Context.BLUETOOTH_SERVICE) } returns mockk( + relaxed = true + ) + abrevvaBleModule = AbrevvaBleModule(contextMock) } @AfterEach fun afterEach() { unmockkAll() } + + + @SuppressLint("MissingPermission") + @Test + fun `startNotifications notification recieved closure should generate key correctly`() { + val callbackSlot = slot<(data: ByteArray) -> Unit>() + val keySlot = slot() + val successCallback = slot<(success: Boolean) -> Unit>() + val deviceId = "e7f635ac-27ae-4bc6-a5ca-3f07872f49e9" + val service = "01a660db-5dbd-488a-bd01-b42449817c82" + val characteristic = "d0d71305-05b2-4add-9ea9-bcd1cc82211c" + val options = WritableMapTestImplementation( + mutableMapOf( + "deviceId" to deviceId, + "service" to service, + "characteristic" to characteristic + ) + ) + mockkConstructor(BleManager::class) + mockkStatic(Log::class) + every { Log.e(any(), any()) } returns 0 + every { Arguments.createMap() } returns options + every { + anyConstructed().startNotifications( + any(), + any(), + any(), + capture(successCallback), + capture(callbackSlot) + ) + } returns Unit + every { anyConstructed().isBleEnabled() } returns false + every { contextMock.emitDeviceEvent(capture(keySlot), any()) } returns Unit + abrevvaBleModule.startNotifications(options, promiseMock) + + callbackSlot.captured(ByteArray(1)) + assert(keySlot.captured == "notification|e7f635ac-27ae-4bc6-a5ca-3f07872f49e9|01a660db-5dbd-488a-bd01-b42449817c82|d0d71305-05b2-4add-9ea9-bcd1cc82211c") + } + + @Test + fun `getBleDeviceFromNordic should save data from BleScanResult in new map`() { + val name = "name" + val address = "deviceAddress" + val bleDevice = WritableMapTestImplementation() + every { Arguments.createMap() } returns bleDevice + val bleScanResult = mockk(relaxed = true) + val device = mockk() + every { bleScanResult.device } returns device + every { device.hasName } returns true + every { device.name } returns name + every { device.address } returns address + every { writeableArrayMock.size() } returns 0 + + abrevvaBleModule.getBleDeviceFromNordic(bleScanResult) + + val ref = WritableMapTestImplementation( + mutableMapOf( + "deviceId" to address, + "name" to name, + ) + ) + assert(ref == bleDevice) + } + + @Test + fun `getScanResultFromNordic should construct ReadableMap from ScanResult`() { + val name = "name" + val deviceId = "deviceId" + val txPower = 10 + val bleSpy = spyk(AbrevvaBleModule(contextMock)) + val result = mockk() + val data = mockk() + val device = mockk() + val scanRecord = mockk() + val bytes = DataByteArray(byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x07, 0x08, 0x09, 0x10)) + val parcelUuid = mockk(relaxed = true) + val serviceData = mapOf( + parcelUuid to DataByteArray( + byteArrayOf( + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x07, + 0x08, + 0x09, + 0x10 + ) + ) + ) + val bleDevice = WritableMapTestImplementation( + mutableMapOf( + "deviceId" to deviceId, + "name" to name + ) + ) + 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 + every { result.device.name } returns "name" + every { data.txPower } returns txPower + every { data.scanRecord } returns scanRecord + every { scanRecord.bytes } returns bytes + every { scanRecord.serviceData } returns serviceData + every { scanRecord.serviceUuids } returns null + every { bleSpy.getBleDeviceFromNordic(any()) } returns bleDevice + every { Arguments.createMap() } returns scanResult andThen manufacturerData andThen serviceDataMap + + bleSpy.getScanResultFromNordic(result) + + val ref = WritableMapTestImplementation( + mutableMapOf( + "device" to bleDevice, + "localName" to name, + "txPower" to txPower, + "manufacturerData" to WritableMapTestImplementation(mutableMapOf("2055" to "09 10")), + "rawAdvertisement" to "(0x) 01:02:03:04:05:07:08:09:10", + "uuids" to writeableArrayMock, + "serviceData" to serviceDataMap + ) + ) + assert(ref == scanResult) + + } } \ No newline at end of file diff --git a/android/src/test/java/com/evva/xesar/abrevva/reactnative/WriteableMapTestImplementation.kt b/android/src/test/java/com/evva/xesar/abrevva/reactnative/WriteableMapTestImplementation.kt index 8fd086e..a7381e1 100644 --- a/android/src/test/java/com/evva/xesar/abrevva/reactnative/WriteableMapTestImplementation.kt +++ b/android/src/test/java/com/evva/xesar/abrevva/reactnative/WriteableMapTestImplementation.kt @@ -8,8 +8,31 @@ import com.facebook.react.bridge.ReadableType import com.facebook.react.bridge.WritableMap import io.mockk.mockk -class WritableMapTestImplementation : WritableMap { - private val map = mutableMapOf() +class WritableMapTestImplementation(args: MutableMap = mutableMapOf()) : + WritableMap { + private var map = mutableMapOf() + + init { + map = args + } + + fun print() { + map.forEach { key, value -> + println("${key}: ${value}") + } + } + + override fun equals(other: Any?): Boolean { + return map == (other as WritableMapTestImplementation).getMap() + } + + fun getSize(): Int { + return map.size + } + + fun getMap(): MutableMap { + return map + } override fun hasKey(p0: String): Boolean { return map.containsKey(p0) diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index eb54c35..ce457ba 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -104,7 +104,7 @@ android { } packagingOptions { resources { - excludes += ['META-INF/INDEX.LIST', 'META-INF/io.netty.versions.properties', 'META-INF/*.properties', 'META-INF/INDEX.LIST'] + excludes += ['META-INF/INDEX.LIST', 'META-INF/io.netty.versions.properties', 'META-INF/*.properties', 'META-INF/INDEX.LIST', 'META-INF/LICENSE.md', 'META-INF/LICENSE-notice.md'] } } } From 574d33000ac192c863c8656cc165289264ee97fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hochst=C3=B6ger=20Matthias?= <116495532+mhochsto@users.noreply.github.com> Date: Wed, 14 Aug 2024 10:47:22 +0200 Subject: [PATCH 14/36] chore: added startNotification() testcase --- android/build.gradle | 7 ++++++- .../reactnative/AbrevvaBleModuleTest.kt | 21 ++++++++++--------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 6c1f4a3..71e50ed 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -75,6 +75,11 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + testOptions { + unitTests.all { + useJUnitPlatform() + } + } } repositories { @@ -102,4 +107,4 @@ dependencies { androidTestImplementation("androidx.test.ext:junit:1.2.1") androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") -} +} \ 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 6b89e84..6690799 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 @@ -4,7 +4,6 @@ import android.annotation.SuppressLint import android.bluetooth.BluetoothManager import android.content.Context import android.os.ParcelUuid -import android.util.Log import com.evva.xesar.abrevva.ble.BleManager import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Promise @@ -65,12 +64,13 @@ class AbrevvaBleModuleTest { } + /* https://github.com/mockk/mockk/issues/586#issuecomment-1404973825 */ @SuppressLint("MissingPermission") @Test fun `startNotifications notification recieved closure should generate key correctly`() { + mockkConstructor(BleManager::class) val callbackSlot = slot<(data: ByteArray) -> Unit>() val keySlot = slot() - val successCallback = slot<(success: Boolean) -> Unit>() val deviceId = "e7f635ac-27ae-4bc6-a5ca-3f07872f49e9" val service = "01a660db-5dbd-488a-bd01-b42449817c82" val characteristic = "d0d71305-05b2-4add-9ea9-bcd1cc82211c" @@ -81,24 +81,25 @@ class AbrevvaBleModuleTest { "characteristic" to characteristic ) ) - mockkConstructor(BleManager::class) - mockkStatic(Log::class) - every { Log.e(any(), any()) } returns 0 every { Arguments.createMap() } returns options every { anyConstructed().startNotifications( any(), any(), any(), - capture(successCallback), - capture(callbackSlot) + any(), + capture(callbackSlot), + any() ) } returns Unit - every { anyConstructed().isBleEnabled() } returns false every { contextMock.emitDeviceEvent(capture(keySlot), any()) } returns Unit - abrevvaBleModule.startNotifications(options, promiseMock) + abrevvaBleModule = AbrevvaBleModule(contextMock) + + abrevvaBleModule.startNotifications( + options, promiseMock + ) + callbackSlot.captured.invoke(ByteArray(0)) - callbackSlot.captured(ByteArray(1)) assert(keySlot.captured == "notification|e7f635ac-27ae-4bc6-a5ca-3f07872f49e9|01a660db-5dbd-488a-bd01-b42449817c82|d0d71305-05b2-4add-9ea9-bcd1cc82211c") } From b0500770822b193d37bb1560745e4e85742fb877 Mon Sep 17 00:00:00 2001 From: mhochsto <116495532+mhochsto@users.noreply.github.com> Date: Wed, 14 Aug 2024 10:54:12 +0200 Subject: [PATCH 15/36] chore: README.md from main From 45145b15aa465eb72ff15639f1e345dcccc0b9dc Mon Sep 17 00:00:00 2001 From: Aleksandar Palic <1425202+codepushr@users.noreply.github.com> Date: Thu, 22 Aug 2024 11:12:20 +0200 Subject: [PATCH 16/36] chore: update AbrevvaSDK version; add Makefile for ios test-command fix: test cases --- Makefile | 10 ++++++ abrevva-react-native.podspec | 2 +- .../project.pbxproj | 4 +-- .../AbrevvaBleTests.swift | 2 +- .../AbrevvaCryptoTests.swift | 2 +- example/ios/Podfile | 1 + example/ios/Podfile.lock | 32 +++++++++++++------ ios/nfc/AbrevvaNfc.swift | 12 +++---- 8 files changed, 43 insertions(+), 22 deletions(-) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2099896 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +test: + $(MAKE) test-ios + +test-ios: + xcodebuild \ + -workspace example/ios/ExampleAppExample.xcworkspace \ + -scheme ExampleAppExample \ + -sdk iphonesimulator \ + -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.4' \ + test diff --git a/abrevva-react-native.podspec b/abrevva-react-native.podspec index 36135ce..7f2fe3e 100644 --- a/abrevva-react-native.podspec +++ b/abrevva-react-native.podspec @@ -18,7 +18,7 @@ Pod::Spec.new do |s| s.dependency "CocoaMQTT" s.dependency "CryptoSwift" - s.dependency "AbrevvaSDK", '~> 1.0.20' + s.dependency "AbrevvaSDK", '~> 1.0.23' # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0. # See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79. diff --git a/example/ios/ExampleAppExample.xcodeproj/project.pbxproj b/example/ios/ExampleAppExample.xcodeproj/project.pbxproj index 8776832..64ec82f 100644 --- a/example/ios/ExampleAppExample.xcodeproj/project.pbxproj +++ b/example/ios/ExampleAppExample.xcodeproj/project.pbxproj @@ -553,7 +553,7 @@ "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios", ); - IPHONEOS_DEPLOYMENT_TARGET = 13.4; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD = ""; LDPLUSPLUS = ""; LD_RUNPATH_SEARCH_PATHS = ( @@ -637,7 +637,7 @@ "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios", ); - IPHONEOS_DEPLOYMENT_TARGET = 13.4; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD = ""; LDPLUSPLUS = ""; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/example/ios/ExampleAppExampleTests/AbrevvaBleTests.swift b/example/ios/ExampleAppExampleTests/AbrevvaBleTests.swift index eb732ee..19d8236 100644 --- a/example/ios/ExampleAppExampleTests/AbrevvaBleTests.swift +++ b/example/ios/ExampleAppExampleTests/AbrevvaBleTests.swift @@ -3,8 +3,8 @@ import Quick import Nimble import Mockingbird import CoreBluetooth -@testable import react_native_example_app import AbrevvaSDK +@testable import abrevva_react_native final class AbrevvaBleTests: QuickSpec { override class func spec(){ diff --git a/example/ios/ExampleAppExampleTests/AbrevvaCryptoTests.swift b/example/ios/ExampleAppExampleTests/AbrevvaCryptoTests.swift index c7771f1..e5b880d 100644 --- a/example/ios/ExampleAppExampleTests/AbrevvaCryptoTests.swift +++ b/example/ios/ExampleAppExampleTests/AbrevvaCryptoTests.swift @@ -2,8 +2,8 @@ import XCTest import Quick import Nimble import Mockingbird -@testable import react_native_example_app import AbrevvaSDK +@testable import abrevva_react_native class RCTPromise{ var data: Any? diff --git a/example/ios/Podfile b/example/ios/Podfile index defcce6..6dbb744 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -66,6 +66,7 @@ target 'ExampleAppExample' do installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES' + config.build_settings["IPHONEOS_DEPLOYMENT_TARGET"] = "15.0" end end end diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 1cfb253..2a8f127 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,6 +1,6 @@ PODS: - abrevva-react-native (0.1.0): - - AbrevvaSDK (~> 1.0.20) + - AbrevvaSDK (~> 1.0.23) - CocoaMQTT - CryptoSwift - DoubleConversion @@ -23,7 +23,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - AbrevvaSDK (1.0.20): + - AbrevvaSDK (1.0.23): - CocoaMQTT - CryptoSwift - boost (1.83.0) @@ -32,6 +32,15 @@ PODS: - CocoaMQTT/Core (2.1.6): - MqttCocoaAsyncSocket (~> 1.0.8) - CryptoSwift (1.8.3) + - CwlCatchException (2.2.0): + - CwlCatchExceptionSupport (~> 2.2.0) + - CwlCatchExceptionSupport (2.2.0) + - CwlMachBadInstructionHandler (2.2.0) + - CwlPosixPreconditionTesting (2.2.0) + - CwlPreconditionTesting (2.2.1): + - CwlCatchException (~> 2.2.0) + - CwlMachBadInstructionHandler (~> 2.2.0) + - CwlPosixPreconditionTesting (~> 2.2.0) - DoubleConversion (1.1.6) - FBLazyVector (0.74.3) - fmt (9.1.0) @@ -41,7 +50,7 @@ PODS: - hermes-engine/Pre-built (0.74.3) - MockingbirdFramework (0.20.0) - MqttCocoaAsyncSocket (1.0.8) - - Nimble (13.3.0): + - Nimble (13.4.0): - CwlPreconditionTesting (~> 2.2.0) - Quick (7.6.2) - RCT-Folly (2024.01.01.00): @@ -1205,7 +1214,7 @@ PODS: - React-utils (= 0.74.3) - RNFS (2.20.0): - React-Core - - RNScreens (3.32.0): + - RNScreens (3.34.0): - DoubleConversion - glog - hermes-engine @@ -1431,11 +1440,16 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - abrevva-react-native: a71c4e72921fc2f516bc32d9818839b48842f02f - AbrevvaSDK: fa79760425864800f524905612f708fa9e323fc5 + abrevva-react-native: 9892809596e672fc85a43c42e02d11c22e6e7fe2 + AbrevvaSDK: 4edd60273dd1b2d171e8adb5a84756aa5084347c boost: d3f49c53809116a5d38da093a8aa78bf551aed09 CocoaMQTT: 1f206228b29318eabdacad0c2e4e88575922c27a CryptoSwift: 967f37cea5a3294d9cce358f78861652155be483 + CwlCatchException: 51bf8319009a31104ea6f0568730d1ecc25b6454 + CwlCatchExceptionSupport: 1345d6adb01a505933f2bc972dab60dcb9ce3e50 + CwlMachBadInstructionHandler: ea1030428925d9bf340882522af30712fb4bf356 + CwlPosixPreconditionTesting: a125dee731883f2582715f548c6b6c92c7fde145 + CwlPreconditionTesting: ccfd08aca58d14e04062b2a3dd2fd52e09857453 DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 FBLazyVector: 7e977dd099937dc5458851233141583abba49ff2 fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 @@ -1443,7 +1457,7 @@ SPEC CHECKSUMS: hermes-engine: 1f547997900dd0752dc0cc0ae6dd16173c49e09b MockingbirdFramework: 54e35fbbb47b806c1a1fae2cf3ef99f6eceb55e5 MqttCocoaAsyncSocket: 77d3b74f76228dd5a05d1f9526eab101d415b30c - Nimble: 3ac6c6b0b7e9835d1540b6507d8054b12a415536 + Nimble: c3d7c9848a0adae88a665ca52f8da23dd4d2cd94 Quick: b8bec97cd4b9f21da0472d45580f763b801fc353 RCT-Folly: 02617c592a293bd6d418e0a88ff4ee1f88329b47 RCTDeprecation: 4c7eeb42be0b2e95195563c49be08d0b839d22b4 @@ -1494,10 +1508,10 @@ SPEC CHECKSUMS: React-utils: 6f7ac39d9a0de447d4334bb25d144a28c0c5d8c9 ReactCommon: 4a09c7d8a06e93c1e2e988a3b9f3db3d2449f2fc RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 - RNScreens: fd2722bcc59f36a629205af8cc7b48e4bc0d09f5 + RNScreens: db442e7b8c7bc8befec2ce057927305ff8598cc8 SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d Yoga: bd92064a0d558be92786820514d74fc4dddd1233 -PODFILE CHECKSUM: c096dde368bb25db14174c2f8e4a6b2095e483e8 +PODFILE CHECKSUM: c2b657d91fc21203dd436e57c3a2a27527cbae3b COCOAPODS: 1.14.3 diff --git a/ios/nfc/AbrevvaNfc.swift b/ios/nfc/AbrevvaNfc.swift index 638bbb9..628219d 100644 --- a/ios/nfc/AbrevvaNfc.swift +++ b/ios/nfc/AbrevvaNfc.swift @@ -23,16 +23,14 @@ class AbrevvaNfc: NSObject, NFCSessionDelegate { } @objc func connect() { - var clientCertArray = getClientCertFromP12File(certName: "client-ios.p12", certPassword: "123") + let clientCertArray = getClientCertFromP12File(certName: "client-ios.p12", certPassword: "123") + self.clientID = CLIENTID mqtt5Client = MQTT5Client(clientID: clientID, host: HOST, port: PORT, clientCertArray:clientCertArray) mqtt5Client?.setOnMessageRecieveHandler(handler: onMessageRecieveHandler) mqtt5Client?.setDidStateChangeToHandler(handler: didStateChangeToHandler) - - mqtt5Client?.connect() - } @objc func disconnect() { @@ -109,18 +107,16 @@ class AbrevvaNfc: NSObject, NFCSessionDelegate { } func sessionDidStart(_ withSuccess: Bool) { - } func sessionDidClose(_ withError: (any Error)?) { - } func sessionDidReceiveKeyOnEvent(_ tagID: Data, _ historicalBytes: Data) { - mqtt5Client?.publishKyOn(identifier: tagID, historicalBytes: historicalBytes) + mqtt5Client?.publishKyOn(identifier: tagID.toHexString(), historicalBytes: historicalBytes.toHexString()) } func sessionDidReceiveKeyOffEvent(_ tagID: Data, _ historicalBytes: Data) { - mqtt5Client?.publishKyOff(identifier: tagID, historicalBytes: historicalBytes) + mqtt5Client?.publishKyOff(identifier: tagID.toHexString(), historicalBytes: historicalBytes.toHexString()) } } From f116d43c8a0850bbbb19ec58784769e664f0ba75 Mon Sep 17 00:00:00 2001 From: Aleksandar Palic <1425202+codepushr@users.noreply.github.com> Date: Thu, 22 Aug 2024 13:23:46 +0200 Subject: [PATCH 17/36] ci: add ios test workflow --- .github/workflows/test.yml | 20 +++++++++++++++++++- example/ios/Podfile.lock | 4 ++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a598eb1..a2eff6a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,7 @@ on: pull_request: branches: [ "main" ] jobs: - test: + test-web: runs-on: ubuntu-latest strategy: matrix: @@ -22,3 +22,21 @@ jobs: cache: 'yarn' - run: yarn - run: yarn test + test-ios: + runs-on: macos-latest + strategy: + matrix: + node-version: [lts/*] + # See supported Node.js release schedule at https://nodejs.org/en/about/previous-releases + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'yarn' + - run: corepack enable + - run: yarn + - run: cd example; bundle; cd - + - run: cd example/ios; bundle exec pod install; cd - + - run: make test diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 2a8f127..3cb4010 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - abrevva-react-native (0.1.0): + - abrevva-react-native (0.1.3): - AbrevvaSDK (~> 1.0.23) - CocoaMQTT - CryptoSwift @@ -1440,7 +1440,7 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - abrevva-react-native: 9892809596e672fc85a43c42e02d11c22e6e7fe2 + abrevva-react-native: cee28cab53b8e2f4b8cd543785817258ce246e93 AbrevvaSDK: 4edd60273dd1b2d171e8adb5a84756aa5084347c boost: d3f49c53809116a5d38da093a8aa78bf551aed09 CocoaMQTT: 1f206228b29318eabdacad0c2e4e88575922c27a From a6a0934209433b88f369b3d82051f71433e330ef Mon Sep 17 00:00:00 2001 From: Aleksandar Palic <1425202+codepushr@users.noreply.github.com> Date: Thu, 22 Aug 2024 13:37:28 +0200 Subject: [PATCH 18/36] ci: add quiet flag for ios test command --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2099896..6a88793 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ test: $(MAKE) test-ios test-ios: - xcodebuild \ + xcodebuild -quiet \ -workspace example/ios/ExampleAppExample.xcworkspace \ -scheme ExampleAppExample \ -sdk iphonesimulator \ From 22c8adbdd23f5e24c9e820f293f8d5bf1cd5007f Mon Sep 17 00:00:00 2001 From: Aleksandar Palic <1425202+codepushr@users.noreply.github.com> Date: Thu, 22 Aug 2024 13:56:29 +0200 Subject: [PATCH 19/36] ci: add ruby version & cache; add swift version & cache --- .github/workflows/test.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a2eff6a..7971a55 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,6 +35,22 @@ jobs: with: node-version: ${{ matrix.node-version }} cache: 'yarn' + - uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.3' + bundler-cache: true + - uses: SwiftyLab/setup-swift@latest + with: + swift-version: "5.10.0" + - name: Get swift version + run: swift --version + - name: Get swift version in macOS + if: runner.os == 'macOS' + run: xcrun --toolchain ${{ env.TOOLCHAINS }} swift --version + - uses: irgaly/xcode-cache@v1 + with: + key: xcode-cache-deriveddata-${{ github.workflow }}-${{ github.sha }} + restore-keys: xcode-cache-deriveddata-${{ github.workflow }}- - run: corepack enable - run: yarn - run: cd example; bundle; cd - From 0e0910db3ccb792189fb902befb823bd673803af Mon Sep 17 00:00:00 2001 From: Aleksandar Palic <1425202+codepushr@users.noreply.github.com> Date: Thu, 22 Aug 2024 15:07:07 +0200 Subject: [PATCH 20/36] chore: bump deps --- .../project.pbxproj | 10 +- example/ios/Podfile.lock | 4 +- yarn.lock | 299 +++++++++--------- 3 files changed, 159 insertions(+), 154 deletions(-) diff --git a/example/ios/ExampleAppExample.xcodeproj/project.pbxproj b/example/ios/ExampleAppExample.xcodeproj/project.pbxproj index 64ec82f..92ed2f6 100644 --- a/example/ios/ExampleAppExample.xcodeproj/project.pbxproj +++ b/example/ios/ExampleAppExample.xcodeproj/project.pbxproj @@ -575,7 +575,10 @@ "-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1", ); - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; @@ -658,7 +661,10 @@ "-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1", ); - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 3cb4010..610d80f 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -981,7 +981,7 @@ PODS: - React-Mapbuffer (0.74.3): - glog - React-debug - - react-native-safe-area-context (4.10.8): + - react-native-safe-area-context (4.10.9): - React-Core - React-nativeconfig (0.74.3) - React-NativeModulesApple (0.74.3): @@ -1483,7 +1483,7 @@ SPEC CHECKSUMS: React-jsitracing: 1aa5681c353b41573b03e0e480a5adf5fa1c56d8 React-logger: fa92ba4d3a5d39ac450f59be2a3cec7b099f0304 React-Mapbuffer: 70da5955150a58732e9336a0c7e71cd49e909f25 - react-native-safe-area-context: b7daa1a8df36095a032dff095a1ea8963cb48371 + react-native-safe-area-context: ab8f4a3d8180913bd78ae75dd599c94cce3d5e9a React-nativeconfig: 84806b820491db30175afbf027e99e8415dc63f0 React-NativeModulesApple: 7b79212f8cf496ab554e0b7b09acbd4aa4690260 React-perflogger: 7bb9ba49435ff66b666e7966ee10082508a203e8 diff --git a/yarn.lock b/yarn.lock index 96b255c..7cac958 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25,10 +25,10 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.20.5, @babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.25.2": - version: 7.25.2 - resolution: "@babel/compat-data@npm:7.25.2" - checksum: b61bc9da7cfe249f19d08da00f4f0c20550cd9ad5bffcde787c2bf61a8a6fa5b66d92bbd89031f3a6e5495a799a2a2499f2947b6cc7964be41979377473ab132 +"@babel/compat-data@npm:^7.20.5, @babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.25.2, @babel/compat-data@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/compat-data@npm:7.25.4" + checksum: b12a91d27c3731a4b0bdc9312a50b1911f41f7f728aaf0d4b32486e2257fd2cb2d3ea1a295e98449600c48f2c7883a3196ca77cda1cef7d97a10c2e83d037974 languageName: node linkType: hard @@ -69,15 +69,15 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.20.0, @babel/generator@npm:^7.25.0, @babel/generator@npm:^7.7.2": - version: 7.25.0 - resolution: "@babel/generator@npm:7.25.0" +"@babel/generator@npm:^7.20.0, @babel/generator@npm:^7.25.0, @babel/generator@npm:^7.25.4, @babel/generator@npm:^7.7.2": + version: 7.25.4 + resolution: "@babel/generator@npm:7.25.4" dependencies: - "@babel/types": ^7.25.0 + "@babel/types": ^7.25.4 "@jridgewell/gen-mapping": ^0.3.5 "@jridgewell/trace-mapping": ^0.3.25 jsesc: ^2.5.1 - checksum: bf25649dde4068bff8e387319bf820f2cb3b1af7b8c0cfba0bd90880656427c8bad96cd5cb6db7058d20cffe93149ee59da16567018ceaa21ecaefbf780a785c + checksum: 0096986585ccb2dbb8feb78aeaddb1b089ed7f6aafb16ec0f7bd931f04e1fc7d3d12e96999b6746f72d851b09453141522ddcf924bcac8cfe07c5d631bb09c8d languageName: node linkType: hard @@ -113,24 +113,24 @@ __metadata: languageName: node linkType: hard -"@babel/helper-create-class-features-plugin@npm:^7.18.6, @babel/helper-create-class-features-plugin@npm:^7.24.7, @babel/helper-create-class-features-plugin@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/helper-create-class-features-plugin@npm:7.25.0" +"@babel/helper-create-class-features-plugin@npm:^7.18.6, @babel/helper-create-class-features-plugin@npm:^7.24.7, @babel/helper-create-class-features-plugin@npm:^7.25.0, @babel/helper-create-class-features-plugin@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/helper-create-class-features-plugin@npm:7.25.4" dependencies: "@babel/helper-annotate-as-pure": ^7.24.7 "@babel/helper-member-expression-to-functions": ^7.24.8 "@babel/helper-optimise-call-expression": ^7.24.7 "@babel/helper-replace-supers": ^7.25.0 "@babel/helper-skip-transparent-expression-wrappers": ^7.24.7 - "@babel/traverse": ^7.25.0 + "@babel/traverse": ^7.25.4 semver: ^6.3.1 peerDependencies: "@babel/core": ^7.0.0 - checksum: e986c1187e16837b71f12920bd77e672b4bc19ac6dfe30b9d9d515a311c5cc5a085a8e337ac8597b1cb7bd0efdbfcc66f69bf652786c9a022070f9b782deec0d + checksum: 4544ebda4516eb25efdebd47ca024bd7bdb1eb6e7cc3ad89688c8ef8e889734c2f4411ed78981899c641394f013f246f2af63d92a0e9270f6c453309b4cb89ba languageName: node linkType: hard -"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.24.7, @babel/helper-create-regexp-features-plugin@npm:^7.25.0": +"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.24.7, @babel/helper-create-regexp-features-plugin@npm:^7.25.0, @babel/helper-create-regexp-features-plugin@npm:^7.25.2": version: 7.25.2 resolution: "@babel/helper-create-regexp-features-plugin@npm:7.25.2" dependencies: @@ -317,14 +317,14 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.13.16, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.25.0, @babel/parser@npm:^7.25.3": - version: 7.25.3 - resolution: "@babel/parser@npm:7.25.3" +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.13.16, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.25.0, @babel/parser@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/parser@npm:7.25.4" dependencies: - "@babel/types": ^7.25.2 + "@babel/types": ^7.25.4 bin: parser: ./bin/babel-parser.js - checksum: b55aba64214fa1d66ccd0d29f476d2e55a48586920d280f88c546f81cbbececc0e01c9d05a78d6bf206e8438b9c426caa344942c1a581eecc4d365beaab8a20e + checksum: fe4f083d4ad34f019dd7fad672cd007003004fb0a3df9b7315a5da9a5e8e56c1fed95acab6862e7d76cfccb2e8e364bcc307e9117718e6bb6dfb2e87ad065abf languageName: node linkType: hard @@ -532,7 +532,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-class-properties@npm:^7.12.13, @babel/plugin-syntax-class-properties@npm:^7.8.3": +"@babel/plugin-syntax-class-properties@npm:^7.12.13": version: 7.12.13 resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" dependencies: @@ -620,7 +620,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-import-meta@npm:^7.10.4, @babel/plugin-syntax-import-meta@npm:^7.8.3": +"@babel/plugin-syntax-import-meta@npm:^7.10.4": version: 7.10.4 resolution: "@babel/plugin-syntax-import-meta@npm:7.10.4" dependencies: @@ -653,7 +653,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-logical-assignment-operators@npm:^7.10.4, @babel/plugin-syntax-logical-assignment-operators@npm:^7.8.3": +"@babel/plugin-syntax-logical-assignment-operators@npm:^7.10.4": version: 7.10.4 resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4" dependencies: @@ -675,7 +675,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-numeric-separator@npm:^7.10.4, @babel/plugin-syntax-numeric-separator@npm:^7.8.3": +"@babel/plugin-syntax-numeric-separator@npm:^7.10.4": version: 7.10.4 resolution: "@babel/plugin-syntax-numeric-separator@npm:7.10.4" dependencies: @@ -730,7 +730,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-top-level-await@npm:^7.14.5, @babel/plugin-syntax-top-level-await@npm:^7.8.3": +"@babel/plugin-syntax-top-level-await@npm:^7.14.5": version: 7.14.5 resolution: "@babel/plugin-syntax-top-level-await@npm:7.14.5" dependencies: @@ -742,13 +742,13 @@ __metadata: linkType: hard "@babel/plugin-syntax-typescript@npm:^7.24.7, @babel/plugin-syntax-typescript@npm:^7.7.2": - version: 7.24.7 - resolution: "@babel/plugin-syntax-typescript@npm:7.24.7" + version: 7.25.4 + resolution: "@babel/plugin-syntax-typescript@npm:7.25.4" dependencies: - "@babel/helper-plugin-utils": ^7.24.7 + "@babel/helper-plugin-utils": ^7.24.8 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 56fe84f3044ecbf038977281648db6b63bd1301f2fff6595820dc10ee276c1d1586919d48d52a8d497ecae32c958be38f42c1c8d174dc58aad856c516dc5b35a + checksum: 9b89b8930cd5983f64251d75c9fcdc17a8dc73837d6de12220ff972888ecff4054a6467cf0c423cad242aa96c0f0564a39a0823073728cc02239b80d13f02230 languageName: node linkType: hard @@ -775,17 +775,17 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-async-generator-functions@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-transform-async-generator-functions@npm:7.25.0" +"@babel/plugin-transform-async-generator-functions@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/plugin-transform-async-generator-functions@npm:7.25.4" dependencies: "@babel/helper-plugin-utils": ^7.24.8 "@babel/helper-remap-async-to-generator": ^7.25.0 "@babel/plugin-syntax-async-generators": ^7.8.4 - "@babel/traverse": ^7.25.0 + "@babel/traverse": ^7.25.4 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: cce2bab70ad871ac11751bede006bd4861888f4c63bc9954be38620b14cc6890a4cbc633c1062b89c5fe288ce74b9d1974cc0d43c04baeeb2b13231a236fba85 + checksum: 4235444735a1946f8766fe56564a8134c2c36c73e6cf83b3f2ed5624ebc84ff5979506a6a5b39acdb23aa09d442a6af471710ed408ccce533a2c4d2990b9df6a languageName: node linkType: hard @@ -824,15 +824,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-class-properties@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-class-properties@npm:7.24.7" +"@babel/plugin-transform-class-properties@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/plugin-transform-class-properties@npm:7.25.4" dependencies: - "@babel/helper-create-class-features-plugin": ^7.24.7 - "@babel/helper-plugin-utils": ^7.24.7 + "@babel/helper-create-class-features-plugin": ^7.25.4 + "@babel/helper-plugin-utils": ^7.24.8 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 1348d7ce74da38ba52ea85b3b4289a6a86913748569ef92ef0cff30702a9eb849e5eaf59f1c6f3517059aa68115fb3067e389735dccacca39add4e2b0c67e291 + checksum: b73f7d968639c6c2dfc13f4c5a8fe45cefd260f0faa7890ae12e65d41211072544ff5e128c8b61a86887b29ffd3df8422dbdfbf61648488e71d4bb599c41f4a5 languageName: node linkType: hard @@ -849,19 +849,19 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:^7.0.0, @babel/plugin-transform-classes@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-transform-classes@npm:7.25.0" +"@babel/plugin-transform-classes@npm:^7.0.0, @babel/plugin-transform-classes@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/plugin-transform-classes@npm:7.25.4" dependencies: "@babel/helper-annotate-as-pure": ^7.24.7 - "@babel/helper-compilation-targets": ^7.24.8 + "@babel/helper-compilation-targets": ^7.25.2 "@babel/helper-plugin-utils": ^7.24.8 "@babel/helper-replace-supers": ^7.25.0 - "@babel/traverse": ^7.25.0 + "@babel/traverse": ^7.25.4 globals: ^11.1.0 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: ff97f168e6a18fa4e7bb439f1a170dc83c470973091c22c74674769350ab572be5af017cdb64fbd261fe99d068a4ee88f1b7fa7f5ab524d84c2f2833b116e577 + checksum: 0bf20e46eeb691bd60cee5d1b01950fc37accec88018ecace25099f7c8d8509c1ac54d11b8caf9f2157c6945969520642a3bc421159c1a14e80224dc9a7611de languageName: node linkType: hard @@ -1202,15 +1202,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-private-methods@npm:^7.22.5, @babel/plugin-transform-private-methods@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-private-methods@npm:7.24.7" +"@babel/plugin-transform-private-methods@npm:^7.22.5, @babel/plugin-transform-private-methods@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/plugin-transform-private-methods@npm:7.25.4" dependencies: - "@babel/helper-create-class-features-plugin": ^7.24.7 - "@babel/helper-plugin-utils": ^7.24.7 + "@babel/helper-create-class-features-plugin": ^7.25.4 + "@babel/helper-plugin-utils": ^7.24.8 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: c151548e34909be2adcceb224d8fdd70bafa393bc1559a600906f3f647317575bf40db670470934a360e90ee8084ef36dffa34ec25d387d414afd841e74cf3fe + checksum: cb1dabfc03e2977990263d65bc8f43a9037dffbb5d9a5f825c00d05447ff68015099408c1531d9dd88f18a41a90f5062dc48f3a1d52b415d2d2ee4827dedff09 languageName: node linkType: hard @@ -1334,18 +1334,18 @@ __metadata: linkType: hard "@babel/plugin-transform-runtime@npm:^7.0.0": - version: 7.24.7 - resolution: "@babel/plugin-transform-runtime@npm:7.24.7" + version: 7.25.4 + resolution: "@babel/plugin-transform-runtime@npm:7.25.4" dependencies: "@babel/helper-module-imports": ^7.24.7 - "@babel/helper-plugin-utils": ^7.24.7 + "@babel/helper-plugin-utils": ^7.24.8 babel-plugin-polyfill-corejs2: ^0.4.10 - babel-plugin-polyfill-corejs3: ^0.10.1 + babel-plugin-polyfill-corejs3: ^0.10.6 babel-plugin-polyfill-regenerator: ^0.6.1 semver: ^6.3.1 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 98bcbbdc833d5c451189a6325f88820fe92973e119c59ce74bf28681cf4687c8280decb55b6c47f22e98c3973ae3a13521c4f51855a2b8577b230ecb1b4ca5b4 + checksum: 40ea3519840c1b2062fc53dd0e4ce2b37cd43995bfc8bbb741f1985622138fbfd873307217692d7bf3ab0629faf0ce277e302e8446673fddaf470d3e07dd0fb2 languageName: node linkType: hard @@ -1466,23 +1466,23 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-sets-regex@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.24.7" +"@babel/plugin-transform-unicode-sets-regex@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.25.4" dependencies: - "@babel/helper-create-regexp-features-plugin": ^7.24.7 - "@babel/helper-plugin-utils": ^7.24.7 + "@babel/helper-create-regexp-features-plugin": ^7.25.2 + "@babel/helper-plugin-utils": ^7.24.8 peerDependencies: "@babel/core": ^7.0.0 - checksum: 08a2844914f33dacd2ce1ab021ce8c1cc35dc6568521a746d8bf29c21571ee5be78787b454231c4bb3526cbbe280f1893223c82726cec5df2be5dae0a3b51837 + checksum: 6d1a7e9fdde4ffc9a81c0e3f261b96a9a0dfe65da282ec96fe63b36c597a7389feac638f1df2a8a4f8c9128337bba8e984f934e9f19077930f33abf1926759ea languageName: node linkType: hard "@babel/preset-env@npm:^7.20.0, @babel/preset-env@npm:^7.25.2": - version: 7.25.3 - resolution: "@babel/preset-env@npm:7.25.3" + version: 7.25.4 + resolution: "@babel/preset-env@npm:7.25.4" dependencies: - "@babel/compat-data": ^7.25.2 + "@babel/compat-data": ^7.25.4 "@babel/helper-compilation-targets": ^7.25.2 "@babel/helper-plugin-utils": ^7.24.8 "@babel/helper-validator-option": ^7.24.8 @@ -1511,13 +1511,13 @@ __metadata: "@babel/plugin-syntax-top-level-await": ^7.14.5 "@babel/plugin-syntax-unicode-sets-regex": ^7.18.6 "@babel/plugin-transform-arrow-functions": ^7.24.7 - "@babel/plugin-transform-async-generator-functions": ^7.25.0 + "@babel/plugin-transform-async-generator-functions": ^7.25.4 "@babel/plugin-transform-async-to-generator": ^7.24.7 "@babel/plugin-transform-block-scoped-functions": ^7.24.7 "@babel/plugin-transform-block-scoping": ^7.25.0 - "@babel/plugin-transform-class-properties": ^7.24.7 + "@babel/plugin-transform-class-properties": ^7.25.4 "@babel/plugin-transform-class-static-block": ^7.24.7 - "@babel/plugin-transform-classes": ^7.25.0 + "@babel/plugin-transform-classes": ^7.25.4 "@babel/plugin-transform-computed-properties": ^7.24.7 "@babel/plugin-transform-destructuring": ^7.24.8 "@babel/plugin-transform-dotall-regex": ^7.24.7 @@ -1545,7 +1545,7 @@ __metadata: "@babel/plugin-transform-optional-catch-binding": ^7.24.7 "@babel/plugin-transform-optional-chaining": ^7.24.8 "@babel/plugin-transform-parameters": ^7.24.7 - "@babel/plugin-transform-private-methods": ^7.24.7 + "@babel/plugin-transform-private-methods": ^7.25.4 "@babel/plugin-transform-private-property-in-object": ^7.24.7 "@babel/plugin-transform-property-literals": ^7.24.7 "@babel/plugin-transform-regenerator": ^7.24.7 @@ -1558,16 +1558,16 @@ __metadata: "@babel/plugin-transform-unicode-escapes": ^7.24.7 "@babel/plugin-transform-unicode-property-regex": ^7.24.7 "@babel/plugin-transform-unicode-regex": ^7.24.7 - "@babel/plugin-transform-unicode-sets-regex": ^7.24.7 + "@babel/plugin-transform-unicode-sets-regex": ^7.25.4 "@babel/preset-modules": 0.1.6-no-external-plugins babel-plugin-polyfill-corejs2: ^0.4.10 - babel-plugin-polyfill-corejs3: ^0.10.4 + babel-plugin-polyfill-corejs3: ^0.10.6 babel-plugin-polyfill-regenerator: ^0.6.1 core-js-compat: ^3.37.1 semver: ^6.3.1 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 9735a44e557f7ef4ade87f59c0d69e4af3383432a23ae7a3cba33e3741bd7812f2d6403a0d94ebfda5f4bd9fdc6250a52c4a156407029f590fde511a792e64e2 + checksum: 752be43f0b78a2eefe5007076aed3d21b505e1c09d134b61e7de8838f1bbb1e7af81023d39adb14b6eae23727fb5a9fd23f8115a44df043319be22319be17913 languageName: node linkType: hard @@ -1651,11 +1651,11 @@ __metadata: linkType: hard "@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.8.4": - version: 7.25.0 - resolution: "@babel/runtime@npm:7.25.0" + version: 7.25.4 + resolution: "@babel/runtime@npm:7.25.4" dependencies: regenerator-runtime: ^0.14.0 - checksum: 4a2a374a58eb01aaa65c5762606e90b3a1f448e0c637d42278b6cc0b42a9f5399b5f381ba9f237ee087da2860d14dd2d1de7bddcbe18be6a3cafba97e44bed64 + checksum: 5c2aab03788e77f1f959d7e6ce714c299adfc9b14fb6295c2a17eb7cad0dd9c2ebfb2d25265f507f68c43d5055c5cd6f71df02feb6502cea44b68432d78bcbbe languageName: node linkType: hard @@ -1670,29 +1670,29 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.20.0, @babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.0, @babel/traverse@npm:^7.25.1, @babel/traverse@npm:^7.25.2, @babel/traverse@npm:^7.25.3": - version: 7.25.3 - resolution: "@babel/traverse@npm:7.25.3" +"@babel/traverse@npm:^7.20.0, @babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.0, @babel/traverse@npm:^7.25.1, @babel/traverse@npm:^7.25.2, @babel/traverse@npm:^7.25.3, @babel/traverse@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/traverse@npm:7.25.4" dependencies: "@babel/code-frame": ^7.24.7 - "@babel/generator": ^7.25.0 - "@babel/parser": ^7.25.3 + "@babel/generator": ^7.25.4 + "@babel/parser": ^7.25.4 "@babel/template": ^7.25.0 - "@babel/types": ^7.25.2 + "@babel/types": ^7.25.4 debug: ^4.3.1 globals: ^11.1.0 - checksum: 5661308b1357816f1d4e2813a5dd82c6053617acc08c5c95db051b8b6577d07c4446bc861c9a5e8bf294953ac8266ae13d7d9d856b6b889fc0d34c1f51abbd8c + checksum: 3b6d879b9d843b119501585269b3599f047011ae21eb7820d00aef62fc3a2bcdaf6f4cdf2679795a2d7c0b6b5d218974916e422f08dea08613dc42188ef21e4b languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.25.0, @babel/types@npm:^7.25.2, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": - version: 7.25.2 - resolution: "@babel/types@npm:7.25.2" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.25.0, @babel/types@npm:^7.25.2, @babel/types@npm:^7.25.4, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": + version: 7.25.4 + resolution: "@babel/types@npm:7.25.4" dependencies: "@babel/helper-string-parser": ^7.24.8 "@babel/helper-validator-identifier": ^7.24.7 to-fast-properties: ^2.0.0 - checksum: f73f66ba903c6f7e38f519a33d53a67d49c07e208e59ea65250362691dc546c6da7ab90ec66ee79651ef697329872f6f97eb19a6dfcacc026fd05e76a563c5d2 + checksum: 497f8b583c54a92a59c3ec542144695064cd5c384fcca46ba1aa301d5e5dd6c1d011f312ca024cb0f9c956da07ae82fb4c348c31a30afa31a074c027720d2aa8 languageName: node linkType: hard @@ -1938,11 +1938,11 @@ __metadata: linkType: hard "@evilmartians/lefthook@npm:^1.7.11": - version: 1.7.12 - resolution: "@evilmartians/lefthook@npm:1.7.12" + version: 1.7.14 + resolution: "@evilmartians/lefthook@npm:1.7.14" bin: lefthook: bin/index.js - checksum: 2c2e19b298ca40dca6d87482e997bb559f4a481d39453dd8893b60b5e9cff8b8db89917eee38e9f8c482e91d26040ea1aa5a1da11cec6c93a6501a1c6cb8ce6a + checksum: ddd1b6eda95d0d4f8728900d65a597075499ebec2d3181dab87fd3de2897d68ff47a111da6672e226c54952a477807dde7d1300f5622822701864846166887e2 conditions: (os=darwin | os=linux | os=win32) & (cpu=x64 | cpu=arm64 | cpu=ia32) languageName: node linkType: hard @@ -3340,20 +3340,20 @@ __metadata: linkType: hard "@types/node@npm:*": - version: 22.2.0 - resolution: "@types/node@npm:22.2.0" + version: 22.5.0 + resolution: "@types/node@npm:22.5.0" dependencies: - undici-types: ~6.13.0 - checksum: 63724799337bfb57719a1992690e738341d824e1744a2ac52c5278a008fbfadf99765519c19858feb80418cc7da0d5c8bdf7ea4d82973869b3882bd602c48ade + undici-types: ~6.19.2 + checksum: 3710b6f42416796061cf47cff0a37955f2ca0afc63ab281cc23e46b3ec8dffcabc66b970e4ee34fff5e2082617bed47610b4a1122c7b3880f551d3c673c40f84 languageName: node linkType: hard "@types/node@npm:^18.0.0": - version: 18.19.44 - resolution: "@types/node@npm:18.19.44" + version: 18.19.45 + resolution: "@types/node@npm:18.19.45" dependencies: undici-types: ~5.26.4 - checksum: 008f89b04afc9fdb4cd5ea71072ca64a08ef0453cbfc012863991959aa3ce2cf99c1e73cbcb5e0e67b37a81f88673e97ac180f56eec396ce1b68d5881aac2ce4 + checksum: b82006827911919b1f97d06f10a373c157988442337a0f35e355f351ec815fdcf5de4a68e33206c35379416a65aea7d0c34f435f42d55a0935f9aac3841f1cfe languageName: node linkType: hard @@ -3372,12 +3372,12 @@ __metadata: linkType: hard "@types/react@npm:^18.2.44": - version: 18.3.3 - resolution: "@types/react@npm:18.3.3" + version: 18.3.4 + resolution: "@types/react@npm:18.3.4" dependencies: "@types/prop-types": "*" csstype: ^3.0.2 - checksum: c63d6a78163244e2022b01ef79b0baec4fe4da3475dc4a90bb8accefad35ef0c43560fd0312e5974f92a0f1108aa4d669ac72d73d66396aa060ea03b5d2e3873 + checksum: 555ccd1af86a23c781dea0360de64b2f7a0708cdcbf9e6496744b77630065868526fd55147c727dc5ef11b7fd712b04f7898757a84c67e2eb9dfd4c4ead10d95 languageName: node linkType: hard @@ -4130,7 +4130,7 @@ __metadata: languageName: node linkType: hard -"babel-plugin-polyfill-corejs3@npm:^0.10.1, babel-plugin-polyfill-corejs3@npm:^0.10.4": +"babel-plugin-polyfill-corejs3@npm:^0.10.6": version: 0.10.6 resolution: "babel-plugin-polyfill-corejs3@npm:0.10.6" dependencies: @@ -4163,24 +4163,27 @@ __metadata: linkType: hard "babel-preset-current-node-syntax@npm:^1.0.0": - version: 1.0.1 - resolution: "babel-preset-current-node-syntax@npm:1.0.1" + version: 1.1.0 + resolution: "babel-preset-current-node-syntax@npm:1.1.0" dependencies: "@babel/plugin-syntax-async-generators": ^7.8.4 "@babel/plugin-syntax-bigint": ^7.8.3 - "@babel/plugin-syntax-class-properties": ^7.8.3 - "@babel/plugin-syntax-import-meta": ^7.8.3 + "@babel/plugin-syntax-class-properties": ^7.12.13 + "@babel/plugin-syntax-class-static-block": ^7.14.5 + "@babel/plugin-syntax-import-attributes": ^7.24.7 + "@babel/plugin-syntax-import-meta": ^7.10.4 "@babel/plugin-syntax-json-strings": ^7.8.3 - "@babel/plugin-syntax-logical-assignment-operators": ^7.8.3 + "@babel/plugin-syntax-logical-assignment-operators": ^7.10.4 "@babel/plugin-syntax-nullish-coalescing-operator": ^7.8.3 - "@babel/plugin-syntax-numeric-separator": ^7.8.3 + "@babel/plugin-syntax-numeric-separator": ^7.10.4 "@babel/plugin-syntax-object-rest-spread": ^7.8.3 "@babel/plugin-syntax-optional-catch-binding": ^7.8.3 "@babel/plugin-syntax-optional-chaining": ^7.8.3 - "@babel/plugin-syntax-top-level-await": ^7.8.3 + "@babel/plugin-syntax-private-property-in-object": ^7.14.5 + "@babel/plugin-syntax-top-level-await": ^7.14.5 peerDependencies: "@babel/core": ^7.0.0 - checksum: d118c2742498c5492c095bc8541f4076b253e705b5f1ad9a2e7d302d81a84866f0070346662355c8e25fc02caa28dc2da8d69bcd67794a0d60c4d6fab6913cc8 + checksum: 9f93fac975eaba296c436feeca1031ca0539143c4066eaf5d1ba23525a31850f03b651a1049caea7287df837a409588c8252c15627ad3903f17864c8e25ed64b languageName: node linkType: hard @@ -5012,11 +5015,11 @@ __metadata: linkType: hard "core-js-compat@npm:^3.37.1, core-js-compat@npm:^3.38.0": - version: 3.38.0 - resolution: "core-js-compat@npm:3.38.0" + version: 3.38.1 + resolution: "core-js-compat@npm:3.38.1" dependencies: browserslist: ^4.23.3 - checksum: bd410be723e3621f7e8c7a4dce91eaefc603d95133da89c042dd961aca368c7281894bd9af14116a455a4473288fb6c121b185cb8a1e8290b8ace15aedb315f2 + checksum: a0a5673bcd59f588f0cd0b59cdacd4712b82909738a87406d334dd412eb3d273ae72b275bdd8e8fef63fca9ef12b42ed651be139c7c44c8a1acb423c8906992e languageName: node linkType: hard @@ -5168,9 +5171,9 @@ __metadata: linkType: hard "dayjs@npm:^1.8.15": - version: 1.11.12 - resolution: "dayjs@npm:1.11.12" - checksum: 40a4f67c2df3af125ae0ddec68d3a6d806d3009a7414bf45479aaf82f1dd82f3e139e6642e72391abccc37488897830c56afcabb4c819014130d283644df8128 + version: 1.11.13 + resolution: "dayjs@npm:1.11.13" + checksum: f388db88a6aa93956c1f6121644e783391c7b738b73dbc54485578736565c8931bdfba4bb94e9b1535c6e509c97d5deb918bbe1ae6b34358d994de735055cca9 languageName: node linkType: hard @@ -5504,9 +5507,9 @@ __metadata: linkType: hard "electron-to-chromium@npm:^1.5.4": - version: 1.5.6 - resolution: "electron-to-chromium@npm:1.5.6" - checksum: 09ca45c781e3e3ef87de87fb74019228f41e1a4abd2e703319aa7d942866815f3df89cc4bf61af81a4cac81271992d4f59a5eca7a093c07322ae0608bf98a427 + version: 1.5.13 + resolution: "electron-to-chromium@npm:1.5.13" + checksum: f18ac84dd3bf9a200654a6a9292b9ec4bced0cf9bd26cec9941b775f4470c581c9d043e70b37a124d9752dcc0f47fc96613d52b2defd8e59632852730cb418b9 languageName: node linkType: hard @@ -6518,9 +6521,9 @@ __metadata: linkType: hard "flow-parser@npm:0.*": - version: 0.243.0 - resolution: "flow-parser@npm:0.243.0" - checksum: e95c931196e502aed946b95e03d6561940db6c200d94b02623e47ac4bd92643f3b3d166c53db4b2133de00b6f750c51fae6874ef34528e35f81f3e11a2a1d0da + version: 0.244.0 + resolution: "flow-parser@npm:0.244.0" + checksum: 0d714d18c82d6cbefc556c306970af4710e77a17de78848e5a03a55207116719d9947f4ccba4666c13409e23bd9cbba2bcf45eb92cb55dd0e0b08747576c1b02 languageName: node linkType: hard @@ -7504,11 +7507,11 @@ __metadata: linkType: hard "is-core-module@npm:^2.13.0, is-core-module@npm:^2.13.1, is-core-module@npm:^2.5.0": - version: 2.15.0 - resolution: "is-core-module@npm:2.15.0" + version: 2.15.1 + resolution: "is-core-module@npm:2.15.1" dependencies: hasown: ^2.0.2 - checksum: a9f7a52707c9b59d7164094d183bda892514fc3ba3139f245219c7abe7f6e8d3e2cdcf861f52a891a467f785f1dfa5d549f73b0ee715f4ba56e8882d335ea585 + checksum: df134c168115690724b62018c37b2f5bba0d5745fa16960b329c5a00883a8bea6a5632fdb1e3efcce237c201826ba09f93197b7cd95577ea56b0df335be23633 languageName: node linkType: hard @@ -8809,9 +8812,9 @@ __metadata: linkType: hard "ky@npm:^1.2.0": - version: 1.5.0 - resolution: "ky@npm:1.5.0" - checksum: 74204547b3bfc358b3edfd207f3b3d30411e652c3892c9f869a2cf74dfadb130b5ef312b3d85cb5b4a363d38f74f1a2df5fe3f643554a61349fe814a29ce8ef3 + version: 1.7.1 + resolution: "ky@npm:1.7.1" + checksum: 3164196db3ad964d7ccf93f4257be30a0c01d4972bd5c06c2ca4b79cce5c9946d35e53a87b24201b421ffad0fa71c5a93310ce1101403ca1b799cd1d73aa8c1d languageName: node linkType: hard @@ -10810,14 +10813,10 @@ __metadata: linkType: hard "react-native-builder-bob@npm:^0.29.0": - version: 0.29.0 - resolution: "react-native-builder-bob@npm:0.29.0" + version: 0.29.1 + resolution: "react-native-builder-bob@npm:0.29.1" dependencies: "@babel/core": ^7.25.2 - "@babel/plugin-transform-class-properties": ^7.24.7 - "@babel/plugin-transform-classes": ^7.25.0 - "@babel/plugin-transform-private-methods": ^7.24.7 - "@babel/plugin-transform-private-property-in-object": ^7.24.7 "@babel/plugin-transform-strict-mode": ^7.24.7 "@babel/preset-env": ^7.25.2 "@babel/preset-flow": ^7.24.7 @@ -10841,7 +10840,7 @@ __metadata: yargs: ^17.5.1 bin: bob: bin/bob - checksum: 2af3139966a37ae07346e3e29a225e0df2a6e5421972288a83d770ec62d91643e32871e7b2b4cb12c59ac21d90c7b580f8ce3bc5bf9785c59960e03613cf1a4d + checksum: 9325ee6b9a41e59e8a0add825318d6c7830b9b2fec1f98c485f722ef60128240146eced878e494c17699cef8ae0cdc92788384f718bcf38078d865969cff6c14 languageName: node linkType: hard @@ -10862,12 +10861,12 @@ __metadata: linkType: hard "react-native-safe-area-context@npm:^4.10.8": - version: 4.10.8 - resolution: "react-native-safe-area-context@npm:4.10.8" + version: 4.10.9 + resolution: "react-native-safe-area-context@npm:4.10.9" peerDependencies: react: "*" react-native: "*" - checksum: eced388ae7cc712f75e43cba302b612c8fecceb8ec8b39cff21b6bc29debe2fdc24423f67609af244d919c3ed871dd1d36c6adc97a8960a938984d333490e653 + checksum: 150bf4fa7607e8de565cce0c45d321048a3c982f4c82cc2a49eb342def968559f538d4554c30c20fa8b2e6dc716c24846745b129dc25396e083d98c2a29962a2 languageName: node linkType: hard @@ -11843,9 +11842,9 @@ __metadata: linkType: hard "spdx-license-ids@npm:^3.0.0": - version: 3.0.18 - resolution: "spdx-license-ids@npm:3.0.18" - checksum: 457825df5dd1fc0135b0bb848c896143f70945cc2da148afc71c73ed0837d1d651f809006e406d82109c9dd71a8cb39785a3604815fe46bc0548e9d3976f6b69 + version: 3.0.20 + resolution: "spdx-license-ids@npm:3.0.20" + checksum: 0c57750bedbcff48f3d0e266fbbdaf0aab54217e182f669542ffe0b5a902dce69e8cdfa126a131e1ddd39a9bef4662e357b2b41315d7240b4a28c0a7e782bb40 languageName: node linkType: hard @@ -12238,8 +12237,8 @@ __metadata: linkType: hard "terser@npm:^5.15.0": - version: 5.31.5 - resolution: "terser@npm:5.31.5" + version: 5.31.6 + resolution: "terser@npm:5.31.6" dependencies: "@jridgewell/source-map": ^0.3.3 acorn: ^8.8.2 @@ -12247,7 +12246,7 @@ __metadata: source-map-support: ~0.5.20 bin: terser: bin/terser - checksum: edd2de6569116f637bb2e154c913e6ea434cadd6bcf5df41cf62000e3c69ec41f95be66fe639b57b95564545c9f7c0fcb28ca63351424a08e244a8b49ed12ac3 + checksum: 60d3faf39c9ad7acc891e17888bbd206e0b777f442649cf49873a5fa317b8b8a17179a46970d884d5f93e8addde0206193ed1e2e4f1ccb1cafb167f7d1ddee96 languageName: node linkType: hard @@ -12529,9 +12528,9 @@ __metadata: linkType: hard "type-fest@npm:^4.2.0": - version: 4.24.0 - resolution: "type-fest@npm:4.24.0" - checksum: f02e28bb12ce290f9880937b3c65d3cc4f0fdd89776b4b78d392aece0eb031c7ea9c1d6c536011c63bb8d0a6c72359336287f23f940204508a42a442c98c4d25 + version: 4.25.0 + resolution: "type-fest@npm:4.25.0" + checksum: 4c318d0fbfc6e51bb52db6ce4609066888e45346594db502e6040681fc1985a9f5845a80b6bd46ca64310271d1cdee52a9110ccabaf199b978388b098bbc5985 languageName: node linkType: hard @@ -12658,10 +12657,10 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~6.13.0": - version: 6.13.0 - resolution: "undici-types@npm:6.13.0" - checksum: 9d0ef6bf58994bebbea6a4ab75f381c69a89a7ed151bfbae0d4ef95450d56502c9eccb323abf17b7d099c1d9c1cbae62e909e4dfeb8d204612d2f1fdada24707 +"undici-types@npm:~6.19.2": + version: 6.19.8 + resolution: "undici-types@npm:6.19.8" + checksum: de51f1b447d22571cf155dfe14ff6d12c5bdaec237c765085b439c38ca8518fc360e88c70f99469162bf2e14188a7b0bcb06e1ed2dc031042b984b0bb9544017 languageName: node linkType: hard From ee6126f6a24e1112d8aeb26937693ae5908f1907 Mon Sep 17 00:00:00 2001 From: Aleksandar Palic <1425202+codepushr@users.noreply.github.com> Date: Thu, 22 Aug 2024 16:08:11 +0200 Subject: [PATCH 21/36] chore: disable unsupported devices; remove xcode github workflow step --- .github/workflows/test.yml | 4 --- .../project.pbxproj | 32 +++++++++++++------ example/ios/Podfile.lock | 4 +-- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7971a55..6b01051 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -47,10 +47,6 @@ jobs: - name: Get swift version in macOS if: runner.os == 'macOS' run: xcrun --toolchain ${{ env.TOOLCHAINS }} swift --version - - uses: irgaly/xcode-cache@v1 - with: - key: xcode-cache-deriveddata-${{ github.workflow }}-${{ github.sha }} - restore-keys: xcode-cache-deriveddata-${{ github.workflow }}- - run: corepack enable - run: yarn - run: cd example; bundle; cd - diff --git a/example/ios/ExampleAppExample.xcodeproj/project.pbxproj b/example/ios/ExampleAppExample.xcodeproj/project.pbxproj index 92ed2f6..4e4c346 100644 --- a/example/ios/ExampleAppExample.xcodeproj/project.pbxproj +++ b/example/ios/ExampleAppExample.xcodeproj/project.pbxproj @@ -394,8 +394,13 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.evva.xesar.mcs; PRODUCT_NAME = ExampleAppExample; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; @@ -421,7 +426,12 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.evva.xesar.mcs; PRODUCT_NAME = ExampleAppExample; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; @@ -451,11 +461,15 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.evva.xesasr.mcs.ExampleAppExampleTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = 1; }; name = Debug; }; @@ -484,10 +498,14 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.evva.xesasr.mcs.ExampleAppExampleTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = 1; }; name = Release; }; @@ -575,10 +593,7 @@ "-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1", ); - OTHER_LDFLAGS = ( - "$(inherited)", - " ", - ); + OTHER_LDFLAGS = "$(inherited) "; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; @@ -661,10 +676,7 @@ "-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1", ); - OTHER_LDFLAGS = ( - "$(inherited)", - " ", - ); + OTHER_LDFLAGS = "$(inherited) "; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 610d80f..eb28ed5 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - abrevva-react-native (0.1.3): + - abrevva-react-native (0.1.5): - AbrevvaSDK (~> 1.0.23) - CocoaMQTT - CryptoSwift @@ -1440,7 +1440,7 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - abrevva-react-native: cee28cab53b8e2f4b8cd543785817258ce246e93 + abrevva-react-native: 6980035371bf192213003a60418276baced5d569 AbrevvaSDK: 4edd60273dd1b2d171e8adb5a84756aa5084347c boost: d3f49c53809116a5d38da093a8aa78bf551aed09 CocoaMQTT: 1f206228b29318eabdacad0c2e4e88575922c27a From bebc7d00371ba7abf8b8f952fd2b0af13bef930a Mon Sep 17 00:00:00 2001 From: Aleksandar Palic <1425202+codepushr@users.noreply.github.com> Date: Thu, 22 Aug 2024 16:59:05 +0200 Subject: [PATCH 22/36] chore: adapt makefile and github ios workflow --- .github/workflows/test.yml | 7 +++++-- Makefile | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6b01051..ad0c126 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,6 +22,7 @@ jobs: cache: 'yarn' - run: yarn - run: yarn test + test-ios: runs-on: macos-latest strategy: @@ -39,12 +40,14 @@ jobs: with: ruby-version: '3.3' bundler-cache: true + - name: "Get Ruby Version" + run: ruby --version - uses: SwiftyLab/setup-swift@latest with: swift-version: "5.10.0" - - name: Get swift version + - name: Get Swift Version run: swift --version - - name: Get swift version in macOS + - name: Set Swift Version if: runner.os == 'macOS' run: xcrun --toolchain ${{ env.TOOLCHAINS }} swift --version - run: corepack enable diff --git a/Makefile b/Makefile index 6a88793..95df5f4 100644 --- a/Makefile +++ b/Makefile @@ -7,4 +7,4 @@ test-ios: -scheme ExampleAppExample \ -sdk iphonesimulator \ -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.4' \ - test + test || exit 1 From ec93e42312aaba4e144875b541697fbbf6cab9fc Mon Sep 17 00:00:00 2001 From: Aleksandar Palic <1425202+codepushr@users.noreply.github.com> Date: Fri, 23 Aug 2024 09:35:21 +0200 Subject: [PATCH 23/36] ci: display xcodebuild version --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ad0c126..ed70dc6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -50,6 +50,8 @@ jobs: - name: Set Swift Version if: runner.os == 'macOS' run: xcrun --toolchain ${{ env.TOOLCHAINS }} swift --version + - name: Get Xcode version + run: xcodebuild -version - run: corepack enable - run: yarn - run: cd example; bundle; cd - From 7d713ff0a4c032c9ee506115d9fbed05a41c0b09 Mon Sep 17 00:00:00 2001 From: Aleksandar Palic <1425202+codepushr@users.noreply.github.com> Date: Fri, 23 Aug 2024 09:59:23 +0200 Subject: [PATCH 24/36] ci: list xcode versions --- .github/workflows/test.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ed70dc6..6ec3022 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -50,10 +50,12 @@ jobs: - name: Set Swift Version if: runner.os == 'macOS' run: xcrun --toolchain ${{ env.TOOLCHAINS }} swift --version + - name: List available Xcode versions + run: ls /Applications | grep Xcode - name: Get Xcode version run: xcodebuild -version - - run: corepack enable - - run: yarn - - run: cd example; bundle; cd - - - run: cd example/ios; bundle exec pod install; cd - - - run: make test +# - run: corepack enable +# - run: yarn +# - run: cd example; bundle; cd - +# - run: cd example/ios; bundle exec pod install; cd - +# - run: make test From a667454b687b434772784bf351309a6c8e801df9 Mon Sep 17 00:00:00 2001 From: Aleksandar Palic <1425202+codepushr@users.noreply.github.com> Date: Fri, 23 Aug 2024 10:04:27 +0200 Subject: [PATCH 25/36] ci: set xcode version --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6ec3022..a144a3c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,6 +52,8 @@ jobs: run: xcrun --toolchain ${{ env.TOOLCHAINS }} swift --version - name: List available Xcode versions run: ls /Applications | grep Xcode + - name: Set Xcode version + run: xcode-select -s /Applications/Xcode_15.4.app/Contents/Developer - name: Get Xcode version run: xcodebuild -version # - run: corepack enable From bf9cbe06b3842a6295697b8d0b5eb6b80d3a546b Mon Sep 17 00:00:00 2001 From: Aleksandar Palic <1425202+codepushr@users.noreply.github.com> Date: Fri, 23 Aug 2024 10:09:02 +0200 Subject: [PATCH 26/36] ci: use sudo due to xcode-select error --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a144a3c..7123377 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -53,7 +53,7 @@ jobs: - name: List available Xcode versions run: ls /Applications | grep Xcode - name: Set Xcode version - run: xcode-select -s /Applications/Xcode_15.4.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_15.4.app/Contents/Developer - name: Get Xcode version run: xcodebuild -version # - run: corepack enable From af01f048b54f71538426ec6a0388cf8ef1908b36 Mon Sep 17 00:00:00 2001 From: Aleksandar Palic <1425202+codepushr@users.noreply.github.com> Date: Fri, 23 Aug 2024 10:13:55 +0200 Subject: [PATCH 27/36] ci: show available software --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7123377..38030cf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -56,6 +56,8 @@ jobs: run: sudo xcode-select -s /Applications/Xcode_15.4.app/Contents/Developer - name: Get Xcode version run: xcodebuild -version + - name: Show available software + run: softwareupdate --list --all # - run: corepack enable # - run: yarn # - run: cd example; bundle; cd - From bfb769c2359bed374d60853fd7c9ff55c8937a44 Mon Sep 17 00:00:00 2001 From: Aleksandar Palic <1425202+codepushr@users.noreply.github.com> Date: Fri, 23 Aug 2024 10:24:25 +0200 Subject: [PATCH 28/36] ci: print env.toolchains --- .github/workflows/test.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 38030cf..84bdabe 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -48,8 +48,9 @@ jobs: - name: Get Swift Version run: swift --version - name: Set Swift Version - if: runner.os == 'macOS' - run: xcrun --toolchain ${{ env.TOOLCHAINS }} swift --version + run: | + echo ${{ env.TOOLCHAINS }} + xcrun --toolchain ${{ env.TOOLCHAINS }} swift --version - name: List available Xcode versions run: ls /Applications | grep Xcode - name: Set Xcode version From 3141efd0ba9f55e493c4a0117a255a05eaf125ef Mon Sep 17 00:00:00 2001 From: Aleksandar Palic <1425202+codepushr@users.noreply.github.com> Date: Fri, 23 Aug 2024 10:30:07 +0200 Subject: [PATCH 29/36] ci: try developer_dir env var --- .github/workflows/test.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 84bdabe..5f1fa03 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -47,15 +47,13 @@ jobs: swift-version: "5.10.0" - name: Get Swift Version run: swift --version - - name: Set Swift Version - run: | - echo ${{ env.TOOLCHAINS }} - xcrun --toolchain ${{ env.TOOLCHAINS }} swift --version - name: List available Xcode versions run: ls /Applications | grep Xcode - - name: Set Xcode version - run: sudo xcode-select -s /Applications/Xcode_15.4.app/Contents/Developer +# - name: Set Xcode version +# run: sudo xcode-select -s /Applications/Xcode_15.4.app/Contents/Developer - name: Get Xcode version + env: + DEVELOPER_DIR: /Applications/Xcode_15.4.app run: xcodebuild -version - name: Show available software run: softwareupdate --list --all From ae7d9ab09e8c98f79eda89e07b56b753e30200b7 Mon Sep 17 00:00:00 2001 From: Aleksandar Palic <1425202+codepushr@users.noreply.github.com> Date: Fri, 23 Aug 2024 10:33:17 +0200 Subject: [PATCH 30/36] ci: set proper xcode version for test command --- .github/workflows/test.yml | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5f1fa03..4251df5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -47,18 +47,15 @@ jobs: swift-version: "5.10.0" - name: Get Swift Version run: swift --version - - name: List available Xcode versions - run: ls /Applications | grep Xcode -# - name: Set Xcode version -# run: sudo xcode-select -s /Applications/Xcode_15.4.app/Contents/Developer - name: Get Xcode version env: DEVELOPER_DIR: /Applications/Xcode_15.4.app run: xcodebuild -version - - name: Show available software - run: softwareupdate --list --all -# - run: corepack enable -# - run: yarn -# - run: cd example; bundle; cd - -# - run: cd example/ios; bundle exec pod install; cd - -# - run: make test + - run: corepack enable + - run: yarn + - run: cd example; bundle; cd - + - run: cd example/ios; bundle exec pod install; cd - + - name: Run Tests + env: + DEVELOPER_DIR: /Applications/Xcode_15.4.app + run: make test From 014c9e66c41f7a66bf88b92b4aeee1ecb174d8d6 Mon Sep 17 00:00:00 2001 From: Aleksandar Palic <1425202+codepushr@users.noreply.github.com> Date: Fri, 23 Aug 2024 10:48:39 +0200 Subject: [PATCH 31/36] chore: bump readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6b5588d..8322b04 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ The EVVA React-Native Module is a collection of tools to work with electronical - Java 17+ (Android) - Android SDK (Android) - Android 10+ (API level 29) (Android) -- Xcode 15+ (iOS) +- Xcode 15.4 (iOS) - iOS 15.0+ (iOS) ## Installation From 5a2b7bcfcfbd74cf3d620f93bb831f74f1ad4e1b Mon Sep 17 00:00:00 2001 From: Aleksandar Palic <1425202+codepushr@users.noreply.github.com> Date: Fri, 23 Aug 2024 13:16:22 +0200 Subject: [PATCH 32/36] ci: add android test step --- .editorconfig | 3 + .github/workflows/test.yml | 34 ++- .gitignore | 1 + Makefile | 14 +- android/.idea/.gitignore | 3 + android/.idea/android.iml | 9 + android/.idea/caches/deviceStreaming.xml | 285 +++++++++++++++++++++++ android/.idea/gradle.xml | 12 + android/.idea/misc.xml | 4 + android/.idea/modules.xml | 8 + android/.idea/vcs.xml | 6 + 11 files changed, 373 insertions(+), 6 deletions(-) create mode 100644 android/.idea/.gitignore create mode 100644 android/.idea/android.iml create mode 100644 android/.idea/caches/deviceStreaming.xml create mode 100644 android/.idea/gradle.xml create mode 100644 android/.idea/misc.xml create mode 100644 android/.idea/modules.xml create mode 100644 android/.idea/vcs.xml diff --git a/.editorconfig b/.editorconfig index 36bc585..1f88320 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,6 +8,9 @@ indent_size = 2 insert_final_newline = true trim_trailing_whitespace = true +[Makefile] +indent_size = 4 + [*.gradle] indent_size = 4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4251df5..c0cf0e5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -58,4 +58,36 @@ jobs: - name: Run Tests env: DEVELOPER_DIR: /Applications/Xcode_15.4.app - run: make test + run: make test-ios + + test-android: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + - name: Setup Nodejs + uses: actions/setup-node@v4 + with: + node-version: '22' + - name: Cache Gradle + id: cache-gradle + uses: actions/cache@v4 + with: + path: | + .gradle + example/android/.gradle + key: ${{ runner.os }}-gradle + - run: | + corepack enable + yarn + - name: Run Gradle + if: steps.cache-gradle.outputs.cache-hit != 'true' + run: ./gradlew + - name: Run Tests + run: make test-android diff --git a/.gitignore b/.gitignore index c0eae07..ffe0b15 100644 --- a/.gitignore +++ b/.gitignore @@ -140,6 +140,7 @@ example/vendor/ example/android/.idea/ example/android/local.properties android/build +android/local.properties # Exclude because this repo forces to use yarn instead of npm package-lock.json diff --git a/Makefile b/Makefile index 95df5f4..8affb42 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,14 @@ test: $(MAKE) test-ios + $(MAKE) test-android test-ios: xcodebuild -quiet \ - -workspace example/ios/ExampleAppExample.xcworkspace \ - -scheme ExampleAppExample \ - -sdk iphonesimulator \ - -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.4' \ - test || exit 1 + -workspace example/ios/ExampleAppExample.xcworkspace \ + -scheme ExampleAppExample \ + -sdk iphonesimulator \ + -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.4' \ + test || exit 1 + +test-android: + cd example/android && ./gradlew :evva-sfw_abrevva-react-native:testDebugUnitTest && cd - diff --git a/android/.idea/.gitignore b/android/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/android/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/android/.idea/android.iml b/android/.idea/android.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/android/.idea/android.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/caches/deviceStreaming.xml b/android/.idea/caches/deviceStreaming.xml new file mode 100644 index 0000000..a154f16 --- /dev/null +++ b/android/.idea/caches/deviceStreaming.xml @@ -0,0 +1,285 @@ + + + + + + \ No newline at end of file diff --git a/android/.idea/gradle.xml b/android/.idea/gradle.xml new file mode 100644 index 0000000..1912657 --- /dev/null +++ b/android/.idea/gradle.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/android/.idea/misc.xml b/android/.idea/misc.xml new file mode 100644 index 0000000..6ed36dd --- /dev/null +++ b/android/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/android/.idea/modules.xml b/android/.idea/modules.xml new file mode 100644 index 0000000..9dddca5 --- /dev/null +++ b/android/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/android/.idea/vcs.xml b/android/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/android/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file From c9f406d237727374e47bebc793cf534080a1c33d Mon Sep 17 00:00:00 2001 From: Aleksandar Palic <1425202+codepushr@users.noreply.github.com> Date: Fri, 23 Aug 2024 13:17:55 +0200 Subject: [PATCH 33/36] ci: add exit 1 for all other exit types on android --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8affb42..713f6ec 100644 --- a/Makefile +++ b/Makefile @@ -11,4 +11,4 @@ test-ios: test || exit 1 test-android: - cd example/android && ./gradlew :evva-sfw_abrevva-react-native:testDebugUnitTest && cd - + cd example/android && ./gradlew :evva-sfw_abrevva-react-native:testDebugUnitTest || exit 1 From 438579925498a519d1a4fdc18932e8f015b90fa2 Mon Sep 17 00:00:00 2001 From: Aleksandar Palic <1425202+codepushr@users.noreply.github.com> Date: Fri, 23 Aug 2024 13:23:01 +0200 Subject: [PATCH 34/36] ci: add npm cache --- .github/workflows/test.yml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c0cf0e5..206028f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -75,19 +75,27 @@ jobs: uses: actions/setup-node@v4 with: node-version: '22' + - name: Cache NPM + id: cache-test-android-node-modules + uses: actions/cache@v4 + with: + path: | + node_modules + example/node_modules + key: ${{ runner.os }}_cache-test-android-node-modules - name: Cache Gradle - id: cache-gradle + id: cache-test-android-gradle uses: actions/cache@v4 with: path: | .gradle example/android/.gradle - key: ${{ runner.os }}-gradle + key: ${{ runner.os }}_cache-test-android-gradle - run: | corepack enable yarn - name: Run Gradle if: steps.cache-gradle.outputs.cache-hit != 'true' - run: ./gradlew + run: example/android/gradlew - name: Run Tests run: make test-android From 6006289aabbc7a9501d075934fd29880c321c8a1 Mon Sep 17 00:00:00 2001 From: Aleksandar Palic <1425202+codepushr@users.noreply.github.com> Date: Fri, 23 Aug 2024 14:50:40 +0200 Subject: [PATCH 35/36] ci: streamline node_modules cache; add derived-data cache --- .github/workflows/test.yml | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 206028f..42ff8b0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -51,6 +51,19 @@ jobs: env: DEVELOPER_DIR: /Applications/Xcode_15.4.app run: xcodebuild -version + - name: Cache node_modules + id: cache-test-node-modules + uses: actions/cache@v4 + with: + path: | + node_modules + example/node_modules + key: cache-test-node-modules + - name: Cache Xcode DerivedData + uses: irgaly/xcode-cache@v1.7.2 + with: + key: cache-test-xcode-deriveddata-${{ github.workflow }}-${{ github.sha }} + restore-keys: cache-test-xcode-deriveddata-${{ github.workflow }}- - run: corepack enable - run: yarn - run: cd example; bundle; cd - @@ -75,14 +88,14 @@ jobs: uses: actions/setup-node@v4 with: node-version: '22' - - name: Cache NPM - id: cache-test-android-node-modules + - name: Cache node_modules + id: cache-test-node-modules uses: actions/cache@v4 with: path: | node_modules example/node_modules - key: ${{ runner.os }}_cache-test-android-node-modules + key: cache-test-node-modules - name: Cache Gradle id: cache-test-android-gradle uses: actions/cache@v4 From d0ac466824408914777376687590e585a3dd7eba Mon Sep 17 00:00:00 2001 From: Aleksandar Palic <1425202+codepushr@users.noreply.github.com> Date: Fri, 23 Aug 2024 15:32:09 +0200 Subject: [PATCH 36/36] ci: add cache for pods --- .github/workflows/test.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 42ff8b0..880cc2e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -51,6 +51,13 @@ jobs: env: DEVELOPER_DIR: /Applications/Xcode_15.4.app run: xcodebuild -version + - name: Cache Pods + id: cache-test-ios-pods + uses: actions/cache@v4 + with: + path: | + example/ios/Pods + key: cache-test-ios-pods - name: Cache node_modules id: cache-test-node-modules uses: actions/cache@v4