@finos_sdk/sdk-ekyc 1.4.5 → 1.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  React Native SDK for eKYC (electronic Know Your Customer) and eSign. Features include Vietnamese CCCD NFC reading, OCR, Liveness detection, Face matching, C06 residence verification, SMS OTP verification, and Electronic Signature (eSign) capabilities.
7
7
 
8
- **Version**: 1.4.2
8
+ **Version**: 1.4.7
9
9
 
10
10
  ## Features
11
11
 
@@ -65,7 +65,7 @@ dependencies {
65
65
  implementation 'com.facebook.react:react-android'
66
66
 
67
67
  // Finos eKYC SDK dependencies from GitHub Packages Maven repository
68
- def sdkVersion = "1.4.5.2"
68
+ def sdkVersion = "1.4.7"
69
69
  implementation("finos.sdk.ekyc:ekyc:$sdkVersion")
70
70
  implementation("finos.sdk.ekyc:ekycui:$sdkVersion")
71
71
  implementation("finos.sdk.ekyc:nfc:$sdkVersion")
@@ -27,6 +27,7 @@ import finos.sdk.core.model.sdk.config.AppKeyConfig
27
27
  import finos.sdk.core.model.sdk.config.C06Config
28
28
  import finos.sdk.core.model.sdk.config.EKYCConfigSDK
29
29
  import finos.sdk.core.model.sdk.config.FaceServiceConfig
30
+ import finos.sdk.core.model.sdk.config.LivenessBackConfirmConfig
30
31
  import finos.sdk.core.model.sdk.config.LivenessConfig
31
32
  import finos.sdk.core.model.sdk.config.NfcConfig
32
33
  import finos.sdk.core.model.sdk.config.OcrConfig
@@ -96,6 +97,46 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
96
97
  return Pair(eventMap, promiseMap)
97
98
  }
98
99
 
100
+ /** Recursively convert org.json.JSONObject → WritableMap (preserves key names as-is). */
101
+ private fun jsonObjectToWritableMap(jsonObject: JSONObject): WritableMap {
102
+ val map = Arguments.createMap()
103
+ val keys = jsonObject.keys()
104
+ while (keys.hasNext()) {
105
+ val key = keys.next()
106
+ when (val value = jsonObject.get(key)) {
107
+ is JSONObject -> map.putMap(key, jsonObjectToWritableMap(value))
108
+ is JSONArray -> map.putArray(key, jsonArrayToWritableArray(value))
109
+ is Boolean -> map.putBoolean(key, value)
110
+ is Int -> map.putInt(key, value)
111
+ is Long -> map.putDouble(key, value.toDouble())
112
+ is Double -> map.putDouble(key, value)
113
+ is String -> map.putString(key, value)
114
+ JSONObject.NULL -> map.putNull(key)
115
+ else -> map.putString(key, value.toString())
116
+ }
117
+ }
118
+ return map
119
+ }
120
+
121
+ /** Recursively convert org.json.JSONArray → WritableArray. */
122
+ private fun jsonArrayToWritableArray(jsonArray: JSONArray): WritableArray {
123
+ val array = Arguments.createArray()
124
+ for (i in 0 until jsonArray.length()) {
125
+ when (val value = jsonArray.get(i)) {
126
+ is JSONObject -> array.pushMap(jsonObjectToWritableMap(value))
127
+ is JSONArray -> array.pushArray(jsonArrayToWritableArray(value))
128
+ is Boolean -> array.pushBoolean(value)
129
+ is Int -> array.pushInt(value)
130
+ is Long -> array.pushDouble(value.toDouble())
131
+ is Double -> array.pushDouble(value)
132
+ is String -> array.pushString(value)
133
+ JSONObject.NULL -> array.pushNull()
134
+ else -> array.pushString(value.toString())
135
+ }
136
+ }
137
+ return array
138
+ }
139
+
99
140
  /** Convert ESignOpenSessionResult to WritableMap for RN. */
100
141
  private fun eSignOpenSessionResultToWritableMap(result: ESignModels.ESignOpenSessionResult): WritableMap =
101
142
  Arguments.createMap().apply {
@@ -235,7 +276,7 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
235
276
  val eventMap =
236
277
  Arguments.createMap().apply {
237
278
  putString("event", event.name.toString())
238
- putString("data", data.toString())
279
+ putString("data", Gson().toJson(data))
239
280
  }
240
281
  sendEvent("onNfcScanStart", eventMap)
241
282
  }
@@ -260,7 +301,7 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
260
301
  val eventMap =
261
302
  Arguments.createMap().apply {
262
303
  putString("event", event.name.toString())
263
- putString("data", data.toString())
304
+ putString("data", Gson().toJson(data))
264
305
  }
265
306
  sendEvent("onNfcEvent", eventMap)
266
307
  }
@@ -314,18 +355,19 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
314
355
  ekycConfigSDK = ekycConfig,
315
356
  callbackSuccess = { event, data ->
316
357
  Log.d(TAG, "✅ checkC06() success")
358
+ val c06Json = Gson().toJson((data as? SDKEkycResult)?.checkC06Response)
317
359
 
318
360
  // Create separate maps for event and promise
319
361
  val eventMap =
320
362
  Arguments.createMap().apply {
321
363
  putString("event", event.name.toString())
322
- putString("data", data.toString())
364
+ putString("data", c06Json)
323
365
  }
324
366
 
325
367
  val promiseMap =
326
368
  Arguments.createMap().apply {
327
369
  putString("event", event.name.toString())
328
- putString("data", data.toString())
370
+ putString("data", c06Json)
329
371
  }
330
372
  sendEvent("onC06Success", eventMap)
331
373
  promise.resolve(promiseMap)
@@ -389,7 +431,7 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
389
431
  Log.d(TAG, "✅ startOcr() success")
390
432
  val (eventMap, promiseMap) = createSeparateMaps { map ->
391
433
  map.putString("event", event.name.toString())
392
- map.putString("data", (data as SDKEkycResult).ocrResponse.toString())
434
+ map.putString("data", Gson().toJson((data as SDKEkycResult).ocrResponse))
393
435
  }
394
436
  sendEvent("onOcrSuccess", eventMap)
395
437
  promise.resolve(promiseMap)
@@ -428,6 +470,8 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
428
470
  activeActionCount: Int?,
429
471
  forceCaptureTimeout: Double?,
430
472
  isActiveLivenessColor: Boolean?,
473
+ isShowBackConfirmation: Boolean?,
474
+ backConfirmConfigJson: String?,
431
475
  promise: Promise
432
476
  ) {
433
477
  Log.d(TAG, "▶️ startLiveness() called")
@@ -461,6 +505,7 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
461
505
  null
462
506
  }
463
507
 
508
+ val backConfirmConfig = parseBackConfirmConfig(backConfirmConfigJson)
464
509
  val livenessConfig =
465
510
  LivenessConfig(
466
511
  isActiveLiveness = isActiveLiveness ?: false,
@@ -475,7 +520,9 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
475
520
  activeActionCount = activeActionCount ?: 2,
476
521
  forceCaptureTimeout = (forceCaptureTimeout ?: 0.0).toLong(),
477
522
  selfieImage = imageFile,
478
- transactionId = transactionId
523
+ transactionId = transactionId,
524
+ isShowBackConfirmation = isShowBackConfirmation ?: false,
525
+ backConfirmConfig = backConfirmConfig,
479
526
  )
