Add all dependencies, refactor messaging (#581)

This commit is contained in:
Leendert de Borst
2025-03-06 18:47:16 +01:00
parent 28a0c7eb1f
commit 8e698a21fa
12 changed files with 289 additions and 179 deletions

View File

@@ -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",

View File

@@ -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"
}
}

View File

@@ -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));
}
});

View File

@@ -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');
}
});
}

View File

@@ -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<BoolResponse> {
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<BoolResponse> {
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 };
})();
}

View File

@@ -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<void> {
) : Promise<messageBoolResponse> {
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<void> {
) : Promise<messageBoolResponse> {
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<void> {
) : Promise<messageVaultResponse> {
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<void> {
) : Promise<messageCredentialsResponse> {
// 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<void> {
message: any,
) : Promise<messageBoolResponse> {
// 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<messageDefaultEmailDomainResponse> {
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<void> {
) : Promise<messageDefaultEmailDomainResponse> {
// 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 };
}
/**

View File

@@ -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: ['<all_urls>'],
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 };
});
},
});
});

View File

@@ -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<boolean> {
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<void> {
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);
}
});
})();
}
/**

View File

@@ -0,0 +1,4 @@
export type BoolResponse = {
success: boolean,
error?: string
};

View File

@@ -0,0 +1,7 @@
import { Credential } from "../Credential";
export type CredentialsResponse = {
success: boolean,
error?: string,
credentials?: Credential[]
};

View File

@@ -0,0 +1,5 @@
export type DefaultEmailDomainResponse = {
success: boolean,
error?: string,
domain?: string
};

View File

@@ -0,0 +1,7 @@
export type VaultResponse = {
success: boolean, error?: string,
vault?: string,
publicEmailDomains?: string[],
privateEmailDomains?: string[],
vaultRevisionNumber?: number
};