From 8e698a21fa69b382ffb9dbf8fd3223b24613d2e5 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Thu, 6 Mar 2025 18:47:16 +0100 Subject: [PATCH] Add all dependencies, refactor messaging (#581) --- browser-extension/package-lock.json | 70 +++++++++++++- browser-extension/package.json | 13 +-- .../src/entrypoints/background.ts | 52 +++------- .../src/entrypoints/background/ContextMenu.ts | 13 +-- .../background/PopupMessageHandler.ts | 43 +++++---- .../background/VaultMessageHandler.ts | 85 ++++++++--------- browser-extension/src/entrypoints/content.ts | 75 ++++++++++++++- .../src/entrypoints/contentScript/Popup.ts | 94 +++++++++---------- .../src/utils/types/messaging/BoolResponse.ts | 4 + .../types/messaging/CredentialsResponse.ts | 7 ++ .../messaging/DefaultEmailDomainResponse.ts | 5 + .../utils/types/messaging/VaultResponse.ts | 7 ++ 12 files changed, 289 insertions(+), 179 deletions(-) create mode 100644 browser-extension/src/utils/types/messaging/BoolResponse.ts create mode 100644 browser-extension/src/utils/types/messaging/CredentialsResponse.ts create mode 100644 browser-extension/src/utils/types/messaging/DefaultEmailDomainResponse.ts create mode 100644 browser-extension/src/utils/types/messaging/VaultResponse.ts diff --git a/browser-extension/package-lock.json b/browser-extension/package-lock.json index c3873a599..d320641ab 100644 --- a/browser-extension/package-lock.json +++ b/browser-extension/package-lock.json @@ -15,7 +15,8 @@ "react-dom": "^19.0.0", "react-router-dom": "^7.1.4", "secure-remote-password": "github:LinusU/secure-remote-password#73e5f732b6ca0cdbdc19da1a0c5f48cdbad2cbf0", - "sql.js": "^1.12.0" + "sql.js": "^1.12.0", + "webext-bridge": "^6.0.1" }, "devDependencies": { "@types/chrome": "^0.0.280", @@ -6057,6 +6058,15 @@ "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1" } }, + "node_modules/nanoevents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/nanoevents/-/nanoevents-6.0.2.tgz", + "integrity": "sha512-FRS2otuFcPPYDPYViNWQ42+1iZqbXydinkRHTHFxrF4a1CpBfmydR9zkI44WSXAXCyPrkcGtPk5CnpW6Y3lFKQ==", + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", @@ -7862,6 +7872,33 @@ "node": ">=10" } }, + "node_modules/serialize-error": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-9.1.1.tgz", + "integrity": "sha512-6uZQLGyUkNA4N+Zii9fYukmNu9PEA1F5rqcwXzN/3LtBjwl2dFBbVZ1Zyn08/CGkB4H440PIemdOQBt1Wvjbrg==", + "license": "MIT", + "dependencies": { + "type-fest": "^2.5.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-error/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/set-cookie-parser": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", @@ -8523,6 +8560,12 @@ "dev": true, "license": "MIT" }, + "node_modules/tiny-uid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tiny-uid/-/tiny-uid-1.1.2.tgz", + "integrity": "sha512-0beRFXR+fv4C40ND2PqgNjq6iyB1dKXciKJjslLw0kPYCcR82aNd2b+Tt2yy06LimIlvtoehgvrm/fUZCutSfg==", + "license": "MIT" + }, "node_modules/tinyexec": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", @@ -9143,6 +9186,31 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/webext-bridge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webext-bridge/-/webext-bridge-6.0.1.tgz", + "integrity": "sha512-GruIrN+vNwbxVCi8UW4Dqk5YkcGA9V0ZfJ57jXP9JXHbrsDs5k2N6NNYQR5e+wSCnQpGYOGAGihwUpKlhg8QIw==", + "license": "MIT", + "dependencies": { + "@types/webextension-polyfill": "^0.8.3", + "nanoevents": "^6.0.2", + "serialize-error": "^9.0.0", + "tiny-uid": "^1.1.1", + "webextension-polyfill": "^0.9.0" + } + }, + "node_modules/webext-bridge/node_modules/@types/webextension-polyfill": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@types/webextension-polyfill/-/webextension-polyfill-0.8.3.tgz", + "integrity": "sha512-GN+Hjzy9mXjWoXKmaicTegv3FJ0WFZ3aYz77Wk8TMp1IY3vEzvzj1vnsa0ggV7vMI1i+PUxe4qqnIJKCzf9aTg==", + "license": "MIT" + }, + "node_modules/webext-bridge/node_modules/webextension-polyfill": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.9.0.tgz", + "integrity": "sha512-LTtHb0yR49xa9irkstDxba4GATDAcDw3ncnFH9RImoFwDlW47U95ME5sn5IiQX2ghfaECaf6xyXM8yvClIBkkw==", + "license": "MPL-2.0" + }, "node_modules/webextension-polyfill": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.12.0.tgz", diff --git a/browser-extension/package.json b/browser-extension/package.json index 050fed7a1..cb95b7c63 100644 --- a/browser-extension/package.json +++ b/browser-extension/package.json @@ -15,25 +15,26 @@ "postinstall": "wxt prepare" }, "dependencies": { - "react": "^19.0.0", - "react-dom": "^19.0.0", "argon2-browser": "^1.18.0", "buffer": "^6.0.3", + "react": "^19.0.0", + "react-dom": "^19.0.0", "react-router-dom": "^7.1.4", "secure-remote-password": "github:LinusU/secure-remote-password#73e5f732b6ca0cdbdc19da1a0c5f48cdbad2cbf0", - "sql.js": "^1.12.0" + "sql.js": "^1.12.0", + "webext-bridge": "^6.0.1" }, "devDependencies": { "@types/chrome": "^0.0.280", "@types/react": "^19.0.1", "@types/react-dom": "^19.0.2", "@wxt-dev/module-react": "^1.1.2", - "typescript": "^5.6.3", - "wxt": "^0.19.13", "autoprefixer": "^10.4.20", "jsdom": "^26.0.0", "postcss": "^8.5.1", "tailwindcss": "^3.4.17", - "vite-plugin-static-copy": "^2.2.0" + "typescript": "^5.6.3", + "vite-plugin-static-copy": "^2.2.0", + "wxt": "^0.19.13" } } diff --git a/browser-extension/src/entrypoints/background.ts b/browser-extension/src/entrypoints/background.ts index a9117ec9a..1ac07a2e7 100644 --- a/browser-extension/src/entrypoints/background.ts +++ b/browser-extension/src/entrypoints/background.ts @@ -1,4 +1,5 @@ import { browser } from "wxt/browser"; +import { onMessage } from "webext-bridge/background"; import { setupContextMenus, handleContextMenuClick } from './background/ContextMenu'; import { handleClearVault, handleCreateIdentity, handleGetCredentials, handleGetDefaultEmailDomain, handleGetDerivedKey, handleGetVault, handleStoreVault, handleSyncVault } from './background/VaultMessageHandler'; import { handleOpenPopup, handlePopupWithCredential } from './background/PopupMessageHandler'; @@ -9,45 +10,16 @@ export default defineBackground({ setupContextMenus(); browser.contextMenus.onClicked.addListener(handleContextMenuClick as any); - // Listen for messages from popup - browser.runtime.onMessage.addListener((message: any, sender: any) => { - switch (message.type) { - // Vault-related messages - case 'STORE_VAULT': - return handleStoreVault(message, sender); - - case 'SYNC_VAULT': - return handleSyncVault(sender); - - case 'GET_VAULT': - return handleGetVault(sender); - - case 'CLEAR_VAULT': - return handleClearVault(sender); - - case 'GET_CREDENTIALS': - return handleGetCredentials(sender); - - case 'CREATE_IDENTITY': - return handleCreateIdentity(message, sender); - - case 'GET_DEFAULT_EMAIL_DOMAIN': - return handleGetDefaultEmailDomain(sender); - - case 'GET_DERIVED_KEY': - return handleGetDerivedKey(sender); - - // Popup-related messages - case 'OPEN_POPUP': - return handleOpenPopup(message, sender); - - case 'OPEN_POPUP_WITH_CREDENTIAL': - return handlePopupWithCredential(message, sender); - - default: - console.error(`Unknown message type: ${message.type}`); - return; - } - }); + // Listen for messages using webext-bridge + onMessage('STORE_VAULT', ({ data }) => handleStoreVault(data)); + onMessage('SYNC_VAULT', () => handleSyncVault()); + onMessage('GET_VAULT', () => handleGetVault()); + onMessage('CLEAR_VAULT', () => handleClearVault()); + onMessage('GET_CREDENTIALS', () => handleGetCredentials()); + onMessage('CREATE_IDENTITY', ({ data }) => handleCreateIdentity(data)); + onMessage('GET_DEFAULT_EMAIL_DOMAIN', () => handleGetDefaultEmailDomain()); + onMessage('GET_DERIVED_KEY', () => handleGetDerivedKey()); + onMessage('OPEN_POPUP', () => handleOpenPopup()); + onMessage('OPEN_POPUP_WITH_CREDENTIAL', ({ data }) => handlePopupWithCredential(data)); } }); \ No newline at end of file diff --git a/browser-extension/src/entrypoints/background/ContextMenu.ts b/browser-extension/src/entrypoints/background/ContextMenu.ts index cd5d601e7..268a72a24 100644 --- a/browser-extension/src/entrypoints/background/ContextMenu.ts +++ b/browser-extension/src/entrypoints/background/ContextMenu.ts @@ -1,3 +1,4 @@ +import { sendMessage } from 'webext-bridge/background'; import { PasswordGenerator } from '../../utils/generators/Password/PasswordGenerator'; /** @@ -63,16 +64,8 @@ export function handleContextMenuClick(info: chrome.contextMenus.OnClickData, ta }, (results) => { const elementIdentifier = results[0]?.result; if (elementIdentifier) { - // Then send message to content script with proper error handling - chrome.tabs.sendMessage( - tab.id, - { - type: 'OPEN_ALIASVAULT_POPUP', - elementIdentifier - } - ).catch(error => { - console.error('Error sending message to content script:', error); - }); + // Then send message to content script. + sendMessage('OPEN_AUTOFILL_POPUP', { elementIdentifier }, 'content-script'); } }); } diff --git a/browser-extension/src/entrypoints/background/PopupMessageHandler.ts b/browser-extension/src/entrypoints/background/PopupMessageHandler.ts index 3c0f43c4d..5802f6546 100644 --- a/browser-extension/src/entrypoints/background/PopupMessageHandler.ts +++ b/browser-extension/src/entrypoints/background/PopupMessageHandler.ts @@ -1,29 +1,34 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ - +import { browser } from "wxt/browser"; +import { BoolResponse } from '../../utils/types/messaging/BoolResponse'; /** * Handle opening the popup. */ -export function handleOpenPopup(message: any, sendResponse: (response: any) => void) : void { - chrome.windows.create({ - url: chrome.runtime.getURL('index.html?mode=inline_unlock'), - type: 'popup', - width: 400, - height: 600, - focused: true - }); - sendResponse({ success: true }); +export function handleOpenPopup() : Promise { + return (async () => { + browser.windows.create({ + url: browser.runtime.getURL('/popup.html?mode=inline_unlock'), + type: 'popup', + width: 400, + height: 600, + focused: true + }); + return { success: true }; + })(); } /** * Handle opening the popup with a credential. */ -export function handlePopupWithCredential(message: any, sendResponse: (response: any) => void) : void { - chrome.windows.create({ - url: chrome.runtime.getURL(`index.html?popup=true#/credentials/${message.credentialId}`), - type: 'popup', - width: 400, - height: 600, - focused: true - }); - sendResponse({ success: true }); +export function handlePopupWithCredential(message: any) : Promise { + return (async () => { + browser.windows.create({ + url: browser.runtime.getURL(`/popup.html#/credentials/${message.credentialId}`), + type: 'popup', + width: 400, + height: 600, + focused: true + }); + return { success: true }; + })(); } \ No newline at end of file diff --git a/browser-extension/src/entrypoints/background/VaultMessageHandler.ts b/browser-extension/src/entrypoints/background/VaultMessageHandler.ts index 2cb73c4de..0c14423e2 100644 --- a/browser-extension/src/entrypoints/background/VaultMessageHandler.ts +++ b/browser-extension/src/entrypoints/background/VaultMessageHandler.ts @@ -3,18 +3,20 @@ import EncryptionUtility from '../../utils/EncryptionUtility'; import SqliteClient from '../../utils/SqliteClient'; import { WebApiService } from '../../utils/WebApiService'; import { Vault } from '../../utils/types/webapi/Vault'; -import { Credential } from '../../utils/types/Credential'; import { VaultResponse } from '../../utils/types/webapi/VaultResponse'; import { VaultPostResponse } from '../../utils/types/webapi/VaultPostResponse'; import { storage } from 'wxt/storage'; +import { BoolResponse as messageBoolResponse } from '../../utils/types/messaging/BoolResponse'; +import { VaultResponse as messageVaultResponse } from '../../utils/types/messaging/VaultResponse'; +import { CredentialsResponse as messageCredentialsResponse } from '../../utils/types/messaging/CredentialsResponse'; +import { DefaultEmailDomainResponse as messageDefaultEmailDomainResponse } from '../../utils/types/messaging/DefaultEmailDomainResponse'; /** * Store the vault in browser storage. */ export async function handleStoreVault( message: any, - sendResponse: (response: any) => void -) : Promise { + ) : Promise { try { const vaultResponse = message.vaultResponse as VaultResponse; const encryptedVaultBlob = vaultResponse.vault.blob; @@ -28,10 +30,10 @@ export async function handleStoreVault( { key: 'session:vaultRevisionNumber', value: vaultResponse.vault.currentRevisionNumber } ]); - sendResponse({ success: true }); + return { success: true }; } catch (error) { console.error('Failed to store vault:', error); - sendResponse({ success: false, error: 'Failed to store vault' }); + return { success: false, error: 'Failed to store vault' }; } } @@ -39,14 +41,12 @@ export async function handleStoreVault( * Sync the vault with the server to check if a newer vault is available. If so, the vault will be updated. */ export async function handleSyncVault( - sendResponse: (response: any) => void -) : Promise { + ) : Promise { const webApi = new WebApiService(() => {}); const statusResponse = await webApi.getStatus(); const statusError = webApi.validateStatusResponse(statusResponse); if (statusError !== null) { - sendResponse({ success: false, error: statusError }); - return; + return { success: false, error: statusError }; } const vaultRevisionNumber = await storage.getItem('session:vaultRevisionNumber') as number; @@ -64,15 +64,14 @@ export async function handleSyncVault( ]); } - sendResponse({ success: true }); + return { success: true }; } /** * Get the vault from browser storage. */ export async function handleGetVault( - sendResponse: (response: any) => void -) : Promise { + ) : Promise { try { const encryptedVault = await storage.getItem('session:encryptedVault') as string; const derivedKey = await storage.getItem('session:derivedKey') as string; @@ -82,8 +81,7 @@ export async function handleGetVault( if (!encryptedVault) { console.error('Vault not available'); - sendResponse({ vault: null }); - return; + return { success: false, error: 'Vault not available' }; } const decryptedVault = await EncryptionUtility.symmetricDecrypt( @@ -91,15 +89,16 @@ export async function handleGetVault( derivedKey ); - sendResponse({ + return { + success: true, vault: decryptedVault, publicEmailDomains: publicEmailDomains ?? [], privateEmailDomains: privateEmailDomains ?? [], vaultRevisionNumber: vaultRevisionNumber ?? 0 - }); + }; } catch (error) { console.error('Failed to get vault:', error); - sendResponse({ vault: null, error: 'Failed to get vault' }); + return { success: false, error: 'Failed to get vault' }; } } @@ -107,8 +106,7 @@ export async function handleGetVault( * Clear the vault from browser storage. */ export function handleClearVault( - sendResponse: (response: any) => void -) : void { + ) : messageBoolResponse { storage.removeItems([ 'session:encryptedVault', 'session:derivedKey', @@ -116,30 +114,29 @@ export function handleClearVault( 'session:privateEmailDomains', 'session:vaultRevisionNumber' ]); - sendResponse({ success: true }); + + return { success: true }; } /** * Get all credentials. */ export async function handleGetCredentials( - sendResponse: (response: any) => void -) : Promise { + ) : Promise { // Get derived key from chrome.storage.session. const derivedKey = await storage.getItem('session:derivedKey') as string; if (!derivedKey) { - sendResponse({ credentials: [], status: 'LOCKED' }); - return; + return { success: false, error: 'Vault is locked' }; } try { const sqliteClient = await createVaultSqliteClient(); const credentials = sqliteClient.getAllCredentials(); - sendResponse({ credentials: credentials, status: 'OK' }); + return { success: true, credentials: credentials }; } catch (error) { console.error('Error getting credentials:', error); - sendResponse({ credentials: [], status: 'LOCKED', error: 'Failed to get credentials' }); + return { success: false, error: 'Failed to get credentials' }; } } @@ -147,15 +144,13 @@ export async function handleGetCredentials( * Create an identity. */ export async function handleCreateIdentity( - message: { credential: Credential }, - sendResponse: (response: any) => void -) : Promise { + message: any, + ) : Promise { // Get derived key from chrome.storage.session. const derivedKey = await storage.getItem('session:derivedKey') as string; if (!derivedKey) { - sendResponse({ success: false, error: 'Vault is locked' }); - return; + return { success: false, error: 'Vault is locked' }; } try { @@ -167,10 +162,10 @@ export async function handleCreateIdentity( // Upload the new vault to the server. await uploadNewVaultToServer(sqliteClient); - sendResponse({ success: true }); + return { success: true }; } catch (error) { console.error('Failed to create identity:', error); - sendResponse({ success: false, error: 'Failed to create identity' }); + return { success: false, error: 'Failed to create identity' }; } } @@ -201,10 +196,8 @@ export async function getEmailAddressesForVault( * Get default email domain for a vault. */ export function handleGetDefaultEmailDomain( - sendResponse: (response: any) => void -) : void { - // Wrap async operations in an IIFE - (async () => { + ) : Promise { + return (async () => { try { const privateEmailDomains = await storage.getItem('session:privateEmailDomains') as string[]; const publicEmailDomains = await storage.getItem('session:publicEmailDomains') as string[]; @@ -225,31 +218,28 @@ export function handleGetDefaultEmailDomain( // First check if the default domain that is configured in the vault is still valid. if (defaultEmailDomain && isValidDomain(defaultEmailDomain)) { - sendResponse({ domain: defaultEmailDomain }); - return; + return { success: true, domain: defaultEmailDomain }; } // If default domain is not valid, fall back to first available private domain. const firstPrivate = privateEmailDomains.find(isValidDomain); if (firstPrivate) { - sendResponse({ domain: firstPrivate }); - return; + return { success: true, domain: firstPrivate }; } // Return first valid public domain if no private domains are available. const firstPublic = publicEmailDomains.find(isValidDomain); if (firstPublic) { - sendResponse({ domain: firstPublic }); - return; + return { success: true, domain: firstPublic }; } // Return null if no valid domains are found - sendResponse({ domain: null }); + return { success: true }; } catch (error) { console.error('Error getting default email domain:', error); - sendResponse({ domain: null, error: 'Failed to get default email domain' }); + return { success: false, error: 'Failed to get default email domain' }; } })(); } @@ -258,11 +248,10 @@ export function handleGetDefaultEmailDomain( * Get the derived key for the encrypted vault. */ export async function handleGetDerivedKey( - sendResponse: (response: any) => void -) : Promise { + ) : Promise { // Get derived key from chrome.storage.session. const derivedKey = await storage.getItem('session:derivedKey') as string; - sendResponse(derivedKey ?? null); + return { success: true, domain: derivedKey ?? null }; } /** diff --git a/browser-extension/src/entrypoints/content.ts b/browser-extension/src/entrypoints/content.ts index 264a52820..fbcb70881 100644 --- a/browser-extension/src/entrypoints/content.ts +++ b/browser-extension/src/entrypoints/content.ts @@ -1,6 +1,73 @@ +import { FormDetector } from '../utils/formDetector/FormDetector'; +import { isAutoShowPopupDisabled, openAutofillPopup, removeExistingPopup } from './contentScript/Popup'; +import { canShowPopup, injectIcon } from './contentScript/Form'; +import { sendMessage, onMessage } from "webext-bridge/content-script"; + export default defineContentScript({ - matches: ['*://*.google.com/*'], - main() { - console.log('Hello content.'); + matches: [''], + main(ctx) { + // Listen for input field focus + document.addEventListener('focusin', async (e) => { + if (ctx.isInvalid) { + return; + } + + const target = e.target as HTMLInputElement; + const textInputTypes = ['text', 'email', 'tel', 'password', 'search', 'url']; + + if (target.tagName === 'INPUT' && + textInputTypes.includes(target.type) && + !target.dataset.aliasvaultIgnore) { + const formDetector = new FormDetector(document, target); + + if (!formDetector.containsLoginForm()) return; + + injectIcon(target); + + const isDisabled = await isAutoShowPopupDisabled(); + const canShow = canShowPopup(); + + // Only show popup if it's not disabled and the popup can be shown + if (!isDisabled && canShow) { + openAutofillPopup(target); + } + } + }); + + // Listen for popstate events (back/forward navigation) + window.addEventListener('popstate', () => { + if (ctx.isInvalid) { + return; + } + + removeExistingPopup(); + }); + + // Listen for messages from the background script + onMessage('OPEN_AUTOFILL_POPUP', async (message: any) => { + const { data, sender } = message; + const { elementIdentifier } = data; + + if (!elementIdentifier) { + return { success: false, error: 'No element identifier provided' }; + } + + const target = document.getElementById(elementIdentifier) || + document.getElementsByName(elementIdentifier)[0]; + + if (!(target instanceof HTMLInputElement)) { + return { success: false, error: 'Target element is not an input field' }; + } + + const formDetector = new FormDetector(document, target); + + if (!formDetector.containsLoginForm(true)) { + return { success: false, error: 'No form found' }; + } + + injectIcon(target); + openAutofillPopup(target); + return { success: true }; + }); }, -}); +}); \ No newline at end of file diff --git a/browser-extension/src/entrypoints/contentScript/Popup.ts b/browser-extension/src/entrypoints/contentScript/Popup.ts index 1097e892d..36159eb2e 100644 --- a/browser-extension/src/entrypoints/contentScript/Popup.ts +++ b/browser-extension/src/entrypoints/contentScript/Popup.ts @@ -4,20 +4,16 @@ import { fillCredential } from './Form'; import { filterCredentials } from './Filter'; import { IdentityGeneratorEn } from '../../utils/generators/Identity/implementations/IdentityGeneratorEn'; import { PasswordGenerator } from '../../utils/generators/Password/PasswordGenerator'; +import { browser } from "wxt/browser"; +import { storage } from "wxt/storage"; +import { sendMessage, onMessage } from "webext-bridge/content-script"; +import { CredentialsResponse } from '@/utils/types/messaging/CredentialsResponse'; /** * Placeholder base64 image for credentials without a logo. */ const placeholderBase64 = 'UklGRjoEAABXRUJQVlA4IC4EAAAwFwCdASqAAIAAPpFCm0olo6Ihp5IraLASCWUA0eb/0s56RrLtCnYfLPiBshdXWMx8j1Ez65f169iA4xUDBTEV6ylMQeCIj2b7RngGi7gKZ9WjKdSoy9R8JcgOmjCMlDmLG20KhNo/i/Dc/Ah5GAvGfm8kfniV3AkR6fxN6eKwjDc6xrDgSfS48G5uGV6WzQt24YAVlLSK9BMwndzfHnePK1KFchFrL7O3ulB8cGNCeomu4o+l0SrS/JKblJ4WTzj0DAD++lCUEouSfgRKdiV2TiYCD+H+l3tANKSPQFPQuzi7rbvxqGeRmXB9kDwURaoSTTpYjA9REMUi9uA6aV7PWtBNXgUzMLowYMZeos6Xvyhb34GmufswMHA5ZyYpxzjTphOak4ZjNOiz8aScO5ygiTx99SqwX/uL+HSeVOSraHw8IymrMwm+jLxqN8BS8dGcItLlm/ioulqH2j4V8glDgSut+ExkxiD7m8TGPrrjCQNJbRDzpOFsyCyfBZupvp8QjGKW2KGziSZeIWes4aTB9tRmeEBhnUrmTDZQuXcc67Fg82KHrSfaeeOEq6jjuUjQ8wUnzM4Zz3dhrwSyslVz/WvnKqYkr4V/TTXPFF5EjF4rM1bHZ8bK63EfTnK41+n3n4gEFoYP4mXkNH0hntnYcdTqiE7Gn+q0BpRRxnkpBSZlA6Wa70jpW0FGqkw5e591A5/H+OV+60WAo+4Mi+NlsKrvLZ9EiVaPnoEFZlJQx1fA777AJ2MjXJ4KSsrWDWJi1lE8yPs8V6XvcC0chDTYt8456sKXAagCZyY+fzQriFMaddXyKQdG8qBqcdYjAsiIcjzaRFBBoOK9sU+sFY7N6B6+xtrlu3c37rQKkI3O2EoiJOris54EjJ5OFuumA0M6riNUuBf/MEPFBVx1JRcUEs+upEBsCnwYski7FT3TTqHrx7v5AjgFN97xhPTkmVpu6sxRnWBi1fxIRp8eWZeFM6mUcGgVk1WeVb1yhdV9hoMo2TsNEPE0tHo/wvuSJSzbZo7wibeXM9v/rRfKcx7X93rfiXVnyQ9f/5CaAQ4lxedPp/6uzLtOS4FyL0bCNeZ6L5w+AiuyWCTDFIYaUzhwfG+/YTQpWyeZCdQIKzhV+3GeXI2cxoP0ER/DlOKymf1gm+zRU3sqf1lBVQ0y+mK/Awl9bS3uaaQmI0FUyUwHUKP7PKuXnO+LcwDv4OfPT6hph8smc1EtMe5ib/apar/qZ9dyaEaElALJ1KKxnHziuvVl8atk1fINSQh7OtXDyqbPw9o/nGIpTnv5iFmwmWJLis2oyEgPkJqyx0vYI8rjkVEzKc8eQavAJBYSpjMwM193Swt+yJyjvaGYWPnqExxKiNarpB2WSO7soCAZXhS1uEYHryrK47BH6W1dRiruqT0xpLih3MXiwU3VDwAAAA=='; -/** - * Response from the background script. - */ -type CredentialResponse = { - status: 'OK' | 'LOCKED'; - credentials?: Credential[]; -} - /** * Create basic popup with default style. */ @@ -266,13 +262,10 @@ export function createAutofillPopup(input: HTMLInputElement, credentials: Creden try { // Sync with api to ensure we have the latest vault. - await chrome.runtime.sendMessage({ type: 'SYNC_VAULT' }); + await browser.runtime.sendMessage({ type: 'SYNC_VAULT' }); // Retrieve default email domain from background - const response = await new Promise<{ domain: string }>((resolve) => { - chrome.runtime.sendMessage({ type: 'GET_DEFAULT_EMAIL_DOMAIN' }, resolve); - }); - + const response = await browser.runtime.sendMessage({ type: 'GET_DEFAULT_EMAIL_DOMAIN' }) as { domain: string }; const domain = response.domain; // Generate new identity locally @@ -337,13 +330,14 @@ export function createAutofillPopup(input: HTMLInputElement, credentials: Creden } }; - chrome.runtime.sendMessage({ type: 'CREATE_IDENTITY', credential }, () => { - // Close popup. - removeExistingPopup(); + // Create identity in background. + await browser.runtime.sendMessage({ type: 'CREATE_IDENTITY', credential }); - // Fill the form with the new identity immediately. - fillCredential(credential, input); - }); + // Close popup. + removeExistingPopup(); + + // Fill the form with the new identity immediately. + fillCredential(credential, input); } catch (error) { console.error('Error creating identity:', error); loadingPopup.innerHTML = ` @@ -417,15 +411,15 @@ export function createAutofillPopup(input: HTMLInputElement, credentials: Creden // Handle search input. let searchTimeout: NodeJS.Timeout; - searchInput.addEventListener('input', () => { + searchInput.addEventListener('input', async () => { clearTimeout(searchTimeout); const searchTerm = searchInput.value.toLowerCase(); - chrome.runtime.sendMessage({ type: 'GET_CREDENTIALS' }, (response: CredentialResponse) => { - if (response.status === 'OK' && response.credentials) { - // Ensure we have unique credentials - const uniqueCredentials = Array.from(new Map(response.credentials.map(cred => [cred.Id, cred])).values()); - let filteredCredentials; + const response = await browser.runtime.sendMessage({ type: 'GET_CREDENTIALS' }) as CredentialsResponse; + if (response.success && response.credentials) { + // Ensure we have unique credentials + const uniqueCredentials = Array.from(new Map(response.credentials.map(cred => [cred.Id, cred])).values()); + let filteredCredentials; if (searchTerm === '') { // If search is empty, use original URL-based filtering @@ -463,9 +457,8 @@ export function createAutofillPopup(input: HTMLInputElement, credentials: Creden } // Update popup content with filtered results - updatePopupContent(filteredCredentials, credentialList, input); - } - }); + updatePopupContent(filteredCredentials, credentialList, input); + } }); // Close button @@ -575,7 +568,7 @@ export function createVaultLockedPopup(input: HTMLInputElement): void { // Make the whole popup clickable to open the main extension login popup. popup.addEventListener('click', () => { - chrome.runtime.sendMessage({ type: 'OPEN_POPUP' }); + sendMessage('OPEN_POPUP', {}, 'background'); removeExistingPopup(); }); @@ -781,10 +774,7 @@ function createCredentialList(credentials: Credential[], input: HTMLInputElement // Handle popout click popoutIcon.addEventListener('click', (e) => { e.stopPropagation(); // Prevent credential fill - chrome.runtime.sendMessage({ - type: 'OPEN_POPUP_WITH_CREDENTIAL', - credentialId: cred.Id - }); + sendMessage('OPEN_POPUP_WITH_CREDENTIAL', { credentialId: cred.Id }, 'background'); removeExistingPopup(); }); @@ -829,30 +819,29 @@ function createCredentialList(credentials: Credential[], input: HTMLInputElement return elements; } -export const DISABLED_SITES_KEY = 'aliasvault_disabled_sites'; -export const GLOBAL_POPUP_ENABLED_KEY = 'aliasvault_global_popup_enabled'; +export const DISABLED_SITES_KEY = 'local:aliasvault_disabled_sites'; +export const GLOBAL_POPUP_ENABLED_KEY = 'local:aliasvault_global_popup_enabled'; /** * Check if auto-popup is disabled for current site */ export async function isAutoShowPopupDisabled(): Promise { - const settings = await chrome.storage.local.get([DISABLED_SITES_KEY, GLOBAL_POPUP_ENABLED_KEY]); - const disabledUrls = settings[DISABLED_SITES_KEY] ?? []; - const isGloballyEnabled = settings[GLOBAL_POPUP_ENABLED_KEY] !== false; + const disabledSites = await storage.getItem(DISABLED_SITES_KEY) as string[] ?? []; + const globalPopupEnabled = await storage.getItem(GLOBAL_POPUP_ENABLED_KEY) ?? true; + const currentHostname = window.location.hostname; - return !isGloballyEnabled || disabledUrls.includes(currentHostname); + return !globalPopupEnabled || disabledSites.includes(currentHostname); } /** * Disable auto-popup for current site */ export async function disableAutoShowPopup(): Promise { - const result = await chrome.storage.local.get(DISABLED_SITES_KEY); - const disabledSites = result[DISABLED_SITES_KEY] ?? []; + const disabledSites = await storage.getItem(DISABLED_SITES_KEY) as string[] ?? []; if (!disabledSites.includes(window.location.hostname)) { disabledSites.push(window.location.hostname); - await chrome.storage.local.set({ [DISABLED_SITES_KEY]: disabledSites }); + await storage.setItem(DISABLED_SITES_KEY, disabledSites); } } @@ -1066,17 +1055,20 @@ export function openAutofillPopup(input: HTMLInputElement) : void { }; document.addEventListener('keydown', handleEnterKey); - chrome.runtime.sendMessage({ type: 'GET_CREDENTIALS' }, (response: CredentialResponse) => { - switch (response.status) { - case 'OK': - createAutofillPopup(input, response.credentials); - break; + (async () => { + const response = await sendMessage( + "GET_CREDENTIALS", + { }, + "background" + ) as CredentialsResponse; - case 'LOCKED': - createVaultLockedPopup(input); - break; + console.log('response', response); + if (response.success) { + createAutofillPopup(input, response.credentials); + } else { + createVaultLockedPopup(input); } - }); + })(); } /** diff --git a/browser-extension/src/utils/types/messaging/BoolResponse.ts b/browser-extension/src/utils/types/messaging/BoolResponse.ts new file mode 100644 index 000000000..aa51d281a --- /dev/null +++ b/browser-extension/src/utils/types/messaging/BoolResponse.ts @@ -0,0 +1,4 @@ +export type BoolResponse = { + success: boolean, + error?: string +}; \ No newline at end of file diff --git a/browser-extension/src/utils/types/messaging/CredentialsResponse.ts b/browser-extension/src/utils/types/messaging/CredentialsResponse.ts new file mode 100644 index 000000000..a751a0cee --- /dev/null +++ b/browser-extension/src/utils/types/messaging/CredentialsResponse.ts @@ -0,0 +1,7 @@ +import { Credential } from "../Credential"; + +export type CredentialsResponse = { + success: boolean, + error?: string, + credentials?: Credential[] +}; diff --git a/browser-extension/src/utils/types/messaging/DefaultEmailDomainResponse.ts b/browser-extension/src/utils/types/messaging/DefaultEmailDomainResponse.ts new file mode 100644 index 000000000..48c0e2a67 --- /dev/null +++ b/browser-extension/src/utils/types/messaging/DefaultEmailDomainResponse.ts @@ -0,0 +1,5 @@ +export type DefaultEmailDomainResponse = { + success: boolean, + error?: string, + domain?: string +}; diff --git a/browser-extension/src/utils/types/messaging/VaultResponse.ts b/browser-extension/src/utils/types/messaging/VaultResponse.ts new file mode 100644 index 000000000..e57e54265 --- /dev/null +++ b/browser-extension/src/utils/types/messaging/VaultResponse.ts @@ -0,0 +1,7 @@ +export type VaultResponse = { + success: boolean, error?: string, + vault?: string, + publicEmailDomains?: string[], + privateEmailDomains?: string[], + vaultRevisionNumber?: number +};