480
527
  val ekycConfig =
481
528
  EKYCConfigSDK(
@@ -486,24 +533,25 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
486
533
  eKYCFinOSLiveness.startEkyc(
487
534
  ekycConfigSDK = ekycConfig,
488
535
  callbackSuccess = { event, data ->
489
- // LOG_SUCCESS = internal submit result event (v1.4.5), don't resolve promise
490
- if (event == EKYCEvent.LOG_SUCCESS) {
491
- val logDataStr = data?.customData?.let { Gson().toJson(it) } ?: data?.toString() ?: "null"
492
- Log.d(TAG, "LOG_SUCCESS [Liveness]: $logDataStr")
493
- val logMap = Arguments.createMap().apply {
494
- putString("event", event.name.toString())
495
- putString("data", logDataStr)
496
- }
497
- sendEvent("onLivenessSuccess", logMap)
498
- return@startEkyc
499
- }
500
- Log.d(TAG, "✅ startLiveness() success")
501
- val (eventMap, promiseMap) = createSeparateMaps { map ->
502
- map.putString("event", event.name.toString())
503
- map.putString("data", (data as SDKEkycResult).checkLivenessResponse.toString())
504
- }
505
- sendEvent("onLivenessSuccess", eventMap)
506
- promise.resolve(promiseMap)
536
+ Log.d(TAG, "🔥 NATIVE CALLBACK (Liveness): event=${event.name}")
537
+ val response = (data as? SDKEkycResult)?.checkLivenessResponse
538
+ val jsonStr = data?.customData?.let { Gson().toJson(it) } ?: Gson().toJson(response ?: data)
539
+
540
+ // Helper to create fresh data map to avoid 'Map already consumed' error
541
+ fun createWritableData() = try { jsonObjectToWritableMap(JSONObject(jsonStr)) } catch (e: Exception) { Arguments.createMap() }
542
+
543
+ // Send the event directly to React Native
544
+ sendEvent("onLivenessSuccess", Arguments.createMap().apply {
545
+ putString("event", event.name)
546
+ putMap("data", createWritableData())
547
+ })
548
+
549
+ // Always attempt to resolve the promise.
550
+ // RN safely ignores subsequent resolves if the native SDK fires multiple events.
551
+ promise.resolve(Arguments.createMap().apply {
552
+ putString("event", event.name)
553
+ putMap("data", createWritableData())
554
+ })
507
555
  },
508
556
  callbackError = { event, errorResult ->
509
557
  Log.e(TAG, "❌ startLiveness() failed - Event: $event, Code: ${errorResult.code}, Message: ${errorResult.message}")
@@ -570,24 +618,21 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
570
618
  eKYCFinOSFaceService.startEkyc(
571
619
  ekycConfigSDK = ekycConfig,
572
620
  callbackSuccess = { event, data ->
573
- // LOG_SUCCESS = internal submit result event (v1.4.5), don't resolve promise
574
- if (event == EKYCEvent.LOG_SUCCESS) {
575
- val logDataStr = data?.customData?.let { Gson().toJson(it) } ?: data?.toString() ?: "null"
576
- Log.d(TAG, "LOG_SUCCESS [FaceCompare]: $logDataStr")
577
- val logMap = Arguments.createMap().apply {
578
- putString("event", event.name.toString())
579
- putString("data", logDataStr)
580
- }
581
- sendEvent("onFaceCompareSuccess", logMap)
582
- return@startEkyc
583
- }
584
- Log.d(TAG, "✅ startFaceCompare() success")
585
- val (eventMap, promiseMap) = createSeparateMaps { map ->
586
- map.putString("event", event.name.toString())
587
- map.putString("data", data.toString())
588
- }
589
- sendEvent("onFaceCompareSuccess", eventMap)
590
- promise.resolve(promiseMap)
621
+ Log.d(TAG, "🔥 NATIVE CALLBACK (Face Compare): event=${event.name}")
622
+ val response = (data as? SDKEkycResult)?.checkFaceResponse
623
+ val jsonStr = data?.customData?.let { Gson().toJson(it) } ?: Gson().toJson(response ?: data)
624
+ fun createWritableData() = try { jsonObjectToWritableMap(JSONObject(jsonStr)) } catch (e: Exception) { Arguments.createMap() }
625
+
626
+ sendEvent("onFaceCompareSuccess", Arguments.createMap().apply {
627
+ putString("event", event.name)
628
+ putMap("data", createWritableData())
629
+ })
630
+
631
+ // Always attempt to resolve the promise.
632
+ promise.resolve(Arguments.createMap().apply {
633
+ putString("event", event.name)
634
+ putMap("data", createWritableData())
635
+ })
591
636
  },
592
637
  callbackError = { event, errorResult ->
593
638
  Log.e(TAG, "❌ startFaceCompare() failed - Event: $event, Code: ${errorResult.code}, Message: ${errorResult.message}")
@@ -783,6 +828,9 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
783
828
  else -> AppIDType.NONE
784
829
  }
785
830
 
831
+ val isShowBackConfirmation = extractBooleanValue(optionConfigJson, "isShowBackConfirmation") ?: false
832
+ val backConfirmConfig = parseBackConfirmConfig(extractObjectJson(optionConfigJson, "backConfirmConfig"))
833
+
786
834
  val livenessConfig = if (finalFlow.contains(SDKType.LIVENESS)) {
787
835
  LivenessConfig(
788
836
  isActiveLiveness = isActiveLiveness,
@@ -790,6 +838,8 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
790
838
  isShowCameraFont = switchFrontCamera,
791
839
  customActions = customActionsList?.takeIf { it.isNotEmpty() },
792
840
  activeActionCount = activeActionCount,
841
+ isShowBackConfirmation = isShowBackConfirmation,
842
+ backConfirmConfig = backConfirmConfig,
793
843
  )
794
844
  } else null
795
845
 
@@ -807,41 +857,55 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
807
857
  activity = currentActivity,
808
858
  ekycConfigSDK = ekycConfigSDK,
809
859
  callbackSuccess = { event, data ->
810
- // LOG_SUCCESS = internal submit result event (v1.4.5), emit event only, don't resolve promise
811
- if (event == EKYCEvent.LOG_SUCCESS) {
812
- val logDataStr = data?.customData?.let { Gson().toJson(it) } ?: data?.toString() ?: "null"
813
- Log.d(TAG, "LOG_SUCCESS [EkycUI]: $logDataStr")
814
- sendEvent("onEkycUISuccess", Arguments.createMap().apply {
815
- putString("status", "log")
816
- putString("event", event.name.toString())
817
- putString("data", logDataStr)
818
- })
819
- return@startEkyc
820
- }
821
- Log.d(TAG, "✅ startEkycUI() callback - event=${event.name}")
860
+ Log.d(TAG, "🔥 NATIVE CALLBACK (EkycUI): event=${event.name}")
861
+
822
862
  val result = data as? SDKEkycResult
823
- val hasRealData = result?.ekycStateModel != null
824
- // Chỉ resolve promise khi flow hoàn thành có data (SDK_END_SUCCESS); SDK_START_SUCCESS = activity vừa mở, bỏ qua
825
- if (event == EKYCEvent.SDK_START_SUCCESS && !hasRealData) {
826
- Log.d(TAG, "⏳ startEkycUI() SDK_START_SUCCESS chờ SDK_END_SUCCESS")
827
- sendEvent("onEkycUISuccess", Arguments.createMap().apply {
828
- putString("status", "started")
829
- putString("event", event.name.toString())
830
- })
831
- } else {
863
+ val response = result?.ekycStateModel
864
+ val jsonStr = data?.customData?.let { Gson().toJson(it) } ?: Gson().toJson(response ?: data)
865
+
866
+ // Helper to create fresh data map to avoid 'Map already consumed' error
867
+ fun createWritableData() = try { jsonObjectToWritableMap(JSONObject(jsonStr)) } catch (e: Exception) { Arguments.createMap() }
868
+
869
+ val statusStr = when (event) {
870
+ EKYCEvent.LOG_SUCCESS -> "log"
871
+ EKYCEvent.SDK_START_SUCCESS -> "started"
872
+ else -> "success"
873
+ }
874
+
875
+ // Send EVERY event directly to React Native Event Listener (Pass-through)
876
+ sendEvent("onEkycUISuccess", Arguments.createMap().apply {
877
+ putString("status", statusStr)
878
+ putString("event", event.name)
879
+
880
+ if (event == EKYCEvent.SDK_START_SUCCESS && response == null) {
881
+ // Bỏ qua data nếu là SDK_START_SUCCESS (chỉ có event/status) để giống code cũ
882
+ } else {
883
+ putMap("data", createWritableData())
884
+ }
885
+
886
+ // Retain the convenient root properties cho backward compatibility
832
887
  val ekycFiles = result?.ekycStateModel?.eKYCFileModel
833
888
  val ekycTransactionId = result?.ekycStateModel?.transactionId ?: ""
834
- val (eventMap, promiseMap) = createSeparateMaps { map ->
835
- map.putString("status", "success")
836
- map.putString("event", event.name.toString())
837
- map.putString("data", data.toString())
838
- map.putString("transactionId", ekycTransactionId)
839
- ekycFiles?.imageFace?.absolutePath?.let { map.putString("imageFacePath", it) }
840
- ekycFiles?.imageOcrFront?.absolutePath?.let { map.putString("imageOcrFrontPath", it) }
841
- ekycFiles?.imageOcrBack?.absolutePath?.let { map.putString("imageOcrBackPath", it) }
842
- }
843
- sendEvent("onEkycUISuccess", eventMap)
844
- promise.resolve(promiseMap)
889
+ if (ekycTransactionId.isNotEmpty()) putString("transactionId", ekycTransactionId)
890
+ ekycFiles?.imageFace?.absolutePath?.let { putString("imageFacePath", it) }
891
+ ekycFiles?.imageOcrFront?.absolutePath?.let { putString("imageOcrFrontPath", it) }
892
+ ekycFiles?.imageOcrBack?.absolutePath?.let { putString("imageOcrBackPath", it) }
893
+ })
894
+
895
+ // IMPORTANT: Chỉ resolve promise ở SDK_END_SUCCESS để `await FinosEKYC.startEkycUI()` không bị kết thúc sớm
896
+ if (event == EKYCEvent.SDK_END_SUCCESS) {
897
+ promise.resolve(Arguments.createMap().apply {
898
+ putString("status", "success")
899
+ putString("event", event.name)
900
+ putMap("data", createWritableData())
901
+
902
+ val ekycFiles = result?.ekycStateModel?.eKYCFileModel
903
+ val ekycTransactionId = result?.ekycStateModel?.transactionId ?: ""
904
+ if (ekycTransactionId.isNotEmpty()) putString("transactionId", ekycTransactionId)
905
+ ekycFiles?.imageFace?.absolutePath?.let { putString("imageFacePath", it) }
906
+ ekycFiles?.imageOcrFront?.absolutePath?.let { putString("imageOcrFrontPath", it) }
907
+ ekycFiles?.imageOcrBack?.absolutePath?.let { putString("imageOcrBackPath", it) }
908
+ })
845
909
  }
846
910
  },
847
911
  callbackError = { event, errorResult ->
@@ -853,14 +917,14 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
853
917
  putString("customMessage", errorResult.message)
854
918
  putString("message", errorResult.message)
855
919
  }
920
+ sendEvent("onEkycUIError", errorMap)
921
+
856
922
  val promiseErrorMap = Arguments.createMap().apply {
857
923
  putString("status", "error")
858
924
  putString("event", event.name.toString())
859
925
  putString("customCode", errorResult.code)
860
926
  putString("customMessage", errorResult.message)
861
- putString("message", errorResult.message)
862
927
  }
863
- sendEvent("onEkycUIError", errorMap)
864
928
  promise.reject(event.name.toString(), errorResult.message, null, promiseErrorMap)
865
929
  }
866
930
  )
@@ -895,32 +959,42 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
895
959
  private fun parseOptionConfig(optionConfigJson: String): OptionConfig {
896
960
  return try {
897
961
  if (optionConfigJson.isBlank() || optionConfigJson == "{}") {
898
- // Return default values based on demo OptionConfig.kt
899
- OptionConfig(
900
- baseUrl = null,
901
- countMaxRetry = 3,
902
- language = null
903
- )
962
+ OptionConfig()
904
963
  } else {
905
- // Simple JSON parsing (in real implementation, use proper JSON library)
906
964
  val baseUrl = extractStringValue(optionConfigJson, "baseUrl")
907
965
  val countMaxRetry = extractIntValue(optionConfigJson, "countMaxRetry") ?: 3
908
966
  val language = extractStringValue(optionConfigJson, "language")
967
+ val networkTimeoutMs = extractLongValue(optionConfigJson, "networkTimeoutMs") ?: 30_000L
909
968
 
910
969
  OptionConfig(
911
970
  baseUrl = baseUrl,
912
971
  countMaxRetry = countMaxRetry,
913
- language = language
972
+ language = language,
973
+ networkTimeoutMs = networkTimeoutMs,
914
974
  )
915
975
  }
916
976
  } catch (e: Exception) {
917
977
  Log.e(TAG, "Error parsing optionConfig: ${e.message}", e)
918
- // Return default values
919
- OptionConfig(
920
- baseUrl = null,
921
- countMaxRetry = 3,
922
- language = null
978
+ OptionConfig()
979
+ }
980
+ }
981
+
982
+ private fun parseBackConfirmConfig(json: String?): LivenessBackConfirmConfig? {
983
+ if (json.isNullOrBlank() || json == "{}") return null
984
+ return try {
985
+ LivenessBackConfirmConfig(
986
+ titleVi = extractStringValue(json, "titleVi"),
987
+ titleEn = extractStringValue(json, "titleEn"),
988
+ bodyVi = extractStringValue(json, "bodyVi"),
989
+ bodyEn = extractStringValue(json, "bodyEn"),
990
+ confirmButtonVi = extractStringValue(json, "confirmButtonVi"),
991
+ confirmButtonEn = extractStringValue(json, "confirmButtonEn"),
992
+ cancelButtonVi = extractStringValue(json, "cancelButtonVi"),
993
+ cancelButtonEn = extractStringValue(json, "cancelButtonEn"),
923
994
  )
995
+ } catch (e: Exception) {
996
+ Log.e(TAG, "Error parsing backConfirmConfig: ${e.message}", e)
997
+ null
924
998
  }
925
999
  }
926
1000
 
@@ -934,6 +1008,16 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
934
1008
  }
935
1009
  }
936
1010
 
1011
+ private fun extractLongValue(json: String, key: String): Long? {
1012
+ return try {
1013
+ val pattern = "\"$key\"\\s*:\\s*(-?\\d+)".toRegex()
1014
+ val match = pattern.find(json)
1015
+ match?.groupValues?.get(1)?.toLong()
1016
+ } catch (e: Exception) {
1017
+ null
1018
+ }
1019
+ }
1020
+
937
1021
  private fun extractBooleanValue(json: String, key: String): Boolean? {
938
1022
  return try {
939
1023
  val pattern = "\"$key\"\\s*:\\s*(true|false)".toRegex()
@@ -1088,6 +1172,17 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
1088
1172
  }
1089
1173
  }
1090
1174
 
1175
+ /** Extract a nested JSON object `{ ... }` for a given key. Returns the raw JSON string or null. */
1176
+ private fun extractObjectJson(json: String, key: String): String? {
1177
+ return try {
1178
+ val obj = JSONObject(json)
1179
+ val nested = obj.optJSONObject(key) ?: return null
1180
+ nested.toString()
1181
+ } catch (e: Exception) {
1182
+ null
1183
+ }
1184
+ }
1185
+
1091
1186
  // ==================== SMS OTP Methods ====================
1092
1187
 
1093
1188
  @ReactMethod
@@ -2115,4 +2210,13 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
2115
2210
  }
2116
2211
  }
