@finos_sdk/sdk-ekyc 1.4.8 → 1.5.0
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/android/src/main/java/finos/sdk/ekyc/EKYCModule.kt +129 -71
- package/dist/index.js +7 -0
- package/dist/package.json +1 -1
- package/dist/src/components/ExitConfirmSheet.d.ts +3 -0
- package/dist/src/components/ExitConfirmSheet.js +164 -0
- package/dist/src/components/ExitSheetWrapper.d.ts +12 -0
- package/dist/src/components/ExitSheetWrapper.js +26 -0
- package/dist/src/modules/FinosEKYCModule.d.ts +24 -5
- package/dist/src/modules/FinosEKYCModule.js +33 -7
- package/package.json +1 -1
- package/src/components/ExitConfirmSheet.tsx +180 -0
- package/src/components/ExitSheetWrapper.tsx +24 -0
- package/src/modules/FinosEKYCModule.ts +36 -8
|
@@ -92,97 +92,154 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
92
92
|
private var exitSheetResolvedViaButton = false
|
|
93
93
|
|
|
94
94
|
private fun getTrueCurrentActivity(): Activity? {
|
|
95
|
-
// Try React Native's currentActivity first
|
|
95
|
+
// Try React Native's currentActivity first as a baseline
|
|
96
96
|
val rnActivity = currentActivity
|
|
97
|
-
|
|
97
|
+
|
|
98
|
+
Log.d(TAG, "🔍 getTrueCurrentActivity check: rnActivity=${rnActivity?.javaClass?.simpleName}")
|
|
99
|
+
|
|
100
|
+
// If rnActivity is already an SDK activity, we can likely trust it
|
|
101
|
+
if (rnActivity != null && rnActivity.javaClass.name.startsWith("finos.sdk.")) {
|
|
102
|
+
Log.d(TAG, "✅ Current activity is already an SDK activity: ${rnActivity.javaClass.simpleName}")
|
|
98
103
|
return rnActivity
|
|
99
104
|
}
|
|
100
105
|
|
|
101
|
-
//
|
|
102
|
-
//
|
|
106
|
+
// Use reflection to find the absolute top resumed activity.
|
|
107
|
+
// This is crucial when the SDK is running in a native Activity on top of the RN Activity,
|
|
108
|
+
// and currentActivity might still point to the background Activity.
|
|
109
|
+
try {
|
|
110
|
+
val topActivity = findTopActivityViaReflection()
|
|
111
|
+
if (topActivity != null) {
|
|
112
|
+
Log.d(TAG, "✅ Found top activity via reflection: ${topActivity.javaClass.simpleName}")
|
|
113
|
+
return topActivity
|
|
114
|
+
}
|
|
115
|
+
} catch (e: Exception) {
|
|
116
|
+
Log.e(TAG, "Failed to find top activity via reflection", e)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
Log.d(TAG, "⚠️ Fallback to rnActivity: ${rnActivity?.javaClass?.simpleName}")
|
|
120
|
+
return rnActivity
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Finds the top-most resumed activity using ActivityThread's internal records.
|
|
125
|
+
*/
|
|
126
|
+
private fun findTopActivityViaReflection(): Activity? {
|
|
103
127
|
try {
|
|
104
128
|
val activityThreadClass = Class.forName("android.app.ActivityThread")
|
|
105
|
-
val activityThread = activityThreadClass.getMethod("currentActivityThread").invoke(null)
|
|
129
|
+
val activityThread = activityThreadClass.getMethod("currentActivityThread").invoke(null) ?: return null
|
|
106
130
|
val activitiesField = activityThreadClass.getDeclaredField("mActivities")
|
|
107
131
|
activitiesField.isAccessible = true
|
|
108
132
|
|
|
109
|
-
val activities = activitiesField.get(activityThread) as Map
|
|
110
|
-
Log.d(TAG, "🔍 Scanning ${activities.size} activities...")
|
|
111
|
-
|
|
112
|
-
var topActivity: Activity? = null
|
|
133
|
+
val activities = activitiesField.get(activityThread) as? Map<*, *> ?: return null
|
|
134
|
+
Log.d(TAG, "🔍 Scanning ${activities.size} activities via reflection...")
|
|
113
135
|
|
|
136
|
+
var bestCandidate: Activity? = null
|
|
137
|
+
var highestPriority = -1
|
|
138
|
+
|
|
114
139
|
for (activityRecord in activities.values) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
140
|
+
if (activityRecord == null) continue
|
|
141
|
+
try {
|
|
142
|
+
val activityRecordClass = activityRecord.javaClass
|
|
143
|
+
val activityField = activityRecordClass.getDeclaredField("activity")
|
|
144
|
+
activityField.isAccessible = true
|
|
145
|
+
val activity = activityField.get(activityRecord) as? Activity ?: continue
|
|
146
|
+
|
|
147
|
+
if (activity.isFinishing) continue
|
|
148
|
+
|
|
149
|
+
val pausedField = activityRecordClass.getDeclaredField("paused")
|
|
150
|
+
pausedField.isAccessible = true
|
|
151
|
+
val isPaused = pausedField.get(activityRecord) as Boolean
|
|
152
|
+
|
|
153
|
+
val stoppedField = activityRecordClass.getDeclaredField("stopped")
|
|
154
|
+
stoppedField.isAccessible = true
|
|
155
|
+
val isStopped = stoppedField.get(activityRecord) as Boolean
|
|
156
|
+
|
|
157
|
+
if (isStopped) continue
|
|
158
|
+
|
|
159
|
+
// Identity check: prioritize known SDK activity patterns
|
|
160
|
+
val className = activity.javaClass.name
|
|
161
|
+
val isSDK = className.startsWith("finos.sdk.") ||
|
|
162
|
+
className.contains("EKYC", ignoreCase = true) ||
|
|
163
|
+
className.contains("Liveness", ignoreCase = true) ||
|
|
164
|
+
className.contains("OCR", ignoreCase = true) ||
|
|
165
|
+
className.contains("NFC", ignoreCase = true)
|
|
166
|
+
|
|
167
|
+
// Priority scoring:
|
|
168
|
+
// 3: Resumed SDK Activity
|
|
169
|
+
// 2: Resumed App Activity
|
|
170
|
+
// 1: Paused SDK Activity
|
|
171
|
+
// 0: Paused App Activity
|
|
172
|
+
val currentPriority = (if (isSDK) 1 else 0) + (if (!isPaused) 2 else 0)
|
|
173
|
+
|
|
174
|
+
Log.v(TAG, " [Audit] ${activity.javaClass.simpleName} -> Prio: $currentPriority (SDK=$isSDK, Paused=$isPaused)")
|
|
175
|
+
|
|
176
|
+
if (currentPriority > highestPriority) {
|
|
177
|
+
highestPriority = currentPriority
|
|
178
|
+
bestCandidate = activity
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Optimization: Found the absolute best target
|
|
182
|
+
if (highestPriority == 3) break
|
|
183
|
+
|
|
184
|
+
} catch (e: Exception) {
|
|
185
|
+
// Silently continue for individual activity record failures
|
|
134
186
|
}
|
|
135
187
|
}
|
|
136
|
-
|
|
188
|
+
|
|
189
|
+
if (bestCandidate != null) {
|
|
190
|
+
Log.d(TAG, "✅ Best candidate found: ${bestCandidate.javaClass.simpleName} (Prio: $highestPriority)")
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return bestCandidate
|
|
137
194
|
} catch (e: Exception) {
|
|
138
|
-
Log.e(TAG, "
|
|
195
|
+
Log.e(TAG, "❌ Critical error in findTopActivityViaReflection", e)
|
|
196
|
+
return null
|
|
139
197
|
}
|
|
140
|
-
|
|
141
|
-
return rnActivity
|
|
142
198
|
}
|
|
143
199
|
|
|
144
200
|
@ReactMethod
|
|
145
201
|
fun showRNExitSheet(bundleName: String, initialProps: ReadableMap, promise: Promise) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
Log.e(TAG, "❌ showRNExitSheet failed: Activity is finishing/destroyed")
|
|
154
|
-
promise.reject("ACTIVITY_INVALID", "Activity is finishing or destroyed")
|
|
155
|
-
return
|
|
156
|
-
}
|
|
202
|
+
// Fix: Move activity detection to UI thread to avoid race conditions and ensure correct window attachment
|
|
203
|
+
Handler(Looper.getMainLooper()).post {
|
|
204
|
+
val activity = getTrueCurrentActivity() ?: run {
|
|
205
|
+
Log.e(TAG, "❌ showRNExitSheet failed: No visible activity found")
|
|
206
|
+
promise.reject("NO_ACTIVITY", "No visible activity found")
|
|
207
|
+
return@post
|
|
208
|
+
}
|
|
157
209
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
fun safeReject(code: String, msg: String, e: Throwable? = null) {
|
|
164
|
-
if (promiseSettled.compareAndSet(false, true)) {
|
|
165
|
-
if (e != null) promise.reject(code, msg, e) else promise.reject(code, msg)
|
|
210
|
+
if (activity.isFinishing || (android.os.Build.VERSION.SDK_INT >= 17 && activity.isDestroyed)) {
|
|
211
|
+
Log.e(TAG, "❌ showRNExitSheet failed: Activity is finishing or destroyed")
|
|
212
|
+
promise.reject("ACTIVITY_INVALID", "Activity is finishing or destroyed")
|
|
213
|
+
return@post
|
|
166
214
|
}
|
|
167
|
-
}
|
|
168
215
|
|
|
169
|
-
|
|
216
|
+
// Guard chống double-resolve/reject promise
|
|
217
|
+
val promiseSettled = java.util.concurrent.atomic.AtomicBoolean(false)
|
|
218
|
+
fun safeResolve(value: Any?) {
|
|
219
|
+
if (promiseSettled.compareAndSet(false, true)) promise.resolve(value)
|
|
220
|
+
}
|
|
221
|
+
fun safeReject(code: String, msg: String, e: Throwable? = null) {
|
|
222
|
+
if (promiseSettled.compareAndSet(false, true)) {
|
|
223
|
+
if (e != null) promise.reject(code, msg, e) else promise.reject(code, msg)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
170
226
|
try {
|
|
171
227
|
val activityName = activity.javaClass.simpleName
|
|
172
228
|
Log.d(TAG, "▶️ showRNExitSheet: Top Activity=$activityName, bundle=$bundleName")
|
|
173
229
|
|
|
174
230
|
val reactApplication = activity.application as? ReactApplication
|
|
175
231
|
if (reactApplication == null) {
|
|
176
|
-
Log.e(TAG, "❌ Application does not implement ReactApplication")
|
|
232
|
+
Log.e(TAG, "❌ showRNExitSheet failed: Application class (${activity.application.javaClass.name}) does not implement ReactApplication. Please ensure your Application class implements ReactApplication or provide a way for the SDK to access the ReactInstanceManager.")
|
|
177
233
|
safeReject("NO_REACT_APP", "Application does not implement ReactApplication")
|
|
178
|
-
return@
|
|
234
|
+
return@post
|
|
179
235
|
}
|
|
180
236
|
val reactInstanceManager = reactApplication.reactNativeHost.reactInstanceManager
|
|
181
237
|
|
|
182
238
|
if (reactInstanceManager.currentReactContext != null) {
|
|
239
|
+
Log.d(TAG, "✅ ReactContext is ready, presenting sheet...")
|
|
183
240
|
presentRNBottomSheet(activity, reactInstanceManager, bundleName, initialProps,
|
|
184
241
|
::safeResolve, ::safeReject)
|
|
185
|
-
return@
|
|
242
|
+
return@post
|
|
186
243
|
}
|
|
187
244
|
|
|
188
245
|
// Context chưa sẵn sàng: chờ listener
|
|
@@ -212,7 +269,7 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
212
269
|
Log.d(TAG, "✅ ReactContext init xong giữa lúc add listener — present luôn")
|
|
213
270
|
presentRNBottomSheet(activity, reactInstanceManager, bundleName, initialProps,
|
|
214
271
|
::safeResolve, ::safeReject)
|
|
215
|
-
return@
|
|
272
|
+
return@post
|
|
216
273
|
}
|
|
217
274
|
|
|
218
275
|
// Chỉ trigger background create nếu chưa start (tránh IllegalStateException)
|
|
@@ -250,12 +307,17 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
250
307
|
safeReject: (String, String, Throwable?) -> Unit
|
|
251
308
|
) {
|
|
252
309
|
try {
|
|
310
|
+
val minHeightPx = (400 * activity.resources.displayMetrics.density).toInt()
|
|
253
311
|
val container = LinearLayout(activity).apply {
|
|
254
312
|
orientation = LinearLayout.VERTICAL
|
|
255
313
|
setBackgroundColor(Color.TRANSPARENT)
|
|
314
|
+
minimumHeight = minHeightPx
|
|
256
315
|
}
|
|
257
316
|
|
|
258
317
|
val rootView = ReactRootView(activity)
|
|
318
|
+
// Hardening: Set a minimum height to ensure the sheet is visible even before RN finishes layout
|
|
319
|
+
rootView.minimumHeight = minHeightPx
|
|
320
|
+
|
|
259
321
|
rootView.layoutParams = LinearLayout.LayoutParams(
|
|
260
322
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
261
323
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
|
@@ -328,7 +390,7 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
328
390
|
|
|
329
391
|
bottomSheetDialog.show()
|
|
330
392
|
|
|
331
|
-
Log.d(TAG, "✅ showRNExitSheet: BottomSheetDialog shown
|
|
393
|
+
Log.d(TAG, "✅ showRNExitSheet: BottomSheetDialog shown on ${activity.javaClass.simpleName}, bundle=$bundleName")
|
|
332
394
|
safeResolve(true)
|
|
333
395
|
} catch (e: Exception) {
|
|
334
396
|
Log.e(TAG, "❌ Exception in presentRNBottomSheet", e)
|
|
@@ -903,12 +965,7 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
903
965
|
isActiveLiveness = isActiveLiveness ?: false,
|
|
904
966
|
isActiveLivenessColor = isActiveLivenessColor ?: false,
|
|
905
967
|
isShowCameraFont = isShowCameraFont ?: true,
|
|
906
|
-
// Custom actions từ checkboxes (nếu có chọn)
|
|
907
|
-
// Nếu có actions được chọn → sử dụng customActions
|
|
908
|
-
// Nếu không có actions nào được chọn → sử dụng random actions với activeActionCount
|
|
909
968
|
customActions = customActions,
|
|
910
|
-
// Number of random actions (1-10), only used when customActions = null
|
|
911
|
-
// activeActionCount = 2 → 2 random actions + STRAIGHT
|
|
912
969
|
activeActionCount = activeActionCount ?: 2,
|
|
913
970
|
forceCaptureTimeout = (forceCaptureTimeout ?: 0.0).toLong(),
|
|
914
971
|
selfieImage = imageFile,
|
|
@@ -2645,15 +2702,16 @@ class EKYCModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
2645
2702
|
|
|
2646
2703
|
@ReactMethod
|
|
2647
2704
|
fun setExitSheetHeight(heightDp: Float) {
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2705
|
+
Handler(Looper.getMainLooper()).post {
|
|
2706
|
+
val activity = getTrueCurrentActivity() ?: return@post
|
|
2707
|
+
val heightPx = (heightDp * activity.resources.displayMetrics.density).toInt()
|
|
2708
|
+
Log.d(TAG, "▶️ setExitSheetHeight: ${heightDp}dp → ${heightPx}px")
|
|
2709
|
+
|
|
2710
|
+
val sheet = currentExitSheet ?: return@post
|
|
2711
|
+
if (!sheet.isShowing) return@post
|
|
2654
2712
|
val bs = sheet.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)
|
|
2655
|
-
?: return@
|
|
2656
|
-
val lp = bs.layoutParams ?: return@
|
|
2713
|
+
?: return@post
|
|
2714
|
+
val lp = bs.layoutParams ?: return@post
|
|
2657
2715
|
lp.height = heightPx
|
|
2658
2716
|
bs.layoutParams = lp
|
|
2659
2717
|
val behavior = com.google.android.material.bottomsheet.BottomSheetBehavior.from(bs)
|
package/dist/index.js
CHANGED
|
@@ -32,10 +32,14 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
35
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
39
|
exports.OCR_USE_NEW_ID_DOCUMENT_2 = exports.OCR_USE_NEW_ID_DOCUMENT_1 = exports.OCR_FAKE_MRZ = exports.OCR_INVALID_GENDER_CODE = exports.OCR_INVALID_ID_CARD = exports.OCR_GLARE_ID_CARD = exports.OCR_BLURRY_ID_CARD = exports.OCR_CUT_CORNER_ID_CARD = exports.OCR_CANNOT_RECOGNIZE_PORTRAIT = exports.OCR_ID_CARD_FROM_OTHER_DEVICE = exports.OCR_NOT_ORIGINAL_ID_CARD = exports.OCR_MISSING_ID_CARD_PART = exports.OCR_CANNOT_GET_ISSUE_PLACE = exports.OCR_CANNOT_GET_GENDER = exports.OCR_CANNOT_GET_HOMETOWN = exports.OCR_CANNOT_GET_RESIDENCE = exports.OCR_CANNOT_GET_ISSUE_DATE = exports.OCR_CANNOT_GET_EXPIRY_DATE = exports.OCR_CANNOT_GET_BIRTH_YEAR = exports.OCR_CANNOT_GET_NAME = exports.OCR_CANNOT_GET_ID_NUMBER = exports.OCR_WRONG_ID_CARD_SIDE = exports.OCR_UNRECOGNIZED_ID_CARD = exports.SDK_MISS_KEY = exports.ERROR_UNKNOWN = exports.SDK_START_ERROR = exports.SDK_START_FLOW_ERROR = exports.getMessage = exports.getErrorResultFromDetails = exports.createCustom = exports.fromCode = exports.getLocalizedMessage = exports.parseNfcResponse = exports.AuthorizationStatus = exports.AppIDType = exports.getEkycError = exports.EKYCErrorEvent = exports.EKYCEvent = exports.customActionsToStrings = exports.SDK_LIVENESS_ACTIONS = exports.SDKFaceDetectStatus = exports.flowToStrings = exports.SDK_FLOW_OPTIONS = exports.SDKFlowType = exports.FinosESignModule = exports.FinosESign = exports.FinosEKYCModule = exports.FinosEKYC = exports.SDKeKYC = exports.sdkEKYC = void 0;
|
|
37
40
|
exports.ESIGN_INVALID_USER_ACCOUNT = exports.ESIGN_APP_LIMITED = exports.ESIGN_DEVICE_NOT_INIT = exports.ESIGN_INVALID_LICENSE_CONTENT = exports.ESIGN_INVALID_LICENSE = exports.ESIGN_DEVICE_ALREADY_INIT = exports.ESIGN_SESSION_INVALID = exports.FETCH_HISTORY_ERROR = exports.QRCODE_ERROR = exports.SMS_OTP_RATE_LIMIT = exports.SMS_OTP_NOT_FOUND = exports.SMS_OTP_MAX_ATTEMPTS = exports.SMS_OTP_INVALID_PHONE = exports.SMS_OTP_ERROR = exports.NFC_CHIP_AUTH_FAILED = exports.NFC_USER_CANCEL = exports.NFC_UNKNOWN_ERROR = exports.NFC_IO_ERROR = exports.NFC_INVALID_MRZ_KEY = exports.NFC_CONNECTION_LOST = exports.NFC_MUTUAL_AUTH_FAILED = exports.C06_ERROR = exports.SCAN_NFC_ENABLE = exports.SCAN_NFC_CHECK = exports.SCAN_NFC_ERROR = exports.HEAD_IS_TURNED_IN_SELFIE = exports.NUDITY_DETECTED_IN_SELFIE = exports.READING_GLASSES_DETECTED_IN_SELFIE = exports.EYEWEAR_DETECTED_IN_SELFIE = exports.FACE_OCCLUDED_IN_SELFIE = exports.FACE_IS_BLURRED = exports.MASK_PRESENT_IN_SELFIE = exports.EYES_CLOSED_IN_SELFIE = exports.MULTIPLE_FACES_IN_SELFIE = exports.LIVENESS_FAIL = exports.LIVENESS_ERROR = exports.FACE_ERROR = exports.FACE_HAT_ERROR = exports.OCR_ERROR = exports.OCR_FONT_BACK_NOT_MATCH = exports.OCR_PHOTOCOPY_ID_CARD = exports.OCR_FAKE_PORTRAIT_DETECTED = exports.OCR_FAKE_CHARACTERS_DETECTED_2 = exports.OCR_FAKE_CHARACTERS_DETECTED_1 = exports.OCR_UNKNOWN_ID_NUMBER_LENGTH = exports.OCR_MODIFIED_BIRTH_DATE_DETECTED_CMND = exports.OCR_MODIFIED_SYMBOL_DETECTED_CMND = exports.OCR_FAKE_BIRTH_DATE_DETECTED_CMND = exports.OCR_FAKE_PORTRAIT_DETECTED_CMND = exports.OCR_FAKE_CHARACTERS_DETECTED_CMND = void 0;
|
|
38
41
|
exports.SDK_NAME = exports.SDK_VERSION = exports.USER_CANCEL = exports.ESIGN_INVALID_PIN_CODE = exports.ESIGN_INVALID_RECOVERY_CODE = exports.ESIGN_INVALID_LICENSE_CODE = exports.ESIGN_INVALID_CONTEXT = exports.ESIGN_MISSING_REQUEST_ID = exports.ESIGN_MISSING_SERIAL = exports.ESIGN_MISSING_IDENTITY = exports.ESIGN_MISSING_ACCESS_TOKEN = exports.ESIGN_NO_SESSION_ID = exports.ESIGN_MISSING_CONFIRMATION_DOC = exports.ESIGN_MISSING_REQUEST_JSON = exports.ESIGN_MISSING_CCCD = exports.ESIGN_MISSING_TOKEN = exports.ESIGN_ERROR_UNKNOWN = exports.ESIGN_AUTH_REQUEST_EXISTS = exports.ESIGN_INVALID_CERT_FOR_AUTH = exports.ESIGN_INVALID_SIGN_COUNT_OR_TIME = exports.ESIGN_AUTH_EXISTS = exports.ESIGN_SESSION_INVALID_LIST_CERT = exports.ESIGN_INVALID_RECOVERY_OR_PIN = exports.ESIGN_SESSION_INVALID_REGISTER = void 0;
|
|
42
|
+
const react_native_1 = require("react-native");
|
|
39
43
|
const EKYCModule_1 = __importStar(require("./EKYCModule"));
|
|
40
44
|
exports.sdkEKYC = EKYCModule_1.default;
|
|
41
45
|
Object.defineProperty(exports, "SDKeKYC", { enumerable: true, get: function () { return EKYCModule_1.SDKeKYC; } });
|
|
@@ -47,6 +51,9 @@ Object.defineProperty(exports, "FinosEKYCModule", { enumerable: true, get: funct
|
|
|
47
51
|
const FinosESignModule_1 = require("./src/modules/FinosESignModule");
|
|
48
52
|
Object.defineProperty(exports, "FinosESign", { enumerable: true, get: function () { return FinosESignModule_1.FinosESign; } });
|
|
49
53
|
Object.defineProperty(exports, "FinosESignModule", { enumerable: true, get: function () { return FinosESignModule_1.FinosESignModule; } });
|
|
54
|
+
const ExitSheetWrapper_1 = __importDefault(require("./src/components/ExitSheetWrapper"));
|
|
55
|
+
// Auto-register exit sheet wrapper — khách hàng không cần khai báo AppRegistry
|
|
56
|
+
react_native_1.AppRegistry.registerComponent('SDKExitSheetWrapper', () => ExitSheetWrapper_1.default);
|
|
50
57
|
console.log('✅ SDK modules loaded successfully');
|
|
51
58
|
// Export main SDK instance and class (legacy)
|
|
52
59
|
exports.default = EKYCModule_1.default;
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@finos_sdk/sdk-ekyc",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
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",
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const react_1 = __importDefault(require("react"));
|
|
7
|
+
const react_native_1 = require("react-native");
|
|
8
|
+
const FinosEKYCModule_1 = require("../modules/FinosEKYCModule");
|
|
9
|
+
const BellIcon = () => (<react_native_1.View style={styles.bellWrapper}>
|
|
10
|
+
<react_native_1.View style={styles.bellMount}/>
|
|
11
|
+
<react_native_1.View style={styles.bellBody}/>
|
|
12
|
+
<react_native_1.View style={styles.bellSkirt}/>
|
|
13
|
+
<react_native_1.View style={styles.bellClapper}/>
|
|
14
|
+
</react_native_1.View>);
|
|
15
|
+
const ExitConfirmSheet = (props) => {
|
|
16
|
+
const message = props.message || 'Giao dịch bị hủy khi điều hướng sang tính năng tiếp theo. Quý khách có chắc chắn hủy giao dịch?';
|
|
17
|
+
const confirmText = props.confirmText || 'Đồng ý';
|
|
18
|
+
const cancelText = props.cancelText || 'Ở lại';
|
|
19
|
+
const handleConfirm = async () => {
|
|
20
|
+
await FinosEKYCModule_1.FinosEKYC.resolveExit('CONFIRM');
|
|
21
|
+
};
|
|
22
|
+
const handleCancel = async () => {
|
|
23
|
+
await FinosEKYCModule_1.FinosEKYC.resolveExit('CANCEL');
|
|
24
|
+
};
|
|
25
|
+
return (<react_native_1.View style={styles.container} onLayout={(e) => {
|
|
26
|
+
var _a, _b;
|
|
27
|
+
const h = e.nativeEvent.layout.height;
|
|
28
|
+
if (h > 0)
|
|
29
|
+
(_b = (_a = react_native_1.NativeModules.EKYCModule) === null || _a === void 0 ? void 0 : _a.setExitSheetHeight) === null || _b === void 0 ? void 0 : _b.call(_a, h);
|
|
30
|
+
}}>
|
|
31
|
+
<react_native_1.View style={styles.handle}/>
|
|
32
|
+
|
|
33
|
+
<react_native_1.View style={styles.content}>
|
|
34
|
+
<BellIcon />
|
|
35
|
+
<react_native_1.Text style={styles.message}>{message}</react_native_1.Text>
|
|
36
|
+
</react_native_1.View>
|
|
37
|
+
|
|
38
|
+
<react_native_1.View style={styles.footer}>
|
|
39
|
+
<react_native_1.TouchableOpacity style={styles.confirmButton} onPress={handleConfirm} activeOpacity={0.8}>
|
|
40
|
+
<react_native_1.View style={styles.gradientContainer}>
|
|
41
|
+
<react_native_1.View style={[styles.gradientStep, { backgroundColor: '#E53935' }]}/>
|
|
42
|
+
<react_native_1.View style={[styles.gradientStep, { backgroundColor: '#EA4F28' }]}/>
|
|
43
|
+
<react_native_1.View style={[styles.gradientStep, { backgroundColor: '#EF6519' }]}/>
|
|
44
|
+
<react_native_1.View style={[styles.gradientStep, { backgroundColor: '#F47A0B' }]}/>
|
|
45
|
+
<react_native_1.View style={[styles.gradientStep, { backgroundColor: '#F98F00' }]}/>
|
|
46
|
+
<react_native_1.View style={[styles.gradientStep, { backgroundColor: '#FCA300' }]}/>
|
|
47
|
+
<react_native_1.View style={[styles.gradientStep, { backgroundColor: '#FFB800' }]}/>
|
|
48
|
+
<react_native_1.View style={[styles.gradientStep, { backgroundColor: '#FFCC00' }]}/>
|
|
49
|
+
<react_native_1.View style={[styles.gradientStep, { backgroundColor: '#FFD600' }]}/>
|
|
50
|
+
</react_native_1.View>
|
|
51
|
+
<react_native_1.Text style={styles.confirmText}>{confirmText}</react_native_1.Text>
|
|
52
|
+
</react_native_1.TouchableOpacity>
|
|
53
|
+
|
|
54
|
+
<react_native_1.TouchableOpacity style={styles.cancelButton} onPress={handleCancel} activeOpacity={0.7}>
|
|
55
|
+
<react_native_1.Text style={styles.cancelText}>{cancelText}</react_native_1.Text>
|
|
56
|
+
</react_native_1.TouchableOpacity>
|
|
57
|
+
</react_native_1.View>
|
|
58
|
+
</react_native_1.View>);
|
|
59
|
+
};
|
|
60
|
+
const styles = react_native_1.StyleSheet.create({
|
|
61
|
+
container: {
|
|
62
|
+
backgroundColor: 'white',
|
|
63
|
+
paddingHorizontal: 24,
|
|
64
|
+
paddingBottom: 32,
|
|
65
|
+
borderTopLeftRadius: 32,
|
|
66
|
+
borderTopRightRadius: 32,
|
|
67
|
+
},
|
|
68
|
+
handle: {
|
|
69
|
+
width: 40,
|
|
70
|
+
height: 4,
|
|
71
|
+
backgroundColor: '#CCCCCC',
|
|
72
|
+
borderRadius: 2,
|
|
73
|
+
alignSelf: 'center',
|
|
74
|
+
marginTop: 12,
|
|
75
|
+
marginBottom: 20,
|
|
76
|
+
},
|
|
77
|
+
content: {
|
|
78
|
+
alignItems: 'center',
|
|
79
|
+
marginBottom: 28,
|
|
80
|
+
},
|
|
81
|
+
bellWrapper: {
|
|
82
|
+
alignItems: 'center',
|
|
83
|
+
width: 72,
|
|
84
|
+
height: 72,
|
|
85
|
+
marginBottom: 20,
|
|
86
|
+
},
|
|
87
|
+
bellMount: {
|
|
88
|
+
width: 9,
|
|
89
|
+
height: 7,
|
|
90
|
+
borderRadius: 4,
|
|
91
|
+
backgroundColor: '#E53935',
|
|
92
|
+
marginTop: 7,
|
|
93
|
+
},
|
|
94
|
+
bellBody: {
|
|
95
|
+
width: 36,
|
|
96
|
+
height: 34,
|
|
97
|
+
borderTopLeftRadius: 18,
|
|
98
|
+
borderTopRightRadius: 18,
|
|
99
|
+
backgroundColor: '#E53935',
|
|
100
|
+
marginTop: -1,
|
|
101
|
+
},
|
|
102
|
+
bellSkirt: {
|
|
103
|
+
width: 48,
|
|
104
|
+
height: 9,
|
|
105
|
+
borderBottomLeftRadius: 5,
|
|
106
|
+
borderBottomRightRadius: 5,
|
|
107
|
+
backgroundColor: '#E53935',
|
|
108
|
+
},
|
|
109
|
+
bellClapper: {
|
|
110
|
+
width: 12,
|
|
111
|
+
height: 7,
|
|
112
|
+
borderBottomLeftRadius: 6,
|
|
113
|
+
borderBottomRightRadius: 6,
|
|
114
|
+
backgroundColor: '#E53935',
|
|
115
|
+
marginTop: 2,
|
|
116
|
+
},
|
|
117
|
+
message: {
|
|
118
|
+
fontSize: 15,
|
|
119
|
+
color: '#1A1A1A',
|
|
120
|
+
textAlign: 'center',
|
|
121
|
+
lineHeight: 19.5,
|
|
122
|
+
},
|
|
123
|
+
footer: {
|
|
124
|
+
width: '100%',
|
|
125
|
+
gap: 12,
|
|
126
|
+
},
|
|
127
|
+
confirmButton: {
|
|
128
|
+
width: '100%',
|
|
129
|
+
height: 52,
|
|
130
|
+
borderRadius: 24,
|
|
131
|
+
alignItems: 'center',
|
|
132
|
+
justifyContent: 'center',
|
|
133
|
+
overflow: 'hidden',
|
|
134
|
+
elevation: 4,
|
|
135
|
+
shadowColor: '#E53935',
|
|
136
|
+
shadowOffset: { width: 0, height: 4 },
|
|
137
|
+
shadowOpacity: 0.3,
|
|
138
|
+
shadowRadius: 8,
|
|
139
|
+
},
|
|
140
|
+
gradientContainer: Object.assign(Object.assign({}, react_native_1.StyleSheet.absoluteFillObject), { flexDirection: 'row' }),
|
|
141
|
+
gradientStep: {
|
|
142
|
+
flex: 1,
|
|
143
|
+
},
|
|
144
|
+
confirmText: {
|
|
145
|
+
fontSize: 16,
|
|
146
|
+
fontWeight: '700',
|
|
147
|
+
color: '#fff',
|
|
148
|
+
},
|
|
149
|
+
cancelButton: {
|
|
150
|
+
width: '100%',
|
|
151
|
+
height: 52,
|
|
152
|
+
borderRadius: 24,
|
|
153
|
+
borderWidth: 1.5,
|
|
154
|
+
borderColor: '#CCCCCC',
|
|
155
|
+
alignItems: 'center',
|
|
156
|
+
justifyContent: 'center',
|
|
157
|
+
},
|
|
158
|
+
cancelText: {
|
|
159
|
+
fontSize: 16,
|
|
160
|
+
fontWeight: '600',
|
|
161
|
+
color: '#555555',
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
exports.default = ExitConfirmSheet;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Set custom exit sheet component.
|
|
4
|
+
* Gọi trước khi showRNExitSheet để override default ExitConfirmSheet.
|
|
5
|
+
*/
|
|
6
|
+
export declare function setCustomExitComponent(component: React.ComponentType<any> | null): void;
|
|
7
|
+
/**
|
|
8
|
+
* Wrapper component được SDK auto-register với tên 'SDKExitSheetWrapper'.
|
|
9
|
+
* Render custom component nếu được set, fallback về ExitConfirmSheet mặc định.
|
|
10
|
+
*/
|
|
11
|
+
declare const ExitSheetWrapper: (props: any) => React.JSX.Element;
|
|
12
|
+
export default ExitSheetWrapper;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.setCustomExitComponent = setCustomExitComponent;
|
|
7
|
+
const react_1 = __importDefault(require("react"));
|
|
8
|
+
const ExitConfirmSheet_1 = __importDefault(require("./ExitConfirmSheet"));
|
|
9
|
+
// Module-level variable — set bởi FinosEKYC.setExitSheetComponent()
|
|
10
|
+
let _customComponent = null;
|
|
11
|
+
/**
|
|
12
|
+
* Set custom exit sheet component.
|
|
13
|
+
* Gọi trước khi showRNExitSheet để override default ExitConfirmSheet.
|
|
14
|
+
*/
|
|
15
|
+
function setCustomExitComponent(component) {
|
|
16
|
+
_customComponent = component;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Wrapper component được SDK auto-register với tên 'SDKExitSheetWrapper'.
|
|
20
|
+
* Render custom component nếu được set, fallback về ExitConfirmSheet mặc định.
|
|
21
|
+
*/
|
|
22
|
+
const ExitSheetWrapper = (props) => {
|
|
23
|
+
const Component = _customComponent || ExitConfirmSheet_1.default;
|
|
24
|
+
return <Component {...props}/>;
|
|
25
|
+
};
|
|
26
|
+
exports.default = ExitSheetWrapper;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import React from 'react';
|
|
1
2
|
import { SDK_VERSION, SDK_NAME } from '../../EKYCModule';
|
|
2
3
|
import { NfcConfig } from '../types/ekycNFCType';
|
|
3
4
|
import { SDKEkycResultWithEvent, SDKEkycResultStringWithEvent, EKYCError, AppIDType } from '../types/ekycType';
|
|
@@ -110,13 +111,31 @@ export declare class FinosEKYCModule {
|
|
|
110
111
|
*/
|
|
111
112
|
resolveExit(action: 'CONFIRM' | 'CANCEL' | 'CLOSE'): Promise<boolean>;
|
|
112
113
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
114
|
+
* Set custom component to render inside the exit bottom sheet.
|
|
115
|
+
* Thay thế ExitConfirmSheet mặc định bằng component tùy chỉnh.
|
|
116
|
+
* Không cần AppRegistry — SDK tự lo phần registration.
|
|
115
117
|
*
|
|
116
|
-
* @param
|
|
117
|
-
*
|
|
118
|
+
* @param component React component để hiển thị, hoặc null để về default
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* FinosEKYC.setExitSheetComponent(WarningBottomSheet);
|
|
122
|
+
*/
|
|
123
|
+
setExitSheetComponent(component: React.ComponentType<any> | null): void;
|
|
124
|
+
/**
|
|
125
|
+
* Show the exit bottom sheet on top of the current SDK screen.
|
|
126
|
+
* Mặc định dùng 'SDKExitSheetWrapper' (đã register sẵn trong SDK).
|
|
127
|
+
*
|
|
128
|
+
* @param props Props truyền vào component (optional)
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* // Dùng default ExitConfirmSheet
|
|
132
|
+
* FinosEKYC.showRNExitSheet();
|
|
133
|
+
*
|
|
134
|
+
* // Dùng custom component (set trước bằng setExitSheetComponent)
|
|
135
|
+
* FinosEKYC.setExitSheetComponent(WarningBottomSheet);
|
|
136
|
+
* FinosEKYC.showRNExitSheet();
|
|
118
137
|
*/
|
|
119
|
-
showRNExitSheet(
|
|
138
|
+
showRNExitSheet(props?: any): Promise<boolean>;
|
|
120
139
|
/**
|
|
121
140
|
* Show a native alert dialog on top of the current activity (useful for SDK screens)
|
|
122
141
|
* @param config Dialog configuration { title, message, confirmText, cancelText }
|
|
@@ -38,6 +38,7 @@ const react_native_1 = require("react-native");
|
|
|
38
38
|
const EKYCModule_1 = __importStar(require("../../EKYCModule"));
|
|
39
39
|
Object.defineProperty(exports, "SDK_VERSION", { enumerable: true, get: function () { return EKYCModule_1.SDK_VERSION; } });
|
|
40
40
|
Object.defineProperty(exports, "SDK_NAME", { enumerable: true, get: function () { return EKYCModule_1.SDK_NAME; } });
|
|
41
|
+
const ExitSheetWrapper_1 = require("../components/ExitSheetWrapper");
|
|
41
42
|
const ekycFlowType_1 = require("../types/ekycFlowType");
|
|
42
43
|
/**
|
|
43
44
|
* Finos eKYC SDK Module
|
|
@@ -313,16 +314,36 @@ class FinosEKYCModule {
|
|
|
313
314
|
}
|
|
314
315
|
}
|
|
315
316
|
/**
|
|
316
|
-
*
|
|
317
|
-
*
|
|
317
|
+
* Set custom component to render inside the exit bottom sheet.
|
|
318
|
+
* Thay thế ExitConfirmSheet mặc định bằng component tùy chỉnh.
|
|
319
|
+
* Không cần AppRegistry — SDK tự lo phần registration.
|
|
318
320
|
*
|
|
319
|
-
* @param
|
|
320
|
-
*
|
|
321
|
+
* @param component React component để hiển thị, hoặc null để về default
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* FinosEKYC.setExitSheetComponent(WarningBottomSheet);
|
|
325
|
+
*/
|
|
326
|
+
setExitSheetComponent(component) {
|
|
327
|
+
(0, ExitSheetWrapper_1.setCustomExitComponent)(component);
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Show the exit bottom sheet on top of the current SDK screen.
|
|
331
|
+
* Mặc định dùng 'SDKExitSheetWrapper' (đã register sẵn trong SDK).
|
|
332
|
+
*
|
|
333
|
+
* @param props Props truyền vào component (optional)
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* // Dùng default ExitConfirmSheet
|
|
337
|
+
* FinosEKYC.showRNExitSheet();
|
|
338
|
+
*
|
|
339
|
+
* // Dùng custom component (set trước bằng setExitSheetComponent)
|
|
340
|
+
* FinosEKYC.setExitSheetComponent(WarningBottomSheet);
|
|
341
|
+
* FinosEKYC.showRNExitSheet();
|
|
321
342
|
*/
|
|
322
|
-
async showRNExitSheet(
|
|
343
|
+
async showRNExitSheet(props = {}) {
|
|
323
344
|
this.validateSDKReady();
|
|
324
345
|
try {
|
|
325
|
-
return await this.sdk.showRNExitSheet(
|
|
346
|
+
return await this.sdk.showRNExitSheet('SDKExitSheetWrapper', props);
|
|
326
347
|
}
|
|
327
348
|
catch (error) {
|
|
328
349
|
console.error('❌ Failed to show RN exit sheet:', error);
|
|
@@ -805,7 +826,11 @@ const isMethod = (prop) => {
|
|
|
805
826
|
prop === 'checkC06' ||
|
|
806
827
|
prop === 'startOcr' ||
|
|
807
828
|
prop === 'startLiveness' ||
|
|
808
|
-
prop === 'startFaceCompare'
|
|
829
|
+
prop === 'startFaceCompare' ||
|
|
830
|
+
prop === 'registerExitHandler' ||
|
|
831
|
+
prop === 'resolveExit' ||
|
|
832
|
+
prop === 'showRNExitSheet' ||
|
|
833
|
+
prop === 'setExitSheetComponent';
|
|
809
834
|
};
|
|
810
835
|
// Create a comprehensive stub object with all methods to prevent undefined errors
|
|
811
836
|
const createFinosEKYCStub = () => {
|
|
@@ -835,6 +860,7 @@ const createFinosEKYCStub = () => {
|
|
|
835
860
|
'startEkycUI', 'sendOtp', 'verifyOtp', 'resendOtp', 'initializeESign', 'openSessionId',
|
|
836
861
|
'registerDevice', 'listCerts', 'verifyCert', 'listSignRequest', 'confirmSign',
|
|
837
862
|
'registerRemoteSigning', 'signPdf', 'sendConfirmationDocument',
|
|
863
|
+
'registerExitHandler', 'resolveExit', 'showRNExitSheet', 'setExitSheetComponent',
|
|
838
864
|
'onResume', 'onPause', 'isSDKReady', 'getSDKInfo'
|
|
839
865
|
];
|
|
840
866
|
otherMethods.forEach(method => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@finos_sdk/sdk-ekyc",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
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",
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
TouchableOpacity,
|
|
7
|
+
NativeModules,
|
|
8
|
+
} from 'react-native';
|
|
9
|
+
import { FinosEKYC } from '../modules/FinosEKYCModule';
|
|
10
|
+
|
|
11
|
+
const BellIcon = () => (
|
|
12
|
+
<View style={styles.bellWrapper}>
|
|
13
|
+
<View style={styles.bellMount} />
|
|
14
|
+
<View style={styles.bellBody} />
|
|
15
|
+
<View style={styles.bellSkirt} />
|
|
16
|
+
<View style={styles.bellClapper} />
|
|
17
|
+
</View>
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const ExitConfirmSheet = (props: any) => {
|
|
21
|
+
const message = props.message || 'Giao dịch bị hủy khi điều hướng sang tính năng tiếp theo. Quý khách có chắc chắn hủy giao dịch?';
|
|
22
|
+
const confirmText = props.confirmText || 'Đồng ý';
|
|
23
|
+
const cancelText = props.cancelText || 'Ở lại';
|
|
24
|
+
|
|
25
|
+
const handleConfirm = async () => {
|
|
26
|
+
await FinosEKYC.resolveExit('CONFIRM');
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const handleCancel = async () => {
|
|
30
|
+
await FinosEKYC.resolveExit('CANCEL');
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<View
|
|
35
|
+
style={styles.container}
|
|
36
|
+
onLayout={(e) => {
|
|
37
|
+
const h = e.nativeEvent.layout.height;
|
|
38
|
+
if (h > 0) NativeModules.EKYCModule?.setExitSheetHeight?.(h);
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
<View style={styles.handle} />
|
|
42
|
+
|
|
43
|
+
<View style={styles.content}>
|
|
44
|
+
<BellIcon />
|
|
45
|
+
<Text style={styles.message}>{message}</Text>
|
|
46
|
+
</View>
|
|
47
|
+
|
|
48
|
+
<View style={styles.footer}>
|
|
49
|
+
<TouchableOpacity style={styles.confirmButton} onPress={handleConfirm} activeOpacity={0.8}>
|
|
50
|
+
<View style={styles.gradientContainer}>
|
|
51
|
+
<View style={[styles.gradientStep, { backgroundColor: '#E53935' }]} />
|
|
52
|
+
<View style={[styles.gradientStep, { backgroundColor: '#EA4F28' }]} />
|
|
53
|
+
<View style={[styles.gradientStep, { backgroundColor: '#EF6519' }]} />
|
|
54
|
+
<View style={[styles.gradientStep, { backgroundColor: '#F47A0B' }]} />
|
|
55
|
+
<View style={[styles.gradientStep, { backgroundColor: '#F98F00' }]} />
|
|
56
|
+
<View style={[styles.gradientStep, { backgroundColor: '#FCA300' }]} />
|
|
57
|
+
<View style={[styles.gradientStep, { backgroundColor: '#FFB800' }]} />
|
|
58
|
+
<View style={[styles.gradientStep, { backgroundColor: '#FFCC00' }]} />
|
|
59
|
+
<View style={[styles.gradientStep, { backgroundColor: '#FFD600' }]} />
|
|
60
|
+
</View>
|
|
61
|
+
<Text style={styles.confirmText}>{confirmText}</Text>
|
|
62
|
+
</TouchableOpacity>
|
|
63
|
+
|
|
64
|
+
<TouchableOpacity style={styles.cancelButton} onPress={handleCancel} activeOpacity={0.7}>
|
|
65
|
+
<Text style={styles.cancelText}>{cancelText}</Text>
|
|
66
|
+
</TouchableOpacity>
|
|
67
|
+
</View>
|
|
68
|
+
</View>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const styles = StyleSheet.create({
|
|
73
|
+
container: {
|
|
74
|
+
backgroundColor: 'white',
|
|
75
|
+
paddingHorizontal: 24,
|
|
76
|
+
paddingBottom: 32,
|
|
77
|
+
borderTopLeftRadius: 32,
|
|
78
|
+
borderTopRightRadius: 32,
|
|
79
|
+
},
|
|
80
|
+
handle: {
|
|
81
|
+
width: 40,
|
|
82
|
+
height: 4,
|
|
83
|
+
backgroundColor: '#CCCCCC',
|
|
84
|
+
borderRadius: 2,
|
|
85
|
+
alignSelf: 'center',
|
|
86
|
+
marginTop: 12,
|
|
87
|
+
marginBottom: 20,
|
|
88
|
+
},
|
|
89
|
+
content: {
|
|
90
|
+
alignItems: 'center',
|
|
91
|
+
marginBottom: 28,
|
|
92
|
+
},
|
|
93
|
+
bellWrapper: {
|
|
94
|
+
alignItems: 'center',
|
|
95
|
+
width: 72,
|
|
96
|
+
height: 72,
|
|
97
|
+
marginBottom: 20,
|
|
98
|
+
},
|
|
99
|
+
bellMount: {
|
|
100
|
+
width: 9,
|
|
101
|
+
height: 7,
|
|
102
|
+
borderRadius: 4,
|
|
103
|
+
backgroundColor: '#E53935',
|
|
104
|
+
marginTop: 7,
|
|
105
|
+
},
|
|
106
|
+
bellBody: {
|
|
107
|
+
width: 36,
|
|
108
|
+
height: 34,
|
|
109
|
+
borderTopLeftRadius: 18,
|
|
110
|
+
borderTopRightRadius: 18,
|
|
111
|
+
backgroundColor: '#E53935',
|
|
112
|
+
marginTop: -1,
|
|
113
|
+
},
|
|
114
|
+
bellSkirt: {
|
|
115
|
+
width: 48,
|
|
116
|
+
height: 9,
|
|
117
|
+
borderBottomLeftRadius: 5,
|
|
118
|
+
borderBottomRightRadius: 5,
|
|
119
|
+
backgroundColor: '#E53935',
|
|
120
|
+
},
|
|
121
|
+
bellClapper: {
|
|
122
|
+
width: 12,
|
|
123
|
+
height: 7,
|
|
124
|
+
borderBottomLeftRadius: 6,
|
|
125
|
+
borderBottomRightRadius: 6,
|
|
126
|
+
backgroundColor: '#E53935',
|
|
127
|
+
marginTop: 2,
|
|
128
|
+
},
|
|
129
|
+
message: {
|
|
130
|
+
fontSize: 15,
|
|
131
|
+
color: '#1A1A1A',
|
|
132
|
+
textAlign: 'center',
|
|
133
|
+
lineHeight: 19.5,
|
|
134
|
+
},
|
|
135
|
+
footer: {
|
|
136
|
+
width: '100%',
|
|
137
|
+
gap: 12,
|
|
138
|
+
},
|
|
139
|
+
confirmButton: {
|
|
140
|
+
width: '100%',
|
|
141
|
+
height: 52,
|
|
142
|
+
borderRadius: 24,
|
|
143
|
+
alignItems: 'center',
|
|
144
|
+
justifyContent: 'center',
|
|
145
|
+
overflow: 'hidden',
|
|
146
|
+
elevation: 4,
|
|
147
|
+
shadowColor: '#E53935',
|
|
148
|
+
shadowOffset: { width: 0, height: 4 },
|
|
149
|
+
shadowOpacity: 0.3,
|
|
150
|
+
shadowRadius: 8,
|
|
151
|
+
},
|
|
152
|
+
gradientContainer: {
|
|
153
|
+
...StyleSheet.absoluteFillObject,
|
|
154
|
+
flexDirection: 'row',
|
|
155
|
+
},
|
|
156
|
+
gradientStep: {
|
|
157
|
+
flex: 1,
|
|
158
|
+
},
|
|
159
|
+
confirmText: {
|
|
160
|
+
fontSize: 16,
|
|
161
|
+
fontWeight: '700',
|
|
162
|
+
color: '#fff',
|
|
163
|
+
},
|
|
164
|
+
cancelButton: {
|
|
165
|
+
width: '100%',
|
|
166
|
+
height: 52,
|
|
167
|
+
borderRadius: 24,
|
|
168
|
+
borderWidth: 1.5,
|
|
169
|
+
borderColor: '#CCCCCC',
|
|
170
|
+
alignItems: 'center',
|
|
171
|
+
justifyContent: 'center',
|
|
172
|
+
},
|
|
173
|
+
cancelText: {
|
|
174
|
+
fontSize: 16,
|
|
175
|
+
fontWeight: '600',
|
|
176
|
+
color: '#555555',
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
export default ExitConfirmSheet;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ExitConfirmSheet from './ExitConfirmSheet';
|
|
3
|
+
|
|
4
|
+
// Module-level variable — set bởi FinosEKYC.setExitSheetComponent()
|
|
5
|
+
let _customComponent: React.ComponentType<any> | null = null;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Set custom exit sheet component.
|
|
9
|
+
* Gọi trước khi showRNExitSheet để override default ExitConfirmSheet.
|
|
10
|
+
*/
|
|
11
|
+
export function setCustomExitComponent(component: React.ComponentType<any> | null) {
|
|
12
|
+
_customComponent = component;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Wrapper component được SDK auto-register với tên 'SDKExitSheetWrapper'.
|
|
17
|
+
* Render custom component nếu được set, fallback về ExitConfirmSheet mặc định.
|
|
18
|
+
*/
|
|
19
|
+
const ExitSheetWrapper = (props: any) => {
|
|
20
|
+
const Component = _customComponent || ExitConfirmSheet;
|
|
21
|
+
return <Component {...props} />;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default ExitSheetWrapper;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import React from 'react';
|
|
1
2
|
import { Platform, DeviceEventEmitter } from 'react-native';
|
|
2
3
|
import sdkEKYC, { SDKeKYC, SDK_VERSION, SDK_NAME } from '../../EKYCModule';
|
|
4
|
+
import { setCustomExitComponent } from '../components/ExitSheetWrapper';
|
|
3
5
|
import { NfcConfig, NfcError } from '../types/ekycNFCType';
|
|
4
6
|
import { SDKEkycResultWithEvent, SDKEkycResultStringWithEvent, EKYCError, getEkycError, AppIDType } from '../types/ekycType';
|
|
5
7
|
import { C06Config } from '../types/ekycC06Type';
|
|
@@ -321,16 +323,37 @@ export class FinosEKYCModule {
|
|
|
321
323
|
}
|
|
322
324
|
|
|
323
325
|
/**
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
* @param
|
|
326
|
+
* Set custom component to render inside the exit bottom sheet.
|
|
327
|
+
* Thay thế ExitConfirmSheet mặc định bằng component tùy chỉnh.
|
|
328
|
+
* Không cần AppRegistry — SDK tự lo phần registration.
|
|
329
|
+
*
|
|
330
|
+
* @param component React component để hiển thị, hoặc null để về default
|
|
331
|
+
*
|
|
332
|
+
* @example
|
|
333
|
+
* FinosEKYC.setExitSheetComponent(WarningBottomSheet);
|
|
329
334
|
*/
|
|
330
|
-
public
|
|
335
|
+
public setExitSheetComponent(component: React.ComponentType<any> | null): void {
|
|
336
|
+
setCustomExitComponent(component);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Show the exit bottom sheet on top of the current SDK screen.
|
|
341
|
+
* Mặc định dùng 'SDKExitSheetWrapper' (đã register sẵn trong SDK).
|
|
342
|
+
*
|
|
343
|
+
* @param props Props truyền vào component (optional)
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* // Dùng default ExitConfirmSheet
|
|
347
|
+
* FinosEKYC.showRNExitSheet();
|
|
348
|
+
*
|
|
349
|
+
* // Dùng custom component (set trước bằng setExitSheetComponent)
|
|
350
|
+
* FinosEKYC.setExitSheetComponent(WarningBottomSheet);
|
|
351
|
+
* FinosEKYC.showRNExitSheet();
|
|
352
|
+
*/
|
|
353
|
+
public async showRNExitSheet(props: any = {}): Promise<boolean> {
|
|
331
354
|
this.validateSDKReady();
|
|
332
355
|
try {
|
|
333
|
-
return await this.sdk.showRNExitSheet(
|
|
356
|
+
return await this.sdk.showRNExitSheet('SDKExitSheetWrapper', props);
|
|
334
357
|
} catch (error) {
|
|
335
358
|
console.error('❌ Failed to show RN exit sheet:', error);
|
|
336
359
|
throw error;
|
|
@@ -978,7 +1001,11 @@ const isMethod = (prop: string | symbol): boolean => {
|
|
|
978
1001
|
prop === 'checkC06' ||
|
|
979
1002
|
prop === 'startOcr' ||
|
|
980
1003
|
prop === 'startLiveness' ||
|
|
981
|
-
prop === 'startFaceCompare'
|
|
1004
|
+
prop === 'startFaceCompare' ||
|
|
1005
|
+
prop === 'registerExitHandler' ||
|
|
1006
|
+
prop === 'resolveExit' ||
|
|
1007
|
+
prop === 'showRNExitSheet' ||
|
|
1008
|
+
prop === 'setExitSheetComponent';
|
|
982
1009
|
};
|
|
983
1010
|
|
|
984
1011
|
// Create a comprehensive stub object with all methods to prevent undefined errors
|
|
@@ -1012,6 +1039,7 @@ const createFinosEKYCStub = (): FinosEKYCModule => {
|
|
|
1012
1039
|
'startEkycUI', 'sendOtp', 'verifyOtp', 'resendOtp', 'initializeESign', 'openSessionId',
|
|
1013
1040
|
'registerDevice', 'listCerts', 'verifyCert', 'listSignRequest', 'confirmSign',
|
|
1014
1041
|
'registerRemoteSigning', 'signPdf', 'sendConfirmationDocument',
|
|
1042
|
+
'registerExitHandler', 'resolveExit', 'showRNExitSheet', 'setExitSheetComponent',
|
|
1015
1043
|
'onResume', 'onPause', 'isSDKReady', 'getSDKInfo'
|
|
1016
1044
|
];
|
|
1017
1045
|
|