datakeen-session-react 1.1.200-rc.1 → 1.1.200
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/README.md +0 -11
- package/dist/cjs/components/document-collection/DocumentError.js +1 -4
- package/dist/cjs/components/jdi/JDIError.js +1 -4
- package/dist/cjs/components/nfc-scan/NfcScanNode.js +26 -3
- package/dist/cjs/i18n/en.json.js +3 -0
- package/dist/cjs/i18n/es.json.js +3 -0
- package/dist/cjs/i18n/fr.json.js +3 -0
- package/dist/cjs/services/sessionCredentials.js +1 -2
- package/dist/cjs/utils/isMobileDevice.js +11 -0
- package/dist/esm/components/document-collection/DocumentError.js +1 -4
- package/dist/esm/components/jdi/JDIError.js +1 -4
- package/dist/esm/components/nfc-scan/NfcScanNode.js +27 -4
- package/dist/esm/i18n/en.json.js +3 -0
- package/dist/esm/i18n/es.json.js +3 -0
- package/dist/esm/i18n/fr.json.js +3 -0
- package/dist/esm/services/sessionCredentials.js +1 -2
- package/dist/esm/utils/isMobileDevice.js +11 -1
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -549,17 +549,6 @@ Voir [docs/JOURNEY_NODE_BUILDER.md](./docs/JOURNEY_NODE_BUILDER.md) pour la chec
|
|
|
549
549
|
|
|
550
550
|
---
|
|
551
551
|
|
|
552
|
-
## Conformité des licences et SBOM
|
|
553
|
-
|
|
554
|
-
La CI refuse toute dépendance de production sous licence AGPL (**bloquant**) ou GPL (warning) qui n'est pas listée avec justification dans `license-allowlist.json`, et archive un SBOM CycloneDX (`sbom.cdx.json`) à chaque release (`dev`, `staging`, `main`).
|
|
555
|
-
|
|
556
|
-
```bash
|
|
557
|
-
npm ci
|
|
558
|
-
node scripts/check-licenses.cjs # contrôle local
|
|
559
|
-
```
|
|
560
|
-
|
|
561
|
-
Procédure complète (ajouter une exception, régénérer le SBOM) : [docs/LICENSE_COMPLIANCE.md](docs/LICENSE_COMPLIANCE.md).
|
|
562
|
-
|
|
563
552
|
## Support
|
|
564
553
|
|
|
565
554
|
- **Email** : support@datakeen.co
|
|
@@ -14,9 +14,6 @@ var analysisErrors = require('../../utils/analysisErrors.js');
|
|
|
14
14
|
var DocumentError = function (_a) {
|
|
15
15
|
var documentType = _a.documentType, documentTypeLabel = _a.documentTypeLabel, onRetry = _a.onRetry, onContinueAnyway = _a.onContinueAnyway, onBackToUserInput = _a.onBackToUserInput, _b = _a.retryCount, retryCount = _b === void 0 ? 0 : _b, _c = _a.predictions, predictions = _c === void 0 ? [] : _c; _a.errorDetails; _a.allowResubmission; var maxResubmissionAttempts = _a.maxResubmissionAttempts, _d = _a.isRetryAllowed, isRetryAllowed = _d === void 0 ? true : _d, errorCode = _a.errorCode, _e = _a.blockContinue, blockContinue = _e === void 0 ? false : _e;
|
|
16
16
|
var t = useI18n.useI18n().t;
|
|
17
|
-
// APP3-1955: a service refusal (no credits / quota) blocks the journey,
|
|
18
|
-
// whatever node.allowContinueOnFailure says.
|
|
19
|
-
var continueAnyway = analysisErrors.isServiceErrorCode(errorCode) ? undefined : onContinueAnyway;
|
|
20
17
|
var _f = React.useState([]), validationPoints = _f[0], setValidationPoints = _f[1];
|
|
21
18
|
React.useEffect(function () {
|
|
22
19
|
// Use shared utility to get validation points
|
|
@@ -65,7 +62,7 @@ var DocumentError = function (_a) {
|
|
|
65
62
|
: t("errors.resubmission.no_resubmission_allowed") })] })), !blockContinue && getUserGuidance() && (jsxRuntime.jsxs("div", { className: "bg-yellow-50 border border-yellow-200 rounded-lg p-4", children: [jsxRuntime.jsx("h3", { className: "font-medium text-yellow-900 mb-2", children: analysisErrors.isServiceErrorCode(errorCode)
|
|
66
63
|
? // Quota / out of credits: nothing for the end user to improve.
|
|
67
64
|
t("errors.service_refusal_title")
|
|
68
|
-
: "\uD83D\uDCA1 ".concat(t("errors.improvement_tips", "Conseils d'amélioration")) }), jsxRuntime.jsx("p", { className: "text-sm text-yellow-800", children: getUserGuidance() })] })), !blockContinue && isRetryAllowed && (jsxRuntime.jsxs("div", { className: "bg-blue-50 border border-blue-200 rounded-lg p-4", children: [jsxRuntime.jsx("h3", { className: "font-medium text-blue-900 mb-2", children: t("errors.next_steps", "Prochaines étapes") }), jsxRuntime.jsx("p", { className: "text-sm text-blue-800", children: t("errors.next_steps_description", "Veuillez reprendre les photos de votre document en suivant les conseils d'amélioration pour une meilleure qualité.") })] }))] }) }), jsxRuntime.jsx("div", { className: "sticky bottom-0 md:static bg-white border-t md:border-t-0 p-4 md:p-0 md:pb-8", children: jsxRuntime.jsxs("div", { className: "w-full max-w-md mx-auto", children: [jsxRuntime.jsx("div", { className: "flex flex-col space-y-3 md:hidden", children: blockContinue ? (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onRetry, type: "continue", children: t("errors.retry_verification", "Recommencer la vérification") })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [onBackToUserInput && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onBackToUserInput, type: "back", children: t("errors.back_to_user_input", "Retour aux informations d'identité") })), retryCount >= 1 && isRetryAllowed &&
|
|
65
|
+
: "\uD83D\uDCA1 ".concat(t("errors.improvement_tips", "Conseils d'amélioration")) }), jsxRuntime.jsx("p", { className: "text-sm text-yellow-800", children: getUserGuidance() })] })), !blockContinue && isRetryAllowed && (jsxRuntime.jsxs("div", { className: "bg-blue-50 border border-blue-200 rounded-lg p-4", children: [jsxRuntime.jsx("h3", { className: "font-medium text-blue-900 mb-2", children: t("errors.next_steps", "Prochaines étapes") }), jsxRuntime.jsx("p", { className: "text-sm text-blue-800", children: t("errors.next_steps_description", "Veuillez reprendre les photos de votre document en suivant les conseils d'amélioration pour une meilleure qualité.") })] }))] }) }), jsxRuntime.jsx("div", { className: "sticky bottom-0 md:static bg-white border-t md:border-t-0 p-4 md:p-0 md:pb-8", children: jsxRuntime.jsxs("div", { className: "w-full max-w-md mx-auto", children: [jsxRuntime.jsx("div", { className: "flex flex-col space-y-3 md:hidden", children: blockContinue ? (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onRetry, type: "continue", children: t("errors.retry_verification", "Recommencer la vérification") })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [onBackToUserInput && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onBackToUserInput, type: "back", children: t("errors.back_to_user_input", "Retour aux informations d'identité") })), retryCount >= 1 && isRetryAllowed && onContinueAnyway && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onContinueAnyway, type: "back", children: t("errors.continue_anyway", "Poursuivre tout de même") })), isRetryAllowed && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onRetry, type: "continue", children: t("errors.retry_verification", "Recommencer la vérification") })), !isRetryAllowed && onContinueAnyway && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onContinueAnyway, type: "back", children: t("errors.continue_anyway", "Poursuivre tout de même") }))] })) }), jsxRuntime.jsx("div", { className: "hidden md:flex flex-col space-y-3", children: jsxRuntime.jsx("div", { className: "flex justify-center space-x-3", children: blockContinue ? (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onRetry, type: "continue", children: t("errors.retry_verification", "Recommencer la vérification") })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [onBackToUserInput && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onBackToUserInput, type: "back", children: t("errors.back_to_user_input", "Retour aux informations d'identité") })), retryCount >= 1 && isRetryAllowed && onContinueAnyway && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onContinueAnyway, type: "back", children: t("errors.continue_anyway", "Poursuivre tout de même") })), isRetryAllowed && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onRetry, type: "continue", children: t("errors.retry_verification", "Recommencer la vérification") })), !isRetryAllowed && onContinueAnyway && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onContinueAnyway, type: "continue", children: t("errors.continue_anyway", "Poursuivre tout de même") }))] })) }) })] }) })] }));
|
|
69
66
|
};
|
|
70
67
|
|
|
71
68
|
exports.default = DocumentError;
|
|
@@ -18,9 +18,6 @@ var JDIError = function (_a) {
|
|
|
18
18
|
var _b;
|
|
19
19
|
_a.documentType; var onRetry = _a.onRetry, onContinueAnyway = _a.onContinueAnyway, onBackToUserInput = _a.onBackToUserInput, _c = _a.retryCount, retryCount = _c === void 0 ? 0 : _c, _d = _a.predictions, predictions = _d === void 0 ? DEFAULT_PREDICTIONS : _d, errorCode = _a.errorCode; _a.allowResubmission; var maxResubmissionAttempts = _a.maxResubmissionAttempts, _e = _a.isRetryAllowed, isRetryAllowed = _e === void 0 ? true : _e, _f = _a.blockContinue, blockContinue = _f === void 0 ? false : _f;
|
|
20
20
|
var t = useI18n.useI18n().t;
|
|
21
|
-
// APP3-1955: a service refusal (no credits / quota) blocks the journey,
|
|
22
|
-
// whatever node.allowContinueOnFailure says.
|
|
23
|
-
var continueAnyway = analysisErrors.isServiceErrorCode(errorCode) ? undefined : onContinueAnyway;
|
|
24
21
|
var _g = React.useState([]), validationPoints = _g[0], setValidationPoints = _g[1];
|
|
25
22
|
// Détail de mismatch d'identité (code 7.0), calculé côté backend et porté
|
|
26
23
|
// par la première prédiction. Absent pour tous les autres codes.
|
|
@@ -36,7 +33,7 @@ var JDIError = function (_a) {
|
|
|
36
33
|
var guidance = t("user_guidance.".concat(errorCode), "");
|
|
37
34
|
return guidance || null;
|
|
38
35
|
};
|
|
39
|
-
return (jsxRuntime.jsx(MobilePageLayout.default, { footer: jsxRuntime.jsxs("div", { className: "w-full max-w-md mx-auto", children: [jsxRuntime.jsx("div", { className: "flex flex-col space-y-3 md:hidden", children: blockContinue ? (jsxRuntime.jsx(Button.default, { onClick: onRetry, className: "w-full py-3", children: t("errors.retry_verification") })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [onBackToUserInput && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onBackToUserInput, type: "back", className: "w-full text-black text-center font-poppins text-sm font-medium py-2", children: t("errors.back_to_user_input") })), retryCount >= 1 && isRetryAllowed &&
|
|
36
|
+
return (jsxRuntime.jsx(MobilePageLayout.default, { footer: jsxRuntime.jsxs("div", { className: "w-full max-w-md mx-auto", children: [jsxRuntime.jsx("div", { className: "flex flex-col space-y-3 md:hidden", children: blockContinue ? (jsxRuntime.jsx(Button.default, { onClick: onRetry, className: "w-full py-3", children: t("errors.retry_verification") })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [onBackToUserInput && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onBackToUserInput, type: "back", className: "w-full text-black text-center font-poppins text-sm font-medium py-2", children: t("errors.back_to_user_input") })), retryCount >= 1 && isRetryAllowed && onContinueAnyway && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onContinueAnyway, type: "back", className: "w-full text-black text-center font-poppins text-sm font-medium py-2", children: t("errors.continue_anyway") })), isRetryAllowed && (jsxRuntime.jsx(Button.default, { onClick: onRetry, className: "w-full py-3", children: t("errors.retry_verification") })), !isRetryAllowed && onContinueAnyway && (jsxRuntime.jsx(Button.default, { onClick: onContinueAnyway, className: "w-full py-3", children: t("errors.continue_anyway") }))] })) }), jsxRuntime.jsx("div", { className: "hidden md:flex flex-col space-y-3", children: jsxRuntime.jsx("div", { className: "flex justify-center space-x-3", children: blockContinue ? (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onRetry, type: "continue", children: t("errors.retry_verification") })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [onBackToUserInput && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onBackToUserInput, type: "back", className: "text-black text-center font-poppins text-sm font-medium py-2", children: t("errors.back_to_user_input") })), retryCount >= 1 && isRetryAllowed && onContinueAnyway && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onContinueAnyway, type: "back", className: "text-black text-center font-poppins text-sm font-medium py-2", children: t("errors.continue_anyway") })), isRetryAllowed && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onRetry, type: "continue", children: t("errors.retry_verification") })), !isRetryAllowed && onContinueAnyway && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onContinueAnyway, type: "continue", children: t("errors.continue_anyway") }))] })) }) })] }), children: jsxRuntime.jsx("div", { className: "px-4 py-6 pt-11 md:px-8 md:py-8", children: jsxRuntime.jsxs("div", { className: "w-full max-w-md mx-auto space-y-6", children: [jsxRuntime.jsx("div", { className: "text-center", children: jsxRuntime.jsx("div", { className: "mx-auto w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mb-4", children: jsxRuntime.jsx("div", { className: "w-8 h-8 bg-red-500 rounded-full flex items-center justify-center", children: jsxRuntime.jsx("span", { className: "text-white text-lg", children: "\u2715" }) }) }) }), jsxRuntime.jsxs("div", { className: "text-center space-y-4", children: [jsxRuntime.jsx(Title.default, { className: "text-xl md:text-2xl lg:text-3xl text-red-600", children: blockContinue
|
|
40
37
|
? t("errors.upload_not_confirmed.title")
|
|
41
38
|
: t("errors.verification_failed") }), jsxRuntime.jsx(Subtitle.default, { className: "text-sm text-gray-600 leading-relaxed", children: blockContinue
|
|
42
39
|
? t("errors.upload_not_confirmed.subtitle")
|
|
@@ -18,6 +18,12 @@ var Title = require('../ui/Title.js');
|
|
|
18
18
|
var Subtitle = require('../ui/Subtitle.js');
|
|
19
19
|
var NfcSuccessAnimation = require('./NfcSuccessAnimation.js');
|
|
20
20
|
|
|
21
|
+
// App native NFC iOS « ID Scan ». Safari n'ouvre jamais l'app via une redirection JS ni via un
|
|
22
|
+
// Universal Link du même domaine que la page (Apple TN3155) : on passe par le schéma custom déclaré
|
|
23
|
+
// par l'app, sur un geste utilisateur. L'hôte du lien = backend Datakeen, que l'app utilise pour
|
|
24
|
+
// choisir l'environnement (même rôle que `intent://<host>/scan/<id>` sur Android).
|
|
25
|
+
var IOS_APP_SCHEME = "datakeenscannerswift";
|
|
26
|
+
var IOS_APP_STORE_URL = "https://apps.apple.com/app/id6762505375";
|
|
21
27
|
var NfcScanStep = function (_a) {
|
|
22
28
|
var sessionId = _a.sessionId, nodeId = _a.nodeId, stepObject = _a.stepObject, template = _a.template, pageTitle = _a.pageTitle, pageDescription = _a.pageDescription, onBack = _a.onBack, onContinueWithoutChip = _a.onContinueWithoutChip;
|
|
23
29
|
var t = useI18n.useI18n().t;
|
|
@@ -27,11 +33,26 @@ var NfcScanStep = function (_a) {
|
|
|
27
33
|
var _d = React.useState(false), isResetting = _d[0], setIsResetting = _d[1];
|
|
28
34
|
// Sur mobile, on ouvre directement l'app de scan au lieu d'afficher un QR code inutile.
|
|
29
35
|
var isMobile = React.useMemo(function () { return isMobileDevice.isMobileDevice(); }, []);
|
|
36
|
+
var isIOS = React.useMemo(function () { return isMobileDevice.isIOSDevice(); }, []);
|
|
30
37
|
var nfcUrl = React.useMemo(function () {
|
|
31
38
|
var origin = typeof window !== "undefined" ? window.location.origin : "";
|
|
32
39
|
return "".concat(origin, "/scan/").concat(sessionId);
|
|
33
40
|
}, [sessionId]);
|
|
34
41
|
var apiBaseUrl = React.useMemo(function () { return api.getBaseURL(); }, []);
|
|
42
|
+
var iosAppLinks = React.useMemo(function () {
|
|
43
|
+
var host = new URL(apiBaseUrl, window.location.href).host;
|
|
44
|
+
return {
|
|
45
|
+
open: "".concat(IOS_APP_SCHEME, "://").concat(host, "/scan/").concat(sessionId),
|
|
46
|
+
// Lu par le bouton « Reprendre ma session » de l'app au premier lancement après installation.
|
|
47
|
+
resume: "https://".concat(host, "/scan/").concat(sessionId),
|
|
48
|
+
};
|
|
49
|
+
}, [apiBaseUrl, sessionId]);
|
|
50
|
+
// Best effort : l'App Store ne transmet aucun contexte à l'app installée (pas d'équivalent du
|
|
51
|
+
// Play Install Referrer), le presse-papier sert de relais. Échec silencieux (permission refusée).
|
|
52
|
+
var copyResumeLink = function () {
|
|
53
|
+
var _a;
|
|
54
|
+
(_a = navigator.clipboard) === null || _a === void 0 ? void 0 : _a.writeText(iosAppLinks.resume).catch(function () { return undefined; });
|
|
55
|
+
};
|
|
35
56
|
var status = useNfcSseStatus.useNfcSseStatus({
|
|
36
57
|
sessionId: sessionId,
|
|
37
58
|
apiBaseUrl: apiBaseUrl,
|
|
@@ -50,13 +71,15 @@ var NfcScanStep = function (_a) {
|
|
|
50
71
|
// Sur mobile : ouvrir directement l'application de scan (pas de QR à se scanner soi-même).
|
|
51
72
|
// Une seule fois, tant que le scan n'a pas progressé. Le SSE reste actif pour faire avancer
|
|
52
73
|
// le parcours quand la lecture est terminée.
|
|
74
|
+
// Pas sur iOS : la redirection resterait dans Safari et quitterait la page (SSE fermé, parcours
|
|
75
|
+
// bloqué). On y affiche des boutons à la place, et la page reste ouverte pendant le scan.
|
|
53
76
|
var hasRedirectedRef = React.useRef(false);
|
|
54
77
|
React.useEffect(function () {
|
|
55
|
-
if (!isMobile || status !== "pending" || hasRedirectedRef.current)
|
|
78
|
+
if (!isMobile || isIOS || status !== "pending" || hasRedirectedRef.current)
|
|
56
79
|
return;
|
|
57
80
|
hasRedirectedRef.current = true;
|
|
58
81
|
window.location.assign(nfcUrl);
|
|
59
|
-
}, [isMobile, status, nfcUrl]);
|
|
82
|
+
}, [isMobile, isIOS, status, nfcUrl]);
|
|
60
83
|
React.useEffect(function () {
|
|
61
84
|
if (isMobile)
|
|
62
85
|
return; // pas de QR sur mobile (redirection directe)
|
|
@@ -141,7 +164,7 @@ var NfcScanStep = function (_a) {
|
|
|
141
164
|
return (jsxRuntime.jsx(MobilePageLayout.default, { footer: footer, children: jsxRuntime.jsx("div", { className: "px-4 py-6 pt-11 md:px-8 md:py-8", children: jsxRuntime.jsxs("div", { className: "w-full max-w-md mx-auto space-y-6", children: [jsxRuntime.jsxs("div", { className: "text-center space-y-4 mt-16 md:mt-20", children: [jsxRuntime.jsx(Title.default, { className: "text-xl md:text-2xl lg:text-3xl", children: title }), jsxRuntime.jsx(Subtitle.default, { className: "text-sm md:text-base text-gray-600 leading-relaxed whitespace-pre-line", children: subtitle })] }), jsxRuntime.jsx("div", { className: "flex-1 flex flex-col justify-center items-center gap-4", children: status === "success" ? (jsxRuntime.jsx(NfcSuccessAnimation.default, {})) : status === "completed" ? (jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-4 p-8", children: [jsxRuntime.jsx("div", { className: "animate-spin w-10 h-10 border-2 border-gray-300 border-t-blue-500 rounded-full" }), jsxRuntime.jsx("p", { className: "text-sm text-gray-600", children: t("nfc_scan.verifying", "Vérification en cours…") })] })) : status === "failed" ? (jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-2 p-8 text-center", children: [jsxRuntime.jsx("p", { className: "text-base font-semibold text-red-600", children: t("nfc_scan.failed_title", "Le scan n'a pas pu être vérifié") }), jsxRuntime.jsx("p", { className: "text-sm text-gray-600 leading-relaxed", children: t("nfc_scan.failed_description", "Après plusieurs tentatives, la lecture de votre document n'a pas pu être validée.") }), resetError && (jsxRuntime.jsx("p", { className: "text-sm text-red-600", role: "alert", children: resetError }))] })) : (
|
|
142
165
|
// Contenu principal QR/mobile — reste monté pour "pending" ET "opened", pour ne
|
|
143
166
|
// jamais démonter le canvas déjà généré.
|
|
144
|
-
jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [qrError ? (jsxRuntime.jsx("div", { className: "text-red-500 text-center p-4", children: qrError })) : isMobile ? (jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-4 p-8 text-center", children: [jsxRuntime.jsx("div", { className: "animate-spin w-10 h-10 border-2 border-gray-300 border-t-blue-500 rounded-full" }), jsxRuntime.jsx("p", { className: "text-sm text-gray-600", children: t("nfc_scan.opening_app", "Ouverture de l'application…") }), jsxRuntime.jsx("a", { href: nfcUrl, className: "text-sm font-medium text-blue-600 underline", children: t("nfc_scan.open_app_link", "Ouvrir l'application") })] })) : (jsxRuntime.jsx("div", { className: "bg-white p-4 rounded-lg shadow-lg border-2 border-gray-200", children: jsxRuntime.jsx("canvas", { ref: canvasRef }) })), status === "opened" && (jsxRuntime.jsxs("div", { className: "text-center space-y-1", children: [jsxRuntime.jsx("p", { className: "text-sm font-semibold", style: { color: "var(--uni-secondary-color, #0a9983)" }, children: t("nfc_scan.qr_opened_title", "QR code scanné") }), jsxRuntime.jsx("p", { className: "text-xs text-gray-500 leading-relaxed", children: t("nfc_scan.qr_opened_description", "L'expérience NFC est ouverte sur votre smartphone. Continuez le scan sur votre téléphone.") }), jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-2 text-xs text-gray-500 pt-1", children: [jsxRuntime.jsx("span", { className: "h-2 w-2 rounded-full animate-pulse", style: { backgroundColor: "var(--uni-primary-color, #11e5c5)" } }), t("nfc_scan.waiting_completion", "En attente de la lecture NFC")] })] }))] })) })] }) }) }));
|
|
167
|
+
jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [qrError ? (jsxRuntime.jsx("div", { className: "text-red-500 text-center p-4", children: qrError })) : isIOS ? (jsxRuntime.jsxs("div", { className: "flex w-full flex-col items-center gap-3 p-4 text-center", children: [jsxRuntime.jsx("a", { href: iosAppLinks.open, className: "flex h-[48px] w-full items-center justify-center rounded-[12px] text-sm font-medium hover:brightness-110 active:brightness-90", style: { backgroundColor: "var(--dk-btn-bg)", color: "var(--dk-btn-text)" }, children: t("nfc_scan.open_app_link", "Ouvrir l'application") }), jsxRuntime.jsx("a", { href: IOS_APP_STORE_URL, onClick: copyResumeLink, className: "flex h-[48px] w-full items-center justify-center rounded-[12px] border border-gray-300 text-sm font-medium text-gray-600 hover:bg-black/5", children: t("nfc_scan.install_app_link", "Installer l'application") }), jsxRuntime.jsx("p", { className: "text-xs text-gray-500 leading-relaxed", children: t("nfc_scan.install_app_hint", "Après l'installation, revenez sur cette page et appuyez sur « Ouvrir l'application ».") })] })) : isMobile ? (jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-4 p-8 text-center", children: [jsxRuntime.jsx("div", { className: "animate-spin w-10 h-10 border-2 border-gray-300 border-t-blue-500 rounded-full" }), jsxRuntime.jsx("p", { className: "text-sm text-gray-600", children: t("nfc_scan.opening_app", "Ouverture de l'application…") }), jsxRuntime.jsx("a", { href: nfcUrl, className: "text-sm font-medium text-blue-600 underline", children: t("nfc_scan.open_app_link", "Ouvrir l'application") })] })) : (jsxRuntime.jsx("div", { className: "bg-white p-4 rounded-lg shadow-lg border-2 border-gray-200", children: jsxRuntime.jsx("canvas", { ref: canvasRef }) })), status === "opened" && (jsxRuntime.jsxs("div", { className: "text-center space-y-1", children: [jsxRuntime.jsx("p", { className: "text-sm font-semibold", style: { color: "var(--uni-secondary-color, #0a9983)" }, children: t("nfc_scan.qr_opened_title", "QR code scanné") }), jsxRuntime.jsx("p", { className: "text-xs text-gray-500 leading-relaxed", children: t("nfc_scan.qr_opened_description", "L'expérience NFC est ouverte sur votre smartphone. Continuez le scan sur votre téléphone.") }), jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-2 text-xs text-gray-500 pt-1", children: [jsxRuntime.jsx("span", { className: "h-2 w-2 rounded-full animate-pulse", style: { backgroundColor: "var(--uni-primary-color, #11e5c5)" } }), t("nfc_scan.waiting_completion", "En attente de la lecture NFC")] })] }))] })) })] }) }) }));
|
|
145
168
|
};
|
|
146
169
|
|
|
147
170
|
exports.default = NfcScanStep;
|
package/dist/cjs/i18n/en.json.js
CHANGED
|
@@ -1015,6 +1015,9 @@ var signature = {
|
|
|
1015
1015
|
token_error: "Error loading signature session."
|
|
1016
1016
|
};
|
|
1017
1017
|
var nfc_scan = {
|
|
1018
|
+
open_app_link: "Open the app",
|
|
1019
|
+
install_app_link: "Install the app",
|
|
1020
|
+
install_app_hint: "Once installed, come back to this page and tap “Open the app”.",
|
|
1018
1021
|
page_title: "NFC scan of your document",
|
|
1019
1022
|
page_description: "Scan the QR code below with your smartphone to read the electronic chip of your identity document.",
|
|
1020
1023
|
verifying: "Verifying…",
|
package/dist/cjs/i18n/es.json.js
CHANGED
|
@@ -1015,6 +1015,9 @@ var signature = {
|
|
|
1015
1015
|
token_error: "Error al cargar la sesión de firma."
|
|
1016
1016
|
};
|
|
1017
1017
|
var nfc_scan = {
|
|
1018
|
+
open_app_link: "Abrir la aplicación",
|
|
1019
|
+
install_app_link: "Instalar la aplicación",
|
|
1020
|
+
install_app_hint: "Después de instalarla, vuelve a esta página y pulsa «Abrir la aplicación».",
|
|
1018
1021
|
page_title: "Escaneo NFC de tu documento",
|
|
1019
1022
|
page_description: "Escanea el código QR siguiente con tu smartphone para leer el chip electrónico de tu documento de identidad.",
|
|
1020
1023
|
verifying: "Verificando…",
|
package/dist/cjs/i18n/fr.json.js
CHANGED
|
@@ -1015,6 +1015,9 @@ var signature = {
|
|
|
1015
1015
|
token_error: "Erreur lors du chargement de la session de signature."
|
|
1016
1016
|
};
|
|
1017
1017
|
var nfc_scan = {
|
|
1018
|
+
open_app_link: "Ouvrir l'application",
|
|
1019
|
+
install_app_link: "Installer l'application",
|
|
1020
|
+
install_app_hint: "Après l'installation, revenez sur cette page et appuyez sur « Ouvrir l'application ».",
|
|
1018
1021
|
page_title: "Scan NFC de votre document",
|
|
1019
1022
|
page_description: "Scannez le QR code ci-dessous avec votre smartphone pour lire la puce électronique de votre document d'identité.",
|
|
1020
1023
|
verifying: "Vérification en cours…",
|
|
@@ -14,8 +14,7 @@ function getSessionCredential(sessionId) {
|
|
|
14
14
|
function credentialForRequest(url) {
|
|
15
15
|
if (/^[a-z]+:/i.test(url) || url.startsWith('//'))
|
|
16
16
|
return undefined;
|
|
17
|
-
|
|
18
|
-
var match = url.match(/^\/?session\/(?:sdk|unissey)\/([^/?]+)/);
|
|
17
|
+
var match = url.match(/^\/?session\/sdk\/([^/?]+)/);
|
|
19
18
|
return match ? credentials.get(decodeURIComponent(match[1])) : undefined;
|
|
20
19
|
}
|
|
21
20
|
|
|
@@ -12,5 +12,16 @@ var isMobileDevice = function () {
|
|
|
12
12
|
return false;
|
|
13
13
|
return /Android|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i.test(navigator.userAgent);
|
|
14
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* iPhone / iPad (UA historique) / iPod. Sur iOS, Safari n'ouvre pas l'app NFC via une redirection
|
|
17
|
+
* JS ni via un Universal Link du même domaine : l'étape NFC doit proposer un bouton (geste
|
|
18
|
+
* utilisateur) vers le schéma custom de l'app.
|
|
19
|
+
*/
|
|
20
|
+
var isIOSDevice = function () {
|
|
21
|
+
if (typeof navigator === "undefined")
|
|
22
|
+
return false;
|
|
23
|
+
return /iPhone|iPad|iPod/i.test(navigator.userAgent);
|
|
24
|
+
};
|
|
15
25
|
|
|
26
|
+
exports.isIOSDevice = isIOSDevice;
|
|
16
27
|
exports.isMobileDevice = isMobileDevice;
|
|
@@ -10,9 +10,6 @@ import { isServiceErrorCode } from '../../utils/analysisErrors.js';
|
|
|
10
10
|
var DocumentError = function (_a) {
|
|
11
11
|
var documentType = _a.documentType, documentTypeLabel = _a.documentTypeLabel, onRetry = _a.onRetry, onContinueAnyway = _a.onContinueAnyway, onBackToUserInput = _a.onBackToUserInput, _b = _a.retryCount, retryCount = _b === void 0 ? 0 : _b, _c = _a.predictions, predictions = _c === void 0 ? [] : _c; _a.errorDetails; _a.allowResubmission; var maxResubmissionAttempts = _a.maxResubmissionAttempts, _d = _a.isRetryAllowed, isRetryAllowed = _d === void 0 ? true : _d, errorCode = _a.errorCode, _e = _a.blockContinue, blockContinue = _e === void 0 ? false : _e;
|
|
12
12
|
var t = useI18n().t;
|
|
13
|
-
// APP3-1955: a service refusal (no credits / quota) blocks the journey,
|
|
14
|
-
// whatever node.allowContinueOnFailure says.
|
|
15
|
-
var continueAnyway = isServiceErrorCode(errorCode) ? undefined : onContinueAnyway;
|
|
16
13
|
var _f = useState([]), validationPoints = _f[0], setValidationPoints = _f[1];
|
|
17
14
|
useEffect(function () {
|
|
18
15
|
// Use shared utility to get validation points
|
|
@@ -61,7 +58,7 @@ var DocumentError = function (_a) {
|
|
|
61
58
|
: t("errors.resubmission.no_resubmission_allowed") })] })), !blockContinue && getUserGuidance() && (jsxs("div", { className: "bg-yellow-50 border border-yellow-200 rounded-lg p-4", children: [jsx("h3", { className: "font-medium text-yellow-900 mb-2", children: isServiceErrorCode(errorCode)
|
|
62
59
|
? // Quota / out of credits: nothing for the end user to improve.
|
|
63
60
|
t("errors.service_refusal_title")
|
|
64
|
-
: "\uD83D\uDCA1 ".concat(t("errors.improvement_tips", "Conseils d'amélioration")) }), jsx("p", { className: "text-sm text-yellow-800", children: getUserGuidance() })] })), !blockContinue && isRetryAllowed && (jsxs("div", { className: "bg-blue-50 border border-blue-200 rounded-lg p-4", children: [jsx("h3", { className: "font-medium text-blue-900 mb-2", children: t("errors.next_steps", "Prochaines étapes") }), jsx("p", { className: "text-sm text-blue-800", children: t("errors.next_steps_description", "Veuillez reprendre les photos de votre document en suivant les conseils d'amélioration pour une meilleure qualité.") })] }))] }) }), jsx("div", { className: "sticky bottom-0 md:static bg-white border-t md:border-t-0 p-4 md:p-0 md:pb-8", children: jsxs("div", { className: "w-full max-w-md mx-auto", children: [jsx("div", { className: "flex flex-col space-y-3 md:hidden", children: blockContinue ? (jsx(ButtonDesktop, { onClick: onRetry, type: "continue", children: t("errors.retry_verification", "Recommencer la vérification") })) : (jsxs(Fragment, { children: [onBackToUserInput && (jsx(ButtonDesktop, { onClick: onBackToUserInput, type: "back", children: t("errors.back_to_user_input", "Retour aux informations d'identité") })), retryCount >= 1 && isRetryAllowed &&
|
|
61
|
+
: "\uD83D\uDCA1 ".concat(t("errors.improvement_tips", "Conseils d'amélioration")) }), jsx("p", { className: "text-sm text-yellow-800", children: getUserGuidance() })] })), !blockContinue && isRetryAllowed && (jsxs("div", { className: "bg-blue-50 border border-blue-200 rounded-lg p-4", children: [jsx("h3", { className: "font-medium text-blue-900 mb-2", children: t("errors.next_steps", "Prochaines étapes") }), jsx("p", { className: "text-sm text-blue-800", children: t("errors.next_steps_description", "Veuillez reprendre les photos de votre document en suivant les conseils d'amélioration pour une meilleure qualité.") })] }))] }) }), jsx("div", { className: "sticky bottom-0 md:static bg-white border-t md:border-t-0 p-4 md:p-0 md:pb-8", children: jsxs("div", { className: "w-full max-w-md mx-auto", children: [jsx("div", { className: "flex flex-col space-y-3 md:hidden", children: blockContinue ? (jsx(ButtonDesktop, { onClick: onRetry, type: "continue", children: t("errors.retry_verification", "Recommencer la vérification") })) : (jsxs(Fragment, { children: [onBackToUserInput && (jsx(ButtonDesktop, { onClick: onBackToUserInput, type: "back", children: t("errors.back_to_user_input", "Retour aux informations d'identité") })), retryCount >= 1 && isRetryAllowed && onContinueAnyway && (jsx(ButtonDesktop, { onClick: onContinueAnyway, type: "back", children: t("errors.continue_anyway", "Poursuivre tout de même") })), isRetryAllowed && (jsx(ButtonDesktop, { onClick: onRetry, type: "continue", children: t("errors.retry_verification", "Recommencer la vérification") })), !isRetryAllowed && onContinueAnyway && (jsx(ButtonDesktop, { onClick: onContinueAnyway, type: "back", children: t("errors.continue_anyway", "Poursuivre tout de même") }))] })) }), jsx("div", { className: "hidden md:flex flex-col space-y-3", children: jsx("div", { className: "flex justify-center space-x-3", children: blockContinue ? (jsx(ButtonDesktop, { onClick: onRetry, type: "continue", children: t("errors.retry_verification", "Recommencer la vérification") })) : (jsxs(Fragment, { children: [onBackToUserInput && (jsx(ButtonDesktop, { onClick: onBackToUserInput, type: "back", children: t("errors.back_to_user_input", "Retour aux informations d'identité") })), retryCount >= 1 && isRetryAllowed && onContinueAnyway && (jsx(ButtonDesktop, { onClick: onContinueAnyway, type: "back", children: t("errors.continue_anyway", "Poursuivre tout de même") })), isRetryAllowed && (jsx(ButtonDesktop, { onClick: onRetry, type: "continue", children: t("errors.retry_verification", "Recommencer la vérification") })), !isRetryAllowed && onContinueAnyway && (jsx(ButtonDesktop, { onClick: onContinueAnyway, type: "continue", children: t("errors.continue_anyway", "Poursuivre tout de même") }))] })) }) })] }) })] }));
|
|
65
62
|
};
|
|
66
63
|
|
|
67
64
|
export { DocumentError as default };
|
|
@@ -14,9 +14,6 @@ var JDIError = function (_a) {
|
|
|
14
14
|
var _b;
|
|
15
15
|
_a.documentType; var onRetry = _a.onRetry, onContinueAnyway = _a.onContinueAnyway, onBackToUserInput = _a.onBackToUserInput, _c = _a.retryCount, retryCount = _c === void 0 ? 0 : _c, _d = _a.predictions, predictions = _d === void 0 ? DEFAULT_PREDICTIONS : _d, errorCode = _a.errorCode; _a.allowResubmission; var maxResubmissionAttempts = _a.maxResubmissionAttempts, _e = _a.isRetryAllowed, isRetryAllowed = _e === void 0 ? true : _e, _f = _a.blockContinue, blockContinue = _f === void 0 ? false : _f;
|
|
16
16
|
var t = useI18n().t;
|
|
17
|
-
// APP3-1955: a service refusal (no credits / quota) blocks the journey,
|
|
18
|
-
// whatever node.allowContinueOnFailure says.
|
|
19
|
-
var continueAnyway = isServiceErrorCode(errorCode) ? undefined : onContinueAnyway;
|
|
20
17
|
var _g = useState([]), validationPoints = _g[0], setValidationPoints = _g[1];
|
|
21
18
|
// Détail de mismatch d'identité (code 7.0), calculé côté backend et porté
|
|
22
19
|
// par la première prédiction. Absent pour tous les autres codes.
|
|
@@ -32,7 +29,7 @@ var JDIError = function (_a) {
|
|
|
32
29
|
var guidance = t("user_guidance.".concat(errorCode), "");
|
|
33
30
|
return guidance || null;
|
|
34
31
|
};
|
|
35
|
-
return (jsx(MobilePageLayout, { footer: jsxs("div", { className: "w-full max-w-md mx-auto", children: [jsx("div", { className: "flex flex-col space-y-3 md:hidden", children: blockContinue ? (jsx(Button, { onClick: onRetry, className: "w-full py-3", children: t("errors.retry_verification") })) : (jsxs(Fragment, { children: [onBackToUserInput && (jsx(ButtonDesktop, { onClick: onBackToUserInput, type: "back", className: "w-full text-black text-center font-poppins text-sm font-medium py-2", children: t("errors.back_to_user_input") })), retryCount >= 1 && isRetryAllowed &&
|
|
32
|
+
return (jsx(MobilePageLayout, { footer: jsxs("div", { className: "w-full max-w-md mx-auto", children: [jsx("div", { className: "flex flex-col space-y-3 md:hidden", children: blockContinue ? (jsx(Button, { onClick: onRetry, className: "w-full py-3", children: t("errors.retry_verification") })) : (jsxs(Fragment, { children: [onBackToUserInput && (jsx(ButtonDesktop, { onClick: onBackToUserInput, type: "back", className: "w-full text-black text-center font-poppins text-sm font-medium py-2", children: t("errors.back_to_user_input") })), retryCount >= 1 && isRetryAllowed && onContinueAnyway && (jsx(ButtonDesktop, { onClick: onContinueAnyway, type: "back", className: "w-full text-black text-center font-poppins text-sm font-medium py-2", children: t("errors.continue_anyway") })), isRetryAllowed && (jsx(Button, { onClick: onRetry, className: "w-full py-3", children: t("errors.retry_verification") })), !isRetryAllowed && onContinueAnyway && (jsx(Button, { onClick: onContinueAnyway, className: "w-full py-3", children: t("errors.continue_anyway") }))] })) }), jsx("div", { className: "hidden md:flex flex-col space-y-3", children: jsx("div", { className: "flex justify-center space-x-3", children: blockContinue ? (jsx(ButtonDesktop, { onClick: onRetry, type: "continue", children: t("errors.retry_verification") })) : (jsxs(Fragment, { children: [onBackToUserInput && (jsx(ButtonDesktop, { onClick: onBackToUserInput, type: "back", className: "text-black text-center font-poppins text-sm font-medium py-2", children: t("errors.back_to_user_input") })), retryCount >= 1 && isRetryAllowed && onContinueAnyway && (jsx(ButtonDesktop, { onClick: onContinueAnyway, type: "back", className: "text-black text-center font-poppins text-sm font-medium py-2", children: t("errors.continue_anyway") })), isRetryAllowed && (jsx(ButtonDesktop, { onClick: onRetry, type: "continue", children: t("errors.retry_verification") })), !isRetryAllowed && onContinueAnyway && (jsx(ButtonDesktop, { onClick: onContinueAnyway, type: "continue", children: t("errors.continue_anyway") }))] })) }) })] }), children: jsx("div", { className: "px-4 py-6 pt-11 md:px-8 md:py-8", children: jsxs("div", { className: "w-full max-w-md mx-auto space-y-6", children: [jsx("div", { className: "text-center", children: jsx("div", { className: "mx-auto w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mb-4", children: jsx("div", { className: "w-8 h-8 bg-red-500 rounded-full flex items-center justify-center", children: jsx("span", { className: "text-white text-lg", children: "\u2715" }) }) }) }), jsxs("div", { className: "text-center space-y-4", children: [jsx(Title, { className: "text-xl md:text-2xl lg:text-3xl text-red-600", children: blockContinue
|
|
36
33
|
? t("errors.upload_not_confirmed.title")
|
|
37
34
|
: t("errors.verification_failed") }), jsx(Subtitle, { className: "text-sm text-gray-600 leading-relaxed", children: blockContinue
|
|
38
35
|
? t("errors.upload_not_confirmed.subtitle")
|
|
@@ -6,7 +6,7 @@ import { useI18n } from '../../hooks/useI18n.js';
|
|
|
6
6
|
import { useNfcSseStatus } from '../../hooks/useNfcSseStatus.js';
|
|
7
7
|
import { getBaseURL } from '../../services/api.js';
|
|
8
8
|
import { resetNfcScan } from '../../services/sessionService.js';
|
|
9
|
-
import { isMobileDevice } from '../../utils/isMobileDevice.js';
|
|
9
|
+
import { isMobileDevice, isIOSDevice } from '../../utils/isMobileDevice.js';
|
|
10
10
|
import MobilePageLayout from '../ui/MobilePageLayout.js';
|
|
11
11
|
import Button from '../ui/Button.js';
|
|
12
12
|
import PageActions from '../ui/PageActions.js';
|
|
@@ -14,6 +14,12 @@ import Title from '../ui/Title.js';
|
|
|
14
14
|
import Subtitle from '../ui/Subtitle.js';
|
|
15
15
|
import NfcSuccessAnimation from './NfcSuccessAnimation.js';
|
|
16
16
|
|
|
17
|
+
// App native NFC iOS « ID Scan ». Safari n'ouvre jamais l'app via une redirection JS ni via un
|
|
18
|
+
// Universal Link du même domaine que la page (Apple TN3155) : on passe par le schéma custom déclaré
|
|
19
|
+
// par l'app, sur un geste utilisateur. L'hôte du lien = backend Datakeen, que l'app utilise pour
|
|
20
|
+
// choisir l'environnement (même rôle que `intent://<host>/scan/<id>` sur Android).
|
|
21
|
+
var IOS_APP_SCHEME = "datakeenscannerswift";
|
|
22
|
+
var IOS_APP_STORE_URL = "https://apps.apple.com/app/id6762505375";
|
|
17
23
|
var NfcScanStep = function (_a) {
|
|
18
24
|
var sessionId = _a.sessionId, nodeId = _a.nodeId, stepObject = _a.stepObject, template = _a.template, pageTitle = _a.pageTitle, pageDescription = _a.pageDescription, onBack = _a.onBack, onContinueWithoutChip = _a.onContinueWithoutChip;
|
|
19
25
|
var t = useI18n().t;
|
|
@@ -23,11 +29,26 @@ var NfcScanStep = function (_a) {
|
|
|
23
29
|
var _d = useState(false), isResetting = _d[0], setIsResetting = _d[1];
|
|
24
30
|
// Sur mobile, on ouvre directement l'app de scan au lieu d'afficher un QR code inutile.
|
|
25
31
|
var isMobile = useMemo(function () { return isMobileDevice(); }, []);
|
|
32
|
+
var isIOS = useMemo(function () { return isIOSDevice(); }, []);
|
|
26
33
|
var nfcUrl = useMemo(function () {
|
|
27
34
|
var origin = typeof window !== "undefined" ? window.location.origin : "";
|
|
28
35
|
return "".concat(origin, "/scan/").concat(sessionId);
|
|
29
36
|
}, [sessionId]);
|
|
30
37
|
var apiBaseUrl = useMemo(function () { return getBaseURL(); }, []);
|
|
38
|
+
var iosAppLinks = useMemo(function () {
|
|
39
|
+
var host = new URL(apiBaseUrl, window.location.href).host;
|
|
40
|
+
return {
|
|
41
|
+
open: "".concat(IOS_APP_SCHEME, "://").concat(host, "/scan/").concat(sessionId),
|
|
42
|
+
// Lu par le bouton « Reprendre ma session » de l'app au premier lancement après installation.
|
|
43
|
+
resume: "https://".concat(host, "/scan/").concat(sessionId),
|
|
44
|
+
};
|
|
45
|
+
}, [apiBaseUrl, sessionId]);
|
|
46
|
+
// Best effort : l'App Store ne transmet aucun contexte à l'app installée (pas d'équivalent du
|
|
47
|
+
// Play Install Referrer), le presse-papier sert de relais. Échec silencieux (permission refusée).
|
|
48
|
+
var copyResumeLink = function () {
|
|
49
|
+
var _a;
|
|
50
|
+
(_a = navigator.clipboard) === null || _a === void 0 ? void 0 : _a.writeText(iosAppLinks.resume).catch(function () { return undefined; });
|
|
51
|
+
};
|
|
31
52
|
var status = useNfcSseStatus({
|
|
32
53
|
sessionId: sessionId,
|
|
33
54
|
apiBaseUrl: apiBaseUrl,
|
|
@@ -46,13 +67,15 @@ var NfcScanStep = function (_a) {
|
|
|
46
67
|
// Sur mobile : ouvrir directement l'application de scan (pas de QR à se scanner soi-même).
|
|
47
68
|
// Une seule fois, tant que le scan n'a pas progressé. Le SSE reste actif pour faire avancer
|
|
48
69
|
// le parcours quand la lecture est terminée.
|
|
70
|
+
// Pas sur iOS : la redirection resterait dans Safari et quitterait la page (SSE fermé, parcours
|
|
71
|
+
// bloqué). On y affiche des boutons à la place, et la page reste ouverte pendant le scan.
|
|
49
72
|
var hasRedirectedRef = useRef(false);
|
|
50
73
|
useEffect(function () {
|
|
51
|
-
if (!isMobile || status !== "pending" || hasRedirectedRef.current)
|
|
74
|
+
if (!isMobile || isIOS || status !== "pending" || hasRedirectedRef.current)
|
|
52
75
|
return;
|
|
53
76
|
hasRedirectedRef.current = true;
|
|
54
77
|
window.location.assign(nfcUrl);
|
|
55
|
-
}, [isMobile, status, nfcUrl]);
|
|
78
|
+
}, [isMobile, isIOS, status, nfcUrl]);
|
|
56
79
|
useEffect(function () {
|
|
57
80
|
if (isMobile)
|
|
58
81
|
return; // pas de QR sur mobile (redirection directe)
|
|
@@ -137,7 +160,7 @@ var NfcScanStep = function (_a) {
|
|
|
137
160
|
return (jsx(MobilePageLayout, { footer: footer, children: jsx("div", { className: "px-4 py-6 pt-11 md:px-8 md:py-8", children: jsxs("div", { className: "w-full max-w-md mx-auto space-y-6", children: [jsxs("div", { className: "text-center space-y-4 mt-16 md:mt-20", children: [jsx(Title, { className: "text-xl md:text-2xl lg:text-3xl", children: title }), jsx(Subtitle, { className: "text-sm md:text-base text-gray-600 leading-relaxed whitespace-pre-line", children: subtitle })] }), jsx("div", { className: "flex-1 flex flex-col justify-center items-center gap-4", children: status === "success" ? (jsx(NfcSuccessAnimation, {})) : status === "completed" ? (jsxs("div", { className: "flex flex-col items-center gap-4 p-8", children: [jsx("div", { className: "animate-spin w-10 h-10 border-2 border-gray-300 border-t-blue-500 rounded-full" }), jsx("p", { className: "text-sm text-gray-600", children: t("nfc_scan.verifying", "Vérification en cours…") })] })) : status === "failed" ? (jsxs("div", { className: "flex flex-col items-center gap-2 p-8 text-center", children: [jsx("p", { className: "text-base font-semibold text-red-600", children: t("nfc_scan.failed_title", "Le scan n'a pas pu être vérifié") }), jsx("p", { className: "text-sm text-gray-600 leading-relaxed", children: t("nfc_scan.failed_description", "Après plusieurs tentatives, la lecture de votre document n'a pas pu être validée.") }), resetError && (jsx("p", { className: "text-sm text-red-600", role: "alert", children: resetError }))] })) : (
|
|
138
161
|
// Contenu principal QR/mobile — reste monté pour "pending" ET "opened", pour ne
|
|
139
162
|
// jamais démonter le canvas déjà généré.
|
|
140
|
-
jsxs(Fragment, { children: [qrError ? (jsx("div", { className: "text-red-500 text-center p-4", children: qrError })) : isMobile ? (jsxs("div", { className: "flex flex-col items-center gap-4 p-8 text-center", children: [jsx("div", { className: "animate-spin w-10 h-10 border-2 border-gray-300 border-t-blue-500 rounded-full" }), jsx("p", { className: "text-sm text-gray-600", children: t("nfc_scan.opening_app", "Ouverture de l'application…") }), jsx("a", { href: nfcUrl, className: "text-sm font-medium text-blue-600 underline", children: t("nfc_scan.open_app_link", "Ouvrir l'application") })] })) : (jsx("div", { className: "bg-white p-4 rounded-lg shadow-lg border-2 border-gray-200", children: jsx("canvas", { ref: canvasRef }) })), status === "opened" && (jsxs("div", { className: "text-center space-y-1", children: [jsx("p", { className: "text-sm font-semibold", style: { color: "var(--uni-secondary-color, #0a9983)" }, children: t("nfc_scan.qr_opened_title", "QR code scanné") }), jsx("p", { className: "text-xs text-gray-500 leading-relaxed", children: t("nfc_scan.qr_opened_description", "L'expérience NFC est ouverte sur votre smartphone. Continuez le scan sur votre téléphone.") }), jsxs("div", { className: "flex items-center justify-center gap-2 text-xs text-gray-500 pt-1", children: [jsx("span", { className: "h-2 w-2 rounded-full animate-pulse", style: { backgroundColor: "var(--uni-primary-color, #11e5c5)" } }), t("nfc_scan.waiting_completion", "En attente de la lecture NFC")] })] }))] })) })] }) }) }));
|
|
163
|
+
jsxs(Fragment, { children: [qrError ? (jsx("div", { className: "text-red-500 text-center p-4", children: qrError })) : isIOS ? (jsxs("div", { className: "flex w-full flex-col items-center gap-3 p-4 text-center", children: [jsx("a", { href: iosAppLinks.open, className: "flex h-[48px] w-full items-center justify-center rounded-[12px] text-sm font-medium hover:brightness-110 active:brightness-90", style: { backgroundColor: "var(--dk-btn-bg)", color: "var(--dk-btn-text)" }, children: t("nfc_scan.open_app_link", "Ouvrir l'application") }), jsx("a", { href: IOS_APP_STORE_URL, onClick: copyResumeLink, className: "flex h-[48px] w-full items-center justify-center rounded-[12px] border border-gray-300 text-sm font-medium text-gray-600 hover:bg-black/5", children: t("nfc_scan.install_app_link", "Installer l'application") }), jsx("p", { className: "text-xs text-gray-500 leading-relaxed", children: t("nfc_scan.install_app_hint", "Après l'installation, revenez sur cette page et appuyez sur « Ouvrir l'application ».") })] })) : isMobile ? (jsxs("div", { className: "flex flex-col items-center gap-4 p-8 text-center", children: [jsx("div", { className: "animate-spin w-10 h-10 border-2 border-gray-300 border-t-blue-500 rounded-full" }), jsx("p", { className: "text-sm text-gray-600", children: t("nfc_scan.opening_app", "Ouverture de l'application…") }), jsx("a", { href: nfcUrl, className: "text-sm font-medium text-blue-600 underline", children: t("nfc_scan.open_app_link", "Ouvrir l'application") })] })) : (jsx("div", { className: "bg-white p-4 rounded-lg shadow-lg border-2 border-gray-200", children: jsx("canvas", { ref: canvasRef }) })), status === "opened" && (jsxs("div", { className: "text-center space-y-1", children: [jsx("p", { className: "text-sm font-semibold", style: { color: "var(--uni-secondary-color, #0a9983)" }, children: t("nfc_scan.qr_opened_title", "QR code scanné") }), jsx("p", { className: "text-xs text-gray-500 leading-relaxed", children: t("nfc_scan.qr_opened_description", "L'expérience NFC est ouverte sur votre smartphone. Continuez le scan sur votre téléphone.") }), jsxs("div", { className: "flex items-center justify-center gap-2 text-xs text-gray-500 pt-1", children: [jsx("span", { className: "h-2 w-2 rounded-full animate-pulse", style: { backgroundColor: "var(--uni-primary-color, #11e5c5)" } }), t("nfc_scan.waiting_completion", "En attente de la lecture NFC")] })] }))] })) })] }) }) }));
|
|
141
164
|
};
|
|
142
165
|
|
|
143
166
|
export { NfcScanStep as default };
|
package/dist/esm/i18n/en.json.js
CHANGED
|
@@ -1011,6 +1011,9 @@ var signature = {
|
|
|
1011
1011
|
token_error: "Error loading signature session."
|
|
1012
1012
|
};
|
|
1013
1013
|
var nfc_scan = {
|
|
1014
|
+
open_app_link: "Open the app",
|
|
1015
|
+
install_app_link: "Install the app",
|
|
1016
|
+
install_app_hint: "Once installed, come back to this page and tap “Open the app”.",
|
|
1014
1017
|
page_title: "NFC scan of your document",
|
|
1015
1018
|
page_description: "Scan the QR code below with your smartphone to read the electronic chip of your identity document.",
|
|
1016
1019
|
verifying: "Verifying…",
|
package/dist/esm/i18n/es.json.js
CHANGED
|
@@ -1011,6 +1011,9 @@ var signature = {
|
|
|
1011
1011
|
token_error: "Error al cargar la sesión de firma."
|
|
1012
1012
|
};
|
|
1013
1013
|
var nfc_scan = {
|
|
1014
|
+
open_app_link: "Abrir la aplicación",
|
|
1015
|
+
install_app_link: "Instalar la aplicación",
|
|
1016
|
+
install_app_hint: "Después de instalarla, vuelve a esta página y pulsa «Abrir la aplicación».",
|
|
1014
1017
|
page_title: "Escaneo NFC de tu documento",
|
|
1015
1018
|
page_description: "Escanea el código QR siguiente con tu smartphone para leer el chip electrónico de tu documento de identidad.",
|
|
1016
1019
|
verifying: "Verificando…",
|
package/dist/esm/i18n/fr.json.js
CHANGED
|
@@ -1011,6 +1011,9 @@ var signature = {
|
|
|
1011
1011
|
token_error: "Erreur lors du chargement de la session de signature."
|
|
1012
1012
|
};
|
|
1013
1013
|
var nfc_scan = {
|
|
1014
|
+
open_app_link: "Ouvrir l'application",
|
|
1015
|
+
install_app_link: "Installer l'application",
|
|
1016
|
+
install_app_hint: "Après l'installation, revenez sur cette page et appuyez sur « Ouvrir l'application ».",
|
|
1014
1017
|
page_title: "Scan NFC de votre document",
|
|
1015
1018
|
page_description: "Scannez le QR code ci-dessous avec votre smartphone pour lire la puce électronique de votre document d'identité.",
|
|
1016
1019
|
verifying: "Vérification en cours…",
|
|
@@ -12,8 +12,7 @@ function getSessionCredential(sessionId) {
|
|
|
12
12
|
function credentialForRequest(url) {
|
|
13
13
|
if (/^[a-z]+:/i.test(url) || url.startsWith('//'))
|
|
14
14
|
return undefined;
|
|
15
|
-
|
|
16
|
-
var match = url.match(/^\/?session\/(?:sdk|unissey)\/([^/?]+)/);
|
|
15
|
+
var match = url.match(/^\/?session\/sdk\/([^/?]+)/);
|
|
17
16
|
return match ? credentials.get(decodeURIComponent(match[1])) : undefined;
|
|
18
17
|
}
|
|
19
18
|
|
|
@@ -10,5 +10,15 @@ var isMobileDevice = function () {
|
|
|
10
10
|
return false;
|
|
11
11
|
return /Android|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i.test(navigator.userAgent);
|
|
12
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* iPhone / iPad (UA historique) / iPod. Sur iOS, Safari n'ouvre pas l'app NFC via une redirection
|
|
15
|
+
* JS ni via un Universal Link du même domaine : l'étape NFC doit proposer un bouton (geste
|
|
16
|
+
* utilisateur) vers le schéma custom de l'app.
|
|
17
|
+
*/
|
|
18
|
+
var isIOSDevice = function () {
|
|
19
|
+
if (typeof navigator === "undefined")
|
|
20
|
+
return false;
|
|
21
|
+
return /iPhone|iPad|iPod/i.test(navigator.userAgent);
|
|
22
|
+
};
|
|
13
23
|
|
|
14
|
-
export { isMobileDevice };
|
|
24
|
+
export { isIOSDevice, isMobileDevice };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "datakeen-session-react",
|
|
3
|
-
"version": "1.1.200
|
|
3
|
+
"version": "1.1.200",
|
|
4
4
|
"description": "React SDK component to manage and render Datakeen session experiences easily.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -74,7 +74,6 @@
|
|
|
74
74
|
"@types/react": "^18.0.0",
|
|
75
75
|
"@types/react-dom": "^18.0.0",
|
|
76
76
|
"@types/testing-library__react": "^10.0.1",
|
|
77
|
-
"@types/ua-parser-js": "0.7.39",
|
|
78
77
|
"autoprefixer": "^10.4.21",
|
|
79
78
|
"axios": "^1.12.0",
|
|
80
79
|
"babel-jest": "^30.1.2",
|
|
@@ -121,7 +120,7 @@
|
|
|
121
120
|
"react-day-picker": "^9.14.0",
|
|
122
121
|
"react-i18next": "^15.7.3",
|
|
123
122
|
"tailwind-merge": "^3.3.1",
|
|
124
|
-
"ua-parser-js": "^
|
|
123
|
+
"ua-parser-js": "^2.0.5"
|
|
125
124
|
},
|
|
126
125
|
"peerDependencies": {
|
|
127
126
|
"axios": "^1.8.0",
|