Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/ios/PassportReader.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ @interface RCT_EXTERN_MODULE(PassportReader, NSObject)
RCT_EXTERN_METHOD(configure:(NSString *)token
enableDebugLogs:(BOOL)enableDebugLogs)

RCT_EXTERN_METHOD(trackEvent:(NSString *)name
properties:(NSDictionary *)properties)

RCT_EXTERN_METHOD(flush)

RCT_EXTERN_METHOD(scanPassport:(NSString *)passportNumber
dateOfBirth:(NSString *)dateOfBirth
dateOfExpiry:(NSString *)dateOfExpiry
Expand Down
18 changes: 18 additions & 0 deletions app/ios/PassportReader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import React
import NFCPassportReader
#endif
import Security
import Mixpanel
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard Mixpanel import to avoid E2E/CI compile breaks

Unconditional import Mixpanel can fail in E2E or CI where Mixpanel isn’t linked. Wrap it.

Apply:

-import Mixpanel
+#if canImport(Mixpanel)
+import Mixpanel
+#endif
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import Mixpanel
#if canImport(Mixpanel)
import Mixpanel
#endif
🤖 Prompt for AI Agents
In app/ios/PassportReader.swift around line 16, the unconditional import
Mixpanel can cause compile failures in E2E/CI when the Mixpanel framework isn’t
linked; wrap the import with a compile-time check using Swift's conditional
import (e.g., #if canImport(Mixpanel) import Mixpanel #endif) and ensure any
subsequent references to Mixpanel are either guarded by the same #if
canImport(Mixpanel) blocks or replaced with a no-op alternative so the file
compiles when Mixpanel is unavailable.


#if !E2E_TESTING
@available(iOS 13, macOS 10.15, *)
Expand Down Expand Up @@ -46,12 +47,29 @@ class PassportReader: NSObject {
super.init()
}

private var analytics: SelfAnalytics?

@objc(configure:enableDebugLogs:)
func configure(token: String, enableDebugLogs: Bool) {
let analytics = SelfAnalytics(token: token, enableDebugLogs: enableDebugLogs)
self.analytics = analytics
self.passportReader = NFCPassportReader.PassportReader(analytics: analytics)
}

@objc(trackEvent:properties:)
func trackEvent(_ name: String, properties: [String: Any]?) {
if let mpProps = properties as? Properties {
analytics?.trackEvent(name, properties: mpProps)
} else {
analytics?.trackEvent(name, properties: nil)
}
}

@objc(flush)
func flush() {
analytics?.flush()
}

Comment on lines +59 to +72
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add E2E stubs for trackEvent/flush to keep the RN API consistent

The bridge declares these methods unconditionally in Objective-C. In E2E builds, missing selectors can cause runtime errors if JS calls them.

Apply near the E2E stub class:

 class PassportReader: NSObject {
     override init() {
         super.init()
     }
 
+    @objc(trackEvent:properties:)
+    func trackEvent(_ name: String, properties: [String: Any]?) {
+        // No-op in E2E testing
+    }
+
+    @objc(flush)
+    func flush() {
+        // No-op in E2E testing
+    }
+
     @objc(configure:enableDebugLogs:)
     func configure(token: String, enableDebugLogs: Bool) {
         // No-op for E2E testing
     }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In app/ios/PassportReader.swift around lines 59 to 72, the Obj-C bridge methods
trackEvent(_:properties:) and flush() are declared unconditionally but not
present in the E2E stub class, which can cause missing-selector crashes when JS
calls them; add matching stub methods to the E2E stub class that expose
@objc(trackEvent:properties:) func trackEvent(_ name: String, properties:
[String: Any]?) and @objc(flush) func flush(), each implementing a no-op (or
safe logging) and returning without side effects so the RN API is consistent
across builds.

func getMRZKey(passportNumber: String, dateOfBirth: String, dateOfExpiry: String ) -> String {

// Pad fields if necessary
Expand Down
26 changes: 15 additions & 11 deletions app/ios/SelfAnalytics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ import Mixpanel

public class SelfAnalytics: Analytics {
private let enableDebugLogs: Bool

public override init(token: String, enableDebugLogs: Bool = false, trackAutomaticEvents: Bool = false) {
self.enableDebugLogs = enableDebugLogs
super.init(token: token, enableDebugLogs: enableDebugLogs, trackAutomaticEvents: trackAutomaticEvents)
}

public override func trackEvent(_ name: String, properties: Properties? = nil) {
super.trackEvent(name, properties: properties)

print("[NFC Analytics] Event: \(name), Properties: \(properties ?? [:])")

if let logger = NativeLoggerBridge.shared {
logger.sendEvent(withName: "logEvent", body: [
"level": "info",
Expand All @@ -29,10 +29,10 @@ public class SelfAnalytics: Analytics {

public override func trackDebugEvent(_ name: String, properties: Properties? = nil) {
super.trackDebugEvent(name, properties: properties)
if enableDebugLogs {

if enableDebugLogs {
print("[NFC Analytics Debug] Event: \(name), Properties: \(properties ?? [:])")

if let logger = NativeLoggerBridge.shared {
logger.sendEvent(withName: "logEvent", body: [
"level": "debug",
Expand All @@ -43,12 +43,12 @@ public class SelfAnalytics: Analytics {
}
}
}

public override func trackError(_ error: Error, context: String) {
super.trackError(error, context: context)

print("[NFC Analytics Error] Context: \(context), Error: \(error.localizedDescription)")

if let logger = NativeLoggerBridge.shared {
logger.sendEvent(withName: "logEvent", body: [
"level": "error",
Expand All @@ -62,4 +62,8 @@ public class SelfAnalytics: Analytics {
])
}
}
}

public func flush() {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fyi @seshanthS added a flush method to the analytics package

Mixpanel.mainInstance().flush()
}
}
2 changes: 1 addition & 1 deletion app/src/utils/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import { AppState, type AppStateStatus } from 'react-native';
import { PassportReader } from 'react-native-passport-reader';
import { ENABLE_DEBUG_LOGS, MIXPANEL_NFC_PROJECT_TOKEN } from '@env';
import NetInfo from '@react-native-community/netinfo';
import type { JsonMap, JsonValue } from '@segment/analytics-react-native';

import { TrackEventParams } from '@selfxyz/mobile-sdk-alpha';

import { createSegmentClient } from '@/Segment';
import { PassportReader } from '@/utils/passportReader';

const segmentClient = createSegmentClient();

Expand Down
10 changes: 5 additions & 5 deletions app/src/utils/nfcScanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@

import { Buffer } from 'buffer';
import { Platform } from 'react-native';
import {
PassportReader,
reset,
scan as scanDocument,
} from 'react-native-passport-reader';

import type { PassportData } from '@selfxyz/common/types';

import { configureNfcAnalytics } from '@/utils/analytics';
import {
PassportReader,
reset,
scan as scanDocument,
} from '@/utils/passportReader';

interface AndroidScanResponse {
mrz: string;
Expand Down
70 changes: 70 additions & 0 deletions app/src/utils/passportReader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import { NativeModules, Platform } from 'react-native';

// Platform-specific PassportReader implementation
let PassportReader: any;
let reset: any;
let scan: any;

if (Platform.OS === 'android') {
// Android uses the react-native-passport-reader package
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const AndroidPassportReader = require('react-native-passport-reader');
PassportReader = AndroidPassportReader;
reset = AndroidPassportReader.reset;
scan = AndroidPassportReader.scan;
} catch (error) {
console.warn('Failed to load Android PassportReader:', error);
PassportReader = null;
reset = null;
scan = null;
}
} else if (Platform.OS === 'ios') {
// iOS uses the native PassportReader module directly
PassportReader = NativeModules.PassportReader || null;

// iOS doesn't have reset function
reset = null;

// iOS uses scanPassport method with different signature
scan = PassportReader?.scanPassport
? (options: any) => {
const {
documentNumber,
dateOfBirth,
dateOfExpiry,
canNumber = '',
useCan = false,
skipPACE = false,
skipCA = false,
extendedMode = false,
usePacePolling = true,
} = options;

return PassportReader.scanPassport(
documentNumber,
dateOfBirth,
dateOfExpiry,
canNumber,
useCan,
skipPACE,
skipCA,
extendedMode,
usePacePolling,
);
}
: null;
} else {
// Unsupported platform
console.warn('PassportReader: Unsupported platform');
PassportReader = null;
reset = null;
scan = null;
}

export { PassportReader, reset, scan };
export default PassportReader;
2 changes: 1 addition & 1 deletion app/tests/src/nativeModules/passportReader.simple.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* These tests verify critical interface requirements without conditional expects
*/

import { PassportReader } from 'react-native-passport-reader';
import { PassportReader } from '@/utils/passportReader';

describe('PassportReader Simple Contract Tests', () => {
describe('Critical Interface Requirements', () => {
Expand Down
2 changes: 1 addition & 1 deletion app/tests/utils/nfcScanner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

import { Platform } from 'react-native';
import { PassportReader } from 'react-native-passport-reader';

import { configureNfcAnalytics } from '@/utils/analytics';
import { parseScanResponse, scan } from '@/utils/nfcScanner';
import { PassportReader } from '@/utils/passportReader';

// Mock the analytics module
jest.mock('@/utils/analytics', () => ({
Expand Down
Loading