2117
2212
 
2213
+ @ReactMethod
2214
+ fun addListener(eventName: String) {
2215
+ // Keep: Required for RN built-in Event Emitter Calls.
2216
+ }
2217
+
2218
+ @ReactMethod
2219
+ fun removeListeners(count: Int) {
2220
+ // Keep: Required for RN built-in Event Emitter Calls.
2221
+ }
2118
2222
  }
@@ -3,8 +3,8 @@ import { NfcConfig, NfcError } from './src/types/ekycNFCType';
3
3
  import { SDKEkycResultWithEvent, SDKEkycResultStringWithEvent, StartEkycUIResult } from './src/types/ekycType';
4
4
  import { C06Config } from './src/types/ekycC06Type';
5
5
  import { OcrConfig } from './src/types/ekycOCRType';
6
- import { LivenessConfig, SDKFaceDetectStatus } from './src/types/ekycLivenessType';
7
- import { FaceServiceConfig } from './src/types/ekycFaceType';
6
+ import { LivenessConfig, SDKFaceDetectStatus, LivenessSuccessEvent } from './src/types/ekycLivenessType';
7
+ import { FaceServiceConfig, FaceCompareSuccessEvent } from './src/types/ekycFaceType';
8
8
  import { SmsOtpConfig, SmsOtpResult, SmsOtpError } from './src/types/ekycSmsOtpType';
