SEL-553: Show NFC Progress (#764)

* feat: add haptics

* fix: BAC FAILED error event

* update lock file

---------

Co-authored-by: Justin Hernandez <transphorm@gmail.com>
This commit is contained in:
Seshanth.S🐺
2025-07-25 13:16:56 +05:30
committed by GitHub
parent 13970fb53c
commit a41558a66b
4 changed files with 76 additions and 15 deletions

View File

@@ -123,6 +123,21 @@ object Messages {
const val COMPARING = "Comparing....."
const val COMPLETED = "Scanning completed"
const val RESET = ""
const val PACE_STARTED = "PACE started"
const val PACE_SUCCEEDED = "PACE succeeded"
const val PACE_FAILED = "PACE failed"
const val BAC_STARTED = "BAC started"
const val BAC_SUCCEEDED = "BAC succeeded"
const val BAC_FAILED = "BAC failed"
const val READING_COM = "Reading COM....."
const val READING_DG1 = "Reading DG1....."
const val READING_DG1_SUCCEEDED = "Reading DG1 succeeded"
const val READING_DG2 = "Reading DG2....."
const val READING_DG2_SUCCEEDED = "Reading DG2 succeeded"
const val READING_SOD = "Reading SOD....."
const val READING_SOD_SUCCEEDED = "Reading SOD succeeded"
const val READING_DG14 = "Reading DG14....."
const val CHIP_AUTH_SUCCEEDED = "Chip authentication succeeded"
}
class Response(json: String) : JSONObject(json) {
@@ -323,6 +338,7 @@ class RNPassportReaderModule(private val reactContext: ReactApplicationContext)
for (securityInfo: SecurityInfo in securityInfoCollection) {
if (securityInfo is PACEInfo) {
Log.e("MY_LOGS", "trying PACE...")
eventMessageEmitter(Messages.PACE_STARTED)
service.doPACE(
authKey,
securityInfo.objectIdentifier,
@@ -331,10 +347,12 @@ class RNPassportReaderModule(private val reactContext: ReactApplicationContext)
)
Log.e("MY_LOGS", "PACE succeeded")
paceSucceeded = true
eventMessageEmitter(Messages.PACE_SUCCEEDED)
}
}
} catch (e: Exception) {
Log.w("MY_LOGS", e)
eventMessageEmitter(Messages.PACE_FAILED)
}
Log.e("MY_LOGS", "Sending select applet command with paceSucceeded: ${paceSucceeded}") // this is false so PACE doesn't succeed
service.sendSelectApplet(paceSucceeded)
@@ -343,6 +361,8 @@ class RNPassportReaderModule(private val reactContext: ReactApplicationContext)
var bacSucceeded = false
var attempts = 0
val maxAttempts = 3
eventMessageEmitter(Messages.BAC_STARTED)
while (!bacSucceeded && attempts < maxAttempts) {
try {
@@ -356,6 +376,7 @@ class RNPassportReaderModule(private val reactContext: ReactApplicationContext)
// Try to read EF_COM first
try {
eventMessageEmitter(Messages.READING_COM)
service.getInputStream(PassportService.EF_COM).read()
} catch (e: Exception) {
// EF_COM failed, do BAC
@@ -364,26 +385,28 @@ class RNPassportReaderModule(private val reactContext: ReactApplicationContext)
bacSucceeded = true
Log.e("MY_LOGS", "BAC succeeded on attempt $attempts")
eventMessageEmitter(Messages.BAC_SUCCEEDED)
} catch (e: Exception) {
Log.e("MY_LOGS", "BAC attempt $attempts failed: ${e.message}")
if (attempts == maxAttempts) {
eventMessageEmitter(Messages.BAC_FAILED)
throw e // Re-throw on final attempt
}
}
}
}
eventMessageEmitter("Reading DG1.....")
eventMessageEmitter(Messages.READING_DG1)
val dg1In = service.getInputStream(PassportService.EF_DG1)
dg1File = DG1File(dg1In)
eventMessageEmitter(Messages.READING_DG1_SUCCEEDED)
// eventMessageEmitter("Reading DG2.....")
// val dg2In = service.getInputStream(PassportService.EF_DG2)
// dg2File = DG2File(dg2In)
eventMessageEmitter("Reading SOD.....")
eventMessageEmitter(Messages.READING_SOD)
val sodIn = service.getInputStream(PassportService.EF_SOD)
sodFile = SODFile(sodIn)
eventMessageEmitter(Messages.READING_SOD_SUCCEEDED)
// val gson = Gson()
// Log.d(TAG, "============FIRST CONSOLE LOG=============")
// Log.d(TAG, "dg1File: " + gson.toJson(dg1File))
@@ -443,7 +466,7 @@ class RNPassportReaderModule(private val reactContext: ReactApplicationContext)
private fun doChipAuth(service: PassportService) {
try {
eventMessageEmitter("Reading DG14.....")
eventMessageEmitter(Messages.READING_DG14)
val dg14In = service.getInputStream(PassportService.EF_DG14)
dg14Encoded = IOUtils.toByteArray(dg14In)
val dg14InByte = ByteArrayInputStream(dg14Encoded)
@@ -458,6 +481,7 @@ class RNPassportReaderModule(private val reactContext: ReactApplicationContext)
securityInfo.subjectPublicKey,
)
chipAuthSucceeded = true
eventMessageEmitter(Messages.CHIP_AUTH_SUCCEEDED)
}
}
} catch (e: Exception) {

View File

@@ -27,7 +27,7 @@ target "Self" do
config = use_native_modules!
use_frameworks!
pod "NFCPassportReader", git: "https://github.com/seshanthS/NFCPassportReader", commit: "cd18444e6d7dbf5a665f8a5b4809c5c655a9114d"
pod "NFCPassportReader", git: "https://github.com/seshanthS/NFCPassportReader", commit: "74098a5e29c23b3f5a58dc14a336556fa89c0ad6"
pod "QKMRZScanner"
pod "lottie-ios"

View File

@@ -1848,7 +1848,7 @@ DEPENDENCIES:
- lottie-ios
- lottie-react-native (from `../../node_modules/lottie-react-native`)
- Mixpanel-swift (~> 5.0.0)
- NFCPassportReader (from `https://github.com/seshanthS/NFCPassportReader`, commit `cd18444e6d7dbf5a665f8a5b4809c5c655a9114d`)
- NFCPassportReader (from `https://github.com/seshanthS/NFCPassportReader`, commit `74098a5e29c23b3f5a58dc14a336556fa89c0ad6`)
- QKMRZScanner
- 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`)
@@ -1978,7 +1978,7 @@ EXTERNAL SOURCES:
lottie-react-native:
:path: "../../node_modules/lottie-react-native"
NFCPassportReader:
:commit: cd18444e6d7dbf5a665f8a5b4809c5c655a9114d
:commit: 74098a5e29c23b3f5a58dc14a336556fa89c0ad6
:git: https://github.com/seshanthS/NFCPassportReader
RCT-Folly:
:podspec: "../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec"
@@ -2143,7 +2143,7 @@ EXTERNAL SOURCES:
CHECKOUT OPTIONS:
NFCPassportReader:
:commit: cd18444e6d7dbf5a665f8a5b4809c5c655a9114d
:commit: 74098a5e29c23b3f5a58dc14a336556fa89c0ad6
:git: https://github.com/seshanthS/NFCPassportReader
SwiftQRScanner:
:commit: c71ff91297640a944de4bca61434155c3f9b0979
@@ -2264,6 +2264,6 @@ SPEC CHECKSUMS:
SwiftyTesseract: 1f3d96668ae92dc2208d9842c8a59bea9fad2cbb
Yoga: b05994d1933f507b0a28ceaa4fdb968dc18da178
PODFILE CHECKSUM: 9caf39dd17c6bf25ed459fdf269610aabdd595b5
PODFILE CHECKSUM: 575af0f0872fa8a7ad99a695e9a9d96845ac15ea
COCOAPODS: 1.16.2

View File

@@ -40,7 +40,12 @@ import useUserStore from '../../stores/userStore';
import analytics from '../../utils/analytics';
import { black, slate100, slate400, slate500, white } from '../../utils/colors';
import { dinot } from '../../utils/fonts';
import { buttonTap } from '../../utils/haptic';
import {
buttonTap,
feedbackSuccess,
feedbackUnsuccessful,
impactLight,
} from '../../utils/haptic';
import { registerModalCallbacks } from '../../utils/modalCallbackRegistry';
import { parseScanResponse, scan } from '../../utils/nfcScanner';
@@ -68,6 +73,7 @@ const PassportNFCScanScreen: React.FC<PassportNFCScanScreenProps> = ({}) => {
const [isNfcEnabled, setIsNfcEnabled] = useState(true);
const [isNfcSheetOpen, setIsNfcSheetOpen] = useState(false);
const [dialogMessage, setDialogMessage] = useState('');
const [nfcMessage, setNfcMessage] = useState<string | null>(null);
const animationRef = useRef<LottieView>(null);
@@ -288,7 +294,32 @@ const PassportNFCScanScreen: React.FC<PassportNFCScanScreenProps> = ({}) => {
if (Platform.OS === 'android' && emitter) {
const subscription = emitter.addListener(
'NativeEvent',
(event: string) => console.info(event),
(event: string) => {
console.info(event);
setNfcMessage(event);
// Haptic feedback mapping for completion/error only
if (
event === 'PACE succeeded' ||
event === 'BAC succeeded' ||
event === 'Chip authentication succeeded'
) {
feedbackSuccess(); // Major success
} else if (
event === 'Reading DG1 succeeded' ||
event === 'Reading DG2 succeeded' ||
event === 'Reading SOD succeeded' ||
event === 'Reading COM succeeded'
) {
impactLight(); // Minor DG step
} else if (
event === 'BAC failed' ||
event === 'PACE failed' ||
event.toLowerCase().includes('failed') ||
event.toLowerCase().includes('error')
) {
feedbackUnsuccessful(); // Error
}
},
);
return () => {
@@ -310,7 +341,7 @@ const PassportNFCScanScreen: React.FC<PassportNFCScanScreenProps> = ({}) => {
animationRef.current?.play();
}, 5000); // Pause 5 seconds before playing again
}}
source={passportVerifyAnimation}
source={passportVerifyAnimation as any}
style={styles.animation}
cacheComposition={true}
renderMode="HARDWARE"
@@ -322,8 +353,14 @@ const PassportNFCScanScreen: React.FC<PassportNFCScanScreenProps> = ({}) => {
<TextsContainer>
<Title children="Ready to scan" />
<BodyText textAlign="center">
Hold your device near the NFC tag and stop moving when it
vibrates.
{nfcMessage && nfcMessage.trim().length > 0 ? (
nfcMessage
) : (
<>
Hold your device near the NFC tag and stop moving when it
vibrates.
</>
)}
</BodyText>
</TextsContainer>
<Image