@trustchex/react-native-sdk 1.381.0 → 1.409.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.
Files changed (111) hide show
  1. package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +1 -12
  2. package/android/src/main/java/com/trustchex/reactnativesdk/mlkit/MLKitModule.kt +1 -1
  3. package/ios/Camera/TrustchexCameraView.swift +1 -12
  4. package/ios/MLKit/MLKitModule.swift +1 -1
  5. package/lib/module/Screens/Debug/BarcodeTestScreen.js +308 -0
  6. package/lib/module/Screens/Debug/MRZTestScreen.js +105 -13
  7. package/lib/module/Screens/Dynamic/ContractAcceptanceScreen.js +49 -29
  8. package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +5 -0
  9. package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +5 -0
  10. package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +26 -6
  11. package/lib/module/Screens/Dynamic/VideoCallScreen.js +676 -0
  12. package/lib/module/Screens/Static/OTPVerificationScreen.js +6 -0
  13. package/lib/module/Screens/Static/QrCodeScanningScreen.js +7 -1
  14. package/lib/module/Screens/Static/ResultScreen.js +27 -13
  15. package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +51 -51
  16. package/lib/module/Shared/Animations/video-call.json +1 -0
  17. package/lib/module/Shared/Components/DebugNavigationPanel.js +180 -14
  18. package/lib/module/Shared/Components/EIDScanner.js +1 -4
  19. package/lib/module/Shared/Components/IdentityDocumentCamera.js +29 -8
  20. package/lib/module/Shared/Components/NavigationManager.js +15 -3
  21. package/lib/module/Shared/Contexts/AppContext.js +1 -0
  22. package/lib/module/Shared/Libs/SignalingClient.js +128 -0
  23. package/lib/module/Shared/Libs/analytics.utils.js +4 -0
  24. package/lib/module/Shared/Libs/deeplink.utils.js +9 -1
  25. package/lib/module/Shared/Libs/http-client.js +9 -0
  26. package/lib/module/Shared/Libs/promise.utils.js +16 -2
  27. package/lib/module/Shared/Libs/status-bar.utils.js +21 -0
  28. package/lib/module/Shared/Services/DataUploadService.js +294 -0
  29. package/lib/module/Shared/Services/VideoSessionService.js +156 -0
  30. package/lib/module/Shared/Services/WebRTCService.js +510 -0
  31. package/lib/module/Shared/Types/analytics.types.js +2 -0
  32. package/lib/module/Translation/Resources/en.js +20 -0
  33. package/lib/module/Translation/Resources/tr.js +20 -0
  34. package/lib/module/Trustchex.js +10 -0
  35. package/lib/module/version.js +1 -1
  36. package/lib/typescript/src/Screens/Debug/BarcodeTestScreen.d.ts +3 -0
  37. package/lib/typescript/src/Screens/Debug/BarcodeTestScreen.d.ts.map +1 -0
  38. package/lib/typescript/src/Screens/Debug/MRZTestScreen.d.ts.map +1 -1
  39. package/lib/typescript/src/Screens/Dynamic/ContractAcceptanceScreen.d.ts.map +1 -1
  40. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
  41. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
  42. package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
  43. package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts +3 -0
  44. package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts.map +1 -0
  45. package/lib/typescript/src/Screens/Static/OTPVerificationScreen.d.ts.map +1 -1
  46. package/lib/typescript/src/Screens/Static/QrCodeScanningScreen.d.ts.map +1 -1
  47. package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
  48. package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
  49. package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts.map +1 -1
  50. package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
  51. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
  52. package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
  53. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts +1 -0
  54. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts.map +1 -1
  55. package/lib/typescript/src/Shared/Libs/SignalingClient.d.ts +24 -0
  56. package/lib/typescript/src/Shared/Libs/SignalingClient.d.ts.map +1 -0
  57. package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -1
  58. package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts.map +1 -1
  59. package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
  60. package/lib/typescript/src/Shared/Libs/promise.utils.d.ts.map +1 -1
  61. package/lib/typescript/src/Shared/Libs/status-bar.utils.d.ts +9 -0
  62. package/lib/typescript/src/Shared/Libs/status-bar.utils.d.ts.map +1 -0
  63. package/lib/typescript/src/Shared/Services/DataUploadService.d.ts +25 -0
  64. package/lib/typescript/src/Shared/Services/DataUploadService.d.ts.map +1 -0
  65. package/lib/typescript/src/Shared/Services/VideoSessionService.d.ts +33 -0
  66. package/lib/typescript/src/Shared/Services/VideoSessionService.d.ts.map +1 -0
  67. package/lib/typescript/src/Shared/Services/WebRTCService.d.ts +58 -0
  68. package/lib/typescript/src/Shared/Services/WebRTCService.d.ts.map +1 -0
  69. package/lib/typescript/src/Shared/Types/analytics.types.d.ts +2 -0
  70. package/lib/typescript/src/Shared/Types/analytics.types.d.ts.map +1 -1
  71. package/lib/typescript/src/Shared/Types/identificationInfo.d.ts +4 -1
  72. package/lib/typescript/src/Shared/Types/identificationInfo.d.ts.map +1 -1
  73. package/lib/typescript/src/Translation/Resources/en.d.ts +20 -0
  74. package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
  75. package/lib/typescript/src/Translation/Resources/tr.d.ts +20 -0
  76. package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
  77. package/lib/typescript/src/Trustchex.d.ts.map +1 -1
  78. package/lib/typescript/src/version.d.ts +1 -1
  79. package/package.json +29 -2
  80. package/src/Screens/Debug/BarcodeTestScreen.tsx +317 -0
  81. package/src/Screens/Debug/MRZTestScreen.tsx +107 -13
  82. package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +59 -33
  83. package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +6 -0
  84. package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +6 -0
  85. package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +34 -6
  86. package/src/Screens/Dynamic/VideoCallScreen.tsx +764 -0
  87. package/src/Screens/Static/OTPVerificationScreen.tsx +6 -0
  88. package/src/Screens/Static/QrCodeScanningScreen.tsx +7 -1
  89. package/src/Screens/Static/ResultScreen.tsx +58 -23
  90. package/src/Screens/Static/VerificationSessionCheckScreen.tsx +58 -72
  91. package/src/Shared/Animations/video-call.json +1 -0
  92. package/src/Shared/Components/DebugNavigationPanel.tsx +185 -9
  93. package/src/Shared/Components/EIDScanner.tsx +1 -5
  94. package/src/Shared/Components/IdentityDocumentCamera.tsx +29 -8
  95. package/src/Shared/Components/NavigationManager.tsx +14 -1
  96. package/src/Shared/Contexts/AppContext.ts +2 -0
  97. package/src/Shared/Libs/SignalingClient.ts +189 -0
  98. package/src/Shared/Libs/analytics.utils.ts +4 -0
  99. package/src/Shared/Libs/deeplink.utils.ts +12 -1
  100. package/src/Shared/Libs/http-client.ts +10 -0
  101. package/src/Shared/Libs/promise.utils.ts +16 -2
  102. package/src/Shared/Libs/status-bar.utils.ts +19 -0
  103. package/src/Shared/Services/DataUploadService.ts +395 -0
  104. package/src/Shared/Services/VideoSessionService.ts +190 -0
  105. package/src/Shared/Services/WebRTCService.ts +636 -0
  106. package/src/Shared/Types/analytics.types.ts +2 -0
  107. package/src/Shared/Types/identificationInfo.ts +5 -1
  108. package/src/Translation/Resources/en.ts +25 -0
  109. package/src/Translation/Resources/tr.ts +27 -0
  110. package/src/Trustchex.tsx +12 -2
  111. package/src/version.ts +1 -1
