fix: remove file picker from popup setup wizard

Chrome closes popups when file pickers steal focus. Instead, check
chrome.storage.local for an existing image (pushed by init wizard),
and redirect to the full-page setup.html if no image is found.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
adlee-was-taken
2026-04-12 11:52:35 -04:00
parent 0551efe69e
commit 4c26b4c534

View File

@@ -10,6 +10,17 @@ let wizardConfig: Partial<VaultConfig> = {
let wizardImageBase64: string | null = null; let wizardImageBase64: string | null = null;
export function renderSetupWizard(app: HTMLElement): void { export function renderSetupWizard(app: HTMLElement): void {
// Check if image is already in storage (e.g. pushed by init wizard).
if (!wizardImageBase64) {
chrome.storage.local.get(['imageBase64'], (result: Record<string, string>) => {
if (result.imageBase64) {
wizardImageBase64 = result.imageBase64;
// Re-render now that we have the image.
renderSetupWizard(app);
}
});
}
const state = getState(); const state = getState();
// Progress bar. // Progress bar.
@@ -120,66 +131,46 @@ function attachStep0Listeners(): void {
}); });
} }
// --- Step 1: Reference image upload --- // --- Step 1: Reference image ---
//
// Chrome closes the popup when a file picker steals focus, which makes
// in-popup file uploads unreliable. Instead we check if an image is
// already in chrome.storage.local (pushed by the init wizard or a
// previous setup). If not, we link the user to the full-page setup.html
// where the file picker works without focus issues.
function renderStep1(): string { function renderStep1(): string {
if (wizardImageBase64) {
// Image already loaded (from storage or previous attempt).
return `
<div class="wizard-step">
<h3>reference image</h3>
<p class="secondary" style="margin-bottom:12px;">✓ reference image loaded</p>
<div class="form-actions" style="margin-top:16px;">
<button class="btn" id="back-btn">back</button>
<button class="btn btn-primary" id="next-btn">next</button>
</div>
</div>
`;
}
return ` return `
<div class="wizard-step"> <div class="wizard-step">
<h3>reference image</h3> <h3>reference image</h3>
<p class="muted" style="margin-bottom:12px;"> <p class="muted" style="margin-bottom:12px;">
Upload the JPEG that contains your embedded secret. No reference image found. Use the full-page setup wizard to
This is the second factor for vault decryption. upload your reference image — file pickers don't work reliably
in the popup.
</p> </p>
<div class="file-drop ${wizardImageBase64 ? 'has-file' : ''}" id="file-drop">
<input type="file" id="file-input" accept="image/jpeg" style="display:none;">
${wizardImageBase64
? '<p class="secondary">image loaded</p>'
: '<p class="secondary">click to select JPEG</p>'}
</div>
<div class="form-actions" style="margin-top:16px;"> <div class="form-actions" style="margin-top:16px;">
<button class="btn" id="back-btn">back</button> <button class="btn" id="back-btn">back</button>
<button class="btn btn-primary" id="next-btn" ${!wizardImageBase64 ? 'disabled' : ''}>next</button> <button class="btn btn-primary" id="open-setup-btn">open setup page</button>
</div> </div>
</div> </div>
`; `;
} }
function attachStep1Listeners(): void { function attachStep1Listeners(): void {
const fileDrop = document.getElementById('file-drop')!;
const fileInput = document.getElementById('file-input') as HTMLInputElement;
fileDrop.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', () => {
const file = fileInput.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = () => {
try {
const result = reader.result as string;
// Remove the data:image/jpeg;base64, prefix.
const base64 = result.split(',')[1] ?? result;
wizardImageBase64 = base64;
// Update UI without a full re-render to avoid resetting file input state.
const dropEl = document.getElementById('file-drop');
if (dropEl) {
dropEl.classList.add('has-file');
const p = dropEl.querySelector('p');
if (p) p.textContent = `image loaded (${(file.size / 1024).toFixed(0)} KB)`;
}
const nextBtn = document.getElementById('next-btn') as HTMLButtonElement;
if (nextBtn) nextBtn.disabled = false;
} catch (err) {
setState({ error: `Failed to read image: ${err}` });
}
};
reader.onerror = () => {
setState({ error: 'Failed to read image file' });
};
reader.readAsDataURL(file);
});
document.getElementById('back-btn')?.addEventListener('click', () => { document.getElementById('back-btn')?.addEventListener('click', () => {
wizardStep = 0; wizardStep = 0;
setState({ error: null }); setState({ error: null });
@@ -190,6 +181,12 @@ function attachStep1Listeners(): void {
wizardStep = 2; wizardStep = 2;
setState({ error: null }); setState({ error: null });
}); });
document.getElementById('open-setup-btn')?.addEventListener('click', () => {
// Open the full-page setup wizard in a new tab.
chrome.tabs.create({ url: chrome.runtime.getURL('setup.html') });
window.close();
});
} }
// --- Step 2: Test unlock --- // --- Step 2: Test unlock ---