@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.
- package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +1 -12
- package/android/src/main/java/com/trustchex/reactnativesdk/mlkit/MLKitModule.kt +1 -1
- package/ios/Camera/TrustchexCameraView.swift +1 -12
- package/ios/MLKit/MLKitModule.swift +1 -1
- package/lib/module/Screens/Debug/BarcodeTestScreen.js +308 -0
- package/lib/module/Screens/Debug/MRZTestScreen.js +105 -13
- package/lib/module/Screens/Dynamic/ContractAcceptanceScreen.js +49 -29
- package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +5 -0
- package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +5 -0
- package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +26 -6
- package/lib/module/Screens/Dynamic/VideoCallScreen.js +676 -0
- package/lib/module/Screens/Static/OTPVerificationScreen.js +6 -0
- package/lib/module/Screens/Static/QrCodeScanningScreen.js +7 -1
- package/lib/module/Screens/Static/ResultScreen.js +27 -13
- package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +51 -51
- package/lib/module/Shared/Animations/video-call.json +1 -0
- package/lib/module/Shared/Components/DebugNavigationPanel.js +180 -14
- package/lib/module/Shared/Components/EIDScanner.js +1 -4
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +29 -8
- package/lib/module/Shared/Components/NavigationManager.js +15 -3
- package/lib/module/Shared/Contexts/AppContext.js +1 -0
- package/lib/module/Shared/Libs/SignalingClient.js +128 -0
- package/lib/module/Shared/Libs/analytics.utils.js +4 -0
- package/lib/module/Shared/Libs/deeplink.utils.js +9 -1
- package/lib/module/Shared/Libs/http-client.js +9 -0
- package/lib/module/Shared/Libs/promise.utils.js +16 -2
- package/lib/module/Shared/Libs/status-bar.utils.js +21 -0
- package/lib/module/Shared/Services/DataUploadService.js +294 -0
- package/lib/module/Shared/Services/VideoSessionService.js +156 -0
- package/lib/module/Shared/Services/WebRTCService.js +510 -0
- package/lib/module/Shared/Types/analytics.types.js +2 -0
- package/lib/module/Translation/Resources/en.js +20 -0
- package/lib/module/Translation/Resources/tr.js +20 -0
- package/lib/module/Trustchex.js +10 -0
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/Screens/Debug/BarcodeTestScreen.d.ts +3 -0
- package/lib/typescript/src/Screens/Debug/BarcodeTestScreen.d.ts.map +1 -0
- package/lib/typescript/src/Screens/Debug/MRZTestScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/ContractAcceptanceScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts +3 -0
- package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts.map +1 -0
- package/lib/typescript/src/Screens/Static/OTPVerificationScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/QrCodeScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Contexts/AppContext.d.ts +1 -0
- package/lib/typescript/src/Shared/Contexts/AppContext.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/SignalingClient.d.ts +24 -0
- package/lib/typescript/src/Shared/Libs/SignalingClient.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/promise.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/status-bar.utils.d.ts +9 -0
- package/lib/typescript/src/Shared/Libs/status-bar.utils.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Services/DataUploadService.d.ts +25 -0
- package/lib/typescript/src/Shared/Services/DataUploadService.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Services/VideoSessionService.d.ts +33 -0
- package/lib/typescript/src/Shared/Services/VideoSessionService.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Services/WebRTCService.d.ts +58 -0
- package/lib/typescript/src/Shared/Services/WebRTCService.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Types/analytics.types.d.ts +2 -0
- package/lib/typescript/src/Shared/Types/analytics.types.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Types/identificationInfo.d.ts +4 -1
- package/lib/typescript/src/Shared/Types/identificationInfo.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/en.d.ts +20 -0
- package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts +20 -0
- package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
- package/lib/typescript/src/Trustchex.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +29 -2
- package/src/Screens/Debug/BarcodeTestScreen.tsx +317 -0
- package/src/Screens/Debug/MRZTestScreen.tsx +107 -13
- package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +59 -33
- package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +6 -0
- package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +6 -0
- package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +34 -6
- package/src/Screens/Dynamic/VideoCallScreen.tsx +764 -0
- package/src/Screens/Static/OTPVerificationScreen.tsx +6 -0
- package/src/Screens/Static/QrCodeScanningScreen.tsx +7 -1
- package/src/Screens/Static/ResultScreen.tsx +58 -23
- package/src/Screens/Static/VerificationSessionCheckScreen.tsx +58 -72
- package/src/Shared/Animations/video-call.json +1 -0
- package/src/Shared/Components/DebugNavigationPanel.tsx +185 -9
- package/src/Shared/Components/EIDScanner.tsx +1 -5
- package/src/Shared/Components/IdentityDocumentCamera.tsx +29 -8
- package/src/Shared/Components/NavigationManager.tsx +14 -1
- package/src/Shared/Contexts/AppContext.ts +2 -0
- package/src/Shared/Libs/SignalingClient.ts +189 -0
- package/src/Shared/Libs/analytics.utils.ts +4 -0
- package/src/Shared/Libs/deeplink.utils.ts +12 -1
- package/src/Shared/Libs/http-client.ts +10 -0
- package/src/Shared/Libs/promise.utils.ts +16 -2
- package/src/Shared/Libs/status-bar.utils.ts +19 -0
- package/src/Shared/Services/DataUploadService.ts +395 -0
- package/src/Shared/Services/VideoSessionService.ts +190 -0
- package/src/Shared/Services/WebRTCService.ts +636 -0
- package/src/Shared/Types/analytics.types.ts +2 -0
- package/src/Shared/Types/identificationInfo.ts +5 -1
- package/src/Translation/Resources/en.ts +25 -0
- package/src/Translation/Resources/tr.ts +27 -0
- package/src/Trustchex.tsx +12 -2
- 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
|
|
326
|
-
|
|
327
|
-
|
|
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=
|
|
2265
|
-
backgroundColor=
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
};
|