@sanctum-key/react-native-sdk 1.0.47 → 1.0.48

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 (26) hide show
  1. package/build/package.json +1 -1
  2. package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  3. package/build/src/components/KYCElements/IDCardCapture.js +84 -23
  4. package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
  5. package/build/src/components/KYCElements/VerificationProgressTemplate.d.ts.map +1 -1
  6. package/build/src/components/KYCElements/VerificationProgressTemplate.js +88 -41
  7. package/build/src/components/KYCElements/VerificationProgressTemplate.js.map +1 -1
  8. package/build/src/hooks/useTemplateKYCFlow.d.ts.map +1 -1
  9. package/build/src/hooks/useTemplateKYCFlow.js +15 -1
  10. package/build/src/hooks/useTemplateKYCFlow.js.map +1 -1
  11. package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
  12. package/build/src/modules/api/CardAuthentification.js +37 -59
  13. package/build/src/modules/api/CardAuthentification.js.map +1 -1
  14. package/build/src/modules/api/KYCService.d.ts.map +1 -1
  15. package/build/src/modules/api/KYCService.js +7 -2
  16. package/build/src/modules/api/KYCService.js.map +1 -1
  17. package/build/src/types/KYC.types.d.ts +1 -0
  18. package/build/src/types/KYC.types.d.ts.map +1 -1
  19. package/build/src/types/KYC.types.js.map +1 -1
  20. package/package.json +1 -1
  21. package/src/components/KYCElements/IDCardCapture.tsx +98 -24
  22. package/src/components/KYCElements/VerificationProgressTemplate.tsx +91 -44
  23. package/src/hooks/useTemplateKYCFlow.tsx +19 -1
  24. package/src/modules/api/CardAuthentification.ts +205 -238
  25. package/src/modules/api/KYCService.ts +9 -2
  26. package/src/types/KYC.types.ts +1 -0
@@ -23,173 +23,133 @@ interface ApiVerificationResponse {
23
23
  }
24
24
 
