Add vault upgrade check to autofill popup (#957)

This commit is contained in:
Leendert de Borst
2025-06-25 00:06:58 +02:00
committed by Leendert de Borst
parent 3e82c6e5d0
commit afe2ba52b5
4 changed files with 236 additions and 8 deletions

View File

@@ -14,9 +14,9 @@ import { VaultUploadResponse as messageVaultUploadResponse } from '@/utils/types
import { WebApiService } from '@/utils/WebApiService';
/**
* Check if the user is logged in and if the vault is locked.
* Check if the user is logged in and if the vault is locked, and also check for pending migrations.
*/
export async function handleCheckAuthStatus() : Promise<{ isLoggedIn: boolean, isVaultLocked: boolean }> {
export async function handleCheckAuthStatus() : Promise<{ isLoggedIn: boolean, isVaultLocked: boolean, hasPendingMigrations: boolean, error?: string }> {
const username = await storage.getItem('local:username');
const accessToken = await storage.getItem('local:accessToken');
const vaultData = await storage.getItem('session:encryptedVault');
@@ -24,10 +24,42 @@ export async function handleCheckAuthStatus() : Promise<{ isLoggedIn: boolean, i
const isLoggedIn = username !== null && accessToken !== null;
const isVaultLocked = isLoggedIn && vaultData === null;
return {
isLoggedIn,
isVaultLocked
};
// If vault is locked, we can't check for pending migrations
if (isVaultLocked) {
return {
isLoggedIn,
isVaultLocked,
hasPendingMigrations: false
};
}
// If not logged in, no need to check migrations
if (!isLoggedIn) {
return {
isLoggedIn,
isVaultLocked,
hasPendingMigrations: false
};
}
// Vault is unlocked, check for pending migrations
try {
const sqliteClient = await createVaultSqliteClient();
const hasPendingMigrations = await sqliteClient.hasPendingMigrations();
return {
isLoggedIn,
isVaultLocked,
hasPendingMigrations
};
} catch (error) {
console.error('Error checking pending migrations:', error);
return {
isLoggedIn,
isVaultLocked,
hasPendingMigrations: false,
error: error instanceof Error ? error.message : 'An unknown error occurred'
};
}
}
/**

View File

@@ -2,7 +2,7 @@ import '@/entrypoints/contentScript/style.css';
import { onMessage } from "webext-bridge/content-script";
import { injectIcon, popupDebounceTimeHasPassed, validateInputField } from '@/entrypoints/contentScript/Form';
import { isAutoShowPopupEnabled, openAutofillPopup, removeExistingPopup } from '@/entrypoints/contentScript/Popup';
import { isAutoShowPopupEnabled, openAutofillPopup, removeExistingPopup, createUpgradeRequiredPopup } from '@/entrypoints/contentScript/Popup';
import { FormDetector } from '@/utils/formDetector/FormDetector';
import { BoolResponse as messageBoolResponse } from '@/utils/types/messaging/BoolResponse';
@@ -69,7 +69,7 @@ export default defineContentScript({
// Only show popup if debounce time has passed
if (popupDebounceTimeHasPassed()) {
openAutofillPopup(inputElement, container);
await showPopupWithAuthCheck(inputElement, container);
}
}
}
@@ -132,6 +132,48 @@ export default defineContentScript({
if (canShowPopup) {
injectIcon(inputElement, container);
await showPopupWithAuthCheck(inputElement, container);
}
}
/**
* Show popup with auth check.
*/
async function showPopupWithAuthCheck(inputElement: HTMLInputElement, container: HTMLElement) : Promise<void> {
try {
// Check auth status and pending migrations in a single call
const { sendMessage } = await import('webext-bridge/content-script');
const authStatus = await sendMessage('CHECK_AUTH_STATUS', {}, 'background') as {
isLoggedIn: boolean,
isVaultLocked: boolean,
hasPendingMigrations: boolean,
error?: string
};
if (authStatus.isVaultLocked) {
// Vault is locked, show vault locked popup
const { createVaultLockedPopup } = await import('@/entrypoints/contentScript/Popup');
createVaultLockedPopup(inputElement, container);
return;
}
if (authStatus.hasPendingMigrations) {
// Show upgrade required popup
createUpgradeRequiredPopup(inputElement, container, 'Vault upgrade required.');
return;
}
if (authStatus.error) {
// Show upgrade required popup for version-related errors
createUpgradeRequiredPopup(inputElement, container, authStatus.error);
return;
}
// No upgrade required, show normal autofill popup
openAutofillPopup(inputElement, container);
} catch (error) {
console.error('Error checking vault status:', error);
// Fall back to normal autofill popup if check fails
openAutofillPopup(inputElement, container);
}
}

View File

@@ -1462,3 +1462,92 @@ function addReliableClickHandler(element: HTMLElement, handler: (e: Event) => vo
isMouseDown = false;
}, { capture: true });
}
/**
* Create upgrade required popup.
*/
export function createUpgradeRequiredPopup(input: HTMLInputElement, rootContainer: HTMLElement, errorMessage: string): void {
/**
* Handle upgrade click.
*/
const handleUpgradeClick = () : void => {
sendMessage('OPEN_POPUP', {}, 'background');
removeExistingPopup(rootContainer);
}
const popup = createBasePopup(input, rootContainer);
popup.classList.add('av-upgrade-required');
// Create container for message and button
const container = document.createElement('div');
container.className = 'av-upgrade-required-container';
// Make the entire container clickable
addReliableClickHandler(container, handleUpgradeClick);
container.style.cursor = 'pointer';
// Add message
const messageElement = document.createElement('div');
messageElement.className = 'av-upgrade-required-message';
messageElement.textContent = errorMessage;
container.appendChild(messageElement);
// Add upgrade button with SVG icon
const button = document.createElement('button');
button.title = 'Open AliasVault to upgrade';
button.className = 'av-upgrade-required-button';
button.innerHTML = `
<svg class="av-icon-upgrade" viewBox="0 0 24 24">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"></path>
</svg>
`;
container.appendChild(button);
// Add the container to the popup
popup.appendChild(container);
// Add close button as a separate element positioned to the right
const closeButton = document.createElement('button');
closeButton.className = 'av-button av-button-close av-upgrade-required-close';
closeButton.title = 'Dismiss popup';
closeButton.innerHTML = `
<svg class="av-icon" viewBox="0 0 24 24">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
`;
// Position the close button to the right of the container
closeButton.style.position = 'absolute';
closeButton.style.right = '8px';
closeButton.style.top = '50%';
closeButton.style.transform = 'translateY(-50%)';
// Handle close button click
addReliableClickHandler(closeButton, (e) => {
e.stopPropagation(); // Prevent opening the upgrade popup
removeExistingPopup(rootContainer);
});
popup.appendChild(closeButton);
/**
* Add event listener to document to close popup when clicking outside.
*/
const handleClickOutside = (event: MouseEvent): void => {
const target = event.target as Node;
const targetElement = event.target as HTMLElement;
// Check if the click is outside the popup and outside the shadow UI
if (popup && !popup.contains(target) && !input.contains(target) && targetElement.tagName !== 'ALIASVAULT-UI') {
removeExistingPopup(rootContainer);
document.removeEventListener('mousedown', handleClickOutside);
}
};
setTimeout(() => {
document.addEventListener('mousedown', handleClickOutside);
}, 100);
rootContainer.appendChild(popup);
}

View File

@@ -299,6 +299,71 @@ body {
border: 1px solid #6f6f6f;
}
/* Upgrade Required Popup */
.av-upgrade-required {
padding: 12px 16px;
position: relative;
}
.av-upgrade-required:hover {
background-color: #374151;
}
.av-upgrade-required-container {
display: flex;
align-items: center;
padding-right: 32px;
width: 100%;
transition: background-color 0.2s ease;
border-radius: 4px;
}
.av-upgrade-required-message {
color: #d1d5db;
font-size: 14px;
flex-grow: 1;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
.av-upgrade-required-button {
background: none;
border: none;
cursor: pointer;
padding: 4px;
padding-right: 28px;
display: flex;
align-items: center;
justify-content: center;
color: #f59e0b;
border-radius: 4px;
margin-left: 8px;
}
.av-upgrade-required-close {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
padding: 4px;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
z-index: 1;
border: 1px solid #6f6f6f;
}
.av-icon-upgrade {
width: 16px;
height: 16px;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
/* Create Name Popup */
.av-create-popup-overlay {
position: fixed;