import { Platform } from 'react-native'; import sdkEKYC, { SDKeKYC, SDK_VERSION, SDK_NAME } from '../../EKYCModule'; import { SmsOtpConfig, SmsOtpResult, SmsOtpError, SendOtpResponse, VerifyOtpResponse, ResendOtpResponse } from '../types/ekycSmsOtpType'; import { ESignInitResult, ESignOpenSessionResult, ESignCertificate, ESignSignRequest, ESignError, ESignAuthenticateResult, AuthorizeInfo, ESignPdfResult, ESignApiResponse } from '../types/ekycESignType'; import { LivenessConfig, LivenessError, LivenessSuccessEvent } from '../types/ekycLivenessType'; import { FaceServiceConfig, FaceCompareError, FaceCompareSuccessEvent } from '../types/ekycFaceType'; import { SDKEkycResultStringWithEvent, SDKEkycResultWithEvent, EKYCError, getEkycError } from '../types/ekycType'; /** * Finos eSign SDK Module * * A React Native module for eSign (Electronic Signature), SMS OTP, Liveness detection, * and Face matching operations. This module excludes NFC, C06, and OCR features. * * @version Dynamic from package.json * @author FinOS * @license MIT */ export class FinosESignModule { private static instance: FinosESignModule; private sdk: SDKeKYC; private isInitialized: boolean = false; private platform: string; private constructor() { this.sdk = sdkEKYC; this.platform = Platform.OS; } /** * Get singleton instance of FinosESignModule */ public static getInstance(): FinosESignModule { if (!FinosESignModule.instance) { FinosESignModule.instance = new FinosESignModule(); } return FinosESignModule.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 eSign 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 eSign 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 eSign SDK:', error); throw new Error(`SDK initialization failed: ${error}`); } } /** * Check if SDK is initialized */ public isSDKReady(): boolean { return this.isInitialized && this.sdk.isSDKInitialized(); } // ==================== SMS OTP Methods ==================== /** * Send SMS OTP * @param config SMS OTP configuration */ public async sendOtp(config: SmsOtpConfig): Promise { this.validateSDKReady(); // Pass through to SDK - error handling is done in EKYCModule.ts return await this.sdk.sendOtp(config); } /** * 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(); // Pass through to SDK - error handling is done in EKYCModule.ts return await this.sdk.verifyOtp(config, otpCode); } /** * Resend SMS OTP * @param config SMS OTP configuration (must include requestId) */ public async resendOtp(config: SmsOtpConfig): Promise { this.validateSDKReady(); // Pass through to SDK - error handling is done in EKYCModule.ts return await this.sdk.resendOtp(config); } // 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 onSmsOtpVerifySuccess(callback: (data: VerifyOtpResponse) => void) { const listener = this.sdk.onSmsOtpVerifySuccess(callback); if (!listener) { console.warn('⚠️ onSmsOtpVerifySuccess: Event emitter not ready.'); } 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 * @param finosToken Optional access token (Client Credentials) * @param isProd Optional flag for production environment (default: false) */ public async initializeESign(finosToken?: string, isProd: boolean = false): Promise { this.validateSDKReady(); // Pass through to SDK - error handling is done in EKYCModule.ts return await this.sdk.initializeESign(finosToken, isProd); } /** * Get SDK Token for Session * @param identity User Identity (CCCD/CMND) * @param name User Name * @param deviceId Device ID */ public async getSdkToken(identity: string, name: string, deviceId: string): Promise { this.validateSDKReady(); // Pass through to SDK - error handling is done in EKYCModule.ts return await this.sdk.getSdkToken(identity, name, deviceId); } /** * 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(); // Pass through to SDK - error handling is done in EKYCModule.ts return await this.sdk.registerDevice(recoverCode, pinCode, fcmToken); } /** * 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(); // Pass through to SDK - error handling is done in EKYCModule.ts return await this.sdk.listCerts(pageNumber, pageSize); } /** * Verify eSign certificate * @param serial Certificate serial number */ public async verifyCert(serial: string): Promise<{ code: string; message: string }> { this.validateSDKReady(); // Pass through to SDK - error handling is done in EKYCModule.ts return await this.sdk.verifyCert(serial); } /** * 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(); // Pass through to SDK - error handling is done in EKYCModule.ts return await this.sdk.listSignRequest(pageNumber, pageSize); } /** * 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(); // Pass through to SDK - error handling is done in EKYCModule.ts return await this.sdk.confirmSign(signRequestId, pinCode, authId, authData, confirm); } /** * Start Authorize Init * @param serial Serial number * @param quantity Quantity * @param time Time * @param message Message */ public async initAuthorize( serial: string, quantity: number, time: number, message: string ): Promise<{ code: string; message: string }> { this.validateSDKReady(); return await this.sdk.initAuthorize(serial, quantity, time, message); } /** * List Authorize * @param pageNumber Page number * @param pageSize Page size * @param status Status filter */ public async listAuthorize( pageNumber: number = 1, pageSize: number = 10, status: string = "" ): Promise<{ requests: AuthorizeInfo[] }> { this.validateSDKReady(); return await this.sdk.listAuthorize(pageNumber, pageSize, status); } /** * Register Authorize * @param authId Auth ID * @param authData Auth Data * @param authorizeRequestId Request ID * @param userPin User PIN * @param confirm Confirm (default true) */ public async registerAuthorize( authId: string, authData: string, authorizeRequestId: string, userPin: string, confirm: boolean = true ): Promise<{ code: string; message: string }> { this.validateSDKReady(); return await this.sdk.registerAuthorize(authId, authData, authorizeRequestId, userPin, confirm); } /** * Sign PDF Multiple Positions * @param requestJson Request JSON * @returns ESignPdfResult with status, msg, and data.transactionId */ public async signPdfMultiplePositions( requestJson: string ): Promise { this.validateSDKReady(); return await this.sdk.signPdfMultiplePositions(requestJson); } /** * Register remote signing certificate * @param requestJson JSON request body * @returns ESignApiResponse (status, msg, data.sessionId, response) */ public async registerRemoteSigning( requestJson: string ): Promise { this.validateSDKReady(); return await this.sdk.registerRemoteSigning(requestJson); } /** * Sign PDF document * @param requestJson JSON request body * @returns ESignPdfResult with status, msg, and data.transactionId */ public async signPdf( requestJson: string ): Promise { this.validateSDKReady(); // Pass through to SDK - error handling is done in EKYCModule.ts return await this.sdk.signPdf(requestJson); } /** * Send confirmation document * @param requestJson JSON string containing request data */ public async sendConfirmationDocument( requestJson: string ): Promise<{ response: string }> { this.validateSDKReady(); // Pass through to SDK - error handling is done in EKYCModule.ts return await this.sdk.sendConfirmationDocument(requestJson); } /** * Composite API: Register Remote Signing + Send Confirmation Document * Align với SdkeSignImpl.registerAndConfirm * @param requestJson JSON request body for registerRemoteSigning * @param confirmationDocBase64 PDF base64 for acceptanceDocs * @returns ESignApiResponse (status, msg, data.sessionId, response) */ public async registerAndConfirm( requestJson: string, confirmationDocBase64: string ): Promise { this.validateSDKReady(); return await this.sdk.registerAndConfirm(requestJson, confirmationDocBase64); } /** * Remove all event listeners */ public removeAllListeners(): void { this.sdk.removeAllListeners(); console.log('🧹 All event listeners removed'); } // eSign Event Listeners public onESignInitSuccess(callback: (data: ESignInitResult) => void) { const listener = this.sdk.onESignInitSuccess(callback); if (!listener) { console.warn('⚠️ onESignInitSuccess: Event emitter not ready. Make sure SDK is initialized.'); } return listener; } public onESignOpenSessionSuccess(callback: (data: ESignOpenSessionResult) => void) { const listener = this.sdk.onESignOpenSessionSuccess(callback); if (!listener) { console.warn('⚠️ onESignOpenSessionSuccess: Event emitter not ready.'); } return listener; } public onESignRegisterDeviceSuccess(callback: (data: { code: string; message: string }) => void) { const listener = this.sdk.onESignRegisterDeviceSuccess(callback); if (!listener) { console.warn('⚠️ onESignRegisterDeviceSuccess: Event emitter not ready.'); } return listener; } public onESignListCertsSuccess(callback: (data: { certs: ESignCertificate[] }) => void) { const listener = this.sdk.onESignListCertsSuccess(callback); if (!listener) { console.warn('⚠️ onESignListCertsSuccess: Event emitter not ready.'); } return listener; } public onESignVerifyCertSuccess(callback: (data: { code: string; message: string }) => void) { const listener = this.sdk.onESignVerifyCertSuccess(callback); if (!listener) { console.warn('⚠️ onESignVerifyCertSuccess: Event emitter not ready.'); } return listener; } public onESignListSignRequestSuccess(callback: (data: { requests: ESignSignRequest[] }) => void) { const listener = this.sdk.onESignListSignRequestSuccess(callback); if (!listener) { console.warn('⚠️ onESignListSignRequestSuccess: Event emitter not ready.'); } return listener; } public onESignConfirmSignSuccess(callback: (data: { code: string; message: string }) => void) { const listener = this.sdk.onESignConfirmSignSuccess(callback); if (!listener) { console.warn('⚠️ onESignConfirmSignSuccess: Event emitter not ready.'); } return listener; } public onESignInitAuthorizeSuccess(callback: (data: { code: string; message: string }) => void) { const listener = this.sdk.onESignInitAuthorizeSuccess(callback); if (!listener) { console.warn('⚠️ onESignInitAuthorizeSuccess: Event emitter not ready.'); } return listener; } public onESignListAuthorizeSuccess(callback: (data: { requests: AuthorizeInfo[] }) => void) { const listener = this.sdk.onESignListAuthorizeSuccess(callback); if (!listener) { console.warn('⚠️ onESignListAuthorizeSuccess: Event emitter not ready.'); } return listener; } public onESignRegisterAuthorizeSuccess(callback: (data: { code: string; message: string }) => void) { const listener = this.sdk.onESignRegisterAuthorizeSuccess(callback); if (!listener) { console.warn('⚠️ onESignRegisterAuthorizeSuccess: Event emitter not ready.'); } return listener; } public onESignSignPdfMultiplePositionsSuccess(callback: (data: { response: string }) => void) { const listener = this.sdk.onESignSignPdfMultiplePositionsSuccess(callback); if (!listener) { console.warn('⚠️ onESignSignPdfMultiplePositionsSuccess: Event emitter not ready.'); } return listener; } public onESignRegisterRemoteSigningSuccess(callback: (data: ESignApiResponse) => void) { const listener = this.sdk.onESignRegisterRemoteSigningSuccess(callback); if (!listener) { console.warn('⚠️ onESignRegisterRemoteSigningSuccess: Event emitter not ready.'); } return listener; } public onESignSignPdfSuccess(callback: (data: { response: string }) => void) { const listener = this.sdk.onESignSignPdfSuccess(callback); if (!listener) { console.warn('⚠️ onESignSignPdfSuccess: Event emitter not ready.'); } return listener; } public onESignSendConfirmationDocumentSuccess(callback: (data: { response: string }) => void) { const listener = this.sdk.onESignSendConfirmationDocumentSuccess(callback); if (!listener) { console.warn('⚠️ onESignSendConfirmationDocumentSuccess: Event emitter not ready.'); } return listener; } public onESignRegisterAndConfirmSuccess(callback: (data: ESignApiResponse) => void) { const listener = this.sdk.onESignRegisterAndConfirmSuccess(callback); if (!listener) { console.warn('⚠️ onESignRegisterAndConfirmSuccess: Event emitter not ready.'); } return listener; } /** Payload là EKYCError: { event, code, message }. */ public onESignError(callback: (error: EKYCError) => void) { const listener = this.sdk.onESignError(callback); if (!listener) { console.warn('⚠️ onESignError: Event emitter not ready.'); } return listener; } // ==================== Liveness Methods ==================== /** * 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 - Custom actions array (LEFT, RIGHT, STRAIGHT). If provided, uses these actions instead of random * @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) */ public async startLiveness(config: LivenessConfig): Promise { this.validateSDKReady(); // Pass through to SDK - error handling is done in EKYCModule.ts return await this.sdk.startLiveness(config); } // Liveness Event Listeners public onLivenessSuccess(callback: (data: LivenessSuccessEvent) => void) { return this.sdk.onLivenessSuccess(callback); } /** Payload là EKYCError: { event, code, message }. */ public onLivenessError(callback: (error: EKYCError) => void) { return this.sdk.onLivenessError(callback); } // ==================== Face Service Methods ==================== /** * Face matching (Face Service) * @param config Face service configuration */ public async startFaceCompare(config: FaceServiceConfig): Promise { this.validateSDKReady(); const normalized: FaceServiceConfig = { ...config, appKey: (config.appKeyFaceService || config.appKey), }; // Pass through to SDK - error handling is done in EKYCModule.ts return await this.sdk.startFaceCompare(normalized); } // Face Compare Event Listeners 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); } // ==================== eKYC UI Methods ==================== /** * Start eKYC UI with flow (excluding NFC, C06, OCR) * Only supports LIVENESS and FACE in flowSDK array * @param appKey Main app key * @param flowSDK Array of SDK types (only 'LIVENESS' and 'FACE' are allowed) * @param language Language code (vi/en) * @param transactionId Transaction ID * @param appKeyConfig App key configuration * @param optionConfig Optional configuration settings (includes switchFrontCamera for camera control) * @param styleConfig Optional style configuration */ public async startEkycUI( appKey: string, flowSDK: string[], language: string, transactionId: string, appKeyConfig: { appKey: string; appKeyLiveness: string; appKeyFaceService: string; }, optionConfig?: { baseUrl?: string; countMaxRetry?: number; language?: string; /** Network timeout in milliseconds (default: 30000). callTimeout = networkTimeoutMs * 2 */ networkTimeoutMs?: number; switchFrontCamera?: boolean; }, 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; }; } ): Promise { this.validateSDKReady(); // Validate flowSDK - only allow LIVENESS and FACE const allowedTypes = ['LIVENESS', 'FACE']; const invalidTypes = flowSDK.filter(type => !allowedTypes.includes(type)); if (invalidTypes.length > 0) { throw new Error(`Invalid SDK types in flowSDK: ${invalidTypes.join(', ')}. Only 'LIVENESS' and 'FACE' are allowed in FinosESignModule.`); } if (flowSDK.length === 0) { throw new Error('flowSDK must contain at least one SDK type (LIVENESS or FACE)'); } // Create full appKeyConfig with empty values for excluded modules const fullAppKeyConfig = { appKey: appKeyConfig.appKey, appKeyNfc: '', appKeyOcr: '', appKeyLiveness: appKeyConfig.appKeyLiveness, appKeyC06: '', appKeyFaceService: appKeyConfig.appKeyFaceService, }; // Pass through to SDK - error handling is done in EKYCModule.ts return await this.sdk.startEkycUI( appKey, flowSDK, language, transactionId, fullAppKeyConfig, optionConfig, styleConfig ); } // Private validation methods private validateSDKReady(): void { if (!this.isSDKReady()) { throw new Error('SDK is not initialized. Please call initialize() first.'); } } } // Export singleton instance - ensure it's always initialized let _finosESignInstance: FinosESignModule | null = null; const getFinosESignInstance = (): FinosESignModule => { if (!_finosESignInstance) { try { _finosESignInstance = FinosESignModule.getInstance(); } catch (error) { console.error('Failed to initialize FinosESignModule:', error); throw new Error('FinosESignModule initialization failed. Please check native module setup.'); } } return _finosESignInstance; }; // Initialize immediately try { _finosESignInstance = FinosESignModule.getInstance(); } catch (error) { // Silent initialization - will be created on first access } // Helper to check if a property is a method on FinosESignModule const isMethod = (prop: string | symbol): boolean => { if (typeof prop !== 'string') return false; // Check if it's a known method from the class const prototype = FinosESignModule.prototype as any; return typeof prototype[prop] === 'function' || prop.startsWith('on') || prop === 'initialize' || prop === 'initializeESign' || prop === 'getSdkToken' || prop === 'openSessionId' || prop === 'sendOtp' || prop === 'verifyOtp' || prop === 'resendOtp' || prop === 'registerDevice' || prop === 'listCerts' || prop === 'verifyCert' || prop === 'listSignRequest' || prop === 'confirmSign' || prop === 'registerRemoteSigning' || prop === 'registerAndConfirm' || prop === 'signPdf' || prop === 'signPdfMultiplePositions' || prop === 'initAuthorize' || prop === 'listAuthorize' || prop === 'registerAuthorize' || prop === 'sendConfirmationDocument' || prop === 'startLiveness' || prop === 'startFaceCompare' || prop === 'startEkycUI'; }; // Create a safe wrapper using Proxy - ensure it's always an object const createFinosESignProxy = (): FinosESignModule => { try { return new Proxy({} as FinosESignModule, { get(_target, prop) { try { const instance = getFinosESignInstance(); const value = (instance as any)[prop]; if (typeof value === 'function') { return value.bind(instance); } return value; } catch (error) { console.warn(`⚠️ FinosESign.${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(`⚠️ FinosESign.${prop} called but module is not initialized`); return null; }; } // For other methods, return a no-op async function return async (...args: any[]) => { console.warn(`⚠️ FinosESign.${String(prop)} called but module is not initialized`); throw new Error(`FinosESign.${String(prop)} is not available. Module may not be initialized.`); }; } return undefined; } } }); } catch (error) { console.error('❌ Failed to create FinosESign proxy:', error); // Return a comprehensive stub object return createFinosESignStub(); } }; // Create a comprehensive stub object with all methods to prevent undefined errors const createFinosESignStub = (): FinosESignModule => { const stub = {} as any; // Add all event listener methods (on* methods) const eventListenerMethods = [ 'onSmsOtpSendSuccess', 'onSmsOtpVerifySuccess', 'onSmsOtpResendSuccess', 'onSmsOtpError', 'onESignInitSuccess', 'onESignOpenSessionSuccess', 'onESignRegisterDeviceSuccess', 'onESignListCertsSuccess', 'onESignVerifyCertSuccess', 'onESignListSignRequestSuccess', 'onESignConfirmSignSuccess', 'onESignRegisterRemoteSigningSuccess', 'onESignSignPdfSuccess', 'onESignSignPdfMultiplePositionsSuccess', 'onESignSendConfirmationDocumentSuccess', 'onESignRegisterAndConfirmSuccess', 'onESignError', 'onESignInitAuthorizeSuccess', 'onESignListAuthorizeSuccess', 'onESignRegisterAuthorizeSuccess', 'onLivenessSuccess', 'onLivenessError', 'onFaceCompareSuccess', 'onFaceCompareError' ]; eventListenerMethods.forEach(method => { stub[method] = (callback?: any) => { console.warn(`⚠️ FinosESign.${method} called but module is not initialized`); return null; }; }); // Add all other methods const otherMethods = [ 'initialize', 'startNfcScan', 'checkC06', 'startOcr', 'startLiveness', 'startFaceCompare', 'startEkycUI', 'sendOtp', 'verifyOtp', 'resendOtp', 'initializeESign', 'getSdkToken', 'openSessionId', 'registerDevice', 'listCerts', 'verifyCert', 'listSignRequest', 'confirmSign', 'initAuthorize', 'listAuthorize', 'registerAuthorize', 'registerRemoteSigning', 'registerAndConfirm', 'signPdf', 'signPdfMultiplePositions', 'sendConfirmationDocument', 'onResume', 'onPause', 'isSDKReady', 'getSDKInfo' ]; otherMethods.forEach(method => { stub[method] = async (...args: any[]) => { console.warn(`⚠️ FinosESign.${method} called but module is not initialized`); throw new Error(`FinosESign.${method} is not available. Module may not be initialized.`); }; }); return stub as FinosESignModule; }; // Create a wrapper that always returns a valid object // This ensures FinosESign is NEVER undefined and always has all methods const createFinosESignWrapper = (): FinosESignModule => { // Always start with stub to ensure all methods exist const stub = createFinosESignStub(); const wrapper = { ...stub } as any; // Try to get real instance and override methods try { const realInstance = getFinosESignInstance(); // 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 FinosESignModule; }; // Export - this ensures FinosESign is never undefined export const FinosESign = createFinosESignWrapper(); // Export types export type { SmsOtpConfig, SmsOtpResult, SmsOtpError, SendOtpResponse, VerifyOtpResponse, ResendOtpResponse } from '../types/ekycSmsOtpType'; export type { ESignInitResult, ESignOpenSessionResult, ESignCertificate, ESignSignRequest, ESignError, ESignAuthenticateResult, AuthorizeInfo, ESignApiResponse } from '../types/ekycESignType'; export type { LivenessConfig, LivenessError } from '../types/ekycLivenessType'; export { SDKFaceDetectStatus } from '../types/ekycLivenessType'; export type { FaceServiceConfig, FaceCompareError } from '../types/ekycFaceType'; export type { SDKEkycResultStringWithEvent, SDKEkycResultWithEvent, EKYCError } from '../types/ekycType'; export { getEkycError } from '../types/ekycType'; // Export constants export { SDK_VERSION, SDK_NAME }; // Default export export default FinosESign;