9
9
  import { ESignInitResult, ESignOpenSessionResult, ESignCertificate, ESignSignRequest, ESignError, AuthorizeInfo, ESignPdfResult, ESignApiResponse } from './src/types/ekycESignType';
10
10
  export declare const SDK_VERSION: string;
@@ -29,8 +29,8 @@ declare class SDKeKYC {
29
29
  startOcr(config: OcrConfig): Promise<SDKEkycResultStringWithEvent>;
30
30
  startNfcScan(config: NfcConfig): Promise<SDKEkycResultStringWithEvent>;
31
31
  checkC06(config: C06Config): Promise<SDKEkycResultStringWithEvent>;
32
- startLiveness(config: LivenessConfig): Promise<SDKEkycResultStringWithEvent>;
33
- startFaceCompare(config: FaceServiceConfig): Promise<SDKEkycResultStringWithEvent>;
32
+ startLiveness(config: LivenessConfig): Promise<LivenessSuccessEvent>;
33
+ startFaceCompare(config: FaceServiceConfig): Promise<FaceCompareSuccessEvent>;
34
34
  onResume(): void;
35
35
  onPause(): void;
36
36
  onNfcScanStart(callback: (data: SDKEkycResultWithEvent) => void): EmitterSubscription | null;
@@ -40,9 +40,9 @@ declare class SDKeKYC {
40
40
  onC06Error(callback: (error: NfcError) => void): EmitterSubscription | null;
41
41
  onOcrSuccess(callback: (data: SDKEkycResultStringWithEvent) => void): EmitterSubscription | null;
42
42
  onOcrError(callback: (error: any) => void): EmitterSubscription | null;
43
- onLivenessSuccess(callback: (data: SDKEkycResultStringWithEvent) => void): EmitterSubscription | null;
43
+ onLivenessSuccess(callback: (data: LivenessSuccessEvent) => void): EmitterSubscription | null;
44
44
  onLivenessError(callback: (error: any) => void): EmitterSubscription | null;
45
- onFaceCompareSuccess(callback: (data: SDKEkycResultWithEvent) => void): EmitterSubscription | null;
45
+ onFaceCompareSuccess(callback: (data: FaceCompareSuccessEvent) => void): EmitterSubscription | null;
46
46
  onFaceCompareError(callback: (error: any) => void): EmitterSubscription | null;
47
47
  onEkycUISuccess(callback: (data: any) => void): EmitterSubscription | null;
48
48
  onEkycUIError(callback: (error: any) => void): EmitterSubscription | null;
@@ -148,6 +148,16 @@ declare class SDKeKYC {
148
148
  baseUrl?: string;
149
149
  countMaxRetry?: number;
150
150
  language?: string;
151
+ networkTimeoutMs?: number;
152
+ switchFrontCamera?: boolean;
153
+ isActiveLiveness?: boolean;
154
+ autoCapture?: boolean;
155
+ forceCaptureTimeout?: number;
156
+ customActions?: string[];
157
+ activeActionCount?: number;
158
+ appIDType?: string;
159
+ isShowBackConfirmation?: boolean;
160
+ backConfirmConfig?: import('./src/types/ekycLivenessType').LivenessBackConfirmConfig;
151
161
  }, styleConfig?: {
152
162
  textSize?: number;
153
163
  textFont?: string;
@@ -213,7 +213,10 @@ class SDKeKYC {
213
213
  try {
214
214
  // Convert SDKFaceDetectStatus enum array to string array
215
215
  const customActionsStrings = (_b = (_a = config.customActions) === null || _a === void 0 ? void 0 : _a.map(action => String(action))) !== null && _b !== void 0 ? _b : undefined;
216
- return await nativeModule.startLiveness(config.appKey, config.selfieImage, (_c = config.transactionId) !== null && _c !== void 0 ? _c : '', config.isActiveLiveness, config.isShowCameraFont, customActionsStrings, config.activeActionCount, config.forceCaptureTimeout, config.isActiveLivenessColor);
216
+ const backConfirmConfigJson = config.backConfirmConfig
217
+ ? JSON.stringify(config.backConfirmConfig)
218
+ : undefined;
219
+ return await nativeModule.startLiveness(config.appKey, config.selfieImage, (_c = config.transactionId) !== null && _c !== void 0 ? _c : '', config.isActiveLiveness, config.isShowCameraFont, customActionsStrings, config.activeActionCount, config.forceCaptureTimeout, config.isActiveLivenessColor, config.isShowBackConfirmation, backConfirmConfigJson);
217
220
  }
218
221
  catch (error) {
219
222
  console.error('Liveness Error:', error);
package/dist/index.d.ts CHANGED
@@ -9,9 +9,9 @@ export { SDKFlowType, SDK_FLOW_OPTIONS, flowToStrings } from './src/types/ekycFl
9
9
  export type { NfcConfig, NFCData, NfcInfo } from './src/types/ekycNFCType';
10
10
  export type { C06Config } from './src/types/ekycC06Type';
11
11
  export type { OcrConfig } from './src/types/ekycOCRType';
12
- export type { LivenessConfig } from './src/types/ekycLivenessType';
12
+ export type { LivenessConfig, LivenessSuccessEvent, CheckLivenessResponse, Metadata, LivenessCheckResult, Details, QualityChecks, QualityCheck, AgeRange, LiveFace } from './src/types/ekycLivenessType';
13
13
  export { SDKFaceDetectStatus, SDK_LIVENESS_ACTIONS, customActionsToStrings } from './src/types/ekycLivenessType';
14
- export type { FaceServiceConfig } from './src/types/ekycFaceType';
14
+ export type { FaceServiceConfig, FaceCompareSuccessEvent, CheckFaceResponse, FaceVerifyResult } from './src/types/ekycFaceType';
15
15
  export { EKYCEvent, EKYCErrorEvent } from './src/types/EKYCEvent';
16
16
  export type { SDKEkycResultWithEvent, SDKEkycResultStringWithEvent, StartEkycUIResult, EKYCError } from './src/types/ekycType';
17
17
  export { getEkycError, AppIDType } from './src/types/ekycType';
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finos_sdk/sdk-ekyc",
3
- "version": "1.4.5",
3
+ "version": "1.4.7",
4
4
  "description": "React Native SDK for eKYC - Vietnamese CCCD NFC reading, OCR, Liveness detection, Face matching, and C06, eSign, SmsOTP residence verification",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -3,8 +3,8 @@ import { NfcConfig } from '../types/ekycNFCType';
3
3
  import { SDKEkycResultWithEvent, SDKEkycResultStringWithEvent, EKYCError, AppIDType } from '../types/ekycType';
4
4
  import { C06Config } from '../types/ekycC06Type';
5
5
  import { OcrConfig } from '../types/ekycOCRType';
6
- import { LivenessConfig, SDKFaceDetectStatus } from '../types/ekycLivenessType';
7
- import { FaceServiceConfig } from '../types/ekycFaceType';
6
+ import { LivenessConfig, SDKFaceDetectStatus, LivenessSuccessEvent } from '../types/ekycLivenessType';
7
+ import { FaceServiceConfig, FaceCompareSuccessEvent } from '../types/ekycFaceType';
8
8
  import { SmsOtpConfig, SmsOtpResult } from '../types/ekycSmsOtpType';
9
9
  import { ESignInitResult, ESignOpenSessionResult, ESignCertificate, ESignSignRequest, ESignPdfResult, ESignApiResponse } from '../types/ekycESignType';
10
10
  import { SDKFlowType } from '../types/ekycFlowType';
@@ -77,12 +77,12 @@ export declare class FinosEKYCModule {
77
77
  * @param config.activeActionCount - Number of random actions (1-10), only used when customActions is null (default: 2)
78
78
  * @param config.switchFrontCamera - Use front camera (default: false)
79
79
  */
80
- startLiveness(config: LivenessConfig): Promise<SDKEkycResultStringWithEvent>;
80
+ startLiveness(config: LivenessConfig): Promise<LivenessSuccessEvent>;
81
81
  /**
82
82
  * Start face comparison (Face Service)
83
83
  * @param config Face service configuration
84
84
  */
85
- startFaceCompare(config: FaceServiceConfig): Promise<SDKEkycResultStringWithEvent>;
85
+ startFaceCompare(config: FaceServiceConfig): Promise<FaceCompareSuccessEvent>;
86
86
  /**
87
87
  * Extract NFC data for C06 verification
88
88
  * @param nfcResultJson NFC result JSON string
@@ -134,7 +134,7 @@ export declare class FinosEKYCModule {
134
134
  /**
135
135
  * Listen for liveness success events
136
136
  */
137
- onLivenessSuccess(callback: (data: SDKEkycResultStringWithEvent) => void): import("react-native").EmitterSubscription | null;
137
+ onLivenessSuccess(callback: (data: LivenessSuccessEvent) => void): import("react-native").EmitterSubscription | null;
138
138
  /**
139
139
  * Listen for liveness error events
140
140
  */
@@ -143,7 +143,7 @@ export declare class FinosEKYCModule {
143
143
  /**
144
144
  * Listen for face compare success events
145
145
  */
146
- onFaceCompareSuccess(callback: (data: SDKEkycResultWithEvent) => void): import("react-native").EmitterSubscription | null;
146
+ onFaceCompareSuccess(callback: (data: FaceCompareSuccessEvent) => void): import("react-native").EmitterSubscription | null;
147
147
  /** Payload là EKYCError: { event, code, message }. */
148
148
  onFaceCompareError(callback: (error: EKYCError) => void): import("react-native").EmitterSubscription | null;
149
149
  /**
@@ -308,6 +308,8 @@ export declare class FinosEKYCModule {
308
308
  baseUrl?: string;
309
309
  countMaxRetry?: number;
310
310
  language?: string;
311
+ /** Network timeout in milliseconds (default: 30000). callTimeout = networkTimeoutMs * 2 */
312
+ networkTimeoutMs?: number;
311
313
  switchFrontCamera?: boolean;
312
314
  /** LivenessConfig – SDKeKYCActivity (157-169) */
313
315
  isActiveLiveness?: boolean;
@@ -318,6 +320,10 @@ export declare class FinosEKYCModule {
318
320
  activeActionCount?: number;
319
321
  /** AppIDType: NONE | HD_BANK | VIKKI (default: AppIDType.NONE) */
320
322
  appIDType?: AppIDType;
323
+ /** Show confirmation bottom sheet when user presses back in liveness screen (v1.4.6+) */
324
+ isShowBackConfirmation?: boolean;
325
+ /** Text config for the back confirmation bottom sheet (v1.4.6+) */
326
+ backConfirmConfig?: import('../types/ekycLivenessType').LivenessBackConfirmConfig;
321
327
  }, styleConfig?: {
322
328
  textSize?: number;
323
329
  textFont?: string;
@@ -1,9 +1,9 @@
1
1
  import { SDK_VERSION, SDK_NAME } from '../../EKYCModule';
2
2
  import { SmsOtpConfig, SmsOtpResult } from '../types/ekycSmsOtpType';
3
3
  import { ESignInitResult, ESignOpenSessionResult, ESignCertificate, ESignSignRequest, AuthorizeInfo, ESignPdfResult, ESignApiResponse } from '../types/ekycESignType';
4
- import { LivenessConfig } from '../types/ekycLivenessType';
5
- import { FaceServiceConfig } from '../types/ekycFaceType';
6
- import { SDKEkycResultStringWithEvent, SDKEkycResultWithEvent, EKYCError } from '../types/ekycType';
4
+ import { LivenessConfig, LivenessSuccessEvent } from '../types/ekycLivenessType';
5
+ import { FaceServiceConfig, FaceCompareSuccessEvent } from '../types/ekycFaceType';
6
+ import { EKYCError } from '../types/ekycType';
7
7
  /**
8
8
  * Finos eSign SDK Module
9
9
  *
@@ -255,16 +255,16 @@ export declare class FinosESignModule {
255
255
  * @param config.activeActionCount - Number of random actions (1-10), only used when customActions is null (default: 2)
256
256
  * @param config.switchFrontCamera - Use front camera (default: false)
257
257
  */
258
- startLiveness(config: LivenessConfig): Promise<SDKEkycResultStringWithEvent>;
259
- onLivenessSuccess(callback: (data: SDKEkycResultStringWithEvent) => void): import("react-native").EmitterSubscription | null;
258
+ startLiveness(config: LivenessConfig): Promise<LivenessSuccessEvent>;
259
+ onLivenessSuccess(callback: (data: LivenessSuccessEvent) => void): import("react-native").EmitterSubscription | null;
260
260
  /** Payload là EKYCError: { event, code, message }. */
261
261
  onLivenessError(callback: (error: EKYCError) => void): import("react-native").EmitterSubscription | null;
262
262
  /**
263
263
  * Face matching (Face Service)
264
264
  * @param config Face service configuration
265
265
  */
266
- startFaceCompare(config: FaceServiceConfig): Promise<SDKEkycResultStringWithEvent>;
267
- onFaceCompareSuccess(callback: (data: SDKEkycResultWithEvent) => void): import("react-native").EmitterSubscription | null;
266
+ startFaceCompare(config: FaceServiceConfig): Promise<FaceCompareSuccessEvent>;
267
+ onFaceCompareSuccess(callback: (data: FaceCompareSuccessEvent) => void): import("react-native").EmitterSubscription | null;
268
268
  /** Payload là EKYCError: { event, code, message }. */
269
269
  onFaceCompareError(callback: (error: EKYCError) => void): import("react-native").EmitterSubscription | null;
270
270
  /**
@@ -286,6 +286,8 @@ export declare class FinosESignModule {
286
286
  baseUrl?: string;
287
287
  countMaxRetry?: number;
288
288
  language?: string;
289
+ /** Network timeout in milliseconds (default: 30000). callTimeout = networkTimeoutMs * 2 */
290
+ networkTimeoutMs?: number;
289
291
  switchFrontCamera?: boolean;
290
292
  }, styleConfig?: {
291
293
  textSize?: number;
@@ -12,6 +12,15 @@ export interface FaceVerifyResult {
12
12
  matchScore?: number;
13
13
  toBeReviewed?: string;
14
14
  }
15
+ /**
16
+ * Payload emitted by onFaceCompareSuccess listener.
17
+ * event = "FACE_SUCCESS" | "LOG_SUCCESS"
18
+ * data = CheckFaceResponse object (bridge serialises individual fields, not a JSON string)
19
+ */
20
+ export interface FaceCompareSuccessEvent {
21
+ event: string;
22
+ data: CheckFaceResponse;
23
+ }
15
24
  export interface FaceServiceConfig {
16
25
  /**
17
26
  * Backward/forward compatible:
@@ -20,6 +20,17 @@ export declare enum SDKFaceDetectStatus {
20
20
  export declare const SDK_LIVENESS_ACTIONS: readonly SDKFaceDetectStatus[];
21
21
  /** Chuyển customActions enum[] sang string[] khi gửi xuống native. */
22
22
  export declare function customActionsToStrings(actions: SDKFaceDetectStatus[]): string[];
23
+ /** Text config for the back-press confirmation bottom sheet (v1.4.6+). All fields optional – fallback to built-in strings. */
24
+ export interface LivenessBackConfirmConfig {
25
+ titleVi?: string;
26
+ titleEn?: string;
27
+ bodyVi?: string;
28
+ bodyEn?: string;
29
+ confirmButtonVi?: string;
30
+ confirmButtonEn?: string;
31
+ cancelButtonVi?: string;
32
+ cancelButtonEn?: string;
33
+ }
23
34
  export interface LivenessConfig {
24
35
  appKey: string;
25
36
  transactionId?: string;
@@ -30,6 +41,8 @@ export interface LivenessConfig {
30
41
  customActions?: SDKFaceDetectStatus[];
31
42
  activeActionCount?: number;
32
43
  forceCaptureTimeout?: number;
44
+ isShowBackConfirmation?: boolean;
45
+ backConfirmConfig?: LivenessBackConfirmConfig;
33
46
  }
34
47
  export interface CheckLivenessResponse {
35
48
  metadata?: Metadata;
@@ -84,3 +97,12 @@ export interface QualityCheck {
84
97
  }
85
98
  /** Liveness error – dùng chung EKYCError (event, code, message). */
86
99
  export type LivenessError = import('./ekycType').EKYCError;
100
+ /**
101
+ * Payload emitted by onLivenessSuccess listener.
102
+ * event = "LIVENESS_SUCCESS" | "LOG_SUCCESS"
103
+ * data = CheckLivenessResponse object (bridge serialises fields directly, not a JSON string)
104
+ */
105
+ export interface LivenessSuccessEvent {
106
+ event: string;
107
+ data: CheckLivenessResponse;
108
+ }
@@ -96,14 +96,14 @@ class EKYCModule: RCTEventEmitter {
96
96
  callbackSuccess: { event, data in
97
97
  switch event {
98
98
  case .SCAN_NFC_START:
99
- self?.emit("onNfcScanStart", body: ["event": event.description, "data": "\(String(describing: data))"])
99
+ self?.emit("onNfcScanStart", body: ["event": event.description, "data": (data as? SDKEkycResult)?.nfcResponse ?? "{}"])
100
100
  case .SCAN_NFC_SUCCESS:
101
101
  let nfcResponse = (data as? SDKEkycResult)?.nfcResponse ?? ""
102
102
  let body: [String: Any] = ["event": event.description, "data": nfcResponse]
103
103
  self?.emit("onNfcScanSuccess", body: body)
104
104
  resolve(body)
105
105
  default:
106
- self?.emit("onNfcEvent", body: ["event": event.description, "data": "\(String(describing: data))"])
106
+ self?.emit("onNfcEvent", body: ["event": event.description, "data": (data as? SDKEkycResult)?.nfcResponse ?? "{}"])
107
107
  }
108
108
  },
109
109
  callbackError: { event, errorResult in
@@ -123,7 +123,8 @@ class EKYCModule: RCTEventEmitter {
123
123
  let ekycConfig = EKYCConfigSDK(appKey: AppKeyConfig(appKey: appKey), c06Config: c06Config)
124
124
  SdkEkycC06.startEkyc(ekycConfigSDK: ekycConfig,
125
125
  callbackSuccess: { [weak self] event, data in
126
- let body: [String: Any] = ["event": event.description, "data": "\(String(describing: data))"]
126
+ let c06Response = (data as? SDKEkycResult)?.checkC06Response?.description ?? "{}"
127
+ let body: [String: Any] = ["event": event.description, "data": c06Response]
127
128
  self?.emit("onC06Success", body: body)
128
129
  resolve(body)
129
130
  },
@@ -183,8 +184,16 @@ class EKYCModule: RCTEventEmitter {
183
184
  let ekycConfig = EKYCConfigSDK(appKey: AppKeyConfig(appKey: appKey), sdkType: .liveness, livenessConfig: livenessConfig)
184
185
  SdkEkycLiveness.startEkyc(from: vc, ekycConfigSDK: ekycConfig,
185
186
  callbackSuccess: { event, data in
186
- let livenessResponse = (data as? SDKEkycResult)?.checkLivenessResponse?.description ?? ""
187
- let body: [String: Any] = ["event": event.description, "data": livenessResponse]
187
+ let livenessResponse = (data as? SDKEkycResult)?.checkLivenessResponse
188
+ let livenessDict: Any
189
+ if let resp = livenessResponse,
190
+ let encoded = try? JSONEncoder().encode(resp),
191
+ let dict = try? JSONSerialization.jsonObject(with: encoded) as? [String: Any] {
192
+ livenessDict = dict
193
+ } else {
194
+ livenessDict = [String: Any]()
195
+ }
196
+ let body: [String: Any] = ["event": event.description, "data": livenessDict]
188
197
  self?.emit("onLivenessSuccess", body: body)
189
198
  resolve(body)
190
199
  },
@@ -210,7 +219,17 @@ class EKYCModule: RCTEventEmitter {
210
219
  DispatchQueue.main.async { [weak self] in
211
220
  SdkEkycFaceService.startEkyc(ekycConfigSDK: ekycConfig,
212
221
  callbackSuccess: { event, data in
213
- let body: [String: Any] = ["event": event.description, "data": "\(String(describing: data))"]
222
+ let faceResponse = (data as? SDKEkycResult)?.checkFaceResponse
223
+ var faceDict: [String: Any] = [:]
224
+ if let v = faceResponse?.requestId { faceDict["requestId"] = v }
225
+ if let r = faceResponse?.result {
226
+ faceDict["result"] = ["conf": r.conf, "match": r.match, "matchScore": r.matchScore, "toBeReviewed": r.toBeReviewed]
227
+ }
228
+ if let v = faceResponse?.status { faceDict["status"] = v }
229
+ if let v = faceResponse?.statusCode { faceDict["statusCode"] = v }
230
+ if let v = faceResponse?.error { faceDict["error"] = v }
231
+ if let v = faceResponse?.type { faceDict["type"] = v }
232
+ let body: [String: Any] = ["event": event.description, "data": faceDict]
214
233
  self?.emit("onFaceCompareSuccess", body: body)
215
234
  resolve(body)
216
235
  },
@@ -291,7 +310,16 @@ class EKYCModule: RCTEventEmitter {
291
310
  return
292
311
  }
293
312
  let files = result?.ekycStateModel?.eKYCFileModel
294
- var body: [String: Any] = ["status": "success", "event": event.description, "data": "\(String(describing: data))", "transactionId": result?.ekycStateModel?.transactionId ?? ""]
313
+ var dataDict: [String: Any] = [:]
314
+ if let v = result?.nfcResponse { dataDict["nfcResponse"] = v }
315
+ if let v = result?.checkC06Response?.description { dataDict["checkC06Response"] = v }
316
+ if let v = result?.ocrResponse?.description { dataDict["ocrResponse"] = v }
317
+ if let v = result?.checkLivenessResponse?.description { dataDict["checkLivenessResponse"] = v }
318
+ if let v = result?.checkFaceResponse?.description { dataDict["checkFaceResponse"] = v }
319
+ if let v = result?.customData { dataDict["customData"] = v }
320
+ let dataJson = (try? JSONSerialization.data(withJSONObject: dataDict))
321
+ .flatMap { String(data: $0, encoding: .utf8) } ?? "{}"
322
+ var body: [String: Any] = ["status": "success", "event": event.description, "data": dataJson, "transactionId": result?.ekycStateModel?.transactionId ?? ""]
295
323
  if let facePath = files?.imageFace { body["imageFacePath"] = facePath.path }
296
324
  if let frontPath = files?.imageOcrFront { body["imageOcrFrontPath"] = frontPath.path }
297
325
  if let backPath = files?.imageOcrBack { body["imageOcrBackPath"] = backPath.path }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finos_sdk/sdk-ekyc",
3
- "version": "1.4.5",
3
+ "version": "1.4.7",
4
4
  "description": "React Native SDK for eKYC - Vietnamese CCCD NFC reading, OCR, Liveness detection, Face matching, and C06, eSign, SmsOTP residence verification",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -4,8 +4,8 @@ import { NfcConfig, NfcError } from '../types/ekycNFCType';
4
4
  import { SDKEkycResultWithEvent, SDKEkycResultStringWithEvent, EKYCError, getEkycError, AppIDType } from '../types/ekycType';
5
5
  import { C06Config } from '../types/ekycC06Type';
6
6
  import { OcrConfig, OcrError } from '../types/ekycOCRType';
7
- import { LivenessConfig, SDKFaceDetectStatus, LivenessError } from '../types/ekycLivenessType';
8
- import { FaceServiceConfig, FaceCompareError } from '../types/ekycFaceType';
7
+ import { LivenessConfig, SDKFaceDetectStatus, LivenessError, LivenessSuccessEvent } from '../types/ekycLivenessType';
8
+ import { FaceServiceConfig, FaceCompareError, FaceCompareSuccessEvent } from '../types/ekycFaceType';
9
9
  import { SmsOtpConfig, SmsOtpResult, SmsOtpError } from '../types/ekycSmsOtpType';
10
10
  import { ESignInitResult, ESignOpenSessionResult, ESignCertificate, ESignSignRequest, ESignError, ESignAuthenticateResult, ESignPdfResult, ESignApiResponse } from '../types/ekycESignType';
11
11
  import { SDKFlowType, flowToStrings } from '../types/ekycFlowType';
@@ -194,7 +194,7 @@ export class FinosEKYCModule {
194
194
  * @param config.activeActionCount - Number of random actions (1-10), only used when customActions is null (default: 2)
195
195
  * @param config.switchFrontCamera - Use front camera (default: false)
196
196
  */
197
- public async startLiveness(config: LivenessConfig): Promise<SDKEkycResultStringWithEvent> {
197
+ public async startLiveness(config: LivenessConfig): Promise<LivenessSuccessEvent> {
198
198
  this.validateSDKReady();
199
199
 
200
200
  try {
@@ -221,7 +221,7 @@ export class FinosEKYCModule {
221
221
  * Start face comparison (Face Service)
222
222
  * @param config Face service configuration
223
223
  */
224
- public async startFaceCompare(config: FaceServiceConfig): Promise<SDKEkycResultStringWithEvent> {
224
+ public async startFaceCompare(config: FaceServiceConfig): Promise<FaceCompareSuccessEvent> {
225
225
  this.validateSDKReady();
226
226
 
227
227
  const normalized: FaceServiceConfig = {
@@ -337,7 +337,7 @@ export class FinosEKYCModule {
337
337
  /**
338
338
  * Listen for liveness success events
339
339
  */
340
- public onLivenessSuccess(callback: (data: SDKEkycResultStringWithEvent) => void) {
340
+ public onLivenessSuccess(callback: (data: LivenessSuccessEvent) => void) {
341
341
  return this.sdk.onLivenessSuccess(callback);
342
342
  }
343
343
 
@@ -352,7 +352,7 @@ export class FinosEKYCModule {
352
352
  /**
353
353
  * Listen for face compare success events
354
354
  */
355
- public onFaceCompareSuccess(callback: (data: SDKEkycResultWithEvent) => void) {
355
+ public onFaceCompareSuccess(callback: (data: FaceCompareSuccessEvent) => void) {
356
356
  return this.sdk.onFaceCompareSuccess(callback);
357
357
  }
358
358
 
@@ -764,6 +764,8 @@ export class FinosEKYCModule {
764
764
  baseUrl?: string;
765
765
  countMaxRetry?: number;
766
766
  language?: string;
767
+ /** Network timeout in milliseconds (default: 30000). callTimeout = networkTimeoutMs * 2 */
768
+ networkTimeoutMs?: number;
767
769
  switchFrontCamera?: boolean;
768
770
  /** LivenessConfig – SDKeKYCActivity (157-169) */
769
771
  isActiveLiveness?: boolean;
@@ -774,6 +776,10 @@ export class FinosEKYCModule {
774
776
  activeActionCount?: number;
775
777
  /** AppIDType: NONE | HD_BANK | VIKKI (default: AppIDType.NONE) */
776
778
  appIDType?: AppIDType;
779
+ /** Show confirmation bottom sheet when user presses back in liveness screen (v1.4.6+) */
780
+ isShowBackConfirmation?: boolean;
781
+ /** Text config for the back confirmation bottom sheet (v1.4.6+) */
782
+ backConfirmConfig?: import('../types/ekycLivenessType').LivenessBackConfirmConfig;
777
783
  },
778
784
  styleConfig?: {
779
785
  textSize?: number;
@@ -2,8 +2,8 @@ import { Platform } from 'react-native';
2
2
  import sdkEKYC, { SDKeKYC, SDK_VERSION, SDK_NAME } from '../../EKYCModule';
3
3
  import { SmsOtpConfig, SmsOtpResult, SmsOtpError } from '../types/ekycSmsOtpType';
4
4
  import { ESignInitResult, ESignOpenSessionResult, ESignCertificate, ESignSignRequest, ESignError, ESignAuthenticateResult, AuthorizeInfo, ESignPdfResult, ESignApiResponse } from '../types/ekycESignType';
5
- import { LivenessConfig, LivenessError } from '../types/ekycLivenessType';
6
- import { FaceServiceConfig, FaceCompareError } from '../types/ekycFaceType';
5
+ import { LivenessConfig, LivenessError, LivenessSuccessEvent } from '../types/ekycLivenessType';
6
+ import { FaceServiceConfig, FaceCompareError, FaceCompareSuccessEvent } from '../types/ekycFaceType';
7
7
  import { SDKEkycResultStringWithEvent, SDKEkycResultWithEvent, EKYCError, getEkycError } from '../types/ekycType';
8
8
 
9
9
  /**
@@ -565,14 +565,14 @@ export class FinosESignModule {
565
565
  * @param config.activeActionCount - Number of random actions (1-10), only used when customActions is null (default: 2)
566
566
  * @param config.switchFrontCamera - Use front camera (default: false)
567
567
  */
568
- public async startLiveness(config: LivenessConfig): Promise<SDKEkycResultStringWithEvent> {
568
+ public async startLiveness(config: LivenessConfig): Promise<LivenessSuccessEvent> {
569
569
  this.validateSDKReady();
570
570
  // Pass through to SDK - error handling is done in EKYCModule.ts
571
571
  return await this.sdk.startLiveness(config);
572
572
  }
573
573
 
574
574
  // Liveness Event Listeners
575
- public onLivenessSuccess(callback: (data: SDKEkycResultStringWithEvent) => void) {
575
+ public onLivenessSuccess(callback: (data: LivenessSuccessEvent) => void) {
576
576
  return this.sdk.onLivenessSuccess(callback);
577
577
  }
578
578
 
@@ -587,7 +587,7 @@ export class FinosESignModule {
587
587
  * Face matching (Face Service)
588
588
  * @param config Face service configuration
589
589
  */
590
- public async startFaceCompare(config: FaceServiceConfig): Promise<SDKEkycResultStringWithEvent> {
590
+ public async startFaceCompare(config: FaceServiceConfig): Promise<FaceCompareSuccessEvent> {
591
591
  this.validateSDKReady();
592
592
  const normalized: FaceServiceConfig = {
593
593
  ...config,
@@ -598,7 +598,7 @@ export class FinosESignModule {
598
598
  }
599
599
 
600
600
  // Face Compare Event Listeners
601
- public onFaceCompareSuccess(callback: (data: SDKEkycResultWithEvent) => void) {
601
+ public onFaceCompareSuccess(callback: (data: FaceCompareSuccessEvent) => void) {
602
602
  return this.sdk.onFaceCompareSuccess(callback);
603
603
  }
604
604
 
@@ -634,6 +634,8 @@ export class FinosESignModule {
634
634
  baseUrl?: string;
635
635
  countMaxRetry?: number;
636
636
  language?: string;
637
+ /** Network timeout in milliseconds (default: 30000). callTimeout = networkTimeoutMs * 2 */
638
+ networkTimeoutMs?: number;
637
639
  switchFrontCamera?: boolean;
638
640
  },
639
641
  styleConfig?: {
@@ -14,6 +14,16 @@ export interface FaceVerifyResult {
14
14
  toBeReviewed?: string;
15
15
  }
16
16
 
17
+ /**
18
+ * Payload emitted by onFaceCompareSuccess listener.
19
+ * event = "FACE_SUCCESS" | "LOG_SUCCESS"
20
+ * data = CheckFaceResponse object (bridge serialises individual fields, not a JSON string)
21
+ */
22
+ export interface FaceCompareSuccessEvent {
23
+ event: string;
24
+ data: CheckFaceResponse;
25
+ }
26
+
17
27
  export interface FaceServiceConfig {
18
28
  /**
19
29
  * Backward/forward compatible:
@@ -38,11 +38,23 @@ export function customActionsToStrings(actions: SDKFaceDetectStatus[]): string[]
38
38
  return actions.map(a => a as string);
39
39
  }
40
40
 
41
+ /** Text config for the back-press confirmation bottom sheet (v1.4.6+). All fields optional – fallback to built-in strings. */
42
+ export interface LivenessBackConfirmConfig {
43
+ titleVi?: string;
44
+ titleEn?: string;
45
+ bodyVi?: string;
46
+ bodyEn?: string;
47
+ confirmButtonVi?: string;
48
+ confirmButtonEn?: string;
49
+ cancelButtonVi?: string;
50
+ cancelButtonEn?: string;
51
+ }
52
+
41
53
  export interface LivenessConfig {
42
54
  appKey: string;
43
55
  transactionId?: string;
44
56
  // old fields usingRandomAction and isStraight removed in 1.3.1
45
- // usingRandomAction: boolean;
57
+ // usingRandomAction: boolean;
46
58
  // isStraight: boolean;
47
59
  selfieImage: string;
48
60
  // New fields for version 1.3.0
@@ -52,6 +64,9 @@ export interface LivenessConfig {
52
64
  customActions?: SDKFaceDetectStatus[];
53
65
  activeActionCount?: number;
54
66
  forceCaptureTimeout?: number;
67
+ // New fields for version 1.4.6
68
+ isShowBackConfirmation?: boolean;
69
+ backConfirmConfig?: LivenessBackConfirmConfig;
55
70
  }
56
71
 
57
72
  export interface CheckLivenessResponse {
@@ -116,3 +131,13 @@ export interface QualityCheck {
116
131
 
117
132
  /** Liveness error – dùng chung EKYCError (event, code, message). */
118
133
  export type LivenessError = import('./ekycType').EKYCError;
134
+
135
+ /**
136
+ * Payload emitted by onLivenessSuccess listener.
137
+ * event = "LIVENESS_SUCCESS" | "LOG_SUCCESS"
138
+ * data = CheckLivenessResponse object (bridge serialises fields directly, not a JSON string)
139
+ */
140
+ export interface LivenessSuccessEvent {
141
+ event: string;
142
+ data: CheckLivenessResponse;
143
+ }