From c47aa4e182c397eeb6881e48e4e6c18c8c7a0473 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 3 Feb 2025 17:24:16 +0100 Subject: [PATCH] Add loading indicator to contentscript (#541) --- .../chrome/src/contentScript.ts | 174 +++++++++++------- 1 file changed, 106 insertions(+), 68 deletions(-) diff --git a/browser-extensions/chrome/src/contentScript.ts b/browser-extensions/chrome/src/contentScript.ts index bb622594a..992e73926 100644 --- a/browser-extensions/chrome/src/contentScript.ts +++ b/browser-extensions/chrome/src/contentScript.ts @@ -37,6 +37,25 @@ const ICON_HTML = ` `; +const getLoadingHtml = (message: string): string => ` +
+ + + + + ${message} +
+`; + +// Declare handleClickOutside at module scope +let handleClickOutside: (event: MouseEvent) => void; + /** * Check if the current theme is dark. */ @@ -139,7 +158,7 @@ function filterCredentials(credentials: Credential[], currentUrl: string, pageTi * Create auto-fill popup */ function createPopup(input: HTMLInputElement, credentials: Credential[]) : void { - // Remove existing popup if any + // Remove existing popup and its event listeners removeExistingPopup(); const popup = document.createElement('div'); @@ -270,71 +289,73 @@ function createPopup(input: HTMLInputElement, credentials: Credential[]) : void New Alias `; createButton.addEventListener('click', async () => { - // TODO: Implement new credential popup if necessary? otherwise remove if simple implementation is sufficient for now. - // chrome.runtime.sendMessage({ type: 'OPEN_NEW_CREDENTIAL' }); - // removeExistingPopup(); + // Show loading state + popup.innerHTML = getLoadingHtml('Creating new identity...'); - // Retrieve default email domain from background - const response = await new Promise<{ domain: string }>((resolve) => { - chrome.runtime.sendMessage({ type: 'GET_DEFAULT_EMAIL_DOMAIN' }, resolve); - }); + try { + // Retrieve default email domain from background + const response = await new Promise<{ domain: string }>((resolve) => { + chrome.runtime.sendMessage({ type: 'GET_DEFAULT_EMAIL_DOMAIN' }, resolve); + }); - const domain = response.domain; + const domain = response.domain; - // Generate new identity locally - const identityGenerator = new IdentityGeneratorEn(); - const identity = await identityGenerator.generateRandomIdentity(); + // Generate new identity locally + const identityGenerator = new IdentityGeneratorEn(); + const identity = await identityGenerator.generateRandomIdentity(); - const passwordGenerator = new PasswordGenerator(); - const password = passwordGenerator.generateRandomPassword(); + const passwordGenerator = new PasswordGenerator(); + const password = passwordGenerator.generateRandomPassword(); - // Extract favicon from page and get the bytes - const favicon = document.querySelector('link[rel="icon"], link[rel="shortcut icon"]') as HTMLLinkElement; - let faviconBytes: ArrayBuffer | null = null; + // Extract favicon from page and get the bytes + const favicon = document.querySelector('link[rel="icon"], link[rel="shortcut icon"]') as HTMLLinkElement; + let faviconBytes: ArrayBuffer | null = null; - if (favicon?.href) { - try { - const response = await fetch(favicon.href); - faviconBytes = await response.arrayBuffer(); - } catch (error) { - console.error('Error fetching favicon:', error); + if (favicon?.href) { + try { + const response = await fetch(favicon.href); + faviconBytes = await response.arrayBuffer(); + } catch (error) { + console.error('Error fetching favicon:', error); + } } + + // Submit new identity to backend to persist in db + const credential: Credential = { + Id: '', + ServiceName: document.title, + ServiceUrl: window.location.href, + Email: `${identity.emailPrefix}@${domain}`, + Logo: faviconBytes ? new Uint8Array(faviconBytes) : undefined, + Username: identity.nickName, + Password: password, + Notes: '', + Alias: { + FirstName: identity.firstName, + LastName: identity.lastName, + NickName: identity.nickName, + BirthDate: identity.birthDate.toISOString(), + Gender: identity.gender, + Email: `${identity.emailPrefix}@${domain}` + } + }; + + chrome.runtime.sendMessage({ type: 'CREATE_IDENTITY', credential }, () => { + // Refresh the popup to show new identity + showCredentialPopup(input); + }); + } catch (error) { + console.error('Error creating identity:', error); + // Show error state in popup + popup.innerHTML = ` +
+ Failed to create identity. Please try again. +
+ `; + setTimeout(() => { + removeExistingPopup(); + }, 2000); } - - // Submit new identity to backend to persist in db - const credential: Credential = { - Id: '', - ServiceName: document.title, - ServiceUrl: window.location.href, - Email: `${identity.emailPrefix}@${domain}`, - Logo: faviconBytes ? new Uint8Array(faviconBytes) : undefined, - Username: identity.nickName, - Password: password, - Notes: '', - Alias: { - FirstName: identity.firstName, - LastName: identity.lastName, - NickName: identity.nickName, - BirthDate: identity.birthDate.toISOString(), - Gender: identity.gender, - Email: `${identity.emailPrefix}@${domain}` - } - }; - - chrome.runtime.sendMessage({ type: 'CREATE_IDENTITY', credential }); - - // create alert with confirmation message - alert(` - New identity created successfully! - First name: ${identity.firstName} - Last name: ${identity.lastName} - Gender: ${identity.gender} - Email: ${identity.emailPrefix}@${domain} - Password: ${password} - `); - - // Refresh the popup to show new identity - showCredentialPopup(input); }); // Search button @@ -383,20 +404,35 @@ function createPopup(input: HTMLInputElement, credentials: Credential[]) : void }); popup.appendChild(closeButton); - /** - * Add click outside handler - * @param event - */ - const handleClickOutside = (event: MouseEvent) : void => { - if (!popup.contains(event.target as Node)) { - removeExistingPopup(); + // Define handleClickOutside + handleClickOutside = (event: MouseEvent) : void => { + const popup = document.getElementById('aliasvault-credential-popup'); + const target = event.target as Node; + + // If popup doesn't exist, remove the listener + if (!popup) { document.removeEventListener('mousedown', handleClickOutside); + return; } + + // Ignore clicks on the popup and its children + if (popup.contains(target)) { + return; + } + + // Check if click target is an input field + const inputFields = document.querySelectorAll('input'); + for (const input of inputFields) { + if (input.contains(target)) { + return; + } + } + + removeExistingPopup(); }; - setTimeout(() => { - document.addEventListener('mousedown', handleClickOutside); - }, 100); + // Add the event listener for clicking outside + document.addEventListener('mousedown', handleClickOutside); // Position popup below input const rect = input.getBoundingClientRect(); @@ -523,6 +559,8 @@ function createStatusPopup(input: HTMLInputElement, message: string): void { function removeExistingPopup() : void { const existing = document.getElementById('aliasvault-credential-popup'); if (existing) { + // Remove the mousedown event listener before removing the popup + document.removeEventListener('mousedown', handleClickOutside); existing.remove(); } }