diff --git a/.coderabbit.yaml b/.coderabbit.yaml index af6a93bc6..f00627118 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -1,16 +1,19 @@ # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json language: "en-US" -tone_instructions: | - You are an expert reviewer for a React Native and TypeScript mobile app with smart contract integration. - Focus on security, performance, and best practices. - Only highlight issues of medium or higher priority. +tone_instructions: "Only report bugs, security vulnerabilities, data loss, or production incidents. No nitpicks, style suggestions, missing comments, optional improvements, or test mock wiring. When unsure, do not post." reviews: profile: "chill" request_changes_workflow: false high_level_summary: true - poem: true + poem: false + sequence_diagrams: false + related_issues: false + related_prs: false + suggested_labels: false + suggested_reviewers: false + enable_prompt_for_ai_agents: false review_status: true auto_review: enabled: true @@ -20,77 +23,22 @@ reviews: github-checks: timeout_ms: 300000 path_instructions: - - path: "app/src/**/*.{ts,tsx,js,jsx}" - instructions: | - Review React Native TypeScript code for: - - Component architecture and reusability - - State management patterns - - Performance optimizations - - TypeScript type safety - - React hooks usage and dependencies - - Navigation patterns - path: "contracts/**/*.sol" instructions: | - Review Solidity smart contracts for: - - Security vulnerabilities (reentrancy, overflow, etc.) - - Gas optimization opportunities - - Access control patterns - - Event emission for important state changes - - Code documentation and NatSpec comments + Focus exclusively on security vulnerabilities (reentrancy, overflow, access control) + and correctness bugs. Ignore style, gas optimization suggestions, and missing NatSpec. - path: "circuits/**/*.circom" instructions: | - Review ZK circuit code for: - - Circuit correctness and completeness - - Constraint efficiency - - Input validation - - Security considerations for zero-knowledge proofs + Focus on circuit correctness, constraint soundness, and input validation bugs only. - path: "noir/**/*.nr" instructions: | - Review Noir circuits for: - - Constraint and witness correctness - - Efficient proof generation and verification - - Soundness and security assumptions - - Clear separation of public and private inputs - - path: "**/*.{test,spec}.{ts,js,tsx,jsx}" - instructions: | - Review test files for: - - Test coverage completeness - - Test case quality and edge cases - - Mock usage appropriateness - - Test readability and maintainability - - path: "common/src/**/*.{ts,tsx,js,jsx}" - instructions: | - Review shared utilities for: - - Reusability and modular design - - Type safety and error handling - - Side-effect management - - Documentation and naming clarity - - path: "sdk/**/*.{ts,tsx,js,jsx}" - instructions: | - Review SDK packages for: - - Public API design and stability - - Asynchronous flows and error handling - - Security and input validation - - Compatibility across environments - - path: "packages/mobile-sdk-alpha/**/*.{ts,tsx,js,jsx}" - instructions: | - Review alpha mobile SDK code for: - - API consistency with core SDK - - Platform-neutral abstractions - - Performance considerations - - Clear experimental notes or TODOs - - path: "app/android/**/*" - instructions: | - Review Android-specific code for: - - Platform-specific implementations - - Performance considerations - - Security best practices for mobile + Focus on constraint correctness, soundness bugs, and public/private input misuse only. - path: "app/ios/**/*" instructions: | - Review iOS-specific code for: - - Platform-specific implementations - - Performance considerations - - Security best practices for mobile + Focus on security issues (keychain misuse, insecure storage) and crash-causing bugs only. + - path: "app/android/**/*" + instructions: | + Focus on security issues and crash-causing bugs only. chat: auto_reply: true diff --git a/.gitignore b/.gitignore index af43f4f99..ed55852af 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,5 @@ contracts/broadcast/ # Keep RN test app config files tracked (global gitignore may ignore *.config.*) !packages/rn-sdk-test-app/metro.config.cjs !packages/rn-sdk-test-app/react-native.config.cjs +packages/native-shell-android/.gradle/ +packages/native-shell-android/build/ diff --git a/app/src/hooks/usePendingKycRecovery.ts b/app/src/hooks/usePendingKycRecovery.ts index 72da3b1d3..f308e2dc1 100644 --- a/app/src/hooks/usePendingKycRecovery.ts +++ b/app/src/hooks/usePendingKycRecovery.ts @@ -8,6 +8,18 @@ import { useDiditWebSocket } from '@/hooks/useDiditWebSocket'; import { navigationRef } from '@/navigation'; import { usePendingKycStore } from '@/stores/pendingKycStore'; +type RecoveryVerification = { + sessionId?: string; + userId?: string; + status: 'pending' | 'processing' | 'failed'; + timeoutAt: number; + documentId?: string; +}; + +function getRecoveryIdentifier(verification: RecoveryVerification) { + return verification.sessionId ?? verification.userId; +} + /** * Hook to recover pending KYC verifications on app restart. * @@ -65,39 +77,46 @@ export function usePendingKycRecovery() { v.status === 'processing' && v.documentId && v.timeoutAt > Date.now() && - !hasAttemptedRecoveryRef.current.has(v.sessionId), + !!getRecoveryIdentifier(v) && + !hasAttemptedRecoveryRef.current.has(getRecoveryIdentifier(v)!), ); if (processingWithDocument) { + const recoveryId = getRecoveryIdentifier(processingWithDocument); + + if (!recoveryId) { + return; + } + console.log( '[PendingKycRecovery] Resuming processing verification, navigating to KYCVerified:', - processingWithDocument.sessionId, + recoveryId, ); if (navigationRef.isReady()) { navigationRef.navigate('KYCVerified', { documentId: processingWithDocument.documentId, }); // Only mark as attempted after successful navigation - hasAttemptedRecoveryRef.current.add(processingWithDocument.sessionId); + hasAttemptedRecoveryRef.current.add(recoveryId); return; } // Navigation not ready yet - poll until ready console.log( '[PendingKycRecovery] Navigation not ready, polling for readiness:', - processingWithDocument.sessionId, + recoveryId, ); const pollInterval = setInterval(() => { if (navigationRef.isReady()) { console.log( '[PendingKycRecovery] Navigation ready, navigating for:', - processingWithDocument.sessionId, + recoveryId, ); navigationRef.navigate('KYCVerified', { documentId: processingWithDocument.documentId, }); - hasAttemptedRecoveryRef.current.add(processingWithDocument.sessionId); + hasAttemptedRecoveryRef.current.add(recoveryId); clearInterval(pollInterval); } }, 100); // Poll every 100ms @@ -112,16 +131,23 @@ export function usePendingKycRecovery() { v => v.status === 'pending' && v.timeoutAt > Date.now() && - !hasAttemptedRecoveryRef.current.has(v.sessionId), + !!getRecoveryIdentifier(v) && + !hasAttemptedRecoveryRef.current.has(getRecoveryIdentifier(v)!), ); if (firstPending) { - hasAttemptedRecoveryRef.current.add(firstPending.sessionId); + const recoveryId = getRecoveryIdentifier(firstPending); + + if (!recoveryId) { + return; + } + + hasAttemptedRecoveryRef.current.add(recoveryId); console.log( '[PendingKycRecovery] Recovering pending verification:', - firstPending.sessionId, + recoveryId, ); - subscribe(firstPending.sessionId); + subscribe(recoveryId); } }, [pendingVerifications, subscribe, unsubscribeAll]); } diff --git a/app/tests/src/hooks/usePendingKycRecovery.test.ts b/app/tests/src/hooks/usePendingKycRecovery.test.ts index 7c25a6b58..c6c0a9a0f 100644 --- a/app/tests/src/hooks/usePendingKycRecovery.test.ts +++ b/app/tests/src/hooks/usePendingKycRecovery.test.ts @@ -176,6 +176,24 @@ describe('usePendingKycRecovery', () => { expect(mockSubscribe).toHaveBeenCalledWith('session-789'); }); + it('should recover legacy pending verification entries that only store userId', () => { + const { usePendingKycStore } = jest.requireMock('@/stores/pendingKycStore'); + usePendingKycStore.mockReturnValue({ + pendingVerifications: [ + { + userId: 'legacy-session-123', + status: 'pending', + timeoutAt: Date.now() + 10000, + }, + ], + removeExpiredVerifications: mockRemoveExpiredVerifications, + }); + + renderHook(() => usePendingKycRecovery()); + + expect(mockSubscribe).toHaveBeenCalledWith('legacy-session-123'); + }); + it('should skip expired verifications', () => { const { usePendingKycStore } = jest.requireMock('@/stores/pendingKycStore'); usePendingKycStore.mockReturnValue({ diff --git a/common/src/utils/kyc/generateInputs.ts b/common/src/utils/kyc/generateInputs.ts index d33874787..c9c2f9018 100644 --- a/common/src/utils/kyc/generateInputs.ts +++ b/common/src/utils/kyc/generateInputs.ts @@ -251,16 +251,19 @@ export const generateKycRegisterInput = async ( pubkeyStr: [string, string], secret: string ) => { - const applicantInfo = deserializeApplicantInfo(applicantInfoBase64); const signature = deserializeSignature(signatureBase64); const pubkey = [BigInt(pubkeyStr[0]), BigInt(pubkeyStr[1])] as [bigint, bigint]; - const serializedData = serializeKycData(applicantInfo).padEnd(KYC_MAX_LENGTH, '\0'); - - const msgPadded = Array.from(serializedData, (x) => x.charCodeAt(0)); + // Use raw bytes directly — deserialize→reserialize strips the namespace prefix + // from id_type, producing different bytes than the TEE signed. + const raw = Buffer.from(applicantInfoBase64, 'base64'); + const dataPadded = [ + ...Array.from(raw, (b) => Number(b)), + ...new Array(Math.max(0, KYC_MAX_LENGTH - raw.length)).fill(0), + ]; const kycRegisterInput: KycRegisterInput = { - data_padded: msgPadded, + data_padded: dataPadded, s: signature.s, R: signature.R, pubKey: pubkey, diff --git a/new-common/src/circuits/inputs/register-kyc.ts b/new-common/src/circuits/inputs/register-kyc.ts index 9d9feb6c8..1ad160259 100644 --- a/new-common/src/circuits/inputs/register-kyc.ts +++ b/new-common/src/circuits/inputs/register-kyc.ts @@ -1,6 +1,6 @@ import { Base8, inCurve, mulPointEscalar, subOrder } from '@zk-kit/baby-jubjub'; -import { deserializeApplicantInfo, deserializeSignature } from '../../documents/kyc/api.js'; +import { deserializeSignature } from '../../documents/kyc/api.js'; import { KYC_MAX_LENGTH } from '../../documents/kyc/constants.js'; import type { KycRegisterInput } from '../../documents/kyc/types.js'; import { serializeKycData } from '../../documents/kyc/types.js'; @@ -13,15 +13,19 @@ export function generateKycRegisterInputs( pubkeyStr: [string, string], secret: string, ): KycRegisterInput { - const applicantInfo = deserializeApplicantInfo(applicantInfoBase64); const signature = deserializeSignature(signatureBase64); const pubkey = [BigInt(pubkeyStr[0]), BigInt(pubkeyStr[1])] as [bigint, bigint]; - const serializedData = serializeKycData(applicantInfo).padEnd(KYC_MAX_LENGTH, '\0'); - const msgPadded = Array.from(serializedData, x => x.charCodeAt(0)); + // Use raw bytes directly — deserialize→reserialize strips the namespace prefix + // from id_type, producing different bytes than the TEE signed. + const raw = Buffer.from(applicantInfoBase64, 'base64'); + const dataPadded = [ + ...Array.from(raw, b => Number(b)), + ...new Array(Math.max(0, KYC_MAX_LENGTH - raw.length)).fill(0), + ]; return { - data_padded: msgPadded, + data_padded: dataPadded, s: signature.s, R: signature.R, pubKey: pubkey, diff --git a/package.json b/package.json index 8f2e5a65a..df3d45d05 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "name": "self-workspace-root", + "private": true, "workspaces": { "packages": [ "app", diff --git a/packages/native-shell-android/src/main/assets/self-wallet/.gitkeep b/packages/native-shell-android/src/main/assets/self-wallet/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/native-shell-android/src/main/assets/self-wallet/assets/aadhaar-registration-background-CyX4r5V8.png b/packages/native-shell-android/src/main/assets/self-wallet/assets/aadhaar-registration-background-CyX4r5V8.png new file mode 100644 index 000000000..a664b774a Binary files /dev/null and b/packages/native-shell-android/src/main/assets/self-wallet/assets/aadhaar-registration-background-CyX4r5V8.png differ diff --git a/packages/native-shell-android/src/main/assets/self-wallet/assets/affirmative-BixXd3iG.wav b/packages/native-shell-android/src/main/assets/self-wallet/assets/affirmative-BixXd3iG.wav new file mode 100644 index 000000000..a962fb292 Binary files /dev/null and b/packages/native-shell-android/src/main/assets/self-wallet/assets/affirmative-BixXd3iG.wav differ diff --git a/packages/native-shell-android/src/main/assets/self-wallet/assets/dev-mode-B7OFUXG_.png b/packages/native-shell-android/src/main/assets/self-wallet/assets/dev-mode-B7OFUXG_.png new file mode 100644 index 000000000..d6ea3a768 Binary files /dev/null and b/packages/native-shell-android/src/main/assets/self-wallet/assets/dev-mode-B7OFUXG_.png differ diff --git a/packages/native-shell-android/src/main/assets/self-wallet/assets/didit-sdk.esm-D5Sol1U1.js b/packages/native-shell-android/src/main/assets/self-wallet/assets/didit-sdk.esm-D5Sol1U1.js new file mode 100644 index 000000000..ee8ef7e96 --- /dev/null +++ b/packages/native-shell-android/src/main/assets/self-wallet/assets/didit-sdk.esm-D5Sol1U1.js @@ -0,0 +1,246 @@ +/** + * Didit SDK for Web v0.1.8 + * (c) 2026 Didit + * @license MIT + */const c={zIndex:9999,showCloseButton:!0,showExitConfirmation:!0,loggingEnabled:!1},a={overlay:"didit-modal-overlay",container:"didit-modal-container",iframe:"didit-verification-iframe",closeButton:"didit-close-button",loading:"didit-loading",confirmOverlay:"didit-confirm-overlay",confirmBox:"didit-confirm-box",embedded:"didit-embedded"},u=["ar","bg","bn","ca","cnr","cs","da","de","el","en","es","et","fa","fi","fr","he","hi","hr","hu","hy","id","it","ja","ka","ko","lt","lv","mk","ms","nl","no","pl","pt-BR","pt","ro","ru","sk","sl","so","sr","sv","th","tr","uk","uz","vi","zh-CN","zh-TW","zh"];class s{static get isEnabled(){return this._enabled}static set isEnabled(e){this._enabled=e}static log(...e){this._enabled&&console.log("[DiditSDK]",...e)}static warn(...e){this._enabled&&console.warn("[DiditSDK]",...e)}static error(...e){this._enabled&&console.error("[DiditSDK]",...e)}}s._enabled=!1;function E(){return`didit-modal-${Date.now()}-${Math.random().toString(36).substr(2,9)}`}function T(r){try{return new URL(r).hostname.endsWith(".didit.me")}catch{return!1}}function M(r,e){return{type:r,message:e||{sessionExpired:"Your verification session has expired.",networkError:"A network error occurred. Please try again.",cameraAccessDenied:"Camera access is required for verification.",unknown:e||"An unknown error occurred."}[r]}}function D(r){try{const{pathname:n}=new URL(r),t=n.split("/").filter(Boolean)[0];if(t&&u.includes(t))return t}catch{}const e=navigator.language;if(u.includes(e))return e;const i=e.split("-")[0];return u.includes(i)?i:"en"}const w={exitTitle:"Exit verification?",exitMessage:"Exiting will end your verification process. Are you sure?",continueButton:"Continue",exitButton:"Exit",ariaLabelModal:"Didit Verification",ariaLabelClose:"Close verification"},z={ar:{exitTitle:"الخروج من التحقق؟",exitMessage:"سيؤدي الخروج إلى إنهاء عملية التحقق الخاصة بك. هل أنت متأكد؟",continueButton:"متابعة",exitButton:"خروج",ariaLabelModal:"التحقق من Didit",ariaLabelClose:"إغلاق التحقق"},bg:{exitTitle:"Излизане от верификацията?",exitMessage:"Излизането ще прекрати процеса на верификация. Сигурни ли сте?",continueButton:"Продължи",exitButton:"Изход",ariaLabelModal:"Верификация Didit",ariaLabelClose:"Затваряне на верификацията"},bn:{exitTitle:"যাচাইকরণ থেকে বের হবেন?",exitMessage:"বের হলে আপনার যাচাইকরণ প্রক্রিয়া শেষ হয়ে যাবে। আপনি কি নিশ্চিত?",continueButton:"চালিয়ে যান",exitButton:"বের হন",ariaLabelModal:"Didit যাচাইকরণ",ariaLabelClose:"যাচাইকরণ বন্ধ করুন"},ca:{exitTitle:"Sortir de la verificació?",exitMessage:"Sortir finalitzarà el procés de verificació. N'esteu segur?",continueButton:"Continua",exitButton:"Sortir",ariaLabelModal:"Verificació Didit",ariaLabelClose:"Tancar verificació"},cnr:{exitTitle:"Izaći iz verifikacije?",exitMessage:"Izlaskom ćete prekinuti proces verifikacije. Jeste li sigurni?",continueButton:"Nastavi",exitButton:"Izađi",ariaLabelModal:"Didit verifikacija",ariaLabelClose:"Zatvori verifikaciju"},cs:{exitTitle:"Opustit ověření?",exitMessage:"Odchodem ukončíte proces ověření. Jste si jisti?",continueButton:"Pokračovat",exitButton:"Odejít",ariaLabelModal:"Ověření Didit",ariaLabelClose:"Zavřít ověření"},da:{exitTitle:"Forlad verifikation?",exitMessage:"Hvis du forlader, afsluttes din verifikationsproces. Er du sikker?",continueButton:"Fortsæt",exitButton:"Forlad",ariaLabelModal:"Didit-verifikation",ariaLabelClose:"Luk verifikation"},de:{exitTitle:"Verifizierung verlassen?",exitMessage:"Das Verlassen beendet Ihren Verifizierungsprozess. Sind Sie sicher?",continueButton:"Fortfahren",exitButton:"Verlassen",ariaLabelModal:"Didit-Verifizierung",ariaLabelClose:"Verifizierung schließen"},el:{exitTitle:"Έξοδος από την επαλήθευση;",exitMessage:"Η έξοδος θα τερματίσει τη διαδικασία επαλήθευσης. Είστε σίγουροι;",continueButton:"Συνέχεια",exitButton:"Έξοδος",ariaLabelModal:"Επαλήθευση Didit",ariaLabelClose:"Κλείσιμο επαλήθευσης"},en:w,es:{exitTitle:"¿Salir de la verificación?",exitMessage:"Salir terminará tu proceso de verificación. ¿Estás seguro?",continueButton:"Continuar",exitButton:"Salir",ariaLabelModal:"Verificación Didit",ariaLabelClose:"Cerrar verificación"},et:{exitTitle:"Lahkuda kinnitamisest?",exitMessage:"Lahkumine lõpetab teie kinnitamisprotsessi. Kas olete kindel?",continueButton:"Jätka",exitButton:"Lahku",ariaLabelModal:"Didit kinnitus",ariaLabelClose:"Sulge kinnitus"},fa:{exitTitle:"خروج از تأیید هویت؟",exitMessage:"خروج باعث پایان فرآیند تأیید هویت شما میشود. آیا مطمئن هستید؟",continueButton:"ادامه",exitButton:"خروج",ariaLabelModal:"تأیید هویت Didit",ariaLabelClose:"بستن تأیید هویت"},fi:{exitTitle:"Poistu vahvistuksesta?",exitMessage:"Poistuminen päättää vahvistusprosessisi. Oletko varma?",continueButton:"Jatka",exitButton:"Poistu",ariaLabelModal:"Didit-vahvistus",ariaLabelClose:"Sulje vahvistus"},fr:{exitTitle:"Quitter la vérification ?",exitMessage:"Quitter mettra fin à votre processus de vérification. Êtes-vous sûr ?",continueButton:"Continuer",exitButton:"Quitter",ariaLabelModal:"Vérification Didit",ariaLabelClose:"Fermer la vérification"},he:{exitTitle:"לצאת מהאימות?",exitMessage:"יציאה תסיים את תהליך האימות שלך. האם אתה בטוח?",continueButton:"המשך",exitButton:"יציאה",ariaLabelModal:"אימות Didit",ariaLabelClose:"סגירת אימות"},hi:{exitTitle:"सत्यापन से बाहर निकलें?",exitMessage:"बाहर निकलने से आपकी सत्यापन प्रक्रिया समाप्त हो जाएगी। क्या आप सुनिश्चित हैं?",continueButton:"जारी रखें",exitButton:"बाहर निकलें",ariaLabelModal:"Didit सत्यापन",ariaLabelClose:"सत्यापन बंद करें"},hr:{exitTitle:"Izaći iz verifikacije?",exitMessage:"Izlaskom ćete prekinuti proces verifikacije. Jeste li sigurni?",continueButton:"Nastavi",exitButton:"Izađi",ariaLabelModal:"Didit verifikacija",ariaLabelClose:"Zatvori verifikaciju"},hu:{exitTitle:"Kilépés az ellenőrzésből?",exitMessage:"A kilépés befejezi az ellenőrzési folyamatot. Biztos benne?",continueButton:"Folytatás",exitButton:"Kilépés",ariaLabelModal:"Didit ellenőrzés",ariaLabelClose:"Ellenőrzés bezárása"},hy:{exitTitle:"Դուրս գա՞լ ստուգումից",exitMessage:"Դուրս գալը կավարտի ձեր ստուգման գործընթացը։ Համոզված ե՞ք?",continueButton:"Շարունակել",exitButton:"Դուրս գալ",ariaLabelModal:"Didit ստուգում",ariaLabelClose:"Փակել ստուգումը"},id:{exitTitle:"Keluar dari verifikasi?",exitMessage:"Keluar akan mengakhiri proses verifikasi Anda. Apakah Anda yakin?",continueButton:"Lanjutkan",exitButton:"Keluar",ariaLabelModal:"Verifikasi Didit",ariaLabelClose:"Tutup verifikasi"},it:{exitTitle:"Uscire dalla verifica?",exitMessage:"L'uscita terminerà il processo di verifica. Sei sicuro?",continueButton:"Continua",exitButton:"Esci",ariaLabelModal:"Verifica Didit",ariaLabelClose:"Chiudi verifica"},ja:{exitTitle:"認証を終了しますか?",exitMessage:"終了すると認証プロセスが中断されます。よろしいですか?",continueButton:"続ける",exitButton:"終了",ariaLabelModal:"Didit 認証",ariaLabelClose:"認証を閉じる"},ka:{exitTitle:"გამოსვლა შემოწმებიდან?",exitMessage:"გამოსვლა დაასრულებს თქვენს შემოწმების პროცესს. დარწმუნებული ხართ?",continueButton:"გაგრძელება",exitButton:"გამოსვლა",ariaLabelModal:"Didit შემოწმება",ariaLabelClose:"შემოწმების დახურვა"},ko:{exitTitle:"인증을 종료하시겠습니까?",exitMessage:"종료하면 인증 절차가 중단됩니다. 확실하십니까?",continueButton:"계속",exitButton:"종료",ariaLabelModal:"Didit 인증",ariaLabelClose:"인증 닫기"},lt:{exitTitle:"Išeiti iš patvirtinimo?",exitMessage:"Išėjimas nutrauks jūsų patvirtinimo procesą. Ar esate tikri?",continueButton:"Tęsti",exitButton:"Išeiti",ariaLabelModal:"Didit patvirtinimas",ariaLabelClose:"Uždaryti patvirtinimą"},lv:{exitTitle:"Iziet no verifikācijas?",exitMessage:"Iziešana pārtrauks jūsu verifikācijas procesu. Vai esat pārliecināts?",continueButton:"Turpināt",exitButton:"Iziet",ariaLabelModal:"Didit verifikācija",ariaLabelClose:"Aizvērt verifikāciju"},mk:{exitTitle:"Излези од верификацијата?",exitMessage:"Излегувањето ќе го прекине процесот на верификација. Дали сте сигурни?",continueButton:"Продолжи",exitButton:"Излези",ariaLabelModal:"Верификација Didit",ariaLabelClose:"Затвори верификација"},ms:{exitTitle:"Keluar dari pengesahan?",exitMessage:"Keluar akan menamatkan proses pengesahan anda. Adakah anda pasti?",continueButton:"Teruskan",exitButton:"Keluar",ariaLabelModal:"Pengesahan Didit",ariaLabelClose:"Tutup pengesahan"},nl:{exitTitle:"Verificatie verlaten?",exitMessage:"Verlaten beëindigt uw verificatieproces. Weet u het zeker?",continueButton:"Doorgaan",exitButton:"Verlaten",ariaLabelModal:"Didit-verificatie",ariaLabelClose:"Verificatie sluiten"},no:{exitTitle:"Forlat verifisering?",exitMessage:"Å forlate vil avslutte verifiseringsprosessen. Er du sikker?",continueButton:"Fortsett",exitButton:"Forlat",ariaLabelModal:"Didit-verifisering",ariaLabelClose:"Lukk verifisering"},pl:{exitTitle:"Czy wyjść z weryfikacji?",exitMessage:"Wyjście zakończy proces weryfikacji. Czy na pewno?",continueButton:"Kontynuuj",exitButton:"Wyjdź",ariaLabelModal:"Weryfikacja Didit",ariaLabelClose:"Zamknij weryfikację"},"pt-BR":{exitTitle:"Sair da verificação?",exitMessage:"Sair encerrará seu processo de verificação. Tem certeza?",continueButton:"Continuar",exitButton:"Sair",ariaLabelModal:"Verificação Didit",ariaLabelClose:"Fechar verificação"},pt:{exitTitle:"Sair da verificação?",exitMessage:"Sair terminará o seu processo de verificação. Tem a certeza?",continueButton:"Continuar",exitButton:"Sair",ariaLabelModal:"Verificação Didit",ariaLabelClose:"Fechar verificação"},ro:{exitTitle:"Ieși din verificare?",exitMessage:"Ieșirea va încheia procesul de verificare. Ești sigur?",continueButton:"Continuă",exitButton:"Ieși",ariaLabelModal:"Verificare Didit",ariaLabelClose:"Închide verificarea"},ru:{exitTitle:"Выйти из верификации?",exitMessage:"Выход завершит процесс верификации. Вы уверены?",continueButton:"Продолжить",exitButton:"Выйти",ariaLabelModal:"Верификация Didit",ariaLabelClose:"Закрыть верификацию"},sk:{exitTitle:"Opustiť overenie?",exitMessage:"Odchodom ukončíte proces overenia. Ste si istí?",continueButton:"Pokračovať",exitButton:"Odísť",ariaLabelModal:"Overenie Didit",ariaLabelClose:"Zavrieť overenie"},sl:{exitTitle:"Zapustiti preverjanje?",exitMessage:"Izhod bo prekinil postopek preverjanja. Ali ste prepričani?",continueButton:"Nadaljuj",exitButton:"Izhod",ariaLabelModal:"Preverjanje Didit",ariaLabelClose:"Zapri preverjanje"},so:{exitTitle:"Ka baxdo xaqiijinta?",exitMessage:"Ka bixitaanku wuxuu dhammayn doonaa habka xaqiijintaada. Ma hubtaa?",continueButton:"Sii wad",exitButton:"Ka bax",ariaLabelModal:"Xaqiijinta Didit",ariaLabelClose:"Xir xaqiijinta"},sr:{exitTitle:"Изаћи из верификације?",exitMessage:"Изласком ћете прекинути процес верификације. Да ли сте сигурни?",continueButton:"Настави",exitButton:"Изађи",ariaLabelModal:"Верификација Didit",ariaLabelClose:"Затвори верификацију"},sv:{exitTitle:"Lämna verifiering?",exitMessage:"Att lämna avslutar din verifieringsprocess. Är du säker?",continueButton:"Fortsätt",exitButton:"Lämna",ariaLabelModal:"Didit-verifiering",ariaLabelClose:"Stäng verifiering"},th:{exitTitle:"ออกจากการยืนยันตัวตน?",exitMessage:"การออกจะสิ้นสุดกระบวนการยืนยันตัวตนของคุณ คุณแน่ใจหรือไม่?",continueButton:"ดำเนินการต่อ",exitButton:"ออก",ariaLabelModal:"การยืนยันตัวตน Didit",ariaLabelClose:"ปิดการยืนยันตัวตน"},tr:{exitTitle:"Doğrulamadan çıkmak istiyor musunuz?",exitMessage:"Çıkış, doğrulama sürecinizi sonlandıracak. Emin misiniz?",continueButton:"Devam et",exitButton:"Çıkış",ariaLabelModal:"Didit doğrulama",ariaLabelClose:"Doğrulamayı kapat"},uk:{exitTitle:"Вийти з верифікації?",exitMessage:"Вихід завершить процес верифікації. Ви впевнені?",continueButton:"Продовжити",exitButton:"Вийти",ariaLabelModal:"Верифікація Didit",ariaLabelClose:"Закрити верифікацію"},uz:{exitTitle:"Tekshiruvdan chiqasizmi?",exitMessage:"Chiqish tekshiruv jarayonini tugatadi. Ishonchingiz komilmi?",continueButton:"Davom etish",exitButton:"Chiqish",ariaLabelModal:"Didit tekshiruvi",ariaLabelClose:"Tekshiruvni yopish"},vi:{exitTitle:"Thoát khỏi xác minh?",exitMessage:"Thoát sẽ kết thúc quá trình xác minh của bạn. Bạn có chắc không?",continueButton:"Tiếp tục",exitButton:"Thoát",ariaLabelModal:"Xác minh Didit",ariaLabelClose:"Đóng xác minh"},"zh-CN":{exitTitle:"退出验证?",exitMessage:"退出将结束您的验证流程。确定要退出吗?",continueButton:"继续",exitButton:"退出",ariaLabelModal:"Didit 验证",ariaLabelClose:"关闭验证"},"zh-TW":{exitTitle:"退出驗證?",exitMessage:"退出將結束您的驗證流程。確定要退出嗎?",continueButton:"繼續",exitButton:"退出",ariaLabelModal:"Didit 驗證",ariaLabelClose:"關閉驗證"},zh:{exitTitle:"退出验证?",exitMessage:"退出将结束您的验证流程。确定要退出吗?",continueButton:"继续",exitButton:"退出",ariaLabelModal:"Didit 验证",ariaLabelClose:"关闭验证"}};function B(r){return z[r]??w}class j{constructor(e,i){this.state={isOpen:!1,isLoading:!0,showConfirmation:!1},this.overlay=null,this.container=null,this.iframe=null,this.loadingEl=null,this.confirmOverlay=null,this.boundHandleMessage=null,this.boundHandleKeydown=null,this.embedded=!1,this.embeddedContainer=null,this.language="en",this.modalId=E(),this.config={zIndex:(e==null?void 0:e.zIndex)??c.zIndex,showCloseButton:(e==null?void 0:e.showCloseButton)??c.showCloseButton,showExitConfirmation:(e==null?void 0:e.showExitConfirmation)??c.showExitConfirmation},this.callbacks=i,this.containerElement=(e==null?void 0:e.containerElement)??document.body,this.embedded=(e==null?void 0:e.embedded)??!1,this.embedded&&(e!=null&&e.embeddedContainerId)&&(this.embeddedContainer=document.getElementById(e.embeddedContainerId))}injectStyles(){const e="didit-sdk-styles";if(document.getElementById(e))return;const i=document.createElement("style");i.id=e,i.textContent=` + .${a.overlay} { + display: none; + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.6); + z-index: ${this.config.zIndex}; + justify-content: center; + align-items: center; + padding: 1rem; + opacity: 0; + transition: opacity 0.2s ease-out; + } + + .${a.overlay}.active { + display: flex; + opacity: 1; + } + + .${a.container} { + position: relative; + width: 100%; + max-width: 500px; + max-height: 90dvh; + border-radius: 16px; + overflow: hidden; + background: transparent; + } + + .${a.overlay}.active .${a.container} { + transform: scale(1); + } + + .${a.iframe} { + width: 100%; + height: 700px; + border: none; + display: block; + } + + .${a.closeButton} { + position: absolute; + top: 4px; + right: 4px; + width: 24px; + height: 24px; + background: transparent; + border: none; + cursor: pointer; + padding: 0; + z-index: 10; + outline: none; + } + + .${a.closeButton}:hover, + .${a.closeButton}:focus { + background: transparent; + opacity: 0.5; + } + + .${a.closeButton} svg { + stroke: #666; + stroke-width: 2; + stroke-linecap: round; + } + + .${a.loading} { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + background: #fafafa; + z-index: 5; + } + + .${a.loading}.hidden { + display: none; + } + + .${a.loading} svg { + width: 4rem; + height: 4rem; + animation: didit-spin 1s linear infinite; + } + + .${a.loading} circle { + stroke: #e5e5e5; + stroke-width: 2.5; + fill: none; + } + + .${a.loading} path { + stroke: #525252; + stroke-width: 2.5; + stroke-linecap: round; + fill: none; + } + + @keyframes didit-spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } + } + + .${a.confirmOverlay} { + display: none; + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.5); + z-index: 20; + justify-content: center; + align-items: center; + opacity: 0; + transition: opacity 0.15s ease-out; + } + + .${a.confirmOverlay}.active { + display: flex; + opacity: 1; + } + + .${a.confirmBox} { + background: #fff; + border-radius: 12px; + padding: 1.5rem; + text-align: center; + max-width: 300px; + margin: 1rem; + transform: scale(0.95); + transition: transform 0.15s ease-out; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); + } + + .${a.confirmOverlay}.active .${a.confirmBox} { + transform: scale(1); + } + + .${a.confirmBox} h3 { + color: #1a1a2e; + margin: 0 0 0.5rem 0; + font-size: 1.125rem; + font-weight: 600; + } + + .${a.confirmBox} p { + color: #666; + font-size: 0.875rem; + margin: 0 0 1.25rem 0; + line-height: 1.5; + } + + .didit-confirm-actions { + display: flex; + align-items: center; + justify-content: center; + gap: 1rem; + } + + .didit-confirm-actions button { + background: #2563eb; + color: #fff; + border: none; + padding: 0.625rem 1.25rem; + border-radius: 8px; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: background 0.15s ease; + } + + .didit-confirm-actions button:hover { + background: #1d4ed8; + } + + .didit-confirm-actions span { + color: #666; + font-size: 0.875rem; + cursor: pointer; + padding: 0.625rem; + transition: color 0.15s ease; + } + + .didit-confirm-actions span:hover { + color: #1a1a2e; + } + + @media (max-width: 540px) { + .${a.overlay} { + padding: 0; + } + + .${a.container} { + max-width: 100%; + max-height: 100dvh; + border-radius: 0; + } + + .${a.iframe} { + height: 100dvh; + } + } + + .${a.embedded} { + position: relative; + width: 100%; + height: 100%; + } + + .${a.embedded} .${a.iframe} { + width: 100%; + height: 100%; + } + + .${a.embedded} .${a.loading} { + border-radius: 0; + } + `,document.head.appendChild(i)}createDOM(){var i,n;if(this.injectStyles(),this.embedded&&this.embeddedContainer){this.createEmbeddedDOM();return}const e=B(this.language);if(this.overlay=document.createElement("div"),this.overlay.id=this.modalId,this.overlay.className=a.overlay,this.overlay.setAttribute("role","dialog"),this.overlay.setAttribute("aria-modal","true"),this.overlay.setAttribute("aria-label",e.ariaLabelModal),this.container=document.createElement("div"),this.container.className=a.container,this.loadingEl=document.createElement("div"),this.loadingEl.className=a.loading,this.loadingEl.innerHTML=` + + `,this.config.showCloseButton){const t=document.createElement("button");t.className=a.closeButton,t.setAttribute("aria-label",e.ariaLabelClose),t.innerHTML=` + + `,t.addEventListener("click",()=>this.handleCloseRequest()),this.container.appendChild(t)}this.iframe=document.createElement("iframe"),this.iframe.className=a.iframe,this.iframe.setAttribute("allow","camera; microphone; fullscreen; autoplay; encrypted-media; geolocation"),this.iframe.setAttribute("title",e.ariaLabelModal),this.iframe.addEventListener("load",()=>this.handleIframeLoad()),this.confirmOverlay=document.createElement("div"),this.confirmOverlay.className=a.confirmOverlay,this.confirmOverlay.innerHTML=` +
${e.exitMessage}
+${t.exitMessage}
\n