import React from 'react'; import { Platform, DeviceEventEmitter } from 'react-native'; import sdkEKYC, { SDKeKYC, SDK_VERSION, SDK_NAME } from '../../EKYCModule'; import { NfcConfig, NfcError } from '../types/ekycNFCType'; import { SDKEkycResultWithEvent, SDKEkycResultStringWithEvent, EKYCError, getEkycError, AppIDType, SDKEnv } from '../types/ekycType'; import { C06Config } from '../types/ekycC06Type'; import { OcrConfig, OcrError } from '../types/ekycOCRType'; import { LivenessConfig, SDKFaceDetectStatus, LivenessError, LivenessSuccessEvent } from '../types/ekycLivenessType'; import { FaceServiceConfig, FaceCompareError, FaceCompareSuccessEvent } from '../types/ekycFaceType'; import { SmsOtpConfig, SmsOtpResult, SmsOtpError, SendOtpResponse, VerifyOtpResponse, ResendOtpResponse } from '../types/ekycSmsOtpType'; import { ESignInitResult, ESignOpenSessionResult, ESignCertificate, ESignSignRequest, ESignError, ESignAuthenticateResult, ESignPdfResult, ESignApiResponse } from '../types/ekycESignType'; import { SDKFlowType, flowToStrings } from '../types/ekycFlowType'; import { ExitConfirmConfig } from '../types/ekycExitConfirmType'; /** * Finos eKYC SDK Module * * A comprehensive React Native module for electronic Know Your Customer (eKYC) operations * including Vietnamese CCCD NFC reading, OCR, Liveness detection, Face matching, and C06 residence verification. * * @version Dynamic from package.json * @author FinOS * @license MIT */ export class FinosEKYCModule { private static instance: FinosEKYCModule; private sdk: SDKeKYC; private isInitialized: boolean = false; private platform: string; private constructor() { this.sdk = sdkEKYC; this.platform = Platform.OS; } /** * Get singleton instance of FinosEKYCModule */ public static getInstance(): FinosEKYCModule { if (!FinosEKYCModule.instance) { FinosEKYCModule.instance = new FinosEKYCModule(); } return FinosEKYCModule.instance; } /** * Get SDK information */ public async getSDKInfo(): Promise<{ name: string; version: string; buildNumber: string; platform: string; isInitialized: boolean; }> { try { const nativeInfo = await this.sdk.getSDKInfo(); return { ...nativeInfo, platform: this.platform, isInitialized: this.isInitialized }; } catch (error) { return { name: SDK_NAME, version: SDK_VERSION, buildNumber: '1', platform: this.platform, isInitialized: this.isInitialized }; } } /** * Initialize the eKYC SDK * Must be called before using any other methods */ public async initialize(isProd: boolean = false): Promise { try { if (this.isInitialized) { console.log('✅ SDK already initialized'); return 'SDK already initialized'; } // Check if SDK is already initialized on native side if (this.sdk.isSDKInitialized()) { this.isInitialized = true; console.log('✅ SDK already initialized on native side'); return 'SDK already initialized on native side'; } const result = await this.sdk.initSdkEkyc(isProd); this.isInitialized = true; console.log(`✅ Finos eKYC SDK v${SDK_VERSION} initialized successfully`); return result; } catch (error) { // Handle Koin Application already started error if (error && error.toString().includes('Koin Application has already been started')) { console.log('✅ SDK already initialized (Koin Application started)'); this.isInitialized = true; return 'SDK already initialized (Koin Application started)'; } console.error('❌ Failed to initialize Finos eKYC SDK:', error); throw new Error(`SDK initialization failed: ${error}`); } } /** * Check if SDK is initialized */ public isSDKReady(): boolean { return this.isInitialized && this.sdk.isSDKInitialized(); } /** * Set transaction ID * @param transactionId Transaction ID string */ public async setTransactionId(transactionId: string): Promise { this.validateSDKReady(); try { console.log('🆔 Setting transaction ID:', transactionId); const result = await this.sdk.setTransactionId(transactionId); console.log('✅ Transaction ID set successfully'); return result; } catch (error) { console.error('❌ Failed to set transaction ID:', error); throw error; } } /** * Set SDK environment (1.5.4+). * @param env SDKEnv.DEV | SDKEnv.PROD */ public async setEnv(env: SDKEnv): Promise { try { return await this.sdk.setEnv(env); } catch (error) { console.error('❌ setEnv failed:', error); throw error; } } /** * Get current SDK environment (1.5.4+). * @returns SDKEnv string ("DEV" | "PROD") */ public async getEnv(): Promise { try { return await this.sdk.getEnv(); } catch (error) { console.error('❌ getEnv failed:', error); throw error; } } /** * Start NFC scanning for Vietnamese CCCD * @param config NFC configuration */ public async startNfcScan(config: NfcConfig): Promise { this.validateSDKReady(); this.validatePlatform('android', 'NFC scanning is only available on Android'); try { console.log('📡 Starting NFC scan...'); const result = await this.sdk.startNfcScan(config); console.log('✅ NFC scan completed:', result.event); return result; } catch (error) { console.error('❌ NFC scan failed:', error); throw error; } } /** * Check C06 residence verification * @param config C06 configuration */ public async checkC06(config: C06Config): Promise { this.validateSDKReady(); try { console.log('🏠 Starting C06 residence check...'); const result = await this.sdk.checkC06(config); console.log('✅ C06 check completed:', result.event); return result; } catch (error) { console.error('❌ C06 check failed:', error); throw error; } } /** * Start OCR document scanning * @param config OCR configuration */ public async startOcr(config: OcrConfig): Promise { this.validateSDKReady(); try { console.log('📄 Starting OCR scan...'); const result = await this.sdk.startOcr(config); console.log('✅ OCR scan completed:', result.event); return result; } catch (error) { console.error('❌ OCR scan failed:', error); throw error; } } /** * Start liveness detection * @param config Liveness configuration * @param config.isActiveLiveness - Enable active liveness detection (default: false) * @param config.autoCapture - Enable auto capture (default: true) * @param config.isShowCameraFont - Show camera font (default: true) * @param config.customActions - SDKFaceDetectStatus[] enum (LEFT, RIGHT, SMILE, BLINK, ...). Bên sử dụng truyền enum thay vì string * @param config.activeActionCount - Number of random actions (1-10), only used when customActions is null (default: 2) * @param config.switchFrontCamera - Use front camera (default: false) * @param exitConfirmConfig - Optional config tuỳ chỉnh bottom sheet xác nhận thoát. null = mặc định SDK. */ public async startLiveness( config: LivenessConfig, exitConfirmConfig?: ExitConfirmConfig ): Promise { this.validateSDKReady(); try { console.log('👁️ Starting liveness detection...'); if (config.isActiveLiveness !== undefined) { console.log('🔴 Active liveness:', config.isActiveLiveness ? 'ON' : 'OFF'); } if (config.customActions && config.customActions.length > 0) { console.log('🎯 Custom actions:', config.customActions.join(', ')); } else if (config.activeActionCount !== undefined) { console.log('🎲 Random actions count:', config.activeActionCount); } if (exitConfirmConfig) { console.log('🚪 ExitConfirmConfig:', exitConfirmConfig); } const result = await this.sdk.startLiveness(config, exitConfirmConfig); console.log('✅ Liveness detection completed:', result.event); DeviceEventEmitter.emit('onLivenessSuccess', { event: 'LIVENESS_SUCCESS', data: result.data, }); return result; } catch (error) { console.error('❌ Liveness detection failed:', error); throw error; } } /** * Start face comparison (Face Service) * @param config Face service configuration */ public async startFaceCompare(config: FaceServiceConfig): Promise { this.validateSDKReady(); const normalized: FaceServiceConfig = { ...config, appKey: (config.appKeyFaceService || config.appKey), }; try { console.log('👤 Starting face compare...'); const result = await this.sdk.startFaceCompare(normalized); console.log('✅ Face compare completed:', result.event); // Native đã emit onFaceCompareSuccess trực tiếp qua sendEvent() cho cả // LOG_SUCCESS và FACE_SUCCESS — không re-emit ở JS để tránh listener bắn 2 lần. return result; } catch (error) { throw error; } } /** * Extract NFC data for C06 verification * @param nfcResultJson NFC result JSON string */ public async extractNfcDataForC06(nfcResultJson: string): Promise { this.validateSDKReady(); try { console.log('🔍 Extracting NFC data for C06...'); // Note: This method needs to be implemented in the native module console.log('✅ NFC data extraction completed'); return { success: true, data: nfcResultJson }; } catch (error) { console.error('❌ NFC data extraction failed:', error); throw error; } } /** * Handle app lifecycle - Resume */ public onResume(): void { this.sdk.onResume(); console.log('📱 App resumed'); } /** * Handle app lifecycle - Pause */ public onPause(): void { this.sdk.onPause(); console.log('📱 App paused'); } /** * Handle new intent (Android) */ public handleNewIntent(): void { // Note: This method needs to be implemented in the native module console.log('📱 New intent handled'); } // ==================== Event Listeners ==================== /** * Listen for NFC scan start events */ public onNfcScanStart(callback: (data: SDKEkycResultWithEvent) => void) { return this.sdk.onNfcScanStart(callback); } /** * Listen for NFC scan success events */ public onNfcScanSuccess(callback: (data: SDKEkycResultStringWithEvent) => void) { return this.sdk.onNfcScanSuccess(callback); } /** * Listen for NFC error events */ /** Payload là EKYCError: { event, code, message }. */ public onNfcError(callback: (error: EKYCError) => void) { return this.sdk.onNfcError(callback); } /** * Listen for C06 success events */ public onC06Success(callback: (data: SDKEkycResultWithEvent) => void) { return this.sdk.onC06Success(callback); } /** * Listen for C06 error events */ /** Payload là EKYCError: { event, code, message }. */ public onC06Error(callback: (error: EKYCError) => void) { return this.sdk.onC06Error(callback); } /** * Listen for OCR success events */ public onOcrSuccess(callback: (data: SDKEkycResultStringWithEvent) => void) { return this.sdk.onOcrSuccess(callback); } /** * Listen for OCR error events */ /** Payload là EKYCError: { event, code, message }. */ public onOcrError(callback: (error: EKYCError) => void) { return this.sdk.onOcrError(callback); } /** * Listen for liveness success events */ public onLivenessSuccess(callback: (data: LivenessSuccessEvent) => void) { return this.sdk.onLivenessSuccess(callback); } /** * Listen for liveness error events */ /** Payload là EKYCError: { event, code, message }. */ public onLivenessError(callback: (error: EKYCError) => void) { return this.sdk.onLivenessError(callback); } /** * Listen for face compare success events */ public onFaceCompareSuccess(callback: (data: FaceCompareSuccessEvent) => void) { return this.sdk.onFaceCompareSuccess(callback); } /** Payload là EKYCError: { event, code, message }. */ public onFaceCompareError(callback: (error: EKYCError) => void) { return this.sdk.onFaceCompareError(callback); } /** * Listen for eKYC UI success events (including LOG_SUCCESS from Liveness/FaceService submit) */ public onEkycUISuccess(callback: (data: any) => void) { return this.sdk.onEkycUISuccess(callback); } /** * Listen for eKYC UI error events */ public onEkycUIError(callback: (error: any) => void) { return this.sdk.onEkycUIError(callback); } /** * Remove all event listeners */ public removeAllListeners(): void { this.sdk.removeAllListeners(); console.log('🧹 All event listeners removed'); } // ==================== SMS OTP Methods ==================== /** * Send SMS OTP * @param config SMS OTP configuration */ public async sendOtp(config: SmsOtpConfig): Promise { this.validateSDKReady(); try { console.log('📱 Sending SMS OTP...'); const result = await this.sdk.sendOtp(config); console.log('✅ SMS OTP sent successfully'); return result; } catch (error) { console.error('❌ SMS OTP send failed:', error); throw error; } } /** * Verify SMS OTP * @param config SMS OTP configuration (must include requestId) * @param otpCode OTP code from SMS */ public async verifyOtp(config: SmsOtpConfig, otpCode: string): Promise { this.validateSDKReady(); try { console.log('🔐 Verifying SMS OTP...'); const result = await this.sdk.verifyOtp(config, otpCode); console.log('✅ SMS OTP verified successfully'); return result; } catch (error) { console.error('❌ SMS OTP verification failed:', error); throw error; } } /** * Resend SMS OTP * @param config SMS OTP configuration (must include requestId) */ public async resendOtp(config: SmsOtpConfig): Promise { this.validateSDKReady(); try { console.log('📱 Resending SMS OTP...'); const result = await this.sdk.resendOtp(config); console.log('✅ SMS OTP resent successfully'); return result; } catch (error) { console.error('❌ SMS OTP resend failed:', error); throw error; } } // SMS OTP Event Listeners public onSmsOtpSendSuccess(callback: (data: SendOtpResponse) => void) { const listener = this.sdk.onSmsOtpSendSuccess(callback); if (!listener) { console.warn('⚠️ onSmsOtpSendSuccess: Event emitter not ready. Make sure SDK is initialized.'); } return listener; } public onSmsOtpResendSuccess(callback: (data: ResendOtpResponse) => void) { const listener = this.sdk.onSmsOtpResendSuccess(callback); if (!listener) { console.warn('⚠️ onSmsOtpResendSuccess: Event emitter not ready.'); } return listener; } /** Payload là EKYCError: { event, code, message }. */ public onSmsOtpError(callback: (error: EKYCError) => void) { const listener = this.sdk.onSmsOtpError(callback); if (!listener) { console.warn('⚠️ onSmsOtpError: Event emitter not ready.'); } return listener; } // ==================== eSign Methods ==================== /** * Initialize eSign SDK */ public async initializeESign(isProd: boolean = false): Promise { this.validateSDKReady(); try { console.log('🔐 Initializing eSign SDK...'); const result = await this.sdk.initializeESign(undefined, isProd); console.log('✅ eSign SDK initialized successfully'); return result; } catch (error) { console.error('❌ eSign initialization failed:', error); throw error; } } /** * Open eSign Session * @param accessToken Access token (JWT) * @param username Username * @param rememberMe Remember me flag * @param userEsignModel User info model (for auto-token generation) * @param privateKeyFilePath Path to private key file (for auto-token generation) */ public async openSessionId( accessToken: string | null, username: string | null, rememberMe: boolean | null ): Promise { this.validateSDKReady(); // Pass through to SDK - error handling is done in EKYCModule.ts return await this.sdk.openSessionId( accessToken, username, rememberMe ); } /** * Register device for eSign * @param recoverCode 8-digit recovery code * @param pinCode 6-digit PIN code * @param fcmToken Optional FCM token */ public async registerDevice( recoverCode: string, pinCode: string, fcmToken?: string ): Promise<{ code: string; message: string }> { this.validateSDKReady(); try { console.log('📱 Registering eSign device...'); const result = await this.sdk.registerDevice(recoverCode, pinCode, fcmToken); console.log('✅ eSign device registered successfully'); return result; } catch (error) { console.error('❌ eSign device registration failed:', error); throw error; } } /** * List eSign certificates * @param pageNumber Page number (default: 1) * @param pageSize Page size (default: 10) */ public async listCerts( pageNumber: number = 1, pageSize: number = 10 ): Promise<{ certs: ESignCertificate[] }> { this.validateSDKReady(); try { console.log('📋 Listing eSign certificates...'); const result = await this.sdk.listCerts(pageNumber, pageSize); console.log(`✅ Found ${result.certs.length} certificates`); return result; } catch (error) { console.error('❌ eSign list certificates failed:', error); throw error; } } /** * Verify eSign certificate * @param serial Certificate serial number */ public async verifyCert(serial: string): Promise<{ code: string; message: string }> { this.validateSDKReady(); try { console.log('✅ Verifying eSign certificate...'); const result = await this.sdk.verifyCert(serial); console.log('✅ eSign certificate verified successfully'); return result; } catch (error) { console.error('❌ eSign certificate verification failed:', error); throw error; } } /** * List eSign sign requests * @param pageNumber Page number (default: 1) * @param pageSize Page size (default: 10) */ public async listSignRequest( pageNumber: number = 1, pageSize: number = 10 ): Promise<{ requests: ESignSignRequest[] }> { this.validateSDKReady(); try { console.log('📋 Listing eSign sign requests...'); const result = await this.sdk.listSignRequest(pageNumber, pageSize); console.log(`✅ Found ${result.requests.length} sign requests`); return result; } catch (error) { console.error('❌ eSign list sign requests failed:', error); throw error; } } /** * Confirm eSign signature * @param signRequestId Sign request ID * @param pinCode 6-digit PIN code * @param authId Optional auth ID * @param authData Optional auth data * @param confirm Confirm the signature (default: true) */ public async confirmSign( signRequestId: string, pinCode: string, authId?: string, authData?: string, confirm: boolean = true ): Promise<{ code: string; message: string }> { this.validateSDKReady(); try { console.log('✍️ Confirming eSign signature...'); const result = await this.sdk.confirmSign(signRequestId, pinCode, authId, authData, confirm); console.log('✅ eSign signature confirmed successfully'); return result; } catch (error) { console.error('❌ eSign signature confirmation failed:', error); throw error; } } /** * Register remote signing certificate * @param requestJson JSON request body * @returns ESignApiResponse (status, msg, data.sessionId, response) */ public async registerRemoteSigning( requestJson: string ): Promise { this.validateSDKReady(); try { console.log('📝 Registering remote signing certificate...'); const result = await this.sdk.registerRemoteSigning(requestJson); console.log('✅ Remote signing certificate registered successfully'); return result; } catch (error) { console.error('❌ Remote signing certificate registration failed:', error); throw error; } } /** * Sign PDF document * @param accessToken JWT access token * @param requestJson JSON request body * @returns ESignPdfResult with status, msg, and data.transactionId */ public async signPdf( requestJson: string ): Promise { this.validateSDKReady(); try { console.log('📄 Signing PDF document...'); const result = await this.sdk.signPdf(requestJson); console.log('✅ PDF document signed successfully'); return result; } catch (error) { console.error('❌ PDF document signing failed:', error); throw error; } } /** * Send confirmation document * @param accessToken JWT access token * @param requestJson JSON string containing request data */ public async sendConfirmationDocument( requestJson: string ): Promise<{ response: string }> { this.validateSDKReady(); try { console.log('📧 Sending confirmation document...'); const result = await this.sdk.sendConfirmationDocument(requestJson); console.log('✅ Confirmation document sent successfully'); return result; } catch (error) { console.error('❌ Confirmation document sending failed:', error); throw error; } } // eSign Event Listeners public onESignInitSuccess(callback: (data: ESignInitResult) => void) { return this.sdk.onESignInitSuccess(callback); } public onESignOpenSessionSuccess(callback: (data: ESignOpenSessionResult) => void) { return this.sdk.onESignOpenSessionSuccess(callback); } public onESignRegisterDeviceSuccess(callback: (data: { code: string; message: string }) => void) { return this.sdk.onESignRegisterDeviceSuccess(callback); } public onESignListCertsSuccess(callback: (data: { certs: ESignCertificate[] }) => void) { return this.sdk.onESignListCertsSuccess(callback); } public onESignVerifyCertSuccess(callback: (data: { code: string; message: string }) => void) { return this.sdk.onESignVerifyCertSuccess(callback); } public onESignListSignRequestSuccess(callback: (data: { requests: ESignSignRequest[] }) => void) { return this.sdk.onESignListSignRequestSuccess(callback); } public onESignConfirmSignSuccess(callback: (data: { code: string; message: string }) => void) { return this.sdk.onESignConfirmSignSuccess(callback); } public onESignRegisterRemoteSigningSuccess(callback: (data: ESignApiResponse) => void) { return this.sdk.onESignRegisterRemoteSigningSuccess(callback); } public onESignSignPdfSuccess(callback: (data: { response: string }) => void) { return this.sdk.onESignSignPdfSuccess(callback); } public onESignSendConfirmationDocumentSuccess(callback: (data: { response: string }) => void) { return this.sdk.onESignSendConfirmationDocumentSuccess(callback); } /** Payload là EKYCError: { event, code, message }. */ public onESignError(callback: (error: EKYCError) => void) { return this.sdk.onESignError(callback); } /** * Start eKYC UI with flow (like MainActivity.kt) * @param appKey Main app key * @param flowSDK Array of SDKFlowType enum (OCR, NFC, LIVENESS) – bên sử dụng truyền enum * @param language Language code (vi/en) * @param transactionId Transaction ID * @param appKeyConfig Optional app key configuration * @param optionConfig Optional configuration settings (includes switchFrontCamera for camera control) * @param styleConfig Optional style configuration * @param exitConfirmConfig Optional config tuỳ chỉnh bottom sheet xác nhận thoát (icon, title, content, buttons). * null = giữ default SDK. Xem EXIT_CONFIRM_CONFIG.md. */ public async startEkycUI( appKey: string, flowSDK: SDKFlowType[], language: string, transactionId: string, appKeyConfig: { appKey: string; appKeyNfc: string; appKeyOcr: string; appKeyLiveness: string; appKeyC06: string; appKeyFaceService: string; }, optionConfig?: { baseUrl?: string; countMaxRetry?: number; language?: string; /** Network timeout in milliseconds (default: 30000). callTimeout = networkTimeoutMs * 2 */ networkTimeoutMs?: number; switchFrontCamera?: boolean; /** LivenessConfig – SDKeKYCActivity (157-169) */ isActiveLiveness?: boolean; autoCapture?: boolean; forceCaptureTimeout?: number; // seconds, native uses ms /** Bên sử dụng truyền enum – SDKFaceDetectStatus[] thay vì string[] */ customActions?: SDKFaceDetectStatus[]; activeActionCount?: number; /** AppIDType: NONE | HD_BANK | VIKKI (default: AppIDType.NONE) */ appIDType?: AppIDType; }, styleConfig?: { textSize?: number; textFont?: string; textColor?: number; statusBarBackground?: number; backIcon?: number; titleStyle?: { textSize?: number; textFont?: string; textColor?: number; }; toolbarStyle?: { textSize?: number; textFont?: string; textColor?: number; }; instructionStyle?: { textSize?: number; textFont?: string; textColor?: number; }; errorStyle?: { textSize?: number; textFont?: string; textColor?: number; }; successStyle?: { textSize?: number; textFont?: string; textColor?: number; }; warningStyle?: { textSize?: number; textFont?: string; textColor?: number; }; captureButtonColor?: number; captureButtonDisabledColor?: number; }, exitConfirmConfig?: ExitConfirmConfig ): Promise { this.validateSDKReady(); try { console.log('🚀 Starting eKYC UI with flow:', flowSDK); console.log('🔧 OptionConfig:', optionConfig); if (optionConfig?.switchFrontCamera !== undefined) { console.log('📷 Front camera setting:', optionConfig.switchFrontCamera ? 'ON' : 'OFF'); } console.log('🔑 AppKeyConfig:', appKeyConfig); console.log('🎨 StyleConfig:', styleConfig); if (exitConfirmConfig) { console.log('🚪 ExitConfirmConfig:', exitConfirmConfig); } const result = await this.sdk.startEkycUI( appKey, flowToStrings(flowSDK), language, transactionId, appKeyConfig, optionConfig, styleConfig, exitConfirmConfig ); console.log('✅ eKYC UI started successfully'); return result; } catch (error) { console.error('❌ eKYC UI failed:', error); throw error; } } // Private validation methods private validateSDKReady(): void { if (!this.isSDKReady()) { throw new Error('SDK is not initialized. Please call initialize() first.'); } } private validatePlatform(requiredPlatform: string, message: string): void { if (this.platform !== requiredPlatform) { throw new Error(message); } } } // Export singleton instance - ensure it's always initialized let _finosEKYCInstance: FinosEKYCModule | null = null; const getFinosEKYCInstance = (): FinosEKYCModule => { if (!_finosEKYCInstance) { try { _finosEKYCInstance = FinosEKYCModule.getInstance(); } catch (error) { console.error('Failed to initialize FinosEKYCModule:', error); throw new Error('FinosEKYCModule initialization failed. Please check native module setup.'); } } return _finosEKYCInstance; }; // Initialize immediately try { _finosEKYCInstance = FinosEKYCModule.getInstance(); } catch (error) { // Silent initialization - will be created on first access } // Helper to check if a property is a method on FinosEKYCModule const isMethod = (prop: string | symbol): boolean => { if (typeof prop !== 'string') return false; // Check if it's a known method from the class const prototype = FinosEKYCModule.prototype as any; return typeof prototype[prop] === 'function' || prop.startsWith('on') || prop === 'initialize' || prop === 'openSessionId' || prop === 'startEkycUI' || prop === 'startNfcScan' || prop === 'checkC06' || prop === 'startOcr' || prop === 'startLiveness' || prop === 'startFaceCompare' || false; }; // Create a comprehensive stub object with all methods to prevent undefined errors const createFinosEKYCStub = (): FinosEKYCModule => { const stub = {} as any; // Add all event listener methods (on* methods) const eventListenerMethods = [ 'onNfcScanStart', 'onNfcScanSuccess', 'onNfcError', 'onC06Success', 'onC06Error', 'onOcrSuccess', 'onOcrError', 'onLivenessSuccess', 'onLivenessError', 'onFaceCompareSuccess', 'onFaceCompareError', 'onSmsOtpSendSuccess', 'onSmsOtpVerifySuccess', 'onSmsOtpResendSuccess', 'onSmsOtpError', 'onESignInitSuccess', 'onESignOpenSessionSuccess', 'onESignRegisterDeviceSuccess', 'onESignListCertsSuccess', 'onESignVerifyCertSuccess', 'onESignListSignRequestSuccess', 'onESignConfirmSignSuccess', 'onESignRegisterRemoteSigningSuccess', 'onESignSignPdfSuccess', 'onESignSendConfirmationDocumentSuccess', 'onESignError' ]; eventListenerMethods.forEach(method => { stub[method] = (callback?: any) => { console.warn(`⚠️ FinosEKYC.${method} called but module is not initialized`); return null; }; }); // Add all other methods const otherMethods = [ 'initialize', 'setEnv', 'getEnv', 'startNfcScan', 'checkC06', 'startOcr', 'startLiveness', 'startFaceCompare', 'startEkycUI', 'sendOtp', 'verifyOtp', 'resendOtp', 'initializeESign', 'openSessionId', 'registerDevice', 'listCerts', 'verifyCert', 'listSignRequest', 'confirmSign', 'registerRemoteSigning', 'signPdf', 'sendConfirmationDocument', 'onResume', 'onPause', 'isSDKReady', 'getSDKInfo' ]; otherMethods.forEach(method => { stub[method] = async (...args: any[]) => { console.warn(`⚠️ FinosEKYC.${method} called but module is not initialized`); throw new Error(`FinosEKYC.${method} is not available. Module may not be initialized.`); }; }); return stub as FinosEKYCModule; }; // Create a safe wrapper using Proxy - ensure it's always an object const createFinosEKYCProxy = (): FinosEKYCModule => { try { return new Proxy({} as FinosEKYCModule, { get(_target, prop) { try { const instance = getFinosEKYCInstance(); const value = (instance as any)[prop]; if (typeof value === 'function') { return value.bind(instance); } return value; } catch (error) { console.warn(`⚠️ FinosEKYC.${String(prop)} is not available:`, error); // Always return a function for methods to prevent "undefined" errors if (isMethod(prop)) { // For event listeners (on* methods), return a function that returns null if (typeof prop === 'string' && prop.startsWith('on')) { return (callback?: any) => { console.warn(`⚠️ FinosEKYC.${prop} called but module is not initialized`); return null; }; } // For other methods, return a no-op async function return async (...args: any[]) => { console.warn(`⚠️ FinosEKYC.${String(prop)} called but module is not initialized`); throw new Error(`FinosEKYC.${String(prop)} is not available. Module may not be initialized.`); }; } return undefined; } } }); } catch (error) { console.error('❌ Failed to create FinosEKYC proxy:', error); // Return a comprehensive stub object return createFinosEKYCStub(); } }; // Create a wrapper that always returns a valid object // This ensures FinosEKYC is NEVER undefined and always has all methods const createFinosEKYCWrapper = (): FinosEKYCModule => { // Always start with stub to ensure all methods exist const stub = createFinosEKYCStub(); const wrapper = { ...stub } as any; // Try to get real instance and override methods try { const realInstance = getFinosEKYCInstance(); // Override with real instance methods Object.getOwnPropertyNames(Object.getPrototypeOf(realInstance)).forEach(key => { if (key !== 'constructor' && typeof (realInstance as any)[key] === 'function') { wrapper[key] = ((realInstance as any)[key]).bind(realInstance); } }); // Also copy any own properties from real instance Object.keys(realInstance).forEach(key => { if ((realInstance as any)[key] !== undefined) { wrapper[key] = (realInstance as any)[key]; } }); } catch (error) { // If we can't get instance, wrapper already has all stub methods // No need to do anything } return wrapper as FinosEKYCModule; }; // Export - this ensures FinosEKYC is never undefined export const FinosEKYC = createFinosEKYCWrapper(); // Export types export type { NfcConfig, NfcError } from '../types/ekycNFCType'; export type { C06Config } from '../types/ekycC06Type'; export type { OcrConfig, OcrError } from '../types/ekycOCRType'; export type { LivenessConfig, LivenessError } from '../types/ekycLivenessType'; export { SDKFaceDetectStatus } from '../types/ekycLivenessType'; export type { FaceServiceConfig, FaceCompareError } from '../types/ekycFaceType'; export type { SDKEkycResultWithEvent, SDKEkycResultStringWithEvent, EKYCError } from '../types/ekycType'; export { getEkycError, AppIDType } from '../types/ekycType'; export type { SmsOtpConfig, SmsOtpResult, SmsOtpError, SendOtpResponse, VerifyOtpResponse, ResendOtpResponse } from '../types/ekycSmsOtpType'; export type { ESignInitResult, ESignOpenSessionResult, ESignCertificate, ESignSignRequest, ESignError, ESignAuthenticateResult } from '../types/ekycESignType'; // Export constants export { SDK_VERSION, SDK_NAME }; // Default export export default FinosEKYC;