25
25
  export async function frontVerification(
26
- result: {
27
- path?: string,
28
- regionMapping: { authMethod: string[], mrzTypes: string[] },
29
- selectedDocumentType: string,
30
- code: string,
31
- currentSide: string,
32
- templatePath?: string,
33
- mrzType?: string,
34
- issuingSubdivision?: string,
35
- clientSideBarcodeData?: string // 🚨 ADDED: Accept the SDK barcode data
36
- },
26
+ result: { path?: string, regionMapping: { authMethod: string[], mrzTypes: string[] }, selectedDocumentType: string, code: string, currentSide: string, templatePath?: string, mrzType?: string, issuingSubdivision?: string, clientSideBarcodeData?: string },
37
27
  env: KycEnvironment = 'PRODUCTION'
38
- ) {
28
+ ) {
39
29
  try {
40
- console.log("Front verification START", JSON.stringify({ result }, null, 2));
41
- logger.log("Front verification", JSON.stringify({ result }, null, 2));
42
-
43
- const authMethods = Array.isArray(result.regionMapping?.authMethod)
44
- ? result.regionMapping.authMethod.map(m => String(m).toUpperCase())
45
- : [];
46
-
47
- const hasMrz = authMethods.some(m => m.includes('MRZ'));
48
- const hasBarcode = authMethods.some(m => m.includes('BARCODE') || m.includes('2D'));
49
-
50
- if (env === 'SANDBOX') {
51
- console.log("SANDBOX mode: Skipping AI verification for front document");
52
- const mockBbox: IBbox = { minX: 400, minY: 800, width: 2200, height: 1400 };
53
- const mockResponse = {
54
- result: true,
55
- detail: [{ confidence: 0.95 }],
56
- card_obb: [{
57
- obb: [[400, 800], [2600, 800], [2600, 2200], [400, 2200]],
58
- confidence: 0.95,
59
- card_in_frame: true,
60
- cropped_sides: []
61
- }],
62
- bbox: mockBbox,
63
- ...(hasMrz ? {
64
- mrz: {
65
- success: true,
66
- parsed_data: { status: 'SUCCESS', document_type: result.selectedDocumentType, mrz_type: result.mrzType || 'TD1' }
67
- }
68
- } : {}),
69
- ...(hasBarcode ? {
70
- barcode: { barcode_data: 'SANDBOX_MOCK_BARCODE' }
71
- } : {})
72
- };
73
- return mockResponse;
74
- }
75
-
76
- const token = await authentification();
77
- const detected = await kycService.detectFaceOnId(
78
- result?.path || '', token, result?.selectedDocumentType || '', env
79
- ) as ApiVerificationResponse;
80
-
81
- if (!detected.result) {
82
- throw new Error('Aucun visage détecté sur la carte. Veuillez reprendre.');
83
- }
84
-
85
- const cardData = detected.card_obb && Array.isArray(detected.card_obb) && detected.card_obb.length > 0
86
- ? detected.card_obb[0]
87
- : null;
88
-
89
- let points: number[][] | null = null;
90
- console.log(points);
91
- let isCardInFrame = true;
92
- let hasCroppedSides = false;
93
-
94
- if (cardData) {
95
- points = cardData.obb;
96
- if (typeof cardData.card_in_frame !== 'undefined') {
97
- isCardInFrame = cardData.card_in_frame === true;
98
- hasCroppedSides = Array.isArray(cardData.cropped_sides) && cardData.cropped_sides.length > 0;
99
- }
100
- }
101
-
102
- if (!isCardInFrame || hasCroppedSides) {
103
- logger.log(`Front Framing failed. Cropped sides detected by backend.`);
104
- throw new Error('CARD_NOT_FULLY_IN_FRAME');
105
- }
106
-
107
- const obbConfidence = getObbConfidence(detected.card_obb);
108
- if (obbConfidence !== null && obbConfidence < OBB_CONFIDENCE_THRESHOLD) {
109
- throw new Error('Carte non entièrement visible. Positionnez toute la carte dans le cadre.');
30
+ console.log("Front verification START", JSON.stringify({ result }, null, 2));
31
+ logger.log("Front verification", JSON.stringify({ result }, null, 2));
32
+
33
+ const authMethods = Array.isArray(result.regionMapping?.authMethod) ? result.regionMapping.authMethod.map(m => String(m).toUpperCase()) : [];
34
+ const hasMrz = authMethods.some(m => m.includes('MRZ'));
35
+ const hasBarcode = authMethods.some(m => m.includes('BARCODE') || m.includes('2D'));
36
+
37
+ if (env === 'SANDBOX') {
38
+ console.log("SANDBOX mode: Skipping AI verification for front document");
39
+ const mockBbox: IBbox = { minX: 400, minY: 800, width: 2200, height: 1400 };
40
+ const mockResponse = {
41
+ result: true, detail: [{ confidence: 0.95 }],
42
+ card_obb: [{ obb: [[400, 800], [2600, 800], [2600, 2200], [400, 2200]], confidence: 0.95, card_in_frame: true, cropped_sides: [] }],
43
+ bbox: mockBbox,
44
+ ...(hasMrz ? { mrz: { success: true, parsed_data: { status: 'SUCCESS', document_type: result.selectedDocumentType, mrz_type: result.mrzType || 'TD1' } } } : {}),
45
+ ...(hasBarcode ? { barcode: { barcode_data: 'SANDBOX_MOCK_BARCODE' } } : {})
46
+ };
47
+ return mockResponse;
48
+ }
49
+
50
+ const token = await authentification();
51
+ const detected = await kycService.detectFaceOnId(
52
+ result?.path || '', token, result?.selectedDocumentType || '', env
53
+ ) as ApiVerificationResponse;
54
+
55
+ if (!detected.result) {
56
+ throw new Error('Aucun visage détecté sur la carte. Veuillez reprendre.');
57
+ }
58
+
59
+ const cardData = detected.card_obb && Array.isArray(detected.card_obb) && detected.card_obb.length > 0 ? detected.card_obb[0] : null;
60
+ let points: number[][] | null = null;
61
+ console.log(points)
62
+ let isCardInFrame = true;
63
+ let hasCroppedSides = false;
64
+
65
+ if (cardData) {
66
+ points = cardData.obb;
67
+ if (typeof cardData.card_in_frame !== 'undefined') {
68
+ isCardInFrame = cardData.card_in_frame === true;
69
+ hasCroppedSides = Array.isArray(cardData.cropped_sides) && cardData.cropped_sides.length > 0;
110
70
  }
111
-
112
- let croppedBase64: string | undefined;
113
- let bbox: IBbox | undefined;
114
- let mrz: any | undefined;
115
- let barcode: any | undefined;
116
- let documentData: any | undefined;
117
-
71
+ }
72
+
73
+ if (!isCardInFrame || hasCroppedSides) {
74
+ logger.log(`Front Framing failed. Cropped sides detected by backend.`);
75
+ throw new Error('CARD_NOT_FULLY_IN_FRAME');
76
+ }
77
+
78
+ const obbConfidence = getObbConfidence(detected.card_obb);
79
+ if (obbConfidence !== null && obbConfidence < OBB_CONFIDENCE_THRESHOLD) {
80
+ throw new Error('Carte non entièrement visible. Positionnez toute la carte dans le cadre.');
81
+ }
82
+
83
+ let croppedBase64: string | undefined;
84
+ let bbox: IBbox | undefined;
85
+ let mrz: any | undefined;
86
+ let barcode: any | undefined;
87
+ let documentData: any | undefined;
88
+
89
+ try {
90
+ const crop = await cropByObb(result?.path || '', detected.card_obb);
91
+ croppedBase64 = crop.base64;
92
+ bbox = crop.bbox;
93
+ } catch { }
94
+
95
+ if (result.code === 'CA') {
118
96
  try {
119
- const crop = await cropByObb(result?.path || '', detected.card_obb);
120
- croppedBase64 = crop.base64;
121
- bbox = crop.bbox;
122
- } catch { }
123
-
124
- if (result.code === 'CA') {
125
- try {
126
- logger.log("Tentative d'extraction OCR (Front) - Canada Only");
127
- documentData = await kycService.extractDocumentInformation({
128
- fileUri: result.path || '',
129
- docType: result.selectedDocumentType,
130
- docRegion: result.code,
131
- templatePath: result.templatePath || '',
132
- issuingSubdivision: result.issuingSubdivision,
133
- token: token,
134
- postfix: result.currentSide || 'front'
135
- });
136
- if (documentData?.no_data || (documentData && documentData.data && Object.keys(documentData.data).length === 0)) {
137
- logger.log("Front OCR Extraction Failed: Backend returned empty data dict.");
138
- if (!hasBarcode && !hasMrz) {
139
- throw new Error("Impossible de lire les informations du document. L'image est peut-être floue ou mal éclairée.");
140
- }
141
- }
142
- } catch (err: any) {
143
- const errorMsg = typeof err === 'object' ? JSON.stringify(err) : err;
144
- logger.log("Front OCR Extraction Error:", errorMsg);
145
- throw new Error(err?.message || "Impossible d'extraire les données. Veuillez reprendre la photo.");
97
+ logger.log("Tentative d'extraction OCR (Front) - Canada Only");
98
+ documentData = await kycService.extractDocumentInformation({
99
+ fileUri: result.path || '', docType: result.selectedDocumentType, docRegion: result.code, templatePath: result.templatePath || '', issuingSubdivision: result.issuingSubdivision, token: token, postfix: result.currentSide || 'front'
100
+ });
101
+
102
+ if (documentData?.no_data || (documentData && documentData.data && Object.keys(documentData.data).length === 0)) {
103
+ logger.log("Front OCR Extraction Failed: Backend returned empty data dict.");
104
+ if (!hasBarcode && !hasMrz) {
105
+ throw new Error("Impossible de lire les informations du document. L'image est peut-être floue ou mal éclairée.");
146
106
  }
107
+ }
108
+ } catch (err: any) {
109
+ // 🚨 FIX: Allow 401/403 errors to bubble up directly!
110
+ if (err?.response?.status === 401 || err?.response?.status === 403 || err?.status === 401) throw err;
111
+
112
+ const errorMsg = typeof err === 'object' ? JSON.stringify(err) : err;
113
+ logger.log("Front OCR Extraction Error:", errorMsg);
114
+ throw new Error(err?.message || "Impossible d'extraire les données. Veuillez reprendre la photo.");
147
115
  }
148
-
149
- if (hasMrz) {
150
- try {
151
- logger.log("Tentative d'extraction MRZ (Front)");
152
- const mrzResponse = await kycService.extractMrzText(
153
- {
154
- fileUri: result.path || '',
155
- docType: result?.selectedDocumentType || '',
156
- docRegion: result?.code || "",
157
- postfix: result?.currentSide || 'front',
158
- token: token,
159
- template_path: result?.templatePath || '',
160
- mrz_type: result?.mrzType || 'TD1'
161
- },
162
- env
163
- );
164
-
165
- mrz = typeof mrzResponse === 'string' ? JSON.parse(mrzResponse) : mrzResponse;
166
-
167
- if (mrz?.parsed_data?.status === 'FAILURE' || mrz?.success === false) {
168
- logger.log("Front MRZ Extraction Failed:", mrz?.parsed_data?.status_message);
169
- throw new Error(`MRZ illisible: ${mrz?.parsed_data?.status_message || 'Veuillez nettoyer l\'objectif et réessayer'}`);
170
- }
171
- } catch (err: any) {
172
- logger.log("MRZ Extraction Error:", err);
173
- throw new Error(err?.message || "Erreur lors de l'extraction MRZ. Veuillez reprendre la photo.");
174
- }
116
+ }
117
+
118
+ if (hasMrz) {
119
+ try {
120
+ logger.log("Tentative d'extraction MRZ (Front)");
121
+ const mrzResponse = await kycService.extractMrzText(
122
+ { fileUri: result.path || '', docType: result?.selectedDocumentType || '', docRegion: result?.code || "", postfix: result?.currentSide || 'front', token: token, template_path: result?.templatePath || '', mrz_type: result?.mrzType || 'TD1' }, env
123
+ );
124
+ mrz = typeof mrzResponse === 'string' ? JSON.parse(mrzResponse) : mrzResponse;
125
+
126
+ if (mrz?.parsed_data?.status === 'FAILURE' || mrz?.success === false) {
127
+ logger.log("Front MRZ Extraction Failed:", mrz?.parsed_data?.status_message);
128
+ throw new Error(`MRZ illisible: ${mrz?.parsed_data?.status_message || 'Veuillez nettoyer l\'objectif et réessayer'}`);
129
+ }
130
+ } catch (err: any) {
131
+ // 🚨 FIX: Allow 401/403 errors to bubble up directly!
132
+ if (err?.response?.status === 401 || err?.response?.status === 403 || err?.status === 401) throw err;
133
+
134
+ logger.log("MRZ Extraction Error:", err);
135
+ throw new Error(err?.message || "Erreur lors de l'extraction MRZ. Veuillez reprendre la photo.");
175
136
  }
176
-
177
-
178
-
179
- return {
180
- ...detected,
181
- croppedBase64,
182
- bbox,
183
- ...(mrz ? { mrz } : {}),
184
- ...(barcode ? { barcode } : {}),
185
- ...(documentData ? { documentData } : {})
186
- };
187
-
137
+ }
138
+
139
+ return { ...detected, croppedBase64, bbox, ...(mrz ? { mrz } : {}), ...(barcode ? { barcode } : {}), ...(documentData ? { documentData } : {}) };
140
+
188
141
  } catch (e: any) {
189
- logger.log('Error front verification:', e?.message);
190
- throw new Error(e?.message || 'Erreur de détection du visage');
142
+ logger.log('Error front verification:', e?.message);
143
+
144
+ // 🚨 FINAL FIX: If the root catch block receives an API error with a status code,
145
+ // we must throw the RAW object so `handleSilentCapture` can read `e.response.status`.
146
+ if (e?.response?.status || e?.status || e?.isAxiosError) {
147
+ throw e;
148
+ }
149
+
150
+ throw new Error(e?.message || 'Erreur de détection du visage');
191
151
  }
192
- }
152
+ }
193
153
 
194
154
  export async function backVerification(
195
155
  result: {
@@ -428,88 +388,95 @@ export async function backVerification(
428
388
  }
429
389
 
430
390
  export async function checkTemplateType(
431
- result: {
432
- path?: string,
433
- docType: string,
434
- docRegion: string,
435
- postfix: string,
436
- issuingSubdivision?: string
437
- },
391
+ result: { path?: string, docType: string, docRegion: string, postfix: string, issuingSubdivision?: string },
438
392
  env: KycEnvironment = 'PRODUCTION'
439
- ) {
393
+ ) {
440
394
  try {
441
- if (env === 'SANDBOX') {
442
- return {
443
- template_path: `templates/${result.docType}_${result.docRegion}_${result.postfix}.jpg`,
444
- card_obb: [{
445
- obb: [[400, 800], [2600, 800], [2600, 2200], [400, 2200]],
446
- confidence: 0.95,
447
- card_in_frame: true,
448
- cropped_sides: []
449
- }]
450
- };
451
- }
452
-
453
- const token = await authentification();
454
- const isCanada = result.docRegion?.toUpperCase() === 'CA';
455
-
456
- const templateType = await kycService.checkTemplateType({
457
- fileUri: result.path || '',
458
- docType: result?.docType as GovernmentDocumentType,
459
- docRegion: result?.docRegion || "",
460
- postfix: result?.postfix,
461
- token: token,
462
- issuingSubdivision: isCanada ? result.issuingSubdivision : undefined
463
- }, env);
464
-
465
- if (templateType.success === false || templateType.Error) {
466
- logger.log("Backend returned an error:", templateType.Error);
467
- throw new Error('Impossible de lire le document. Veuillez vous rapprocher et stabiliser la caméra.');
468
- }
469
-
470
- const cardData = templateType.card_obb && Array.isArray(templateType.card_obb) && templateType.card_obb.length > 0
471
- ? templateType.card_obb[0]
472
- : null;
473
-
474
- if (!cardData) throw new Error('CARD_NOT_FULLY_IN_FRAME');
475
-
476
- let points: number[][] | null = null;
477
- console.log(points)
478
- let isCardInFrame = false;
479
- let hasCroppedSides = true;
480
-
481
- if (cardData.obb) {
482
- points = cardData.obb;
483
- if (typeof cardData.card_in_frame !== 'undefined') {
484
- isCardInFrame = cardData.card_in_frame === true;
485
- hasCroppedSides = Array.isArray(cardData.cropped_sides) && cardData.cropped_sides.length > 0;
486
- }
487
- } else if (Array.isArray(cardData) && Array.isArray(cardData[0])) {
488
- points = cardData[0];
489
- isCardInFrame = true;
490
- hasCroppedSides = false;
491
- }
492
-
493
- if (!isCardInFrame || hasCroppedSides) {
494
- logger.log(`Template Framing failed by backend check.`);
495
- throw new Error('CARD_NOT_FULLY_IN_FRAME');
496
- }
497
-
498
- const LPIPS_THRESHOLD = 0.75;
499
- if (templateType.lpips_score !== undefined && templateType.lpips_score > LPIPS_THRESHOLD) {
500
- logger.log(`Country Mismatch! LPIPS: ${templateType.lpips_score}`);
501
- throw new Error(`Le document présenté ne correspond pas au pays sélectionné (${result.docRegion}).`);
395
+ if (env === 'SANDBOX') {
396
+ return {
397
+ template_path: `templates/${result.docType}_${result.docRegion}_${result.postfix}.jpg`,
398
+ card_obb: [{ obb: [[400, 800], [2600, 800], [2600, 2200], [400, 2200]], confidence: 0.95, card_in_frame: true, cropped_sides: [] }]
399
+ };
400
+ }
401
+
402
+ const token = await authentification();
403
+ const isCanada = result.docRegion?.toUpperCase() === 'CA';
404
+
405
+ const templateType = await kycService.checkTemplateType({
406
+ fileUri: result.path || '',
407
+ docType: result?.docType as GovernmentDocumentType,
408
+ docRegion: result?.docRegion || "",
409
+ postfix: result?.postfix,
410
+ token: token,
411
+ issuingSubdivision: isCanada ? result.issuingSubdivision : undefined
412
+ }, env);
413
+
414
+ if (templateType.success === false || templateType.Error) {
415
+ logger.log("Backend returned an error:", templateType.Error);
416
+ throw new Error('Impossible de lire le document. Veuillez vous rapprocher et stabiliser la caméra.');
417
+ }
418
+
419
+ const cardData = templateType.card_obb && Array.isArray(templateType.card_obb) && templateType.card_obb.length > 0 ? templateType.card_obb[0] : null;
420
+
421
+ if (!cardData) throw new Error('CARD_NOT_FULLY_IN_FRAME');
422
+
423
+ let points: number[][] | null = null;
424
+ console.log(points);
425
+ let isCardInFrame = false;
426
+ let hasCroppedSides = true;
427
+
428
+ if (cardData.obb) {
429
+ points = cardData.obb;
430
+ if (typeof cardData.card_in_frame !== 'undefined') {
431
+ isCardInFrame = cardData.card_in_frame === true;
432
+ hasCroppedSides = Array.isArray(cardData.cropped_sides) && cardData.cropped_sides.length > 0;
502
433
  }
503
-
504
- logger.log("templateType result", JSON.stringify(truncateFields(templateType), null, 2));
505
- return templateType;
506
-
434
+ } else if (Array.isArray(cardData) && Array.isArray(cardData[0])) {
435
+ points = cardData[0];
436
+ isCardInFrame = true;
437
+ hasCroppedSides = false;
438
+ }
439
+
440
+ if (!isCardInFrame || hasCroppedSides) {
441
+ logger.log(`Template Framing failed by backend check.`);
442
+ throw new Error('CARD_NOT_FULLY_IN_FRAME');
443
+ }
444
+
445
+ const LPIPS_THRESHOLD = 0.75;
446
+ if (templateType.lpips_score !== undefined && templateType.lpips_score > LPIPS_THRESHOLD) {
447
+ logger.log(`Country Mismatch! LPIPS: ${templateType.lpips_score}`);
448
+ throw new Error(`Le document présenté ne correspond pas au pays sélectionné (${result.docRegion}).`);
449
+ }
450
+
451
+ logger.log("templateType result", JSON.stringify(truncateFields(templateType), null, 2));
452
+
453
+ return templateType;
507
454
  } catch (e: any) {
508
- logger.log('Error checking template type:', JSON.stringify(errorMessage(e), null, 2));
509
- if (e?.response?.status === 401 || e?.response?.status === 403 || e?.isAxiosError) {
510
- throw e;
511
- }
512
- if (e?.message === 'CARD_NOT_FULLY_IN_FRAME' || e?.message?.includes('ne correspond pas')) throw e;
513
- throw new Error(e?.message || 'Erreur de vérification du template');
455
+ logger.log('Error checking template type:', JSON.stringify(errorMessage(e), null, 2));
456
+
457
+ // Safely extract the raw message to check for string-based errors like "Unauthorized"
458
+ const rawMessage = (typeof e === 'string' ? e : (e?.message || '')).toLowerCase();
459
+
460
+ // 🚨 CRITICAL FIX: Ensure 401/403 and string-based "Unauthorized" errors bubble up exactly as they are!
461
+ if (
462
+ e?.response?.status === 401 ||
463
+ e?.response?.status === 403 ||
464
+ e?.status === 401 ||
465
+ rawMessage.includes('unauthorized') ||
466
+ rawMessage.includes('401')
467
+ ) {
468
+ throw e;
469
+ }
470
+
471
+ if (e?.isAxiosError) {
472
+ throw e;
473
+ }
474
+
475
+ if (e?.message === 'CARD_NOT_FULLY_IN_FRAME' || e?.message?.includes('ne correspond pas')) {
476
+ throw e;
477
+ }
478
+
479
+ // Safely fall back without swallowing string-based errors
480
+ throw new Error(typeof e === 'string' ? e : (e?.message || 'Erreur de vérification du template'));
514
481
  }
515
- }
482
+ }
@@ -474,14 +474,17 @@ export const authentification = async (): Promise<string> => {
474
474
 
475
475
  cachedToken = token;
476
476
  tokenExpiryTime = Date.now() + ((res.data.expires_in || 300) * 1000);
477
+ activeAuthRequest = null; // 🚨 FIX: Clear the promise once resolved
477
478
 
478
- return cachedToken as string; // 🚨 Guaranteed string to satisfy TypeScript
479
+ return cachedToken as string;
479
480
  })();
480
481
 
481
482
  return await activeAuthRequest;
482
483
  } catch (error: any) {
483
484
  cachedToken = null;
485
+ tokenExpiryTime = null;
484
486
  activeAuthRequest = null;
487
+
485
488
  const safeErrorMessage = error?.response?.data?.error_description || error?.message || 'Unknown error';
486
489
  logger.error('Error authentifying:', safeErrorMessage);
487
490
  throw new Error(`Échec de l'authentification: ${safeErrorMessage}`);
@@ -489,9 +492,13 @@ export const authentification = async (): Promise<string> => {
489
492
  }
490
493
 
491
494
  export const clearAuthToken = () => {
492
- cachedToken = null; // Clear memory cache
495
+ logger.log("Wiping AI services auth token cache...");
496
+ cachedToken = null;
497
+ tokenExpiryTime = null;
498
+ activeAuthRequest = null;
493
499
  };
494
500
 
501
+
495
502
  export const errorMessage = (error: any) => {
496
503
  return error.response?.data?.message || error.response?.data?.detail || error.response?.data?.error || error.response?.data || error.message;
497
504
  }
@@ -478,6 +478,7 @@ export interface TemplateActions {
478
478
  validateComponent: (componentId: number, dataOverride?: any) => boolean;
479
479
  submitTemplate: () => Promise<void>;
480
480
  resetTemplate: () => void;
481
+ restartSession: () => void;
481
482
  setLanguage: (language: string) => void;
482
483
  showCustomStepper: (show: boolean) => void;
483
484
  setVerificationState: (state: VerificationState) => void;