@@ -91,7 +91,6 @@ const EIDScanner = ({
91
91
  const appContext = React.useContext(AppContext);
92
92
  const [voiceGuidanceMessage, setVoiceGuidanceMessage] = useState<string>();
93
93
  const [attemptNumber, setAttemptNumber] = useState<number>(0);
94
- const lastVoiceGuidanceMessage = useRef<string>('');
95
94
 
96
95
  const readNFC = useCallback(async () => {
97
96
  const startTime = Date.now();
@@ -102,7 +101,6 @@ const EIDScanner = ({
102
101
  setIsScanned(false);
103
102
  setProgress(0);
104
103
  setProgressStage('');
105
- lastVoiceGuidanceMessage.current = '';
106
104
  resetLastMessage();
107
105
 
108
106
  // Track EID scan start using analytics helper
@@ -365,10 +363,8 @@ const EIDScanner = ({
365
363
  useEffect(() => {
366
364
  if (
367
365
  voiceGuidanceMessage &&
368
- appContext.currentWorkflowStep?.data?.voiceGuidanceActive &&
369
- voiceGuidanceMessage !== lastVoiceGuidanceMessage.current
366
+ appContext.currentWorkflowStep?.data?.voiceGuidanceActive
370
367
  ) {
371
- lastVoiceGuidanceMessage.current = voiceGuidanceMessage;
372
368
  speak(voiceGuidanceMessage, true);
373
369
  }
374
370
  }, [
@@ -174,6 +174,7 @@ const IdentityDocumentCamera = ({
174
174
  const isHologramDetectionInProgress = useRef(false); // Prevent concurrent hologram detection calls
175
175
  const isCompletionCallbackInvoked = useRef(false); // Prevent multiple callback invocations when COMPLETED
176
176
  const isStepTransitionInProgress = useRef(false); // Prevent duplicate step transitions from rapid frames
177
+ const isCompletionAnnouncementSpoken = useRef(false); // Prevent duplicate "scan completed" voice guidance
177
178
 
178
179
  const faceDetectionErrorCount = useRef(0);
179
180
  const brightnessHistory = useRef<number[]>([]);
@@ -181,7 +182,6 @@ const IdentityDocumentCamera = ({
181
182
  const faceImages = useRef<string[]>([]);
182
183
  const hologramImageCountRef = useRef(0);
183
184
  const [hologramImageCount, setHologramImageCount] = useState(0);
184
- const lastVoiceGuidanceMessage = useRef<string>('');
185
185
  const [latestHologramFaceImage, setLatestHologramFaceImage] = useState<
186
186
  string | undefined
187
187
  >(undefined);
@@ -264,6 +264,7 @@ const IdentityDocumentCamera = ({
264
264
  setIsActive(true);
265
265
  isCompletionCallbackInvoked.current = false; // Reset callback flag when starting new scan
266
266
  isStepTransitionInProgress.current = false;
267
+ isCompletionAnnouncementSpoken.current = false;
267
268
  } else {
268
269
  setIsActive(false);
269
270
  faceImages.current = [];
@@ -277,9 +278,9 @@ const IdentityDocumentCamera = ({
277
278
  lastValidMRZFields.current = null;
278
279
  validMRZConsecutiveCount.current = 0;
279
280
  cachedBarcode.current = null;
280
- lastVoiceGuidanceMessage.current = '';
281
281
  isCompletionCallbackInvoked.current = false;
282
282
  isStepTransitionInProgress.current = false;
283
+ isCompletionAnnouncementSpoken.current = false;
283
284
  // Clear all captured image states from previous scan
284
285
  setCurrentFaceImage(undefined);
285
286
  setCurrentHologramImage(undefined);
@@ -293,9 +294,9 @@ const IdentityDocumentCamera = ({
293
294
  hologramImageCountRef.current = 0;
294
295
  setHologramImageCount(0);
295
296
  setLatestHologramFaceImage(undefined);
296
- lastVoiceGuidanceMessage.current = '';
297
297
  isCompletionCallbackInvoked.current = false; // Reset callback flag on unmount
298
298
  isStepTransitionInProgress.current = false;
299
+ isCompletionAnnouncementSpoken.current = false;
299
300
  // Clear all captured image states on unmount
300
301
  setCurrentFaceImage(undefined);
301
302
  setCurrentHologramImage(undefined);
@@ -322,9 +323,17 @@ const IdentityDocumentCamera = ({
322
323
  t
323
324
  );
324
325
 
325
- if (message && message !== lastVoiceGuidanceMessage.current) {
326
- lastVoiceGuidanceMessage.current = message;
327
- speak(message, true);
326
+ if (message) {
327
+ if (nextStep === 'COMPLETED') {
328
+ if (isCompletionAnnouncementSpoken.current) {
329
+ return;
330
+ }
331
+ isCompletionAnnouncementSpoken.current = true;
332
+ } else {
333
+ isCompletionAnnouncementSpoken.current = false;
334
+ }
335
+ const shouldBypassInterval = nextStep !== 'COMPLETED';
336
+ speak(message, shouldBypassInterval);
328
337
  }
329
338
  }, [
330
339
  appContext.currentWorkflowStep?.data?.voiceGuidanceActive,
@@ -365,6 +374,18 @@ const IdentityDocumentCamera = ({
365
374
  isStepTransitionInProgress.current = false;
366
375
  }, [nextStep]);
367
376
 
377
+ // Update status bar based on guide visibility
378
+ useEffect(() => {
379
+ if (hasGuideShown) {
380
+ // Scanning camera view - use light icons on dark background
381
+ StatusBar.setBarStyle('light-content', true);
382
+ } else {
383
+ // Guide screen with white background - use dark icons
384
+ StatusBar.setBarStyle('dark-content', true);
385
+ StatusBar.setBackgroundColor('#ffffff', true);
386
+ }
387
+ }, [hasGuideShown]);
388
+
368
389
  // Error flash animation - flash red text when wrong side detected
369
390
  useEffect(() => {
370
391
  if (status === 'INCORRECT') {
@@ -2261,8 +2282,8 @@ const IdentityDocumentCamera = ({
2261
2282
  return (
2262
2283
  <View style={StyleSheet.absoluteFill}>
2263
2284
  <StatusBar
2264
- barStyle="light-content"
2265
- backgroundColor="transparent"
2285
+ barStyle={hasGuideShown ? 'light-content' : 'dark-content'}
2286
+ backgroundColor={hasGuideShown ? 'transparent' : '#ffffff'}
2266
2287
  translucent
2267
2288
  />
2268
2289
  {!hasGuideShown ? (
@@ -45,6 +45,7 @@ const NavigationManager = forwardRef(
45
45
  IDENTITY_DOCUMENT_SCAN: 'IdentityDocumentScanningScreen',
46
46
  IDENTITY_DOCUMENT_EID_SCAN: 'IdentityDocumentEIDScanningScreen',
47
47
  LIVENESS_CHECK: 'LivenessDetectionScreen',
48
+ VIDEO_CALL: 'VideoCallScreen',
48
49
  },
49
50
  RESULT: 'ResultScreen',
50
51
  };
@@ -109,6 +110,10 @@ const NavigationManager = forwardRef(
109
110
  return routes.DYNAMIC_ROUTES.LIVENESS_CHECK;
110
111
  }
111
112
 
113
+ if (nextStep.type === 'VIDEO_CALL') {
114
+ return routes.DYNAMIC_ROUTES.VIDEO_CALL;
115
+ }
116
+
112
117
  return routes.VERIFICATION_SESSION_CHECK;
113
118
  },
114
119
  [
@@ -207,7 +212,14 @@ const NavigationManager = forwardRef(
207
212
  deviceInfo: '',
208
213
  },
209
214
  locale: appContext.locale || i18n.language,
215
+ // Explicitly reset collected data fields
216
+ scannedDocument: undefined,
217
+ livenessDetection: undefined,
218
+ authToken: undefined,
219
+ videoSessionId: undefined,
210
220
  };
221
+
222
+ // Reset branding to defaults while preserving any custom values
211
223
  appContext.branding = {
212
224
  logoUrl: appContext.branding?.logoUrl || '',
213
225
  primaryColor: appContext.branding?.primaryColor || '#000000',
@@ -220,6 +232,7 @@ const NavigationManager = forwardRef(
220
232
  appContext.setIsDemoSession?.(false);
221
233
  analyticsService.setDemoSession(false);
222
234
  }
235
+ appContext.isTestVideoSession = false;
223
236
 
224
237
  navigation.dispatch(
225
238
  CommonActions.reset({
@@ -238,7 +251,7 @@ const NavigationManager = forwardRef(
238
251
  }, [appContext, navigation, routes.VERIFICATION_SESSION_CHECK]);
239
252
 
240
253
  usePreventRemove(true, ({ data }) => {
241
- if (data.action.type === 'RESET') {
254
+ if (data?.action?.type === 'RESET') {
242
255
  navigation.dispatch(data.action);
243
256
  }
244
257
  });
@@ -18,6 +18,7 @@ export type AppContextType = {
18
18
  workflowSteps?: WorkflowStep[];
19
19
  currentWorkflowStep?: WorkflowStep;
20
20
  isDebugNavigated?: boolean;
21
+ isTestVideoSession?: boolean;
21
22
  onCompleted?: () => void;
22
23
  onError?: (error: string) => void;
23
24
  setSessionId?: (id: string) => void;
@@ -47,6 +48,7 @@ export default createContext<AppContextType>({
47
48
  workflowSteps: [],
48
49
  currentWorkflowStep: undefined,
49
50
  isDebugNavigated: false,
51
+ isTestVideoSession: false,
50
52
  onCompleted: undefined,
51
53
  onError: undefined,
52
54
  setSessionId: undefined,
@@ -0,0 +1,189 @@
1
+ import EventSource, { type EventSourceListener } from 'react-native-sse';
2
+
3
+ export type SignalingMessageType =
4
+ | 'offer'
5
+ | 'answer'
6
+ | 'ice-candidate'
7
+ | 'command';
8
+
9
+ export interface SignalingMessage {
10
+ id?: string;
11
+ type: SignalingMessageType;
12
+ payload: any;
13
+ createdAt?: string;
14
+ }
15
+
16
+ export type SignalingEventHandler = (message: SignalingMessage) => void;
17
+ export type SessionEndedHandler = (state: string) => void;
18
+
19
+ export class SignalingClient {
20
+ private eventSource: EventSource | null = null;
21
+ private sessionId: string;
22
+ private baseUrl: string;
23
+ private onMessage: SignalingEventHandler;
24
+ private onConnected?: () => void;
25
+ private onDisconnected?: () => void;
26
+ private onSessionEnded?: SessionEndedHandler;
27
+ private identificationId?: string;
28
+
29
+ constructor(
30
+ baseUrl: string,
31
+ sessionId: string,
32
+ onMessage: SignalingEventHandler,
33
+ identificationId?: string,
34
+ onSessionEnded?: SessionEndedHandler
35
+ ) {
36
+ this.baseUrl = baseUrl;
37
+ this.sessionId = sessionId;
38
+ this.onMessage = onMessage;
39
+ this.identificationId = identificationId;
40
+ this.onSessionEnded = onSessionEnded;
41
+ }
42
+
43
+ public connect(): void {
44
+ if (this.eventSource) {
45
+ this.eventSource.close();
46
+ }
47
+
48
+ const urlParams = new URLSearchParams();
49
+ if (this.identificationId) {
50
+ urlParams.append('identificationId', this.identificationId);
51
+ }
52
+ const url = `${this.baseUrl}/api/app/mobile/video-sessions/${this.sessionId}/signaling/stream?${urlParams.toString()}`;
53
+
54
+ console.log('[SignalingClient] Connecting to SSE:', url);
55
+
56
+ this.eventSource = new EventSource(url, {
57
+ pollingInterval: 0, // 0 means default/no polling if SSE is real
58
+ });
59
+
60
+ const listener: EventSourceListener = (event) => {
61
+ if (event.type === 'open') {
62
+ console.log('[SignalingClient] Connected');
63
+ this.onConnected?.();
64
+ } else if (event.type === 'error') {
65
+ console.error(
66
+ '[SignalingClient] Connection error:',
67
+ JSON.stringify(event, null, 2)
68
+ );
69
+ this.onDisconnected?.();
70
+ } else if (event.type === 'message') {
71
+ try {
72
+ const data = JSON.parse(event.data || '{}');
73
+
74
+ if (
75
+ ['offer', 'answer', 'ice-candidate', 'command'].includes(data.type)
76
+ ) {
77
+ this.onMessage({
78
+ id: data.id,
79
+ type: data.type as SignalingMessageType,
80
+ payload: data.payload,
81
+ createdAt: data.createdAt,
82
+ });
83
+ }
84
+ } catch (e) {
85
+ console.error('[SignalingClient] Error parsing message:', e);
86
+ }
87
+ }
88
+ };
89
+
90
+ const customEventListener = (event: any) => {
91
+ try {
92
+ const data = JSON.parse(event.data || '{}');
93
+ const eventType = event.type;
94
+
95
+ if (
96
+ ['offer', 'answer', 'ice-candidate', 'command'].includes(eventType)
97
+ ) {
98
+ this.onMessage({
99
+ id: data.id,
100
+ type: eventType as SignalingMessageType,
101
+ payload: data.payload,
102
+ createdAt: data.createdAt,
103
+ });
104
+ }
105
+ } catch (e) {
106
+ console.error('[SignalingClient] Error parsing custom event:', e);
107
+ }
108
+ };
109
+
110
+ // Ping handler - server sends pings to keep connection alive
111
+ const pingListener = (event: any) => {
112
+ console.log('[SignalingClient] Received ping');
113
+ // Just acknowledge by doing nothing, server-side heartbeat keeps connection alive
114
+ };
115
+
116
+ // Session-ended event listener
117
+ const sessionEndedListener = (event: any) => {
118
+ try {
119
+ const data = JSON.parse(event.data || '{}');
120
+ console.log('[SignalingClient] Session ended:', data.state);
121
+ if (this.onSessionEnded) {
122
+ this.onSessionEnded(data.state);
123
+ }
124
+ } catch (e) {
125
+ console.error(
126
+ '[SignalingClient] Error parsing session-ended event:',
127
+ e
128
+ );
129
+ }
130
+ };
131
+
132
+ this.eventSource.addEventListener('open', listener);
133
+ this.eventSource.addEventListener('message', listener);
134
+ this.eventSource.addEventListener('error', listener);
135
+ this.eventSource.addEventListener('ping' as any, pingListener);
136
+ this.eventSource.addEventListener('offer' as any, customEventListener);
137
+ this.eventSource.addEventListener('answer' as any, customEventListener);
138
+ this.eventSource.addEventListener(
139
+ 'ice-candidate' as any,
140
+ customEventListener
141
+ );
142
+ this.eventSource.addEventListener('command' as any, customEventListener);
143
+ this.eventSource.addEventListener(
144
+ 'session-ended' as any,
145
+ sessionEndedListener
146
+ );
147
+ }
148
+
149
+ public async send(type: SignalingMessageType, payload: any): Promise<void> {
150
+ const urlParams = new URLSearchParams();
151
+ if (this.identificationId) {
152
+ urlParams.append('identificationId', this.identificationId);
153
+ }
154
+ const url = `${this.baseUrl}/api/app/mobile/video-sessions/${this.sessionId}/signaling?${urlParams.toString()}`;
155
+
156
+ try {
157
+ const body = {
158
+ type,
159
+ data: payload,
160
+ };
161
+
162
+ const response = await fetch(url, {
163
+ method: 'POST',
164
+ headers: {
165
+ 'Content-Type': 'application/json',
166
+ },
167
+ body: JSON.stringify(body),
168
+ });
169
+
170
+ if (!response.ok) {
171
+ throw new Error(
172
+ `Failed to send signaling message: ${response.statusText}`
173
+ );
174
+ }
175
+ } catch (error) {
176
+ console.error('[SignalingClient] Error sending message:', error);
177
+ throw error;
178
+ }
179
+ }
180
+
181
+ public disconnect(): void {
182
+ if (this.eventSource) {
183
+ this.eventSource.removeAllEventListeners();
184
+ this.eventSource.close();
185
+ this.eventSource = null;
186
+ this.onDisconnected?.();
187
+ }
188
+ }
189
+ }
@@ -289,6 +289,10 @@ const STEP_EVENT_MAP: Record<
289
289
  started: AnalyticsEventName.LIVENESS_CHECK_STARTED,
290
290
  completed: AnalyticsEventName.LIVENESS_CHECK_COMPLETED,
291
291
  },
292
+ video_call: {
293
+ started: AnalyticsEventName.VIDEO_CALL_STARTED,
294
+ completed: AnalyticsEventName.VIDEO_CALL_COMPLETED,
295
+ },
292
296
  };
293
297
 
294
298
  function getStepEventName(
@@ -10,7 +10,10 @@ const handleDeepLink = ({ url }: { url: string }) => {
10
10
  let sessionId = '';
11
11
 
12
12
  for (let i = 0; i < segments.length; i++) {
13
- if (segments[i] === 'verification-session') {
13
+ if (
14
+ segments[i] === 'verification-session' ||
15
+ segments[i] === 'verification-sessions'
16
+ ) {
14
17
  sessionId = segments[i + 1] ?? '';
15
18
  debugLog('handleDeepLink', 'Found sessionId:', sessionId);
16
19
  } else if (segments[i] === 'app-url') {
@@ -19,6 +22,14 @@ const handleDeepLink = ({ url }: { url: string }) => {
19
22
  }
20
23
  }
21
24
 
25
+ // If no app-url segment found, derive baseUrl from the URL itself
26
+ if (!baseUrl && sessionId) {
27
+ const match = url.match(/^(https?:\/\/[^/]+)/);
28
+ if (match) {
29
+ baseUrl = match[1];
30
+ }
31
+ }
32
+
22
33
  debugLog('handleDeepLink', 'Returning:', { baseUrl, sessionId });
23
34
  return [baseUrl, sessionId];
24
35
  };
@@ -64,6 +64,11 @@ const request = async <TResponse, TBody>(
64
64
  let statusCode = 0;
65
65
  let success = false;
66
66
 
67
+ console.log(`[HTTP] ${httpMethod} ${url}`);
68
+ if (body) {
69
+ console.log('[HTTP] Request body:', JSON.stringify(body).substring(0, 200) + '...');
70
+ }
71
+
67
72
  try {
68
73
  const response = await fetch(url, {
69
74
  method: httpMethod,
@@ -75,13 +80,18 @@ const request = async <TResponse, TBody>(
75
80
 
76
81
  statusCode = response.status;
77
82
  success = response.ok;
83
+ console.log(`[HTTP] Response status: ${statusCode} ${response.ok ? '✓' : '✗'}`);
78
84
 
79
85
  let responseJson = null;
80
86
 
81
87
  try {
82
88
  responseJson = await response.json();
89
+ if (responseJson) {
90
+ console.log('[HTTP] Response body:', JSON.stringify(responseJson).substring(0, 200) + '...');
91
+ }
83
92
  } catch (error) {
84
93
  // Invalid JSON response
94
+ console.log('[HTTP] Response body: (non-JSON)');
85
95
  }
86
96
 
87
97
  // Track API call performance (non-blocking)
@@ -4,17 +4,31 @@ const runWithRetry = async <T>(
4
4
  delay: number = 1000
5
5
  ): Promise<T> => {
6
6
  let retries = 0;
7
+ let lastError: Error | unknown;
7
8
  let result: T;
8
9
  while (retries < maxRetries) {
9
10
  try {
11
+ if (retries > 0) {
12
+ console.log(`[Retry] Attempt ${retries + 1}/${maxRetries}...`);
13
+ }
10
14
  result = await fn();
15
+ if (retries > 0) {
16
+ console.log(`[Retry] ✓ Success on attempt ${retries + 1}`);
17
+ }
11
18
  return result;
12
19
  } catch (error) {
20
+ lastError = error;
13
21
  retries++;
14
- await new Promise((resolve) => setTimeout(resolve, delay * retries));
22
+ console.error(`[Retry] Attempt ${retries}/${maxRetries} failed:`, error instanceof Error ? error.message : error);
23
+ if (retries < maxRetries) {
24
+ const waitTime = delay * retries;
25
+ console.log(`[Retry] Waiting ${waitTime}ms before retry...`);
26
+ await new Promise((resolve) => setTimeout(resolve, waitTime));
27
+ }
15
28
  }
16
29
  }
17
- throw new Error('Max retries exceeded');
30
+ console.error('[Retry] ✗ All retries exhausted. Last error:', lastError);
31
+ throw new Error(`Max retries (${maxRetries}) exceeded. Last error: ${lastError instanceof Error ? lastError.message : String(lastError)}`);
18
32
  };
19
33
 
20
34
  export { runWithRetry };
@@ -0,0 +1,19 @@
1
+ import { StatusBar } from 'react-native';
2
+ import { useEffect } from 'react';
3
+
4
+ /**
5
+ * Configure status bar for white background with dark content for better contrast
6
+ */
7
+ export const configureStatusBarForWhiteBackground = (): void => {
8
+ StatusBar.setBarStyle('dark-content', true);
9
+ StatusBar.setBackgroundColor('#ffffff', true);
10
+ };
11
+
12
+ /**
13
+ * Hook to configure status bar for white background on mount
14
+ */
15
+ export const useStatusBarWhiteBackground = (): void => {
16
+ useEffect(() => {
17
+ configureStatusBarForWhiteBackground();
18
+ }, []);
19
+ };