mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-18 21:40:41 -04:00
Add all dependencies, refactor messaging (#581)
This commit is contained in:
70
browser-extension/package-lock.json
generated
70
browser-extension/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
});
|
||||
@@ -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');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
})();
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 };
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export type BoolResponse = {
|
||||
success: boolean,
|
||||
error?: string
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Credential } from "../Credential";
|
||||
|
||||
export type CredentialsResponse = {
|
||||
success: boolean,
|
||||
error?: string,
|
||||
credentials?: Credential[]
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
export type DefaultEmailDomainResponse = {
|
||||
success: boolean,
|
||||
error?: string,
|
||||
domain?: string
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
export type VaultResponse = {
|
||||
success: boolean, error?: string,
|
||||
vault?: string,
|
||||
publicEmailDomains?: string[],
|
||||
privateEmailDomains?: string[],
|
||||
vaultRevisionNumber?: number
|
||||
};
|
||||
Reference in New Issue
Block a user