## Create react native app without expo ``` npx @react-native-community/cli@latest init KExampleProject ``` ## Install `react-native-status-keycard` library ``` npm install https://github.com/keycard-tech/react-native-status-keycard ``` ### Android 1. Add the following lines inside the buildscript block in `android/build.gradle`: ``` subprojects { subproject -> afterEvaluate{ if((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) { android { compileSdkVersion rootProject.ext.compileSdkVersion buildToolsVersion rootProject.ext.buildToolsVersion } } } } ``` This will apply compileSdkVersion and buildToolsVersion to any android modules you have. 2. Update `android/settings.gradle` to add `react-native-status-keycard` library: ``` include ':react-native-status-keycard' project(':react-native-status-keycard').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-status-keycard/android') ``` 3. Provide NFC & Camera permissions by updating `android/app/src/main/AndroidManifest.xml` to : ``` <uses-permission android:name="android.permission.NFC"/> <uses-permission android:name="android.permission.CAMERA"/> <uses-feature android:name="android.hardware.nfc.hce" android:required="true" /> ``` 4. Insert the following lines inside the dependencies block in `android/app/build.gradle`: ``` implementation project(':react-native-status-keycard') ``` ### iOS 1. Add pods to `target` block inside `ios/Podfile` : ``` pod 'SSZipArchive' pod "react-native-status-keycard", path: "../node_modules/react-native-status-keycard" pod "Keycard", git: "https://github.com/status-im/Keycard.swift.git", tag: '3.1.2' pod 'secp256k1', git: "https://github.com/status-im/secp256k1.swift.git", submodules: true ``` In terminal: ``` cd ios pod install ``` 2. Insert the following lines inside `dict` block of `ios/<ProjectDir>/Info.plist` to configure NFC and camera permissions: ``` <key>NFCReaderUsageDescription</key> <string>$(PRODUCT_NAME) needs access to NFC to interact with Keycard.</string> <key>NSCameraUsageDescription</key> <string>$(PRODUCT_NAME) needs access to your Camera in order scan the QR codes.</string> ... <key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key> <array> <string>A00000080400010101</string> <string>A00000080400010301</string> <string>A000000151000000</string> </array> ``` 3. Open your project in Xcode, go to `Signing & Capabilities` and add `Near Field Communication Tag Reading` capability. ### Compiling issues #### Android ~~The app will not compile on android unless you change ` jcenter()` to `mavenCentral()` in `android/build.gradle` of `react-native-status-keycard` library, as JCenter is no longer available.~~ The issue has been solved in [this commit](https://github.com/keycard-tech/react-native-status-keycard/commit/86775cd5419e1373a290b88f1d0942fabc8314bc). #### iOS ~~The app will not compile on ios unless you convert `result.responseData.bytes` of `Sources/Keycard/CoreNFCCardChannel.swift` from `RawSpan` to `[UInt8]` in `Keycard.swift` library.~~ The issue has been solved in [this commit](https://github.com/keycard-tech/Keycard.swift/commit/120052c614d5cc2e839feac5fc1ff01824a4d50e). ##### Example solution Write `result.responseData` to [UInt8] in `send` function instead of returnig `result.responseData.bytes` ``` public func send(_ cmd: APDUCommand) throws -> APDUResponse ... var array = [UInt8](repeating: 0, count: result.responseData.count) for i in 0..<array.count { array[i] = result.responseData[i] } return APDUResponse(sw1: result.sw1, sw2: result.sw2, data: array) ... ``` ## Usage In App.tsx ``` import { NativeEventEmitter } from 'react-native'; ... //@ts-ignore import Keycard from "react-native-status-keycard"; const App() => { ... useEffect(() => { const eventEmitter = new NativeEventEmitter(Keycard); let onConnectedListener = eventEmitter.addListener('keyCardOnConnected', () => console.log("keycard connected")); let onDisconnectedListener = eventEmitter.addListener('keyCardOnDisconnected', () => console.log("keycard disconnected")); let onNFCEnabledListener = eventEmitter.addListener('keyCardOnNFCEnabled', () => console.log("nfc enabled")); let onNFCDisabledListener = eventEmitter.addListener('keyCardOnNFCDisabled', () => console.log("nfc disabled")); }) ... const connectCard = async () => { if (await Keycard.nfcIsSupported() && !await Keycard.nfcIsEnabled()) { await Keycard.openNfcSettings(); } await Keycard.startNFC("Tap your Keycard"); #do something } } ``` ## Create an expo app A development build should be created to be able to use `react-native-status-keycard` library in expo app. More about switching from Expo Go to development build [here](https://docs.expo.dev/develop/development-builds/expo-go-to-dev-build/). There are 2 options: * build on a local machine * build on EAS Check [Expo Docs](https://docs.expo.dev/develop/development-builds/create-a-build/) for more details about development build. ### Install `react-native-status-keycard` ``` npm install https://github.com/keycard-tech/react-native-status-keycard ``` ### Configure your project to use `react-native-status-keycard` #### 1. Update `app.json` file to to configure NFC permissions Android ``` "android": { ... "permissions": ["android.permission.NFC"], ... ``` iOS ``` ... "ios": { .... "entitlements": { "com.apple.developer.nfc.readersession.formats": ["TAG"] }, "infoPlist": { "NFCReaderUsageDescription": "This app needs access to NFC to interact with Keycard.", "com.apple.developer.nfc.readersession.iso7816.select-identifiers": [ "A00000080400010101", "A00000080400010301", "A000000151000000" ] } }, ``` #### 2. Write a custom config plugin for `react-native-status-keycard` to make custom native changes In project directory, create a `plugins` directory and add `custom-config.ts` Example `custom-config.ts` ``` import { ExpoConfig } from '@expo/config-types'; import { ConfigPlugin, withAndroidManifest, withAppBuildGradle, withDangerousMod, withProjectBuildGradle, withSettingsGradle, } from 'expo/config-plugins'; import fs from "fs"; import path from 'path'; // Set the android.hardware.nfc.hce feature required to true in AndroidManifest.xml const withAndroidFeatures: ConfigPlugin = config => { return withAndroidManifest(config, async (config: any) => { const existingFeature = config.modResults.manifest['uses-feature']?.find((p: any) => p['$'] && p['$']['android:name'] === 'android.hardware.nfc.hce'); if (!existingFeature) { config.modResults.manifest['uses-feature'] = config.modResults.manifest['uses-feature'] ?? []; config.modResults.manifest['uses-feature'].push({ $: { 'android:name': 'android.hardware.nfc.hce', 'android:required': 'true' }, }); } return config; }); } // Add ext and subprojects configuration blocks to buildscript block in project build.gradle const withBuildGradle: ConfigPlugin = config => { return withProjectBuildGradle(config, async config => { const buildScriptBlock = 'buildscript {'; const subprojectsBlock = `ext { buildToolsVersion = "36.0.0" minSdkVersion = 24 compileSdkVersion = 36 targetSdkVersion = 36 ndkVersion = "27.1.12297006" kotlinVersion = "2.2.20" } subprojects { subproject -> afterEvaluate{ if((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) { android { compileSdkVersion rootProject.ext.compileSdkVersion buildToolsVersion rootProject.ext.buildToolsVersion } } } }`; if (config.modResults.contents.includes(buildScriptBlock)) { config.modResults.contents = config.modResults.contents.replace( buildScriptBlock, `${buildScriptBlock}\n${subprojectsBlock}` ); } else { config.modResults.contents += `${buildScriptBlock}\n${subprojectsBlock}` } return config; }); }; // Add the 'implementation' configuration to the dependencies in app/build.gradle const withApplicationBuildGradle: ConfigPlugin = config => { return withAppBuildGradle(config, async config => { const dependenciesBlock = 'dependencies {'; const implementationLine = " implementation project(':react-native-status-keycard')"; if (config.modResults.contents.includes(dependenciesBlock)) { config.modResults.contents = config.modResults.contents.replace( dependenciesBlock, `${dependenciesBlock}\n${implementationLine}` ); } return config; }); }; // Add the 'include' and 'project' configuration for react-native-status-keycard in settings.gradle const withGradleSettings: ConfigPlugin = config => { return withSettingsGradle(config, async config => { const includeKeycardLine = `include ':react-native-status-keycard'`; const projectKeycardLine = `project(':react-native-status-keycard').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-status-keycard/android')`; config.modResults.contents += `${includeKeycardLine}\n${projectKeycardLine}`; return config; }); } // Add configuration for keycard pods in Podfile const withPodKeycardPodfile: ConfigPlugin = config => { return withDangerousMod(config, [ 'ios', async config => { const podfilePath = path.join( config.modRequest.platformProjectRoot, "Podfile", ); let podfileContent = fs.readFileSync(podfilePath, "utf8"); // Keycard and its dependencies const keycardPods = `pod "SSZipArchive" pod "react-native-status-keycard", path: "../node_modules/react-native-status-keycard" pod "Keycard", git: "https://github.com/status-im/Keycard.swift.git", tag: '3.1.2' pod "secp256k1", git: "https://github.com/status-im/secp256k1.swift.git", submodules: true`; if (!podfileContent.includes("pod 'Keycard'")) { podfileContent = podfileContent.replace( /use_expo_modules!/, `use_expo_modules!\n${keycardPods}`, ); fs.writeFileSync(podfilePath, podfileContent); } return config; }, ]); } //configuration function const withKeycardSDK = (config: ExpoConfig) => { config = withBuildGradle(config); config = withApplicationBuildGradle(config); config = withAndroidFeatures(config); config = withGradleSettings(config); config = withPodKeycardPodfile(config); return config; }; export default withKeycardSDK; ``` #### 3. Create `app.config.ts` for dynamic plugins configuration in `app.json` Example code to dynamically update plugins block adding `custom-config` plugin: ``` import "tsx/cjs"; import withKeycardSDK from './plugins/custom-config'; export default ({ config }) => { if (!config.plugins) config.plugins = []; config.plugins.push( withKeycardSDK, ); return config; }; ``` #### 4. Prebuild app with `npx expo prebuild --clean`