import { create, get, parseCreationOptionsFromJSON, parseRequestOptionsFromJSON } from './shared/webauthn/json-ponyfill.js'; import { switchTab, switchSubTab, toggleSection, initializeNavigationMenu, } from './shared/ui/navigation.js'; import { showInfoPopup, hideInfoPopup, toggleLanguage, toggleJsonEditorExpansion, updateGlobalScrollLock, closeModal, initializeStickyHeader, } from './shared/ui/core.js'; import { updateFieldLabels, randomizeChallenge, randomizePrfEval, randomizeLargeBlobWrite, validatePrfInputs, validateUserIdInput, validateChallengeInputs, validatePrfEvalInputs, validateLargeBlobWriteInput, checkLargeBlobCapability, updateAuthenticationExtensionAvailability } from './advanced/auth/forms.js'; import { resetRegistrationForm, resetAuthenticationForm } from './advanced/ui/resets.js'; import { initializeSimpleUsername, randomizeUserIdentity, randomizeSimpleUsername } from './shared/auth/username.js'; import { simpleRegister, simpleAuthenticate } from './simple/auth-simple.js'; import { advancedRegister, advancedAuthenticate } from './advanced/auth/advanced.js'; import { processCodec, clearCodec, toggleRawCodec, switchCodecMode } from './decoder/codec.js'; import { saveJsonEditor, resetJsonEditor, updateJsonEditor, updateJsonFromForm, editCreateOptions, editAssertOptions, applyJsonChanges, cancelJsonEdit } from './advanced/editor/index.js'; import { loadSavedCredentials, showCredentialDetails, navigateToMdsAuthenticator, closeCredentialModal, closeRegistrationResultModal, closeRegistrationDetailModal, deleteCredential, clearAllCredentials, updateAllowCredentialsDropdown } from './advanced/credentials/index.js'; import { waitForMetadataLoad } from './advanced/mds/index.js'; import { registerHintsChangeCallback } from './advanced/auth/hints.js'; import { handleJsonEditorKeydown } from './advanced/editor/utils.js'; import { createFakeExcludeCredential, removeFakeExcludeCredential, renderFakeExcludeCredentialList, createFakeAllowCredential, removeFakeAllowCredential, renderFakeAllowCredentialList } from './advanced/auth/exclude-credentials.js'; import { initializeAdvancedSettingsNavigation } from './advanced/ui/settings-nav.js'; import { initializeAnalyzeBrowser } from './shared/browser/analyze.js'; import { initializeLoader, loaderComplete, loaderSetPhase, } from './shared/utils/loader.js'; registerHintsChangeCallback(() => updateAllowCredentialsDropdown()); initializeAnalyzeBrowser(); const TEXT_INPUT_TYPES = new Set([ 'text', 'search', 'email', 'url', 'tel', 'password', 'number' ]); // Disable browser spellchecking globally (including dynamically added inputs). const shouldDisableSpellcheck = (element) => { if (element instanceof HTMLTextAreaElement) { return true; } if (element instanceof HTMLInputElement) { return TEXT_INPUT_TYPES.has((element.type || 'text').toLowerCase()); } if (!(element instanceof HTMLElement)) { return false; } if (element.isContentEditable) { return true; } return element.getAttribute('role') === 'textbox'; }; const applySpellcheckAttributes = (element) => { element.setAttribute('spellcheck', 'false'); element.setAttribute('autocorrect', 'off'); element.setAttribute('autocapitalize', 'off'); element.setAttribute('data-gramm', 'false'); element.setAttribute('data-gramm_editor', 'false'); element.setAttribute('data-enable-grammarly', 'false'); if ('spellcheck' in element) { element.spellcheck = false; } }; const disableSpellcheckInTree = (root) => { if (!root) { return; } if (root instanceof Document) { if (root.documentElement) { applySpellcheckAttributes(root.documentElement); } if (root.body) { applySpellcheckAttributes(root.body); } } else if (root instanceof HTMLElement && shouldDisableSpellcheck(root)) { applySpellcheckAttributes(root); } const queryRoot = root instanceof Document || root instanceof DocumentFragment || root instanceof Element ? root : null; if (!queryRoot || typeof queryRoot.querySelectorAll !== 'function') { return; } const candidates = queryRoot.querySelectorAll('input, textarea, [contenteditable], [role="textbox"]'); candidates.forEach((element) => { if (element instanceof HTMLElement && shouldDisableSpellcheck(element)) { applySpellcheckAttributes(element); } }); }; const delay = (ms) => new Promise(resolve => { if (typeof window !== 'undefined' && typeof window.setTimeout === 'function') { window.setTimeout(resolve, ms); return; } setTimeout(resolve, ms); }); function applyInitialFormDefaults() { randomizeUserIdentity(); randomizeChallenge('reg'); randomizeChallenge('auth'); randomizeLargeBlobWrite(); initializeSimpleUsername(); const prfRegFirst = document.getElementById('prf-eval-first-reg'); const prfRegSecond = document.getElementById('prf-eval-second-reg'); const prfAuthFirst = document.getElementById('prf-eval-first-auth'); const prfAuthSecond = document.getElementById('prf-eval-second-auth'); if (prfRegFirst) prfRegFirst.value = ''; if (prfRegSecond) { prfRegSecond.value = ''; prfRegSecond.disabled = true; } if (prfAuthFirst) prfAuthFirst.value = ''; if (prfAuthSecond) { prfAuthSecond.value = ''; prfAuthSecond.disabled = true; } updateJsonEditor(); renderFakeExcludeCredentialList(); renderFakeAllowCredentialList(); updateAuthenticationExtensionAvailability(); } function bindDeferredUiListeners(jsonEditorElement) { const allInputs = document.querySelectorAll('#advanced-tab input, #advanced-tab select, #advanced-tab input[type="checkbox"]'); allInputs.forEach(input => { input.addEventListener('input', updateJsonEditor); input.addEventListener('change', updateJsonEditor); }); const attachmentSelect = document.getElementById('authenticator-attachment'); if (attachmentSelect) { attachmentSelect.addEventListener('change', () => { updateAllowCredentialsDropdown(); }); } const registrationHintCheckboxes = ['hint-client-device', 'hint-hybrid', 'hint-security-key']; registrationHintCheckboxes.forEach(id => { const checkbox = document.getElementById(id); if (checkbox) { checkbox.addEventListener('change', () => { updateAllowCredentialsDropdown(); }); } }); const usernameInput = document.getElementById('user-name'); const displayNameInput = document.getElementById('user-display-name'); if (usernameInput && displayNameInput) { usernameInput.addEventListener('input', () => { displayNameInput.value = usernameInput.value; updateJsonEditor(); }); } const largeBlobSelect = document.getElementById('large-blob-auth'); if (largeBlobSelect) { largeBlobSelect.addEventListener('change', () => { updateAuthenticationExtensionAvailability(); updateJsonEditor(); }); } const credProtectSelect = document.getElementById('cred-protect'); const enforceCredProtectCheckbox = document.getElementById('enforce-cred-protect'); if (credProtectSelect && enforceCredProtectCheckbox) { const handleCredProtectToggle = () => { if (credProtectSelect.value) { enforceCredProtectCheckbox.disabled = false; } else { enforceCredProtectCheckbox.checked = true; enforceCredProtectCheckbox.disabled = true; } }; credProtectSelect.addEventListener('change', handleCredProtectToggle); handleCredProtectToggle(); } const allowCredentialsSelect = document.getElementById('allow-credentials'); if (allowCredentialsSelect) { allowCredentialsSelect.addEventListener('change', () => { updateAuthenticationExtensionAvailability(); updateJsonEditor(); }); } const residentKeySelect = document.getElementById('resident-key'); const largeBlobRegSelect = document.getElementById('large-blob-reg'); if (residentKeySelect && largeBlobRegSelect) { residentKeySelect.addEventListener('change', () => { const residentKey = residentKeySelect.value; if (residentKey !== 'required') { const largeBlobValue = largeBlobRegSelect.value; if (largeBlobValue === 'preferred' || largeBlobValue === 'required') { largeBlobRegSelect.value = ''; } } updateJsonEditor(); }); } const fakeCredentialButton = document.getElementById('fake-cred-generate'); if (fakeCredentialButton) { fakeCredentialButton.addEventListener('click', () => { const lengthInput = document.getElementById('fake-cred-length-reg'); const lengthValue = lengthInput ? lengthInput.value : ''; const created = createFakeExcludeCredential(lengthValue); if (created) { updateJsonEditor(); } }); } const fakeCredentialList = document.getElementById('fake-cred-generated-list'); if (fakeCredentialList) { fakeCredentialList.addEventListener('click', event => { const button = event.target instanceof HTMLElement ? event.target.closest('button[data-fake-credential-index]') : null; if (!button) { return; } event.preventDefault(); const removed = removeFakeExcludeCredential(button.dataset.fakeCredentialIndex); if (removed) { updateJsonEditor(); } }); } const fakeAllowButton = document.getElementById('fake-cred-generate-auth'); if (fakeAllowButton) { fakeAllowButton.addEventListener('click', () => { const lengthInput = document.getElementById('fake-cred-length-auth'); const lengthValue = lengthInput ? lengthInput.value : ''; const created = createFakeAllowCredential(lengthValue); if (created) { updateJsonEditor(); } }); } const fakeAllowList = document.getElementById('fake-cred-auth-generated-list'); if (fakeAllowList) { fakeAllowList.addEventListener('click', event => { const button = event.target instanceof HTMLElement ? event.target.closest('button[data-fake-credential-index]') : null; if (!button) { return; } event.preventDefault(); const removed = removeFakeAllowCredential(button.dataset.fakeCredentialIndex); if (removed) { updateJsonEditor(); } }); } const modals = document.querySelectorAll('.modal'); modals.forEach(modal => { modal.addEventListener('click', (e) => { if (e.target === modal) { closeModal(modal.id); } }); }); const prfFirstInputs = ['prf-eval-first-reg', 'prf-eval-first-auth']; prfFirstInputs.forEach(inputId => { const input = document.getElementById(inputId); if (input) { input.addEventListener('input', () => { const formType = inputId.includes('reg') ? 'reg' : 'auth'; validatePrfInputs(formType); validatePrfEvalInputs(); }); input.addEventListener('change', () => { const formType = inputId.includes('reg') ? 'reg' : 'auth'; validatePrfInputs(formType); validatePrfEvalInputs(); }); } }); const hexInputs = [ 'user-id', 'challenge-reg', 'challenge-auth', 'prf-eval-first-reg', 'prf-eval-second-reg', 'prf-eval-first-auth', 'prf-eval-second-auth', 'large-blob-write' ]; hexInputs.forEach(inputId => { const input = document.getElementById(inputId); if (input) { input.addEventListener('input', () => { if (inputId === 'user-id') validateUserIdInput(); else if (inputId.includes('challenge')) validateChallengeInputs(); else if (inputId.includes('prf-eval')) validatePrfEvalInputs(); else if (inputId === 'large-blob-write') validateLargeBlobWriteInput(); }); input.addEventListener('blur', () => { if (inputId === 'user-id') validateUserIdInput(); else if (inputId.includes('challenge')) validateChallengeInputs(); else if (inputId.includes('prf-eval')) validatePrfEvalInputs(); else if (inputId === 'large-blob-write') validateLargeBlobWriteInput(); }); } }); if (jsonEditorElement) { jsonEditorElement.addEventListener('click', () => { const container = document.getElementById('json-editor-container'); if (container && !container.classList.contains('expanded')) { toggleJsonEditorExpansion(); } }); jsonEditorElement.addEventListener('focus', () => { const container = document.getElementById('json-editor-container'); if (container && !container.classList.contains('expanded')) { toggleJsonEditorExpansion(); } }); jsonEditorElement.addEventListener('keydown', handleJsonEditorKeydown); } const jsonEditorCloseButton = document.getElementById('json-editor-close'); if (jsonEditorCloseButton) { jsonEditorCloseButton.addEventListener('click', () => toggleJsonEditorExpansion(true)); } document.addEventListener('keydown', (event) => { if (event.key === 'Escape') { const container = document.getElementById('json-editor-container'); if (container?.classList.contains('expanded')) { toggleJsonEditorExpansion(true); } } }); } async function runDeferredStartupTasks(jsonEditorElement) { await delay(100); applyInitialFormDefaults(); bindDeferredUiListeners(jsonEditorElement); await delay(100); updateJsonEditor(); await delay(300); checkLargeBlobCapability(); } window.create = create; window.get = get; window.parseCreationOptionsFromJSON = parseCreationOptionsFromJSON; window.parseRequestOptionsFromJSON = parseRequestOptionsFromJSON; window.switchTab = switchTab; window.updateGlobalScrollLock = updateGlobalScrollLock; window.switchSubTab = switchSubTab; window.toggleSection = toggleSection; window.showInfoPopup = showInfoPopup; window.hideInfoPopup = hideInfoPopup; window.toggleLanguage = toggleLanguage; window.randomizeChallenge = randomizeChallenge; window.randomizePrfEval = randomizePrfEval; window.randomizeLargeBlobWrite = randomizeLargeBlobWrite; window.resetRegistrationForm = resetRegistrationForm; window.resetAuthenticationForm = resetAuthenticationForm; window.randomizeUserIdentity = randomizeUserIdentity; window.randomizeSimpleUsername = randomizeSimpleUsername; window.simpleRegister = simpleRegister; window.simpleAuthenticate = simpleAuthenticate; window.advancedRegister = advancedRegister; window.advancedAuthenticate = advancedAuthenticate; window.processCodec = processCodec; window.clearCodec = clearCodec; window.toggleRawCodec = toggleRawCodec; window.clearDecoder = clearCodec; window.toggleRawDecoder = toggleRawCodec; window.closeModal = closeModal; window.switchCodecMode = switchCodecMode; window.saveJsonEditor = saveJsonEditor; window.resetJsonEditor = resetJsonEditor; window.showCredentialDetails = showCredentialDetails; window.navigateToMdsAuthenticator = navigateToMdsAuthenticator; window.closeCredentialModal = closeCredentialModal; window.closeRegistrationResultModal = closeRegistrationResultModal; window.closeRegistrationDetailModal = closeRegistrationDetailModal; window.deleteCredential = deleteCredential; window.clearAllCredentials = clearAllCredentials; window.editCreateOptions = editCreateOptions; window.editAssertOptions = editAssertOptions; window.applyJsonChanges = applyJsonChanges; window.cancelJsonEdit = cancelJsonEdit; document.addEventListener('DOMContentLoaded', () => { void (async () => { initializeLoader(); loaderSetPhase('Loading saved credentials…', { progress: 12 }); try { initializeNavigationMenu(); initializeStickyHeader(); updateFieldLabels(); initializeAdvancedSettingsNavigation(); disableSpellcheckInTree(document); const spellcheckObserver = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node instanceof HTMLElement || node instanceof DocumentFragment) { disableSpellcheckInTree(node); } }); }); }); if (document.body) { spellcheckObserver.observe(document.body, { childList: true, subtree: true }); } const jsonEditorElement = document.getElementById('json-editor'); if (jsonEditorElement) { jsonEditorElement.setAttribute('spellcheck', 'false'); jsonEditorElement.setAttribute('autocorrect', 'off'); jsonEditorElement.setAttribute('autocapitalize', 'off'); jsonEditorElement.setAttribute('autocomplete', 'off'); jsonEditorElement.setAttribute('data-gramm', 'false'); jsonEditorElement.setAttribute('data-gramm_editor', 'false'); jsonEditorElement.setAttribute('data-enable-grammarly', 'false'); } await loadSavedCredentials(); const deferredStartupTasks = runDeferredStartupTasks(jsonEditorElement); let metadataReady = false; try { metadataReady = await waitForMetadataLoad(); } catch (error) { console.error('Failed while waiting for metadata startup to finish.', error); } await deferredStartupTasks; const formFields = [ 'user-name', 'user-display-name', 'attestation', 'user-verification-reg', 'resident-key', 'user-verification-auth' ]; formFields.forEach(fieldId => { const field = document.getElementById(fieldId); if (field) { field.addEventListener('input', updateJsonFromForm); field.addEventListener('change', updateJsonFromForm); } }); loaderSetPhase( metadataReady ? 'Opening workspace…' : 'Opening workspace with limited metadata…', { progress: 96 }, ); loaderComplete({ message: metadataReady ? 'Application ready!' : 'Application ready with limited metadata.', delay: 420, }); } catch (error) { console.error('Application startup failed.', error); loaderSetPhase('Opening workspace with limited startup…', { progress: 96 }); loaderComplete({ message: 'Application ready with partial startup.', delay: 420, }); } })(); });