This commit is contained in:
Ksenia Lebedeva 2024-09-13 16:16:54 +02:00
commit b5d00c8d69
4 changed files with 99 additions and 15 deletions

31
package-lock.json generated
View File

@ -8,6 +8,7 @@
"name": "KeycardExit",
"version": "0.0.1",
"dependencies": {
"@react-native-async-storage/async-storage": "^2.0.0",
"react": "18.3.1",
"react-native": "0.75.2",
"react-native-modal": "^13.0.1",
@ -2935,6 +2936,17 @@
"node": ">= 8"
}
},
"node_modules/@react-native-async-storage/async-storage": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.0.0.tgz",
"integrity": "sha512-af6H9JjfL6G/PktBfUivvexoiFKQTJGQCtSWxMdivLzNIY94mu9DdiY0JqCSg/LyPCLGKhHPUlRQhNvpu3/KVA==",
"dependencies": {
"merge-options": "^3.0.4"
},
"peerDependencies": {
"react-native": "^0.0.0-0 || >=0.65 <1.0"
}
},
"node_modules/@react-native-community/cli": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-14.0.0.tgz",
@ -8298,6 +8310,14 @@
"node": ">=8"
}
},
"node_modules/is-plain-obj": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
"integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
"engines": {
"node": ">=8"
}
},
"node_modules/is-plain-object": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
@ -10688,6 +10708,17 @@
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
},
"node_modules/merge-options": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz",
"integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==",
"dependencies": {
"is-plain-obj": "^2.1.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",

View File

@ -10,6 +10,7 @@
"test": "jest"
},
"dependencies": {
"@react-native-async-storage/async-storage": "^2.0.0",
"react": "18.3.1",
"react-native": "0.75.2",
"react-native-modal": "^13.0.1",

View File

@ -1,14 +1,16 @@
import React, { useEffect, useRef, useState } from 'react';
import { Platform, SafeAreaView, StyleSheet, useColorScheme, DeviceEventEmitter } from 'react-native';
import { SafeAreaView, StyleSheet, useColorScheme, DeviceEventEmitter } from 'react-native';
import { Colors } from 'react-native/Libraries/NewAppScreen';
import AsyncStorage from '@react-native-async-storage/async-storage';
import DiscoveryScreen from './components/steps/DiscoveryScreen';
import InitializationScreen from './components/steps/InitializationScreen';
import MnemonicScreen from './components/steps/MnemonicScreen';
import AuthenticationScreen from './components/steps/AuthenticationScreen';
import NFCModal from './NFCModal';
//@ts-ignore
import Keycard from "react-native-status-keycard";
import InitializationScreen from './components/steps/InitializationScreen';
import NFCModal from './NFCModal';
import MnemonicScreen from './components/steps/MnemonicScreen';
import AuthenticationScreen from './components/steps/AuthenticationScreen';
enum Step {
Discovery,
@ -26,11 +28,45 @@ const Main = () => {
const stepRef = useRef(step);
const pinRef = useRef("");
const mnemonicRef = useRef("");
const isListeningCard = useRef(false);
const getPairings = async () => {
const pairingJSON = await AsyncStorage.getItem("pairings");
return pairingJSON ? JSON.parse(pairingJSON) : {};
}
const addPairing = async (instanceUID, pairing) => {
const pairings = await getPairings();
pairings[instanceUID] = {pairing: pairing};
return AsyncStorage.setItem("pairings", JSON.stringify(pairings));
}
const isCardLost = (err) => {
return (err == "Tag was lost.") || err.includes("NFCError:100");
}
const wrongPINCounter = (err) => {
const matches = /Unexpected error SW, 0x63C(\d+)|wrongPIN\(retryCounter: (\d+)\)/.exec(err)
if (matches && matches.length == 2) {
return parseInt(matches[1])
}
return null
}
const keycardConnectHandler = async () => {
if (!isListeningCard.current) {
return;
}
try {
const appInfo = await Keycard.getApplicationInfo();
if (appInfo["new-pairing"]) {
await addPairing(appInfo["instance-uid"], appInfo["new-pairing"]);
}
switch (stepRef.current) {
case Step.Discovery:
if (appInfo["initialized?"]) {
@ -58,22 +94,39 @@ const Main = () => {
setStep(Step.Discovery);
break;
}
if (pinRef.current) {
await Keycard.unpair(pinRef.current);
} catch (err: any) {
if (isCardLost(err.message)) {
console.log("connection to card lost");
return;
}
} catch (err) {
const pinRetryCounter = wrongPINCounter(err.message);
if (pinRetryCounter !== null) {
//TODO: better handling
console.log("wrong PIN. Retry counter: " + pinRetryCounter);
return;
}
console.log(err);
}
await Keycard.stopNFC("");
setIsModalVisible(false);
}
useEffect(() => {
stepRef.current = step;
isListeningCard.current = isModalVisible;
if (!didMount.current) {
didMount.current = true;
const loadPairing = async () => {
await Keycard.setPairings(await getPairings());
};
loadPairing().catch(console.log);
DeviceEventEmitter.addListener("keyCardOnConnected", keycardConnectHandler);
DeviceEventEmitter.addListener("keyCardOnDisconnected", () => console.log("keycard disconnected"));
DeviceEventEmitter.addListener("keyCardOnNFCEnabled", () => console.log("nfc enabled"));
@ -92,9 +145,7 @@ const Main = () => {
await Keycard.startNFC("Tap your Keycard");
if (Platform.OS === 'android') {
setIsModalVisible(true);
}
setIsModalVisible(true);
}
const initPin = async (p: string) => {
@ -103,9 +154,10 @@ const Main = () => {
}
const loadMnemonic = (mnemonic: string, p?: string) => {
if(p) {
if (p) {
pinRef.current = p;
}
mnemonicRef.current = mnemonic;
return connectCard();

View File

@ -1,5 +1,5 @@
import React, {FC, useEffect, useState } from "react";
import {StyleSheet, Text, View } from "react-native";
import {Platform, StyleSheet, Text, View } from "react-native";
import Modal from "react-native-modal/dist/modal";
type NFCModalProps = {
@ -11,7 +11,7 @@ const NFCModal: FC<NFCModalProps> = props => {
const {isVisible, onChangeFunc} = props;
return (
<Modal isVisible={isVisible} onSwipeComplete={() => onChangeFunc(!isVisible)} swipeDirection={['up', 'left', 'right', 'down']} style={modalStyle.modalContainer}>
<Modal isVisible={(Platform.OS === 'android') && isVisible} onSwipeComplete={() => onChangeFunc(!isVisible)} swipeDirection={['up', 'left', 'right', 'down']} style={modalStyle.modalContainer}>
<View style={modalStyle.container}>
<Text style={modalStyle.header}>Ready to Scan</Text>
<Text style={modalStyle.prompt}>Tap your Keycard</Text>