@sanctum-key/react-native-sdk 1.0.7 → 1.0.8
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/build/package.json +1 -1
- package/build/src/components/EnhancedCameraView.d.ts.map +1 -1
- package/build/src/components/EnhancedCameraView.js +61 -12
- package/build/src/components/EnhancedCameraView.js.map +1 -1
- package/build/src/components/KYCElements/EmailVerificationTemplate.d.ts.map +1 -1
- package/build/src/components/KYCElements/EmailVerificationTemplate.js +32 -11
- package/build/src/components/KYCElements/EmailVerificationTemplate.js.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.js +1 -2
- package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/src/components/KYCElements/PhoneVerificationTemplate.d.ts.map +1 -1
- package/build/src/components/KYCElements/PhoneVerificationTemplate.js +30 -17
- package/build/src/components/KYCElements/PhoneVerificationTemplate.js.map +1 -1
- package/build/src/modules/api/KYCService.d.ts +2 -1
- package/build/src/modules/api/KYCService.d.ts.map +1 -1
- package/build/src/modules/api/KYCService.js +11 -10
- package/build/src/modules/api/KYCService.js.map +1 -1
- package/package.json +1 -1
- package/src/components/EnhancedCameraView.tsx +99 -34
- package/src/components/KYCElements/EmailVerificationTemplate.tsx +36 -10
- package/src/components/KYCElements/IDCardCapture.tsx +1 -1
- package/src/components/KYCElements/PhoneVerificationTemplate.tsx +36 -22
- package/src/modules/api/KYCService.ts +15 -14
|
@@ -38,6 +38,9 @@ export const EmailVerificationTemplate: React.FC<EmailVerificationTemplateProps>
|
|
|
38
38
|
const [otp, setOtp] = useState('');
|
|
39
39
|
const [localError, setLocalError] = useState<string | null>(null);
|
|
40
40
|
const [isSimulating, setIsSimulating] = useState(false);
|
|
41
|
+
|
|
42
|
+
// 🚨 NEW: Track actual focus state for visual feedback
|
|
43
|
+
const [isInputFocused, setIsInputFocused] = useState(false);
|
|
41
44
|
|
|
42
45
|
const inputRef = useRef<TextInput>(null);
|
|
43
46
|
|
|
@@ -56,6 +59,22 @@ export const EmailVerificationTemplate: React.FC<EmailVerificationTemplateProps>
|
|
|
56
59
|
}
|
|
57
60
|
}, [otp]);
|
|
58
61
|
|
|
62
|
+
// --- AUTO FOCUS LOGIC ---
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
let focusTimer: ReturnType<typeof setTimeout>;
|
|
65
|
+
|
|
66
|
+
// Only attempt focus when we are on the OTP step AND the loading state is completely finished
|
|
67
|
+
if (step === 'otp' && !isSimulating) {
|
|
68
|
+
focusTimer = setTimeout(() => {
|
|
69
|
+
inputRef.current?.focus();
|
|
70
|
+
}, 100);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return () => {
|
|
74
|
+
if (focusTimer) clearTimeout(focusTimer);
|
|
75
|
+
};
|
|
76
|
+
}, [step, isSimulating]);
|
|
77
|
+
|
|
59
78
|
const handleSendCode = async () => {
|
|
60
79
|
const trimmed = email.trim();
|
|
61
80
|
if (!trimmed || !isValidEmail(trimmed)) {
|
|
@@ -69,8 +88,7 @@ export const EmailVerificationTemplate: React.FC<EmailVerificationTemplateProps>
|
|
|
69
88
|
try {
|
|
70
89
|
await kycService.sendEmailVerificationCode(trimmed, auth);
|
|
71
90
|
setStep('otp');
|
|
72
|
-
//
|
|
73
|
-
setTimeout(() => inputRef.current?.focus(), 100);
|
|
91
|
+
// Note: Removed the buggy setTimeout from here. The useEffect handles it now!
|
|
74
92
|
} catch (err: any) {
|
|
75
93
|
const msg = errorMessage(err) ?? err?.message ?? (t('errors.sendCodeFailed') || 'Failed to send verification code');
|
|
76
94
|
setLocalError(typeof msg === 'string' ? msg : JSON.stringify(msg));
|
|
@@ -96,8 +114,8 @@ export const EmailVerificationTemplate: React.FC<EmailVerificationTemplateProps>
|
|
|
96
114
|
} catch (err: any) {
|
|
97
115
|
const msg = errorMessage(err) ?? err?.message ?? (t('errors.wrongCode') || 'Invalid verification code');
|
|
98
116
|
setLocalError(typeof msg === 'string' ? msg : JSON.stringify(msg));
|
|
99
|
-
setOtp('');
|
|
100
|
-
inputRef.current?.focus();
|
|
117
|
+
setOtp('');
|
|
118
|
+
inputRef.current?.focus(); // Refocus on error
|
|
101
119
|
} finally {
|
|
102
120
|
setIsSimulating(false);
|
|
103
121
|
}
|
|
@@ -127,16 +145,19 @@ export const EmailVerificationTemplate: React.FC<EmailVerificationTemplateProps>
|
|
|
127
145
|
<Pressable style={styles.otpBoxesContainer} onPress={() => inputRef.current?.focus()}>
|
|
128
146
|
{boxes.map((_, index) => {
|
|
129
147
|
const digit = otp[index] || '';
|
|
130
|
-
const isCurrent = index === otp.length;
|
|
131
148
|
const isFilled = index < otp.length;
|
|
149
|
+
|
|
150
|
+
// 🚨 NEW: Only highlight if the input is ACTUALLY focused.
|
|
151
|
+
const isActiveIndex = index === otp.length || (index === CODE_LENGTH - 1 && otp.length === CODE_LENGTH);
|
|
152
|
+
const isCurrent = isInputFocused && isActiveIndex;
|
|
132
153
|
|
|
133
154
|
return (
|
|
134
155
|
<View
|
|
135
156
|
key={index}
|
|
136
157
|
style={[
|
|
137
158
|
styles.otpBox,
|
|
138
|
-
|
|
139
|
-
|
|
159
|
+
isFilled && styles.otpBoxFilled,
|
|
160
|
+
isCurrent && styles.otpBoxActive // Active overrides filled
|
|
140
161
|
]}
|
|
141
162
|
>
|
|
142
163
|
<Text style={styles.otpBoxText}>{digit}</Text>
|
|
@@ -175,7 +196,7 @@ export const EmailVerificationTemplate: React.FC<EmailVerificationTemplateProps>
|
|
|
175
196
|
|
|
176
197
|
<View style={styles.otpWrapper}>
|
|
177
198
|
{renderOtpBoxes()}
|
|
178
|
-
|
|
199
|
+
|
|
179
200
|
<TextInput
|
|
180
201
|
ref={inputRef}
|
|
181
202
|
style={styles.hiddenInput}
|
|
@@ -186,6 +207,9 @@ export const EmailVerificationTemplate: React.FC<EmailVerificationTemplateProps>
|
|
|
186
207
|
editable={!isSimulating}
|
|
187
208
|
textContentType="oneTimeCode"
|
|
188
209
|
caretHidden={true}
|
|
210
|
+
// 🚨 NEW: Track focus state
|
|
211
|
+
onFocus={() => setIsInputFocused(true)}
|
|
212
|
+
onBlur={() => setIsInputFocused(false)}
|
|
189
213
|
/>
|
|
190
214
|
</View>
|
|
191
215
|
|
|
@@ -221,6 +245,8 @@ export const EmailVerificationTemplate: React.FC<EmailVerificationTemplateProps>
|
|
|
221
245
|
t('common.codeResent') || 'Code Resent',
|
|
222
246
|
t('common.codeResentMessage', { email }) || 'Code resent to ' + email
|
|
223
247
|
);
|
|
248
|
+
// Refocus after resending
|
|
249
|
+
inputRef.current?.focus();
|
|
224
250
|
} catch (err: any) {
|
|
225
251
|
const msg = errorMessage(err) ?? err?.message ?? (t('errors.sendCodeFailed') || 'Failed to send code');
|
|
226
252
|
setLocalError(typeof msg === 'string' ? msg : JSON.stringify(msg));
|
|
@@ -300,7 +326,7 @@ const styles = StyleSheet.create({
|
|
|
300
326
|
},
|
|
301
327
|
otpBox: {
|
|
302
328
|
width: '14%',
|
|
303
|
-
aspectRatio: 1,
|
|
329
|
+
aspectRatio: 1, // keeps it perfectly square
|
|
304
330
|
borderWidth: 1,
|
|
305
331
|
borderColor: '#e0e0e0',
|
|
306
332
|
borderRadius: 12,
|
|
@@ -328,7 +354,7 @@ const styles = StyleSheet.create({
|
|
|
328
354
|
left: 0,
|
|
329
355
|
width: '100%',
|
|
330
356
|
height: '100%',
|
|
331
|
-
opacity: 0,
|
|
357
|
+
opacity: 0, // completely invisible but handles native keyboard interactions perfectly
|
|
332
358
|
},
|
|
333
359
|
errorText: {
|
|
334
360
|
color: '#dc2626',
|
|
@@ -270,7 +270,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
270
270
|
return (
|
|
271
271
|
<View style={styles.cameraContainer}>
|
|
272
272
|
<EnhancedCameraView
|
|
273
|
-
key={currentSide}
|
|
273
|
+
key={currentSide}
|
|
274
274
|
showCamera={true}
|
|
275
275
|
isProcessing={isBusy}
|
|
276
276
|
cameraType={cameraConfig.cameraType}
|
|
@@ -44,8 +44,6 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
44
44
|
|
|
45
45
|
// State
|
|
46
46
|
const [step, setStep] = useState<VerificationStep>('phone');
|
|
47
|
-
|
|
48
|
-
// Split phone state into code and number
|
|
49
47
|
const [countryCode, setCountryCode] = useState('+254');
|
|
50
48
|
const [phone, setPhone] = useState('');
|
|
51
49
|
const [showCountryPicker, setShowCountryPicker] = useState(false);
|
|
@@ -53,6 +51,9 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
53
51
|
const [otp, setOtp] = useState('');
|
|
54
52
|
const [localError, setLocalError] = useState<string | null>(null);
|
|
55
53
|
const [isSimulating, setIsSimulating] = useState(false);
|
|
54
|
+
|
|
55
|
+
// 🚨 NEW: Track actual focus state for visual feedback
|
|
56
|
+
const [isInputFocused, setIsInputFocused] = useState(false);
|
|
56
57
|
|
|
57
58
|
const inputRef = useRef<TextInput>(null);
|
|
58
59
|
|
|
@@ -70,6 +71,21 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
70
71
|
}
|
|
71
72
|
}, [otp]);
|
|
72
73
|
|
|
74
|
+
// --- AUTO FOCUS LOGIC ---
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
let focusTimer: ReturnType<typeof setTimeout>;
|
|
77
|
+
|
|
78
|
+
if (step === 'otp' && !isSimulating) {
|
|
79
|
+
focusTimer = setTimeout(() => {
|
|
80
|
+
inputRef.current?.focus();
|
|
81
|
+
}, 100);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return () => {
|
|
85
|
+
if (focusTimer) clearTimeout(focusTimer);
|
|
86
|
+
};
|
|
87
|
+
}, [step, isSimulating]); // 🚨 Must watch both states
|
|
88
|
+
|
|
73
89
|
const handleSendCode = async () => {
|
|
74
90
|
const trimmedPhone = phone.trim();
|
|
75
91
|
if (!trimmedPhone || trimmedPhone.length < 5) {
|
|
@@ -80,13 +96,11 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
80
96
|
setLocalError(null);
|
|
81
97
|
setIsSimulating(true);
|
|
82
98
|
|
|
83
|
-
// Combine code and number for the API
|
|
84
99
|
const fullPhoneNumber = `${countryCode}${trimmedPhone}`;
|
|
85
100
|
|
|
86
101
|
try {
|
|
87
102
|
await kycService.sendWhatsAppVerificationCode(sessionId, fullPhoneNumber, auth);
|
|
88
103
|
setStep('otp');
|
|
89
|
-
setTimeout(() => inputRef.current?.focus(), 100);
|
|
90
104
|
} catch (err: any) {
|
|
91
105
|
const msg = errorMessage(err) ?? err?.message ?? (t('errors.sendCodeFailed') || 'Failed to send verification code');
|
|
92
106
|
setLocalError(typeof msg === 'string' ? msg : JSON.stringify(msg));
|
|
@@ -116,14 +130,14 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
116
130
|
const msg = errorMessage(err) ?? err?.message ?? (t('errors.wrongCode') || 'Invalid verification code');
|
|
117
131
|
setLocalError(typeof msg === 'string' ? msg : JSON.stringify(msg));
|
|
118
132
|
setOtp('');
|
|
119
|
-
|
|
133
|
+
// Refocus so they can type immediately after an error
|
|
134
|
+
inputRef.current?.focus();
|
|
120
135
|
} finally {
|
|
121
136
|
setIsSimulating(false);
|
|
122
137
|
}
|
|
123
138
|
};
|
|
124
139
|
|
|
125
140
|
const onChangePhone = (text: string) => {
|
|
126
|
-
// Strip out any non-numeric characters the user might type (like spaces or dashes)
|
|
127
141
|
const cleaned = text.replace(/[^0-9]/g, '');
|
|
128
142
|
setPhone(cleaned);
|
|
129
143
|
if (localError) setLocalError(null);
|
|
@@ -147,16 +161,20 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
147
161
|
<Pressable style={styles.otpBoxesContainer} onPress={() => inputRef.current?.focus()}>
|
|
148
162
|
{boxes.map((_, index) => {
|
|
149
163
|
const digit = otp[index] || '';
|
|
150
|
-
const isCurrent = index === otp.length;
|
|
151
164
|
const isFilled = index < otp.length;
|
|
165
|
+
|
|
166
|
+
// 🚨 NEW: Only highlight if the input is ACTUALLY focused.
|
|
167
|
+
// Highlights the next empty box, or the last box if full.
|
|
168
|
+
const isActiveIndex = index === otp.length || (index === CODE_LENGTH - 1 && otp.length === CODE_LENGTH);
|
|
169
|
+
const isCurrent = isInputFocused && isActiveIndex;
|
|
152
170
|
|
|
153
171
|
return (
|
|
154
172
|
<View
|
|
155
173
|
key={index}
|
|
156
174
|
style={[
|
|
157
175
|
styles.otpBox,
|
|
158
|
-
|
|
159
|
-
|
|
176
|
+
isFilled && styles.otpBoxFilled,
|
|
177
|
+
isCurrent && styles.otpBoxActive // Active overrides filled
|
|
160
178
|
]}
|
|
161
179
|
>
|
|
162
180
|
<Text style={styles.otpBoxText}>{digit}</Text>
|
|
@@ -179,7 +197,6 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
179
197
|
<View style={styles.inputContainer}>
|
|
180
198
|
<Text style={styles.label}>{t('common.phone') || 'Phone Number'}</Text>
|
|
181
199
|
|
|
182
|
-
{/* 🚨 UPGRADED PHONE INPUT ROW */}
|
|
183
200
|
<View style={styles.phoneInputRow}>
|
|
184
201
|
<TouchableOpacity
|
|
185
202
|
style={styles.countryPickerBtn}
|
|
@@ -206,7 +223,7 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
206
223
|
<Text style={styles.label}>{t('common.verificationCode') || 'Verification Code'}</Text>
|
|
207
224
|
|
|
208
225
|
<View style={styles.otpWrapper}>
|
|
209
|
-
{renderOtpBoxes()}
|
|
226
|
+
{renderOtpBoxes()}
|
|
210
227
|
<TextInput
|
|
211
228
|
ref={inputRef}
|
|
212
229
|
style={styles.hiddenInput}
|
|
@@ -217,6 +234,8 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
217
234
|
editable={!isSimulating}
|
|
218
235
|
textContentType="oneTimeCode"
|
|
219
236
|
caretHidden={true}
|
|
237
|
+
onFocus={() => setIsInputFocused(true)}
|
|
238
|
+
onBlur={() => setIsInputFocused(false)}
|
|
220
239
|
/>
|
|
221
240
|
</View>
|
|
222
241
|
|
|
@@ -253,6 +272,8 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
253
272
|
t('common.codeResent') || 'Code Resent',
|
|
254
273
|
t('common.codeResentMessage', { email: fullPhoneNumber }) || 'Code resent to ' + fullPhoneNumber
|
|
255
274
|
);
|
|
275
|
+
// Refocus after resending
|
|
276
|
+
inputRef.current?.focus();
|
|
256
277
|
} catch (err: any) {
|
|
257
278
|
const msg = errorMessage(err) ?? err?.message ?? (t('errors.sendCodeFailed') || 'Failed to send code');
|
|
258
279
|
setLocalError(typeof msg === 'string' ? msg : JSON.stringify(msg));
|
|
@@ -268,7 +289,7 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
268
289
|
)}
|
|
269
290
|
</View>
|
|
270
291
|
|
|
271
|
-
{/*
|
|
292
|
+
{/* COUNTRY PICKER MODAL */}
|
|
272
293
|
<Modal
|
|
273
294
|
visible={showCountryPicker}
|
|
274
295
|
animationType="slide"
|
|
@@ -311,6 +332,7 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
311
332
|
};
|
|
312
333
|
|
|
313
334
|
const styles = StyleSheet.create({
|
|
335
|
+
// ... Keeping all previous styles identical for safety ...
|
|
314
336
|
container: {
|
|
315
337
|
padding: 24,
|
|
316
338
|
backgroundColor: 'white',
|
|
@@ -348,12 +370,10 @@ const styles = StyleSheet.create({
|
|
|
348
370
|
marginBottom: 8,
|
|
349
371
|
marginLeft: 4,
|
|
350
372
|
},
|
|
351
|
-
|
|
352
|
-
// --- Phone Row Styles ---
|
|
353
373
|
phoneInputRow: {
|
|
354
374
|
flexDirection: 'row',
|
|
355
375
|
alignItems: 'center',
|
|
356
|
-
gap: 10,
|
|
376
|
+
gap: 10,
|
|
357
377
|
},
|
|
358
378
|
countryPickerBtn: {
|
|
359
379
|
flexDirection: 'row',
|
|
@@ -378,7 +398,7 @@ const styles = StyleSheet.create({
|
|
|
378
398
|
marginLeft: 8,
|
|
379
399
|
},
|
|
380
400
|
phoneInput: {
|
|
381
|
-
flex: 1,
|
|
401
|
+
flex: 1,
|
|
382
402
|
borderWidth: 1,
|
|
383
403
|
borderColor: '#e0e0e0',
|
|
384
404
|
padding: 16,
|
|
@@ -387,8 +407,6 @@ const styles = StyleSheet.create({
|
|
|
387
407
|
backgroundColor: '#f8f9fa',
|
|
388
408
|
color: '#333',
|
|
389
409
|
},
|
|
390
|
-
|
|
391
|
-
// --- Country Modal Styles ---
|
|
392
410
|
modalOverlay: {
|
|
393
411
|
flex: 1,
|
|
394
412
|
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
@@ -437,8 +455,6 @@ const styles = StyleSheet.create({
|
|
|
437
455
|
fontSize: 18,
|
|
438
456
|
fontWeight: 'bold',
|
|
439
457
|
},
|
|
440
|
-
|
|
441
|
-
// --- OTP Styles ---
|
|
442
458
|
otpWrapper: {
|
|
443
459
|
position: 'relative',
|
|
444
460
|
width: '100%',
|
|
@@ -483,8 +499,6 @@ const styles = StyleSheet.create({
|
|
|
483
499
|
height: '100%',
|
|
484
500
|
opacity: 0,
|
|
485
501
|
},
|
|
486
|
-
|
|
487
|
-
// --- General Styles ---
|
|
488
502
|
errorText: {
|
|
489
503
|
color: '#dc2626',
|
|
490
504
|
marginBottom: 16,
|
|
@@ -620,7 +620,7 @@ export class KYCService {
|
|
|
620
620
|
}
|
|
621
621
|
}
|
|
622
622
|
|
|
623
|
-
/** Send WhatsApp verification code.
|
|
623
|
+
/** Send WhatsApp verification code. */
|
|
624
624
|
async sendWhatsAppVerificationCode(
|
|
625
625
|
sessionId: string,
|
|
626
626
|
phoneNumber: string,
|
|
@@ -629,15 +629,19 @@ export class KYCService {
|
|
|
629
629
|
const url = `${KYCConfig.getBackendUrl()}/accounts/send-whatsapp-verification/`;
|
|
630
630
|
const token = auth?.apiKey ? undefined : (auth?.token ?? await authentification());
|
|
631
631
|
|
|
632
|
+
// 🚨 FORMATTING FIX: Strips the "+" sign (e.g., "+254712345" becomes "254712345")
|
|
633
|
+
const formattedPhone = phoneNumber.replace(/[^0-9]/g, '');
|
|
634
|
+
|
|
632
635
|
const res = await axios.post(
|
|
633
636
|
url,
|
|
634
637
|
{
|
|
635
638
|
session_id: sessionId,
|
|
636
|
-
phone_number:
|
|
639
|
+
phone_number: formattedPhone
|
|
637
640
|
},
|
|
638
641
|
{
|
|
639
642
|
headers: {
|
|
640
643
|
'Content-Type': 'application/json',
|
|
644
|
+
'Referer': KYCConfig.getBackendUrl(), // CSRF Protection
|
|
641
645
|
...(auth?.apiKey ? { 'Authorization': `ApiKey ${auth.apiKey}` } : { 'Authorization': `Bearer ${token}` }),
|
|
642
646
|
},
|
|
643
647
|
}
|
|
@@ -645,31 +649,30 @@ export class KYCService {
|
|
|
645
649
|
return res.data;
|
|
646
650
|
}
|
|
647
651
|
|
|
652
|
+
/** Verify WhatsApp code with OTP. */
|
|
648
653
|
async verifyWhatsAppCode(
|
|
649
654
|
sessionId: string,
|
|
650
655
|
otp: string,
|
|
651
656
|
phoneNumber: string,
|
|
652
657
|
auth?: { apiKey?: string; token?: string }
|
|
653
658
|
): Promise<unknown> {
|
|
654
|
-
|
|
655
|
-
const url = `${KYCConfig.getBackendUrl()}/accounts/verify-whatsapp-verification/`;
|
|
659
|
+
const url = `${KYCConfig.getBackendUrl()}/accounts/verify-whatsapp-code/`;
|
|
656
660
|
const token = auth?.apiKey ? undefined : (auth?.token ?? await authentification());
|
|
657
661
|
|
|
662
|
+
const formattedPhone = phoneNumber.replace(/[^0-9]/g, '');
|
|
663
|
+
const cleanOtp = otp.replace(/[^0-9]/g, '');
|
|
664
|
+
|
|
658
665
|
try {
|
|
659
666
|
const res = await axios.post(
|
|
660
667
|
url,
|
|
661
668
|
{
|
|
662
669
|
session_id: sessionId,
|
|
663
|
-
|
|
664
|
-
|
|
670
|
+
phone_number: formattedPhone,
|
|
671
|
+
verification_code: cleanOtp
|
|
665
672
|
},
|
|
666
673
|
{
|
|
667
674
|
headers: {
|
|
668
|
-
'Content-Type': 'application/json',
|
|
669
|
-
|
|
670
|
-
// 🚨 THE FIX: Spoof the Referer header for Django's CSRF middleware
|
|
671
|
-
'Referer': KYCConfig.getBackendUrl(),
|
|
672
|
-
|
|
675
|
+
'Content-Type': 'application/json',
|
|
673
676
|
...(auth?.apiKey ? { 'Authorization': `ApiKey ${auth.apiKey}` } : { 'Authorization': `Bearer ${token}` }),
|
|
674
677
|
},
|
|
675
678
|
}
|
|
@@ -724,8 +727,7 @@ export const authentification = async (): Promise<string> => {
|
|
|
724
727
|
activeAuthRequest = (async () => {
|
|
725
728
|
logger.log('Authenticating: Fetching new Keycloak token...');
|
|
726
729
|
|
|
727
|
-
|
|
728
|
-
// Use a raw URL-encoded string to guarantee Keycloak understands the payload.
|
|
730
|
+
|
|
729
731
|
const params = 'client_id=kyc_frontend&client_secret=QhgAmvKgmwODzsEp98dnA4PeUEMMaFHd&grant_type=client_credentials';
|
|
730
732
|
|
|
731
733
|
const res = await axios.post(
|
|
@@ -757,7 +759,6 @@ export const authentification = async (): Promise<string> => {
|
|
|
757
759
|
} catch (error: any) {
|
|
758
760
|
cachedToken = null; // Clear bad cache
|
|
759
761
|
|
|
760
|
-
// 🚨 FIX: Extract Keycloak's exact error message so it doesn't log as {}
|
|
761
762
|
const safeErrorMessage =
|
|
762
763
|
error?.response?.data?.error_description ||
|
|
763
764
|
error?.response?.data?.error ||
|