diff --git a/README.md b/README.md index a285e93..4a36f3a 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,14 @@ v18.16.0 > **Note**: Make sure you have completed the [React Native - Environment Setup](https://reactnative.dev/docs/environment-setup) instructions till "Creating a new application" step, before proceeding. +## Step 0: Install dependencies + +### Install node_modules + +```bash +yarn install +``` + ## Step 1: Start the Metro Server First, you will need to start **Metro**, the JavaScript _bundler_ that ships _with_ React Native. diff --git a/ios/CoolWalletAppDemo.xcodeproj/project.pbxproj b/ios/CoolWalletAppDemo.xcodeproj/project.pbxproj index d21849a..8816557 100644 --- a/ios/CoolWalletAppDemo.xcodeproj/project.pbxproj +++ b/ios/CoolWalletAppDemo.xcodeproj/project.pbxproj @@ -43,6 +43,7 @@ 5DCACB8F33CDC322A6C60F78 /* libPods-CoolWalletAppDemo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-CoolWalletAppDemo.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = CoolWalletAppDemo/LaunchScreen.storyboard; sourceTree = ""; }; 89C6BE57DB24E9ADA2F236DE /* Pods-CoolWalletAppDemo-CoolWalletAppDemoTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CoolWalletAppDemo-CoolWalletAppDemoTests.release.xcconfig"; path = "Target Support Files/Pods-CoolWalletAppDemo-CoolWalletAppDemoTests/Pods-CoolWalletAppDemo-CoolWalletAppDemoTests.release.xcconfig"; sourceTree = ""; }; + CCB436F82C2A639400BC0ECD /* CoolWalletAppDemo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = CoolWalletAppDemo.entitlements; path = CoolWalletAppDemo/CoolWalletAppDemo.entitlements; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -86,6 +87,7 @@ 13B07FAE1A68108700A75B9A /* CoolWalletAppDemo */ = { isa = PBXGroup; children = ( + CCB436F82C2A639400BC0ECD /* CoolWalletAppDemo.entitlements */, 13B07FAF1A68108700A75B9A /* AppDelegate.h */, 13B07FB01A68108700A75B9A /* AppDelegate.mm */, 13B07FB51A68108700A75B9A /* Images.xcassets */, @@ -485,6 +487,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = CoolWalletAppDemo/CoolWalletAppDemo.entitlements; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 87HB2LFXY3; ENABLE_BITCODE = NO; @@ -513,6 +516,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = CoolWalletAppDemo/CoolWalletAppDemo.entitlements; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 87HB2LFXY3; INFOPLIST_FILE = CoolWalletAppDemo/Info.plist; diff --git a/ios/CoolWalletAppDemo/CoolWalletAppDemo.entitlements b/ios/CoolWalletAppDemo/CoolWalletAppDemo.entitlements new file mode 100644 index 0000000..91c9872 --- /dev/null +++ b/ios/CoolWalletAppDemo/CoolWalletAppDemo.entitlements @@ -0,0 +1,11 @@ + + + + + com.apple.developer.nfc.readersession.formats + + NDEF + TAG + + + diff --git a/ios/CoolWalletAppDemo/Info.plist b/ios/CoolWalletAppDemo/Info.plist index ca18c98..18ec9ec 100644 --- a/ios/CoolWalletAppDemo/Info.plist +++ b/ios/CoolWalletAppDemo/Info.plist @@ -68,5 +68,12 @@ UIViewControllerBasedStatusBarAppearance + com.apple.developer.nfc.readersession.iso7816.select-identifiers + + 436F6F6C57616C6C65744C495445 + 4261636B75704170706C6574 + + NFCReaderUsageDescription + We need to use NFC diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d02a696..8c009f1 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -381,6 +381,8 @@ PODS: - React-Core - react-native-get-random-values (1.11.0): - React-Core + - react-native-nfc-manager (3.14.14): + - React-Core - react-native-pager-view (6.3.1): - RCT-Folly (= 2021.07.22.00) - React-Core @@ -573,6 +575,7 @@ DEPENDENCIES: - React-logger (from `../node_modules/react-native/ReactCommon/logger`) - react-native-ble-plx (from `../node_modules/react-native-ble-plx`) - react-native-get-random-values (from `../node_modules/react-native-get-random-values`) + - react-native-nfc-manager (from `../node_modules/react-native-nfc-manager`) - react-native-pager-view (from `../node_modules/react-native-pager-view`) - react-native-randombytes (from `../node_modules/react-native-randombytes`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) @@ -670,6 +673,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-ble-plx" react-native-get-random-values: :path: "../node_modules/react-native-get-random-values" + react-native-nfc-manager: + :path: "../node_modules/react-native-nfc-manager" react-native-pager-view: :path: "../node_modules/react-native-pager-view" react-native-randombytes: @@ -768,6 +773,7 @@ SPEC CHECKSUMS: React-logger: da1ebe05ae06eb6db4b162202faeafac4b435e77 react-native-ble-plx: f10240444452dfb2d2a13a0e4f58d7783e92d76e react-native-get-random-values: 21325b2244dfa6b58878f51f9aa42821e7ba3d06 + react-native-nfc-manager: ead2cd1d1eaab139cc8b2dc5fbfe575eaa45efc4 react-native-pager-view: 712081434052f3af89ef00e8a353a7d351e62eeb react-native-randombytes: 421f1c7d48c0af8dbcd471b0324393ebf8fe7846 react-native-safe-area-context: 399a5859f6acbdf67f671c69b53113f535f3b5b0 diff --git a/package.json b/package.json index ad93aba..73963d9 100644 --- a/package.json +++ b/package.json @@ -5,13 +5,14 @@ "scripts": { "android": "react-native run-android", "ios": "react-native run-ios", + "ios:install-pods": "bundle install && cd ios && bundle exec pod install", "lint": "eslint .", "start": "react-native start", "test": "jest", "adbremote": "adb reverse tcp:8081 tcp:8081", "patch:rn-nodeify": "npx rn-nodeify --install --hack", "patch:jetify": "npx jetify", - "postinstall": "yarn patch:rn-nodeify && yarn patch:jetify && react-native setup-ios-permissions && pod-install" + "postinstall": "yarn patch:rn-nodeify && yarn patch:jetify && react-native setup-ios-permissions && yarn ios:install-pods" }, "reactNativePermissionsIOS": [ "BluetoothPeripheral", @@ -20,8 +21,10 @@ "LocationWhenInUse" ], "dependencies": { - "@coolwallet/core": "^1.1.18", - "@coolwallet/evm": "^1.0.27", + "@coolwallet/core": "2.0.0-beta.7", + "@coolwallet/evm": "^2.0.0-beta.0", + "@coolwallet/transport-jre-http": "2.0.0-beta.1", + "@coolwallet/transport-react-native-nfc": "1.0.0-beta.3", "@react-native-async-storage/async-storage": "^1.19.2", "@react-native-clipboard/clipboard": "1.11.2", "@react-navigation/material-top-tabs": "^6.6.3", @@ -76,7 +79,6 @@ "string_decoder": "^0.10.31", "styled-components": "^6.0.7", "timers-browserify": "^1.4.2", - "transport-jre-http": "^1.1.2", "tty-browserify": "^0.0.0", "url": "^0.10.3", "util": "^0.10.4", diff --git a/src/DemoAppNavigator.tsx b/src/DemoAppNavigator.tsx index 89080a1..c358324 100644 --- a/src/DemoAppNavigator.tsx +++ b/src/DemoAppNavigator.tsx @@ -18,9 +18,9 @@ import { RefreshAppKeyPairContainer } from '@src/features/cardPairing/RefreshApp import { GeneratePairingPasswordContainer } from '@src/features/cardPairing/GeneratePairingPasswordContainer'; import { GetCardInfoContainer } from '@src/features/cardPairing/GetCardInfoContainer'; import { HttpScanContainer } from '@src/features/httpScan/HttpScanContainer'; -import NFCScanContainer from "@src/features/nfc/NFCScanContainer"; +import NFCScanContainer from '@src/features/nfcScan/NFCScanContainer'; +import { GenerateMasterKeyContainer } from '@src/features/cardPairing/GenerateMasterKeyContainer'; import { FirmwareUpgradeContainer } from '@src/features/cardPairing/FirmwareUpgradeContainer'; - export type DemoAppParamList = { [RouteName.DEMO_HOME]: undefined; [RouteName.BLUETOOTH_SCAN]: undefined; @@ -41,6 +41,7 @@ export type DemoAppParamList = { [RouteName.SIGN_TYPED_DATA]: undefined; [RouteName.SEND_HEX]: undefined; [RouteName.NFC_SCAN]: undefined; + [RouteName.CREATE_MASTER_KEY]: undefined; }; export type RootNavigationProp = NavigationProp; @@ -95,6 +96,11 @@ export function DemoAppNavigator() { component={RecoverWalletContainer} options={{ headerBackTitleVisible: false }} /> + { try { - return await apdu.info.getCardInfo(this.getTransport()); + return await info.getCardInfo(this.getTransport()); } catch (e) { throw RNApduError.parseError(e as Error); } @@ -67,7 +67,7 @@ export class RNApduManager implements ApduManager { const { publicKey } = appKeyPair; const sePublicKey = await this.getSEPublicKey(); try { - return await apdu.pair.register(this.getTransport(), publicKey, password, name, sePublicKey); + return await wallet.client.register(this.getTransport(), publicKey, password, name, sePublicKey); } catch (e) { throw RNApduError.parseError(e as Error); } @@ -77,7 +77,7 @@ export class RNApduManager implements ApduManager { const appKeyPair = this.getAppKeyPair(); const { privateKey } = appKeyPair; try { - return await apdu.pair.getPairedApps(this.getTransport(), appId, privateKey); + return await wallet.client.getPairedApps(this.getTransport(), appId, privateKey); } catch (e) { throw RNApduError.parseError(e as Error); } @@ -87,7 +87,7 @@ export class RNApduManager implements ApduManager { const appKeyPair = this.getAppKeyPair(); const { privateKey } = appKeyPair; try { - return await apdu.pair.removePairedDevice(this.getTransport(), appId, privateKey, pairedAppId); + return await wallet.client.removePairedDevice(this.getTransport(), appId, privateKey, pairedAppId); } catch (e) { throw RNApduError.parseError(e as Error); } @@ -95,7 +95,7 @@ export class RNApduManager implements ApduManager { async resetDevice(): Promise { try { - const status = await apdu.general.resetCard(this.getTransport()); + const status = await setting.card.resetCard(this.getTransport()); return status; } catch (e) { throw RNApduError.parseError(e as Error); @@ -106,7 +106,7 @@ export class RNApduManager implements ApduManager { const appKeyPair = this.getAppKeyPair(); const { privateKey } = appKeyPair; try { - return await apdu.pair.getPairingPassword(this.getTransport(), appId, privateKey); + return await wallet.client.getPairingPassword(this.getTransport(), appId, privateKey); } catch (e) { throw RNApduError.parseError(e as Error); } @@ -158,4 +158,14 @@ export class RNApduManager implements ApduManager { throw RNApduError.parseError(e as Error); } } + + async createSeedByCard(appId: string, strength: 12 | 18 | 24) { + const appKeyPair = this.getAppKeyPair(); + const { privateKey } = appKeyPair; + try { + return await wallet.secret.creation.createSeedByCard(this.getTransport(), appId, privateKey, strength); + } catch (e) { + throw RNApduError.parseError(e as Error); + } + } } diff --git a/src/features/ble/RNBleTransport.ts b/src/features/ble/RNBleTransport.ts index 9634014..2b4c489 100644 --- a/src/features/ble/RNBleTransport.ts +++ b/src/features/ble/RNBleTransport.ts @@ -1,4 +1,5 @@ -import { Transport as CWTransport, device as CWDevice } from '@coolwallet/core'; +import { device as CWDevice } from '@coolwallet/core'; +import { BleTransport } from '@coolwallet/core/lib/transport'; import { RNBleError } from '@src/features/ble/RNBleError'; import { BleErrorCode, Device as BluetoothDevice, Characteristic } from 'react-native-ble-plx'; @@ -9,7 +10,7 @@ export interface CWServiceCharacteristics { response?: Characteristic; } -export class RNBleTransport extends CWTransport { +export class RNBleTransport extends BleTransport { private characteristics?: CWServiceCharacteristics | null; constructor(device: BluetoothDevice) { diff --git a/src/features/cardPairing/GenerateMasterKeyContainer.tsx b/src/features/cardPairing/GenerateMasterKeyContainer.tsx new file mode 100644 index 0000000..dbfb616 --- /dev/null +++ b/src/features/cardPairing/GenerateMasterKeyContainer.tsx @@ -0,0 +1,35 @@ +import { RNApduManager } from '@src/features/ble/RNApduManager'; +import { useConnectCardUseCase } from '@src/features/cardPairing/hooks/useConnectCardUseCase'; +import { DemoView } from '@src/features/components/DemoView'; +import { useLogUseCase } from '@src/features/home/usecases/useLogUseCase'; +import { useAppId } from '@src/features/store/account/AccountActionHooks'; +import { useCardId } from '@src/features/store/device/DeviceActionHooks'; + +const STRENGTH = 12; +export function GenerateMasterKeyContainer() { + const cardId = useCardId(); + const appId = useAppId(cardId); + const { log, addLog } = useLogUseCase(); + const { connect, disconnect } = useConnectCardUseCase(); + + const generateMasterKey = async () => { + await connect(); + + addLog(`createSeedByCard start...`); + const result = await RNApduManager.getInstance().createSeedByCard(appId, STRENGTH); + addLog(`createSeedByCard is successful. result=${result}`); + + disconnect(); + }; + + return ( + + ); +} diff --git a/src/features/cardPairing/GeneratePairingPasswordContainer.tsx b/src/features/cardPairing/GeneratePairingPasswordContainer.tsx index 54004c3..dfe0616 100644 --- a/src/features/cardPairing/GeneratePairingPasswordContainer.tsx +++ b/src/features/cardPairing/GeneratePairingPasswordContainer.tsx @@ -1,5 +1,6 @@ import { RNApduError } from '@src/features/ble/RNApduError'; import { RNApduManager } from '@src/features/ble/RNApduManager'; +import { useConnectCardUseCase } from '@src/features/cardPairing/hooks/useConnectCardUseCase'; import { DemoView } from '@src/features/components/DemoView'; import { useInitApduEffect } from '@src/features/home/usecases/useCardPairingUseCase'; import { useLogUseCase } from '@src/features/home/usecases/useLogUseCase'; @@ -17,23 +18,27 @@ export function GeneratePairingPasswordContainer() { const isBtnDisable = !cardId || !isConnected || !defaultPairPassword; const [isRefreshing, setIsRefereshing] = useState(false); + const { connect, disconnect } = useConnectCardUseCase(); + useInitApduEffect(); const generatePairPassword = async () => { if (!appId || isBtnDisable) return; - setIsRefereshing(true); - addLog('GENERATING....'); - RNApduManager.getInstance() - .getPairPassword(appId) - .then((newPassword) => { - setPairPassword(newPassword); - addLog('GENERATED SUCCESS, PLEASE COPY NEW PAIR PASSWORD AND GO TO REFRESH APP KEY PAIR'); - }) - .catch((e) => { - const error = e as RNApduError; - addLog('GENERATED FAILED >>> ERROR=' + error); - }) - .finally(() => setIsRefereshing(false)); + + try { + setIsRefereshing(true); + await connect(); + addLog('GENERATING....'); + const newPairedPassword = await RNApduManager.getInstance().getPairPassword(appId); + setPairPassword(newPairedPassword); + addLog('GENERATED SUCCESS, PLEASE COPY NEW PAIR PASSWORD AND GO TO REFRESH APP KEY PAIR'); + await disconnect(); + } catch(e) { + const error = e as RNApduError; + addLog('GENERATED FAILED >>> ERROR=' + error); + } finally { + setIsRefereshing(false); + } }; return ( diff --git a/src/features/cardPairing/GetCardInfoContainer.tsx b/src/features/cardPairing/GetCardInfoContainer.tsx index 375f056..5fd5cc3 100644 --- a/src/features/cardPairing/GetCardInfoContainer.tsx +++ b/src/features/cardPairing/GetCardInfoContainer.tsx @@ -1,4 +1,5 @@ import { RNApduError } from '@src/features/ble/RNApduError'; +import { useConnectCardUseCase } from '@src/features/cardPairing/hooks/useConnectCardUseCase'; import { useGetCardInfoUseCase } from '@src/features/cardPairing/usecases/useGetCardInfoUseCase'; import { BlueButton } from '@src/features/components/BlueButton'; import { LogBox } from '@src/features/components/LogBox'; @@ -35,7 +36,11 @@ export function GetCardInfoContainer(): JSX.Element { const isBtnDisable = !isConnected || isQuerying; const { log, addLog, resetLog } = useLogUseCase(); - const onClick = () => { + const { connect, disconnect } = useConnectCardUseCase(); + + const onClick = async () => { + await connect(); + resetLog(); addLog('QUERYING....'); getCardInfo() @@ -66,6 +71,9 @@ export function GetCardInfoContainer(): JSX.Element { .catch((e) => { const error = e as RNApduError; addLog('QUERIED FAILED >>> ERROR=' + error); + }) + .finally(() => { + disconnect(); }); }; return ( diff --git a/src/features/cardPairing/GetPairedAppsContainer.tsx b/src/features/cardPairing/GetPairedAppsContainer.tsx index 80eaa1b..52519fd 100644 --- a/src/features/cardPairing/GetPairedAppsContainer.tsx +++ b/src/features/cardPairing/GetPairedAppsContainer.tsx @@ -2,6 +2,7 @@ import { useNavigation } from '@react-navigation/native'; import { RNApduError } from '@src/features/ble/RNApduError'; import { RNApduManager } from '@src/features/ble/RNApduManager'; import { PairedApp } from '@src/features/ble/data/PairedApp'; +import { useConnectCardUseCase } from '@src/features/cardPairing/hooks/useConnectCardUseCase'; import { PairedAppsView } from '@src/features/components/PairedAppsView'; import { useInitApduEffect } from '@src/features/home/usecases/useCardPairingUseCase'; import { useLogUseCase } from '@src/features/home/usecases/useLogUseCase'; @@ -24,7 +25,11 @@ export function GetPairedAppsContainer(): JSX.Element { useInitApduEffect(); - const getPairedApps = () => { + const { connect, disconnect } = useConnectCardUseCase(); + + const getPairedApps = async () => { + await connect(); + setIsFetching(true); addLog('FETCHING....'); RNApduManager.getInstance() @@ -37,7 +42,10 @@ export function GetPairedAppsContainer(): JSX.Element { const error = e as RNApduError; addLog('FETCHING FAILED >>> ERROR=' + error); }) - .finally(() => setIsFetching(false)); + .finally(() => { + setIsFetching(false); + disconnect(); + }); }; const onDeleted = (pairedAppId: string) => { diff --git a/src/features/cardPairing/RecoverAddressContainer.tsx b/src/features/cardPairing/RecoverAddressContainer.tsx index af21dd8..cede0be 100644 --- a/src/features/cardPairing/RecoverAddressContainer.tsx +++ b/src/features/cardPairing/RecoverAddressContainer.tsx @@ -1,3 +1,4 @@ +import { useConnectCardUseCase } from '@src/features/cardPairing/hooks/useConnectCardUseCase'; import { DemoView } from '@src/features/components/DemoView'; import { useTransport } from '@src/features/home/usecases/useCardPairingUseCase'; import { useLogUseCase } from '@src/features/home/usecases/useLogUseCase'; @@ -19,10 +20,12 @@ export function RecoverAddressContainer() { const [isRecovering, setIsRecovering] = useState(false); const [addressIndex, setAddressIndex] = useState(defaultAddressIndex); const isBtnDisable = !cardId || !appId || addressIndex === undefined || !transport || isRecovering; + const { connect, disconnect } = useConnectCardUseCase(); const recoverAddress = async () => { if (isBtnDisable) return; try { + await connect(); setIsRecovering(true); const sdkAdapter = new EthereumSdkAdapter(EvmChainId.POLYGON_MAINNET); sdkAdapter.setAppId(appId); @@ -36,6 +39,7 @@ export function RecoverAddressContainer() { } catch (e) { addLog(`RECOVER FAILED >>> ${e}`); } finally { + await disconnect(); setIsRecovering(false); } }; diff --git a/src/features/cardPairing/RecoverWalletContainer.tsx b/src/features/cardPairing/RecoverWalletContainer.tsx index 8169467..306f8c4 100644 --- a/src/features/cardPairing/RecoverWalletContainer.tsx +++ b/src/features/cardPairing/RecoverWalletContainer.tsx @@ -1,5 +1,6 @@ +import { useConnectCardUseCase } from '@src/features/cardPairing/hooks/useConnectCardUseCase'; import { DemoView } from '@src/features/components/DemoView'; -import { useTransport } from '@src/features/home/usecases/useCardPairingUseCase'; +import { useInitApduEffect, useTransport } from '@src/features/home/usecases/useCardPairingUseCase'; import { useLogUseCase } from '@src/features/home/usecases/useLogUseCase'; import { EthereumSdkAdapter } from '@src/features/sdk/evm/EthereumSdkAdapter'; import { EvmChainId } from '@src/features/sdk/evm/EvmChain'; @@ -18,11 +19,15 @@ export function RecoverWalletContainer() { const isBtnDisable = !cardId || !appId || !transport || !mnemonic || isRecovering; const updateMnemonic = useDispatchMnemonicChange(); const updateRecoverdStatus = useDispatchWalletRecoverStatus(); + const { connect, disconnect } = useConnectCardUseCase(); + + useInitApduEffect(); const recoverWallet = async () => { if (isBtnDisable) return; try { setIsRecovering(true); + await connect(); const sdkAdapter = new EthereumSdkAdapter(EvmChainId.POLYGON_MAINNET); sdkAdapter.setAppId(appId); sdkAdapter.setTransport(transport); @@ -37,6 +42,7 @@ export function RecoverWalletContainer() { updateRecoverdStatus(cardId, false); addLog(`RECOVER FAILED >>> ${e}`); } finally { + await disconnect(); setIsRecovering(false); } }; diff --git a/src/features/cardPairing/RegisterCardContainer.tsx b/src/features/cardPairing/RegisterCardContainer.tsx index ba58434..a79efa7 100644 --- a/src/features/cardPairing/RegisterCardContainer.tsx +++ b/src/features/cardPairing/RegisterCardContainer.tsx @@ -1,3 +1,4 @@ +import { useConnectCardUseCase } from '@src/features/cardPairing/hooks/useConnectCardUseCase'; import { useRegisterDeviceUseCase } from '@src/features/cardPairing/usecases/useRegisterDeviceUseCase'; import { BlueButton } from '@src/features/components/BlueButton'; import { LogBox } from '@src/features/components/LogBox'; @@ -45,18 +46,26 @@ export function RegisterCardContainer(): JSX.Element { const changeAppInfo = useDispatchChangeAppInfo(); const changePairedPassword = useDispatchChangePairedPassword(); + const { connect, disconnect } = useConnectCardUseCase(); + const isBtnDisable = !isConnected; - const registerCard = () => { - registerDevice(deviceName, pairingPassword).then((info) => { - if (info) { - const { appId, deviceName, password } = info; - changeAppInfo(cardId, appId, password, deviceName); - changePairedPassword(cardId, password); - setDeviceName(deviceName); - setPairingPassword(password); - } - }); + const registerCard = async () => { + await connect(); + + registerDevice(deviceName, pairingPassword) + .then((info) => { + if (info) { + const { appId, deviceName, password } = info; + changeAppInfo(cardId, appId, password, deviceName); + changePairedPassword(cardId, password); + setDeviceName(deviceName); + setPairingPassword(password); + } + }) + .finally(() => { + disconnect(); + }); }; return ( diff --git a/src/features/cardPairing/ResetCardContainer.tsx b/src/features/cardPairing/ResetCardContainer.tsx index 505927c..7120402 100644 --- a/src/features/cardPairing/ResetCardContainer.tsx +++ b/src/features/cardPairing/ResetCardContainer.tsx @@ -1,4 +1,5 @@ import { RNApduManager } from '@src/features/ble/RNApduManager'; +import { useConnectCardUseCase } from '@src/features/cardPairing/hooks/useConnectCardUseCase'; import { DemoView } from '@src/features/components/DemoView'; import { useInitApduEffect } from '@src/features/home/usecases/useCardPairingUseCase'; import { useLogUseCase } from '@src/features/home/usecases/useLogUseCase'; @@ -12,12 +13,16 @@ export function ResetCardContainer() { const { log, addLog } = useLogUseCase(); const [isReseting, setIsResting] = useState(false); const clearAppInfo = useDispatchClearAppId(); + const { connect, disconnect } = useConnectCardUseCase(); useInitApduEffect(); const resetCard = async () => { if (!cardId) return; setIsResting(true); + + await connect(); + try { addLog(`PLEASE PRESS THE CARD TO RESET`); await RNApduManager.getInstance().resetDevice(); @@ -27,6 +32,7 @@ export function ResetCardContainer() { addLog(`RESET FAILED >>> ${e}`); } finally { setIsResting(false); + await disconnect(); } }; diff --git a/src/features/cardPairing/hooks/useConnectCardUseCase.ts b/src/features/cardPairing/hooks/useConnectCardUseCase.ts new file mode 100644 index 0000000..1c3df10 --- /dev/null +++ b/src/features/cardPairing/hooks/useConnectCardUseCase.ts @@ -0,0 +1,41 @@ +import { NFCManager } from '@src/features/nfcScan/utils/NfcManager'; +import { useTransportType } from '@src/features/store/device/DeviceActionHooks'; +import { TransportType } from '@src/features/store/device/DeviceTypes'; +import { useRef } from 'react'; + +interface Output { + connect: () => Promise; + disconnect: () => Promise; +} +export function useConnectCardUseCase(): Output { + const transportType = useTransportType(); + + const nfcConnection = useNfcConnection(); + + if (transportType === TransportType.NFC) { + return nfcConnection; + } + + return { + connect: async () => { + console.log(`Not Implemented Connection by transportType=${transportType}.`); + }, + disconnect: async () => { + console.log(`Not Implemented Connection by transportType=${transportType}.`); + }, + }; +} + +function useNfcConnection() { + const nfcManager = NFCManager.getInstance(); + + const connect = async () => { + await nfcManager.requestTechnology(); + }; + + const disconnect = async () => { + await nfcManager.cancelTechnologyRequest(); + }; + + return { connect, disconnect }; +} diff --git a/src/features/components/DemoSignView.tsx b/src/features/components/DemoSignView.tsx index ced3aa8..f18e770 100644 --- a/src/features/components/DemoSignView.tsx +++ b/src/features/components/DemoSignView.tsx @@ -1,6 +1,6 @@ import { LogBox } from '@src/features/components/LogBox'; import { Button, VStack, Input } from 'native-base'; -import { View, ViewStyle } from 'react-native'; +import { ReturnKeyTypeOptions, View, ViewStyle } from 'react-native'; type InputMode = 'none' | 'text' | 'decimal' | 'numeric' | 'tel' | 'search' | 'email' | 'url'; type InputType = 'password' | 'text'; @@ -35,6 +35,7 @@ interface Props { input2PlaceHolder?: string; showInput2?: boolean; input2Mode?: InputMode; + input2ReturnKeyType?: ReturnKeyTypeOptions; onInput2Changed?: (text: string) => void; input3?: string; input3Type?: InputType; @@ -73,6 +74,7 @@ export function DemoSignView({ input2PlaceHolder, input2Mode = 'text', input2Type = 'text', + input2ReturnKeyType, onInput2Changed, input3 = '', showInput3 = false, @@ -120,6 +122,7 @@ export function DemoSignView({ type={input2Type} onChangeText={onInput2Changed} inputMode={input2Mode} + returnKeyType={input2ReturnKeyType} style={{ backgroundColor: '#ffffff' }} /> )} diff --git a/src/features/home/CardPairingContainer.tsx b/src/features/home/CardPairingContainer.tsx index 0b19100..26850d7 100644 --- a/src/features/home/CardPairingContainer.tsx +++ b/src/features/home/CardPairingContainer.tsx @@ -1,11 +1,11 @@ import { NavigationProp, useNavigation } from '@react-navigation/native'; import { DemoAppParamList } from '@src/DemoAppNavigator'; -import { RouteItem, RouteListView } from '@src/features/components/RouteListView'; +import { RouteListView } from '@src/features/components/RouteListView'; import { RouteName } from '@src/routes/type'; function useRouteItems() { const navigation = useNavigation>(); - const routeItems: Array = [ + return [ { routeName: RouteName.GET_CARD_INFO, onButtonPress: () => navigation.navigate(RouteName.GET_CARD_INFO), @@ -42,12 +42,15 @@ function useRouteItems() { routeName: RouteName.RECOVER_MNEMONIC, onButtonPress: () => navigation.navigate(RouteName.RECOVER_MNEMONIC), }, + { + routeName: RouteName.CREATE_MASTER_KEY, + onButtonPress: () => navigation.navigate(RouteName.CREATE_MASTER_KEY), + }, { routeName: RouteName.RECOVER_ADDRESS, onButtonPress: () => navigation.navigate(RouteName.RECOVER_ADDRESS), }, ]; - return routeItems; } export function CardPairingContainer() { diff --git a/src/features/home/usecases/useCardPairingUseCase.ts b/src/features/home/usecases/useCardPairingUseCase.ts index 34a251d..64b5e10 100644 --- a/src/features/home/usecases/useCardPairingUseCase.ts +++ b/src/features/home/usecases/useCardPairingUseCase.ts @@ -3,6 +3,7 @@ import { RNApduManager } from '@src/features/ble/RNApduManager'; import { useBleTransport } from '@src/features/ble/usecases/useConnectBleUseCase'; import { useLoadAppKeyPairUseCase } from '@src/features/cardPairing/usecases/useLoadAppKeyPairUseCase'; import { useHttpTransport } from '@src/features/httpScan/useCreateHttpTransportUseCase'; +import { useNfcTransport } from '@src/features/nfcScan/hooks/useCreateNfcTransportUseCase'; import { useTransportType } from '@src/features/store/device/DeviceActionHooks'; import { TransportType } from '@src/features/store/device/DeviceTypes'; import { useEffect } from 'react'; @@ -17,9 +18,11 @@ export function useInitApduEffect() { } export function useTransport(): Transport | undefined { - const bleTransport = useBleTransport(); const type = useTransportType(); + const bleTransport = useBleTransport(); const httpTransport = useHttpTransport(); + const nfcTransport = useNfcTransport(); if (type === TransportType.Bluetooth) return bleTransport; if (type === TransportType.Http) return httpTransport; + if (type === TransportType.NFC) return nfcTransport; } diff --git a/src/features/httpScan/useCreateHttpTransportUseCase.ts b/src/features/httpScan/useCreateHttpTransportUseCase.ts index 52ec7b4..035453b 100644 --- a/src/features/httpScan/useCreateHttpTransportUseCase.ts +++ b/src/features/httpScan/useCreateHttpTransportUseCase.ts @@ -1,8 +1,8 @@ -import { Transport, apdu } from '@coolwallet/core'; +import { Transport, info } from '@coolwallet/core'; import { useLogUseCase } from '@src/features/home/usecases/useLogUseCase'; import { useDispatchHttpInfo, useHttpInfo } from '@src/features/store/device/DeviceActionHooks'; import { useEffect, useState } from 'react'; -import { HttpTransport } from 'transport-jre-http'; +import { HttpTransport } from '@coolwallet/transport-jre-http'; export function useCreateHttpTransportUseCase() { const changeHttpInfo = useDispatchHttpInfo(); @@ -18,7 +18,7 @@ export function useCreateHttpTransportUseCase() { const transport = new HttpTransport(url); addLog(`Successfully created a transport with: ${url} ...`); - const cardIdHex = await apdu.general.getCardId(transport); + const cardIdHex = await info.getCardId(transport); const cardId = Buffer.from(cardIdHex, 'hex').toString(); addLog(`Successfully got card id: ${cardId}`); diff --git a/src/features/nfc/NFCManager.ts b/src/features/nfc/NFCManager.ts deleted file mode 100644 index a5de196..0000000 --- a/src/features/nfc/NFCManager.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { hexStringToNumberArray, numberArrayToHexString } from '@src/features/nfc/util'; -import NfcManager, { NfcTech, TagEvent, NfcEvents, Ndef } from 'react-native-nfc-manager'; - -class NFCManager { - constructor() { - // Pre-step, call this before any NFC operations, ref: https://github.com/revtel/react-native-nfc-manager - this.start(); - } - - async start() { - try { - await NfcManager.start(); - console.log('NfcManager started successfully'); - } catch (error) { - console.error('Failed to start NfcManager:', error); - } - } - - async isEnabled():Promise { - try { - return NfcManager.isEnabled(); - } catch (error) { - console.error('Failed to check if NfcManager is enabled:', error); - return false; - } - } - - async getTag() { - return await NfcManager.getTag(); - } - - async requestNdefTechnology() { - try { - console.log('await NfcTech.Ndef request...'); - await NfcManager.requestTechnology(NfcTech.Ndef); - console.log('NfcTech.Ndef requested successfully'); - } catch (error) { - console.error('Failed to request NfcTech.Ndef:', error); - throw error; - } - } - - async requestIsoDepTechnology() { - try { - console.log('await NfcTech.IsoDep request...'); - await NfcManager.requestTechnology(NfcTech.IsoDep ,{ - alertMessage: 'Ready to write NFC tags!' - }); - console.log('NfcTech.IsoDep requested successfully'); - } catch (error) { - console.error('Failed to request NfcTech.IsoDep:', error); - throw error; - } - } - - async cancelTechnologyRequest() { - try { - await NfcManager.cancelTechnologyRequest(); - console.log('NfcTech request canceled successfully'); - } catch (error) { - console.error('Failed to cancel NfcTech request:', error); - } - } - - async readData(tag: TagEvent): Promise { - try { - if (tag.ndefMessage) { - const ndefRecords = tag.ndefMessage; - return ndefRecords.map(record => { - const payload = record.payload instanceof Uint8Array ? record.payload : new Uint8Array(record.payload); - return Ndef.text.decodePayload(payload); - }).join(' '); - } else { - return tag.id!; - } - } catch (error) { - console.error('Failed to read NFC data:', error); - throw error; - } - } - - async writeData(data: string):Promise { - try { - const apduCommand = hexStringToNumberArray(data); - const response = await NfcManager.isoDepHandler.transceive(apduCommand); - const responseHex = numberArrayToHexString(response) - console.log('Response from NFC:',responseHex ); - return responseHex; - } catch (error) { - console.error('Failed to write NFC data:', error); - throw error; - } - } - - registerTagEvent(listener: (tag: TagEvent) => void) { - try { - NfcManager.setEventListener(NfcEvents.DiscoverTag, listener); - console.log('Register tag listener set successfully'); - } catch (error) { - console.error('Failed to set tag discovered listener:', error); - } - } - - unregisterTagEvent() { - try { - NfcManager.setEventListener(NfcEvents.DiscoverTag, null); - console.log('Unregister tag event successfully'); - } catch (error) { - console.error('Failed to remove tag discovered listener:', error); - } - } - -} - -export default new NFCManager(); diff --git a/src/features/nfc/NFCScanContainer.tsx b/src/features/nfc/NFCScanContainer.tsx deleted file mode 100644 index 90888fd..0000000 --- a/src/features/nfc/NFCScanContainer.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import RNNfcTransport from './RNNfcTransport'; -import { TagEvent } from 'react-native-nfc-manager'; -import { useLogUseCase } from '@src/features/home/usecases/useLogUseCase'; -import { useNavigation } from '@react-navigation/native'; -import { NFCScanView } from '@src/features/nfc/NFCScanView'; - -const NFCScanContainer = () => { - const [nfcData, setNfcData] = useState(null); - //GetCardInfo 80660000 - //SelectApplet - const [inputData, setInputData] = useState('00a404000d436f6f6c57616c6c657450524f'); - const [error, setError] = useState(null); - const [isConnected, setIsConnected] = useState(false); - - const { log, addLog } = useLogUseCase(); - - const handleTagDiscovered = async (tag: TagEvent) => { - console.log('onTagDiscovered', tag); - try { - const data = await RNNfcTransport.readData(tag, handleTagDiscovered); - if (data) { - setNfcData(data); - addLog(`>> read: ${data}`); - } - } catch (err) { - setError(`Failed to read NFC data: ${err}`); - } - }; - - useEffect(() => { - const checkNfc = async () => { - const enabled = await RNNfcTransport.startNfc(); - if (!enabled) { - console.error('NFC is not enabled on this device'); - } - }; - - checkNfc(); - - const connectNfc = async () => { - try { - await RNNfcTransport.connect(handleTagDiscovered); - setIsConnected(true); - } catch (err) { - setError(`Failed to connect to NFC tag: ${err}`); - } - }; - - connectNfc(); - - return () => { - const disconnectNfc = async () => { - try { - await RNNfcTransport.disconnect(true); - } catch (err) { - console.warn('Failed to disconnect NFC:', err); - } finally { - setIsConnected(false); - } - }; - - disconnectNfc(); - }; - }, []); - - const handleWriteData = async () => { - if (!isConnected) return; - - try { - const response = await RNNfcTransport.writeData(inputData, handleTagDiscovered); - setNfcData(`Wrote: ${inputData}`); - addLog(`>> write: ${inputData}`); - addLog(`>> return: ${response}`); - } catch (e: any) { - console.error('Failed to write NFC data:', e); - addLog(`>> write error: ${e.message}`); - } - }; - - const goBack = useNavigation().goBack; - - return ( - - ); -}; - -export default NFCScanContainer; diff --git a/src/features/nfc/NFCScanView.tsx b/src/features/nfc/NFCScanView.tsx deleted file mode 100644 index 8c2521f..0000000 --- a/src/features/nfc/NFCScanView.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import { View, StyleSheet, TextInput, Text } from 'react-native'; -import { LogBox } from '@src/features/components/LogBox'; -import { ButtonLayout, StyledButton } from '@src/features/httpScan/HttpScanView'; - -interface Props { - command: string; - setCommand: (command: string) => void; - log?: string; - onWritePressed: () => void; - onGoBcakPressed: () => void; -} -export function NFCScanView({ command, setCommand, log, onWritePressed, onGoBcakPressed }: Props): JSX.Element { - return ( - - - - - - Please enter the Command sent to the card: - - - - Back - Write - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - }, - inputContainer: { - marginHorizontal: 28, - }, - inputDesc: { - color: 'black', - marginBottom: 12, - }, - input: { - borderWidth: 1, - borderColor: '#ccc', - borderRadius: 4, - padding: 8, - color: 'black', - }, - buttonLayout: { - marginTop: 32, - }, -}); diff --git a/src/features/nfc/RNNfcError.ts b/src/features/nfc/RNNfcError.ts deleted file mode 100644 index 6db29c3..0000000 --- a/src/features/nfc/RNNfcError.ts +++ /dev/null @@ -1,8 +0,0 @@ -class RNNfcError extends Error { - constructor(message: string) { - super(message); - this.name = 'RNNfcError'; - } -} - -export default RNNfcError; diff --git a/src/features/nfc/RNNfcTransport.ts b/src/features/nfc/RNNfcTransport.ts deleted file mode 100644 index f8019b8..0000000 --- a/src/features/nfc/RNNfcTransport.ts +++ /dev/null @@ -1,93 +0,0 @@ -// src/features/nfc/RNNfcTransport.ts -import { NfcEvents, TagEvent } from 'react-native-nfc-manager'; -import NfcManager from './NFCManager'; -import RNNfcError from './RNNfcError'; - -class RNNfcTransport { - isNdefTechnologyRequested: boolean; - isIsoDepTechnologyRequested: boolean; - constructor() { - this.isNdefTechnologyRequested = false; - this.isIsoDepTechnologyRequested = false - } - - async startNfc() { - try { - return await NfcManager.isEnabled(); - } catch (e) { - const errorMsg = `Failed to start NFC: ${JSON.stringify(e)}`; - console.error(errorMsg); - return false; - } - } - - async connect(onTagDiscovered: (tag: TagEvent) => void) { - try { - console.log('execute connect..') - await NfcManager.start(); - if (!this.isNdefTechnologyRequested) { - await NfcManager.requestNdefTechnology(); - this.isNdefTechnologyRequested = true; - } - NfcManager.registerTagEvent(onTagDiscovered); - const tag = await NfcManager.getTag(); - if(tag) - onTagDiscovered(tag); - } catch (e) { - const errorMsg = `Failed to connect to NFC tag: ${JSON.stringify(e)}`; - console.error(errorMsg); - throw new RNNfcError(errorMsg); - } - } - - async disconnect(removeListener = false) { - try { - console.log('execute disconnect..') - await NfcManager.cancelTechnologyRequest(); - this.isNdefTechnologyRequested = false; - NfcManager.unregisterTagEvent(); - console.log('NFC listener removed'); - console.log('Disconnected from NFC tag successfully'); - } catch (e) { - const errorMsg=`Failed to disconnect from NFC tag: ${JSON.stringify(e)}`; - console.error(errorMsg); - throw new RNNfcError(errorMsg); - } - } - - async readData(tag: TagEvent, onTagDiscovered: (tag: TagEvent) => void): Promise { - try { - console.log('execute readData..') - const data = await NfcManager.readData(tag); - console.log('NFC data read successfully:', data); - return data; - } catch (e) { - const errorMsg = `Failed to read NFC data: ${JSON.stringify(e)}`; - console.error(errorMsg); - throw new RNNfcError(errorMsg); - } finally { - // Reconnect after a short delay to avoid race conditions - await this.disconnect(true); - // setTimeout(async () => { - // await this.connect(onTagDiscovered); - // }, 1000); - } - } - - async writeData(data: string, onTagDiscovered: (tag: TagEvent) => void) { - try { - console.log('execute writeData..') - await NfcManager.start(); - await NfcManager.requestIsoDepTechnology(); - return await NfcManager.writeData(data); - } catch (e) { - const errorMsg = `Failed to write NFC data: ${JSON.stringify(e)}`; - console.error(errorMsg); - throw new RNNfcError(errorMsg); - } finally { - await NfcManager.cancelTechnologyRequest(); - } - } -} - -export default new RNNfcTransport(); diff --git a/src/features/nfc/type.ts b/src/features/nfc/type.ts deleted file mode 100644 index bf6cd2b..0000000 --- a/src/features/nfc/type.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type NfcTag = { - id: string; - data: any; -}; diff --git a/src/features/nfcScan/NFCScanContainer.tsx b/src/features/nfcScan/NFCScanContainer.tsx new file mode 100644 index 0000000..86c696e --- /dev/null +++ b/src/features/nfcScan/NFCScanContainer.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { useNavigation } from '@react-navigation/native'; +import { NFCScanView } from '@src/features/nfcScan/NFCScanView'; +import { useCreateNfcTransportUseCase } from '@src/features/nfcScan/hooks/useCreateNfcTransportUseCase'; + +const NFCScanContainer = () => { + const { connect, log, loading } = useCreateNfcTransportUseCase(); + + const goBack = useNavigation().goBack; + + return ; +}; + +export default NFCScanContainer; diff --git a/src/features/nfcScan/NFCScanView.tsx b/src/features/nfcScan/NFCScanView.tsx new file mode 100644 index 0000000..3300a21 --- /dev/null +++ b/src/features/nfcScan/NFCScanView.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { View, StyleSheet } from 'react-native'; +import { LogBox } from '@src/features/components/LogBox'; +import { ButtonLayout, StyledButton } from '@src/features/httpScan/HttpScanView'; + +interface Props { + log?: string; + loading: boolean; + onRequestPressed: () => void; + onGoBcakPressed: () => void; +} +export function NFCScanView({ log, loading, onRequestPressed, onGoBcakPressed }: Props): JSX.Element { + return ( + + + + + + Back + + Request + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + buttonLayout: { + marginTop: 32, + }, +}); diff --git a/src/features/nfcScan/hooks/useCreateNfcTransportUseCase.ts b/src/features/nfcScan/hooks/useCreateNfcTransportUseCase.ts new file mode 100644 index 0000000..025415e --- /dev/null +++ b/src/features/nfcScan/hooks/useCreateNfcTransportUseCase.ts @@ -0,0 +1,64 @@ +import { Transport, info } from '@coolwallet/core'; +import { NfcTransport } from '@coolwallet/transport-react-native-nfc'; +import { useLogUseCase } from '@src/features/home/usecases/useLogUseCase'; +import { NFCManager } from '@src/features/nfcScan/utils/NfcManager'; +import { useDispatchNfcInfo, useNfcInfo } from '@src/features/store/device/DeviceActionHooks'; +import { useEffect, useState } from 'react'; + +export function useCreateNfcTransportUseCase() { + const [loading, setLoading] = useState(false); + const changeNfcInfo = useDispatchNfcInfo(); + + const { log, addLog } = useLogUseCase(); + + const connect = async () => { + setLoading(true); + + const nfcManager = NFCManager.getInstance(); + + try { + const isEnabled = await nfcManager.isEnabled(); + addLog(`Successfully got isEnabled: ${isEnabled}`); + + const isSupported = await nfcManager.isSupported(); + addLog(`Successfully got isSupported: ${isSupported}`); + + addLog(`Waiting for NFC detection...`); + await nfcManager.requestTechnology(); + + const tags = await nfcManager.getTags(); + addLog(`Successfully got tags: ${JSON.stringify(tags, null, 2)}`); + + const transport = new NfcTransport(); + addLog(`Successfully created a transport.`); + + const cardIdHex = await info.getCardId(transport); + const cardId = Buffer.from(cardIdHex, 'hex').toString(); + addLog(`Successfully got card id: ${cardId}`); + + changeNfcInfo(cardId); + addLog(`Successfully saved nfc info. Please Go Back.`); + } catch (e) { + addLog(`${e}`); + } finally { + setLoading(false); + await nfcManager.cancelTechnologyRequest(); + } + }; + + return { + connect, + log, + loading, + }; +} + +export function useNfcTransport() { + const nfcInfo = useNfcInfo(); + const [transport, setTransport] = useState(); + useEffect(() => { + if (!nfcInfo) return; + setTransport(new NfcTransport()); + }, [nfcInfo]); + return transport; +} diff --git a/src/features/nfc/util.ts b/src/features/nfcScan/util.ts similarity index 100% rename from src/features/nfc/util.ts rename to src/features/nfcScan/util.ts diff --git a/src/features/nfcScan/utils/NfcManager.ts b/src/features/nfcScan/utils/NfcManager.ts new file mode 100644 index 0000000..ac0c819 --- /dev/null +++ b/src/features/nfcScan/utils/NfcManager.ts @@ -0,0 +1,84 @@ +import NfcManager, { NfcTech, TagEvent } from 'react-native-nfc-manager'; + +export class NFCManager { + private static instance: NFCManager; + + private isRequested: boolean = false; + + private constructor() { + this.start(); + } + + static getInstance() { + if (!this.instance) this.instance = new NFCManager(); + return this.instance; + } + + private async start(): Promise { + try { + await NfcManager.start(); // Pre-step, call this before any NFC operations, ref: https://github.com/revtel/react-native-nfc-manager + console.log('NFCManager.start is ok.'); + } catch (e) { + console.error(`NFCManager.start is failed. e=${e}`); + } + } + + async isEnabled(): Promise { + try { + const isEnabled = await NfcManager.isEnabled(); + console.log(`NFCManager.isEnabled is ok. isEnabled=${isEnabled}`); + return isEnabled; + } catch (e) { + console.error(`NFCManager.isEnabled is failed. e=${e}`); + return false; + } + } + + async isSupported(): Promise { + try { + const isSupported = await NfcManager.isSupported(); + console.log(`NFCManager.isSupported is ok. isSupported=${isSupported}`); + return isSupported; + } catch (e) { + console.error(`NFCManager.isSupported is failed. e=${e}`); + return false; + } + } + + async getTags(): Promise { + try { + const tags = await NfcManager.getTag(); + console.log(`NFCManager.getTags is ok. tags=${JSON.stringify(tags)}`); + + if (tags) return tags; + } catch (e) { + console.error(`NFCManager.getTags is failed. e=${e}`); + } + } + + async requestTechnology(nfcTech: NfcTech = NfcTech.IsoDep): Promise { + if (this.isRequested) return; + + this.isRequested = true; + + try { + await NfcManager.requestTechnology(nfcTech); + console.log(`NFCManager.requestTechnology is ok.`); + } catch (e) { + console.error(`NFCManager.requestTechnology is failed. e=${e}`); + } + } + + async cancelTechnologyRequest(): Promise { + if (!this.isRequested) return; + + this.isRequested = false; + + try { + await NfcManager.cancelTechnologyRequest(); + console.log(`NFCManager.cancelTechnologyRequest is ok.`); + } catch (e) { + console.error(`NFCManager.cancelTechnologyRequest is failed. e=${e}`); + } + } +} diff --git a/src/features/store/account/AccountActionHooks.ts b/src/features/store/account/AccountActionHooks.ts index 6ca1004..17d97c6 100644 --- a/src/features/store/account/AccountActionHooks.ts +++ b/src/features/store/account/AccountActionHooks.ts @@ -11,6 +11,24 @@ export function useAccountState() { return useAppSelector((state: RootState) => getAccountState(state)); } +export function useMasterKey() { + return useAccountState()?.masterKey; +} + +export function useDispatchMasterKeyChange(): (masterKey: string) => void { + const dispatch = useAppDispatch(); + return (masterKey) => { + dispatch(AccountActions.setMasterKey(masterKey)); + }; +} + +export function useDispatchResetMasterKey(): () => void { + const dispatch = useAppDispatch(); + return () => { + dispatch(AccountActions.resetMasterKey); + }; +} + export function useMnemonic() { return useAccountState()?.mnemonic; } diff --git a/src/features/store/account/AccountSlice.ts b/src/features/store/account/AccountSlice.ts index 8978003..86230bc 100644 --- a/src/features/store/account/AccountSlice.ts +++ b/src/features/store/account/AccountSlice.ts @@ -4,6 +4,7 @@ import { ReducerTypes } from '@src/features/store/types'; const initialState: AccountState = { mnemonic: '', + masterKey: '', accounts: {}, }; @@ -11,9 +12,15 @@ export const AcountSlice = createSlice({ name: ReducerTypes.ACCOUNT, initialState, reducers: { + setMasterKey: (state: AccountState, action: PayloadAction) => { + state.masterKey = action.payload; + }, setMnemonic: (state: AccountState, action: PayloadAction) => { state.mnemonic = action.payload; }, + resetMasterKey: (state: AccountState) => { + state.masterKey = ''; + }, resetMnemonic: (state: AccountState) => { state.mnemonic = ''; }, diff --git a/src/features/store/account/AccountTypes.ts b/src/features/store/account/AccountTypes.ts index cab60c1..e64924a 100644 --- a/src/features/store/account/AccountTypes.ts +++ b/src/features/store/account/AccountTypes.ts @@ -1,7 +1,6 @@ -import { AppKeyPair } from '@src/features/ble/utils/KeyPairUtils'; - export interface AccountState { mnemonic: string; + masterKey: string; accounts: Record; } diff --git a/src/features/store/device/DeviceActionHooks.ts b/src/features/store/device/DeviceActionHooks.ts index d30af96..f266eeb 100644 --- a/src/features/store/device/DeviceActionHooks.ts +++ b/src/features/store/device/DeviceActionHooks.ts @@ -2,7 +2,7 @@ import { RootState } from '@src/features/store/store'; import { useAppSelector, useAppDispatch } from '@src/features/store/hooks'; import { DeviceActions } from '@src/features/store/device/DeviceSlice'; import { Device as BluetoothDevice } from 'react-native-ble-plx'; -import { BluetoothInfo, DeviceInfo, DeviceState, HttpInfo, TransportType } from '@src/features/store/device/DeviceTypes'; +import { BluetoothInfo, DeviceInfo, DeviceState, HttpInfo, NfcInfo, TransportType } from '@src/features/store/device/DeviceTypes'; import { DeviceInfoMapper } from '@src/features/store/device/DeviceInfoMapper'; function getCardInfo(state: RootState): DeviceState { @@ -37,6 +37,12 @@ export function useHttpInfo(): HttpInfo | undefined { return deviceInfo as HttpInfo; } +export function useNfcInfo(): NfcInfo | undefined { + const deviceInfo = useDeviceInfo(TransportType.NFC); + if (!deviceInfo) return; + return deviceInfo as NfcInfo; +} + export function useCardId() { const transportType = useTransportType(); const deviceInfo = useDeviceInfo(transportType); @@ -87,6 +93,21 @@ export function useDispatchHttpInfo(): (hostName: string, port: string, cardId?: }; } +export function useDispatchNfcInfo(): (cardId?: string) => void { + const dispatch = useAppDispatch(); + return (cardId) => { + const type = TransportType.NFC; + const deviceInfo = DeviceInfoMapper.mapFromNfcInfo(cardId); + dispatch( + DeviceActions.updateDeviceInfo({ + type, + deviceInfo, + }), + ); + dispatch(DeviceActions.updateConnectStatus(true)); + }; +} + export function useClearDeviceInfo(): (type: TransportType) => void { const dispatch = useAppDispatch(); return (type) => { diff --git a/src/features/store/device/DeviceInfoMapper.ts b/src/features/store/device/DeviceInfoMapper.ts index d836ea1..ac9a57e 100644 --- a/src/features/store/device/DeviceInfoMapper.ts +++ b/src/features/store/device/DeviceInfoMapper.ts @@ -23,4 +23,10 @@ export class DeviceInfoMapper { cardId, } as DeviceInfo; } + + static mapFromNfcInfo(cardId?: string): DeviceInfo { + return { + cardId, + } as DeviceInfo; + } } diff --git a/src/features/store/device/DeviceTypes.ts b/src/features/store/device/DeviceTypes.ts index 3b22c2f..7dec36b 100644 --- a/src/features/store/device/DeviceTypes.ts +++ b/src/features/store/device/DeviceTypes.ts @@ -40,4 +40,8 @@ export interface HttpInfo { cardId?: string; } -export type DeviceInfo = BluetoothInfo | HttpInfo; +export interface NfcInfo { + cardId?: string; +} + +export type DeviceInfo = BluetoothInfo | HttpInfo | NfcInfo; diff --git a/src/features/tx/EIP1559CoinTransferContainer.tsx b/src/features/tx/EIP1559CoinTransferContainer.tsx index 473bc26..8d42c2d 100644 --- a/src/features/tx/EIP1559CoinTransferContainer.tsx +++ b/src/features/tx/EIP1559CoinTransferContainer.tsx @@ -1,3 +1,4 @@ +import { useConnectCardUseCase } from '@src/features/cardPairing/hooks/useConnectCardUseCase'; import { DemoSignView } from '@src/features/components/DemoSignView'; import { useTransport } from '@src/features/home/usecases/useCardPairingUseCase'; import { useLogUseCase } from '@src/features/home/usecases/useLogUseCase'; @@ -41,7 +42,11 @@ export function EIP1559CoinTransferContainer(): JSX.Element { addLog(`SIGN AUTHORIZED`); }; + const { connect, disconnect } = useConnectCardUseCase(); + const signCoinTransfer = async () => { + await connect(); + try { if (isBtnDisable) return; const apiAdapter = new EthereumApiAdapter(EvmChainId.POLYGON_MAINNET); @@ -74,6 +79,7 @@ export function EIP1559CoinTransferContainer(): JSX.Element { addLog(`SIGN FAILED >>> ${e}`); } finally { setIsSigning(false); + disconnect(); } }; @@ -103,7 +109,8 @@ export function EIP1559CoinTransferContainer(): JSX.Element { isBtnDisable={isBtnDisable} inputPlaceHolder="To Address" input2PlaceHolder="Amount" - input2Mode="numeric" + input2Mode="decimal" + input2ReturnKeyType="done" btnText="Sign" input={toAddress} input2={amount} diff --git a/src/routes/type.ts b/src/routes/type.ts index 15be0cf..a33e3c9 100644 --- a/src/routes/type.ts +++ b/src/routes/type.ts @@ -10,6 +10,7 @@ export enum RouteName { REGISTER_CARD = 'Register Card', CREATE_MNEMONIC = 'Create Mnemonic', RECOVER_MNEMONIC = 'Recover Mnemonic', + CREATE_MASTER_KEY = 'Create Master Key', RECOVER_ADDRESS = 'Recover Address', GET_PAIRING_PASSWORD = 'Get Pairing Password', GET_PAIRED_APPS = 'Get Paired Apps', diff --git a/yarn.lock b/yarn.lock index d01c0ed..bf60c32 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1189,10 +1189,10 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@coolwallet/core@^1.1.18": - version "1.1.24" - resolved "https://registry.yarnpkg.com/@coolwallet/core/-/core-1.1.24.tgz#e5ab630bef3bf648fb93b2d2c223515c461b2bf7" - integrity sha512-1ymjDPCOo8tlXzcxff96NApO2gGVL7nQMBfPsw8O85cCO+Guf0WfEIUu3b8nnX0DaIHgWFr+RLnwZeplitSn2Q== +"@coolwallet/core@2.0.0-beta.2": + version "2.0.0-beta.2" + resolved "https://registry.yarnpkg.com/@coolwallet/core/-/core-2.0.0-beta.2.tgz#e25d4e0e9b5b88303292d7f1a9763eb30f40c7bc" + integrity sha512-jGjXOrzREe+3OfohhlgspoXrgbJgqTxIqDpHaaK5n0cbTzRB34aIGpMBzlo5+xHquT4lYBlcdGZbSXzowlOfHg== dependencies: "@types/elliptic" "^6.4.14" "@types/lodash" "^4.14.177" @@ -1211,11 +1211,78 @@ react-native-ble-plx "^2.0.3" rlp "^2.2.3" -"@coolwallet/evm@^1.0.27": - version "1.0.31" - resolved "https://registry.yarnpkg.com/@coolwallet/evm/-/evm-1.0.31.tgz#b0513038b35784dd06bc633d62095ac2bb4d1586" - integrity sha512-5mCwvZrxJoIuT3P+m448KGIIl2Jmvrl47FmHFD9CtVb0GP80Sz4Zr1vxp0G97hE6JsjDgHYasjChzxoC0wDVqw== +"@coolwallet/core@2.0.0-beta.7": + version "2.0.0-beta.7" + resolved "https://registry.yarnpkg.com/@coolwallet/core/-/core-2.0.0-beta.7.tgz#3f2865094642101976561fb50a402dc08e006a9a" + integrity sha512-iwEy9V0Kw5zQ2ruIgYmP1fIvLm/w9aUyM6VfqlOziIO4r+H0T/HUggx6iLT/++h32J58o/J8G8NfpBjqgV8o4g== dependencies: + "@types/elliptic" "^6.4.14" + "@types/lodash" "^4.14.177" + "@types/node" "^14.0.13" + "@types/pbkdf2" "^3.1.0" + "@types/web-bluetooth" "^0.0.11" + bip32 "^2.0.4" + bip39 "^3.0.2" + bip66 "^1.1.5" + bn.js "^5.1.1" + elliptic "^6.5.3" + jwt-decode "^3.1.2" + key-encoder "^2.0.3" + lodash "^4.17.21" + pbkdf2 "^3.1.2" + react-native-ble-plx "^2.0.3" + rlp "^2.2.3" + +"@coolwallet/core@^2.0.0-beta.2": + version "2.0.0-beta.5" + resolved "https://registry.yarnpkg.com/@coolwallet/core/-/core-2.0.0-beta.5.tgz#93c24449ac2fb26c41275a52907fa88aa8c5096e" + integrity sha512-7pWZUJPo/1CL2tX1mvNMMajJ9ks9O4kuRYKnmn0gd7IAAMrZ7EJ2TyyeOb03DpO0JLyvWubBKRofl0mPC90AnQ== + dependencies: + "@types/elliptic" "^6.4.14" + "@types/lodash" "^4.14.177" + "@types/node" "^14.0.13" + "@types/pbkdf2" "^3.1.0" + "@types/web-bluetooth" "^0.0.11" + bip32 "^2.0.4" + bip39 "^3.0.2" + bip66 "^1.1.5" + bn.js "^5.1.1" + elliptic "^6.5.3" + jwt-decode "^3.1.2" + key-encoder "^2.0.3" + lodash "^4.17.21" + pbkdf2 "^3.1.2" + react-native-ble-plx "^2.0.3" + rlp "^2.2.3" + +"@coolwallet/core@^2.0.0-beta.6": + version "2.0.0-beta.12" + resolved "https://registry.yarnpkg.com/@coolwallet/core/-/core-2.0.0-beta.12.tgz#4e6e2ffe292eac9bee91a31df1c6317dbf111c79" + integrity sha512-ZNWZHyUr9nc99GQ8c5kORITIv7vQ9rlq3xuRgS31EwHFpr6VDfUmohM3MmoZe3baTIvoYZgBkqfg1WEF3kxUxA== + dependencies: + "@types/elliptic" "^6.4.14" + "@types/lodash" "^4.14.177" + "@types/node" "^14.0.13" + "@types/pbkdf2" "^3.1.0" + "@types/web-bluetooth" "^0.0.11" + bip32 "^2.0.4" + bip39 "^3.0.2" + bip66 "^1.1.5" + bn.js "^5.1.1" + elliptic "^6.5.3" + jwt-decode "^3.1.2" + key-encoder "^2.0.3" + lodash "^4.17.21" + pbkdf2 "^3.1.2" + react-native-ble-plx "^2.0.3" + rlp "^2.2.3" + +"@coolwallet/evm@^2.0.0-beta.0": + version "2.0.0-beta.0" + resolved "https://registry.yarnpkg.com/@coolwallet/evm/-/evm-2.0.0-beta.0.tgz#dbc069c5b81e8673e01dd29d893cd5ecdc096a7f" + integrity sha512-z7HfV5SWDGhb9QUIp0J4SXgBJZs7s1HAWVpiJyGnA9KG/wE/MyHkfNNBQqUe/F6DsDMU9Sxhh9vpAWN100zixQ== + dependencies: + "@coolwallet/core" "^2.0.0-beta.6" "@metamask/eth-sig-util" "^4.0.0" ajv "^8.10.0" bn.js "^5.2.0" @@ -1224,6 +1291,23 @@ lodash "^4.17.21" rlp "^3.0.0" +"@coolwallet/transport-jre-http@2.0.0-beta.1": + version "2.0.0-beta.1" + resolved "https://registry.yarnpkg.com/@coolwallet/transport-jre-http/-/transport-jre-http-2.0.0-beta.1.tgz#58276457fbee31f956b8cc15c4e80692aa492395" + integrity sha512-H09kvqd+XME9HAQ59OkGmhlMoAt4ZcKnDz31caYpwMKCzVTFf13whPGqR/QBbbhQBBMgqnYGtqtWQtYIZd0TGw== + dependencies: + "@coolwallet/core" "2.0.0-beta.2" + axios "^0.25.0" + lodash "^4.17.21" + +"@coolwallet/transport-react-native-nfc@1.0.0-beta.3": + version "1.0.0-beta.3" + resolved "https://registry.yarnpkg.com/@coolwallet/transport-react-native-nfc/-/transport-react-native-nfc-1.0.0-beta.3.tgz#9f2bca55af96767e40d3df9cac9885291dcfad7c" + integrity sha512-IzV8wd4jgyZOOmgdk3ynKOfULnwXaV2pQ+be0XhGTPD2/+1huZjQBKjc3tztHVkXvSxBlzTJpmUJbxO4ZakINQ== + dependencies: + "@coolwallet/core" "^2.0.0-beta.2" + react-native-nfc-manager "^3.14.14" + "@egjs/hammerjs@^2.0.17": version "2.0.17" resolved "https://registry.yarnpkg.com/@egjs/hammerjs/-/hammerjs-2.0.17.tgz#5dc02af75a6a06e4c2db0202cae38c9263895124" @@ -10870,14 +10954,6 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== -transport-jre-http@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/transport-jre-http/-/transport-jre-http-1.1.2.tgz#c5b54129cc2e81dcf2cbc9af2aebecdf25b74fec" - integrity sha512-rFCd5WuMm5l3g6DKh5CNNc9wNh3Q19kCieT5GQIm40Wa+2aq9uJ010Xkts6sa+0abULn9SrJgi/gI3PgH7s5NQ== - dependencies: - axios "^0.25.0" - lodash "^4.17.21" - tslib@2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"