From 38288df1442d14d7ded426df2b4c20ca1a446505 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Sat, 25 Jul 2026 21:28:31 +0000 Subject: [PATCH] feat(store): replace alert/confirm/prompt with MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Native dialogs render as browser chrome outside the page's styling and block the tab. The worst offender was the signing-key warning: an irreversible, identity-setting decision asked through a grey OS box that looks indistinguishable from a phishing popup. Adds openModal/uiAlert/uiConfirm/uiChoose on , and replaces all six call sites. The abuse report was a prompt() asking people to type one of "malware, privacy, broken, spam, other" — it is now a beats asking someone to type a keyword. */ +function uiChoose(title, message, options, { confirmLabel = 'Submit' } = {}) { + return openModal((dialog, done) => { + const opts = options.map((o) => ``).join(''); + dialog.innerHTML = modalShell(title, + `${message ? `` : ''}`, + ` + `); + dialog.querySelector('[data-cancel]').addEventListener('click', () => done(null)); + dialog.querySelector('[data-ok]').addEventListener('click', () => done(dialog.querySelector('[data-choice]').value)); + }); +} + async function api(path, opts = {}) { const res = await fetch(API + path, { credentials: 'include', ...opts }); const data = await res.json().catch(() => ({})); @@ -284,25 +352,38 @@ async function renderDetail(slug, root) { hydrateIcons(root); document.getElementById('flagBtn').addEventListener('click', async () => { - const reason = prompt('Reason? (malware, privacy, broken, spam, other)', 'other'); + const reason = await uiChoose('Report this extension', 'What is wrong with it?', [ + { value: 'malware', label: 'Malware or malicious code' }, + { value: 'privacy', label: 'Privacy violation' }, + { value: 'broken', label: "Broken — doesn't work" }, + { value: 'spam', label: 'Spam or misleading listing' }, + { value: 'other', label: 'Something else' }, + ], { confirmLabel: 'Report' }); if (!reason) return; - try { await api(`/extensions/${encodeURIComponent(ext.slug)}/flag`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ reason }) }); alert('Thanks — flagged for review.'); } - catch (e) { alert('Could not flag: ' + e.message); } + try { + await api(`/extensions/${encodeURIComponent(ext.slug)}/flag`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ reason }) }); + await uiAlert('Thanks', 'Flagged for review.'); + } catch (e) { await uiAlert('Could not flag', e.message); } }); wireEditForm(ext, () => renderDetail(slug, root)); document.getElementById('keyBtn')?.addEventListener('click', async (e) => { - if (!confirm('Generate a signing key for this extension?\n\nThe key permanently sets the extension ID — it cannot be rotated later without every install having to be redone.')) return; + const go = await uiConfirm( + 'Generate a signing key?', + 'The key permanently sets this extension\u2019s ID. It cannot be rotated later without every existing install having to be redone.', + { confirmLabel: 'Generate key', danger: true }, + ); + if (!go) return; const btn = e.currentTarget; btn.disabled = true; btn.textContent = '🔑 Generating…'; try { const { crxId } = await api(`/extensions/${encodeURIComponent(ext.id)}/signing-key`, { method: 'POST' }); - alert(`Signing key created.\n\nExtension ID: ${crxId}\n\nInstall now serves a signed .crx.`); + await uiAlert('Signing key created', `Extension ID: ${crxId} — Install now serves a signed .crx.`); await renderDetail(slug, root); } catch (err) { - alert('Could not generate key: ' + err.message); + await uiAlert('Could not generate key', err.message); btn.disabled = false; btn.textContent = '🔑 Generate signing key'; } @@ -318,7 +399,7 @@ async function renderDetail(slug, root) { await api(`/extensions/${encodeURIComponent(ext.id)}/rescan`, { method: 'POST' }); await renderDetail(slug, root); } catch (err) { - alert('Could not scan: ' + err.message); + await uiAlert('Could not scan', err.message); btn.disabled = false; btn.textContent = '🛡 Re-scan'; }