From 8b0ffa330f82eceeec2d764dd1f29b068f7f0f08 Mon Sep 17 00:00:00 2001 From: Jimmy <2489219080@qq.com> Date: Mon, 6 Apr 2026 12:01:20 +0800 Subject: [PATCH] feat: add Inbucket mailbox support and enhance password management in side panel --- .vscode/settings.json | 0 background.js | 84 +++++++++++-- content/inbucket-mail.js | 258 +++++++++++++++++++++++++++++++++++++++ content/mail-163.js | 18 ++- content/utils.js | 3 +- manifest.json | 8 ++ sidepanel/sidepanel.css | 5 + sidepanel/sidepanel.html | 12 ++ sidepanel/sidepanel.js | 49 ++++++++ 9 files changed, 420 insertions(+), 17 deletions(-) delete mode 100644 .vscode/settings.json create mode 100644 content/inbucket-mail.js diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index e69de29..0000000 diff --git a/background.js b/background.js index 67fb472..690796d 100644 --- a/background.js +++ b/background.js @@ -8,6 +8,8 @@ const STOP_ERROR_MESSAGE = 'Flow stopped by user.'; const HUMAN_STEP_DELAY_MIN = 700; const HUMAN_STEP_DELAY_MAX = 2200; +initializeSessionStorageAccess(); + // ============================================================ // State Management (chrome.storage.session) // ============================================================ @@ -28,7 +30,9 @@ const DEFAULT_STATE = { tabRegistry: {}, logs: [], vpsUrl: '', + customPassword: '', mailProvider: '163', // 'qq' or '163' + inbucketMailbox: '', }; async function getState() { @@ -36,6 +40,19 @@ async function getState() { return { ...DEFAULT_STATE, ...state }; } +async function initializeSessionStorageAccess() { + try { + if (chrome.storage?.session?.setAccessLevel) { + await chrome.storage.session.setAccessLevel({ + accessLevel: 'TRUSTED_AND_UNTRUSTED_CONTEXTS', + }); + console.log(LOG_PREFIX, 'Enabled storage.session for content scripts'); + } + } catch (err) { + console.warn(LOG_PREFIX, 'Failed to enable storage.session for content scripts:', err?.message || err); + } +} + async function setState(updates) { console.log(LOG_PREFIX, 'storage.set:', JSON.stringify(updates).slice(0, 200)); await chrome.storage.session.set(updates); @@ -53,18 +70,35 @@ async function setEmailState(email) { broadcastDataUpdate({ email }); } +async function setPasswordState(password) { + await setState({ password }); + broadcastDataUpdate({ password }); +} + async function resetState() { console.log(LOG_PREFIX, 'Resetting all state'); // Preserve settings and persistent data across resets - const prev = await chrome.storage.session.get(['seenCodes', 'accounts', 'tabRegistry', 'vpsUrl', 'mailProvider']); + const prev = await chrome.storage.session.get([ + 'seenCodes', + 'seenInbucketMailIds', + 'accounts', + 'tabRegistry', + 'vpsUrl', + 'customPassword', + 'mailProvider', + 'inbucketMailbox', + ]); await chrome.storage.session.clear(); await chrome.storage.session.set({ ...DEFAULT_STATE, seenCodes: prev.seenCodes || [], + seenInbucketMailIds: prev.seenInbucketMailIds || [], accounts: prev.accounts || [], tabRegistry: prev.tabRegistry || {}, vpsUrl: prev.vpsUrl || '', + customPassword: prev.customPassword || '', mailProvider: prev.mailProvider || '163', + inbucketMailbox: prev.inbucketMailbox || '', }); } @@ -481,7 +515,9 @@ async function handleMessage(message, sender) { case 'SAVE_SETTING': { const updates = {}; if (message.payload.vpsUrl !== undefined) updates.vpsUrl = message.payload.vpsUrl; + if (message.payload.customPassword !== undefined) updates.customPassword = message.payload.customPassword; if (message.payload.mailProvider !== undefined) updates.mailProvider = message.payload.mailProvider; + if (message.payload.inbucketMailbox !== undefined) updates.inbucketMailbox = message.payload.inbucketMailbox; await setState(updates); return { ok: true }; } @@ -725,6 +761,7 @@ async function autoRunLoop(totalRuns) { const keepSettings = { vpsUrl: prevState.vpsUrl, mailProvider: prevState.mailProvider, + inbucketMailbox: prevState.inbucketMailbox, autoRunning: true, }; await resetState(); @@ -881,16 +918,17 @@ async function executeStep3(state) { throw new Error('No email address. Paste email in Side Panel first.'); } - // Generate a unique password for this account - const password = generatePassword(); - await setState({ password }); + const password = state.customPassword || generatePassword(); + await setPasswordState(password); // Save account record const accounts = state.accounts || []; accounts.push({ email: state.email, password, createdAt: new Date().toISOString() }); await setState({ accounts }); - await addLog(`Step 3: Filling email ${state.email}, password generated (${password.length} chars)`); + await addLog( + `Step 3: Filling email ${state.email}, password ${state.customPassword ? 'customized' : 'generated'} (${password.length} chars)` + ); await sendToContentScript('signup-page', { type: 'EXECUTE_STEP', step: 3, @@ -908,18 +946,35 @@ function getMailConfig(state) { if (provider === '163') { return { source: 'mail-163', url: 'https://mail.163.com/js6/main.jsp?df=mail163_letter#module=mbox.ListModule%7C%7B%22fid%22%3A1%2C%22order%22%3A%22date%22%2C%22desc%22%3Atrue%7D', label: '163 Mail' }; } + if (provider === 'inbucket') { + const mailbox = (state.inbucketMailbox || '').trim(); + if (!mailbox) { + return { error: 'Inbucket mailbox name is empty.' }; + } + return { + source: 'inbucket-mail', + url: `https://inbucket.j2to.de/m/${encodeURIComponent(mailbox)}/`, + label: `Inbucket Mailbox (${mailbox})`, + navigateOnReuse: true, + }; + } return { source: 'qq-mail', url: 'https://wx.mail.qq.com/', label: 'QQ Mail' }; } async function executeStep4(state) { const mail = getMailConfig(state); + if (mail.error) throw new Error(mail.error); await addLog(`Step 4: Opening ${mail.label}...`); // For mail tabs, only create if not alive — don't navigate (preserves login session) const alive = await isTabAlive(mail.source); if (alive) { - const tabId = await getTabId(mail.source); - await chrome.tabs.update(tabId, { active: true }); + if (mail.navigateOnReuse) { + await reuseOrCreateTab(mail.source, mail.url); + } else { + const tabId = await getTabId(mail.source); + await chrome.tabs.update(tabId, { active: true }); + } } else { await reuseOrCreateTab(mail.source, mail.url); } @@ -930,8 +985,9 @@ async function executeStep4(state) { source: 'background', payload: { filterAfterTimestamp: state.flowStartTime || 0, - senderFilters: ['openai', 'noreply', 'verify', 'auth'], + senderFilters: ['openai', 'noreply', 'verify', 'auth', 'duckduckgo', 'forward'], subjectFilters: ['verify', 'verification', 'code', '验证', 'confirm'], + targetEmail: state.email, maxAttempts: 20, intervalMs: 3000, }, @@ -1010,12 +1066,17 @@ async function executeStep6(state) { async function executeStep7(state) { const mail = getMailConfig(state); + if (mail.error) throw new Error(mail.error); await addLog(`Step 7: Opening ${mail.label}...`); const alive = await isTabAlive(mail.source); if (alive) { - const tabId = await getTabId(mail.source); - await chrome.tabs.update(tabId, { active: true }); + if (mail.navigateOnReuse) { + await reuseOrCreateTab(mail.source, mail.url); + } else { + const tabId = await getTabId(mail.source); + await chrome.tabs.update(tabId, { active: true }); + } } else { await reuseOrCreateTab(mail.source, mail.url); } @@ -1026,8 +1087,9 @@ async function executeStep7(state) { source: 'background', payload: { filterAfterTimestamp: state.lastEmailTimestamp || state.flowStartTime || 0, - senderFilters: ['openai', 'noreply', 'verify', 'auth', 'chatgpt'], + senderFilters: ['openai', 'noreply', 'verify', 'auth', 'chatgpt', 'duckduckgo', 'forward'], subjectFilters: ['verify', 'verification', 'code', '验证', 'confirm', 'login'], + targetEmail: state.email, maxAttempts: 20, intervalMs: 3000, }, diff --git a/content/inbucket-mail.js b/content/inbucket-mail.js new file mode 100644 index 0000000..26d1c56 --- /dev/null +++ b/content/inbucket-mail.js @@ -0,0 +1,258 @@ +// content/inbucket-mail.js — Content script for Inbucket polling (steps 4, 7) +// Injected on: inbucket.j2to.de +// +// Supported page: +// - /m// + +const INBUCKET_PREFIX = '[MultiPage:inbucket-mail]'; +const isTopFrame = window === window.top; +const SEEN_MAIL_IDS_KEY = 'seenInbucketMailIds'; + +console.log(INBUCKET_PREFIX, 'Content script loaded on', location.href, 'frame:', isTopFrame ? 'top' : 'child'); + +if (!isTopFrame) { + console.log(INBUCKET_PREFIX, 'Skipping child frame'); +} else { + +let seenMailIds = new Set(); + +async function loadSeenMailIds() { + try { + const data = await chrome.storage.session.get(SEEN_MAIL_IDS_KEY); + if (Array.isArray(data[SEEN_MAIL_IDS_KEY])) { + seenMailIds = new Set(data[SEEN_MAIL_IDS_KEY]); + console.log(INBUCKET_PREFIX, `Loaded ${seenMailIds.size} previously seen mail ids`); + } + } catch (err) { + console.warn(INBUCKET_PREFIX, 'Session storage unavailable, using in-memory seen mail ids:', err?.message || err); + } +} + +async function persistSeenMailIds() { + try { + await chrome.storage.session.set({ [SEEN_MAIL_IDS_KEY]: [...seenMailIds] }); + } catch (err) { + console.warn(INBUCKET_PREFIX, 'Could not persist seen mail ids, continuing in-memory only:', err?.message || err); + } +} + +loadSeenMailIds(); + +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.type === 'POLL_EMAIL') { + resetStopState(); + handlePollEmail(message.step, message.payload).then(result => { + sendResponse(result); + }).catch(err => { + if (isStopError(err)) { + log(`Step ${message.step}: Stopped by user.`, 'warn'); + sendResponse({ stopped: true, error: err.message }); + return; + } + reportError(message.step, err.message); + sendResponse({ error: err.message }); + }); + return true; + } +}); + +function normalizeText(value) { + return (value || '').replace(/\s+/g, ' ').trim().toLowerCase(); +} + +function extractVerificationCode(text) { + const matchCn = text.match(/(?:代码为|验证码[^0-9]*?)[\s::]*(\d{6})/); + if (matchCn) return matchCn[1]; + + const matchEn = text.match(/code[:\s]+is[:\s]+(\d{6})|code[:\s]+(\d{6})/i); + if (matchEn) return matchEn[1] || matchEn[2]; + + const match6 = text.match(/\b(\d{6})\b/); + if (match6) return match6[1]; + + return null; +} + +function rowMatchesFilters(mail, senderFilters, subjectFilters, targetEmail) { + const sender = normalizeText(mail.sender); + const subject = normalizeText(mail.subject); + const mailbox = normalizeText(mail.mailbox); + const combined = normalizeText(mail.combinedText); + const targetLocal = normalizeText((targetEmail || '').split('@')[0]); + + const senderMatch = senderFilters.some(f => sender.includes(f.toLowerCase()) || combined.includes(f.toLowerCase())); + const subjectMatch = subjectFilters.some(f => subject.includes(f.toLowerCase()) || combined.includes(f.toLowerCase())); + const mailboxMatch = Boolean(targetLocal) && mailbox.includes(targetLocal); + const forwardedDuck = /duckduckgo|forward(?:ed)?\s*by/i.test(mail.combinedText); + const code = extractVerificationCode(mail.combinedText); + const keywordMatch = /openai|chatgpt|verify|verification|confirm|login|验证码|代码/.test(combined); + + if (mailboxMatch) return { matched: true, mailboxMatch, code }; + if (senderMatch || subjectMatch) return { matched: true, mailboxMatch: false, code }; + if (code && (forwardedDuck || keywordMatch)) return { matched: true, mailboxMatch: false, code }; + + return { matched: false, mailboxMatch: false, code }; +} + +function findMailboxEntries() { + return document.querySelectorAll('.message-list-entry'); +} + +function getMailboxEntryId(entry, index = 0) { + const explicitId = entry.getAttribute('data-id') || entry.dataset?.id || ''; + if (explicitId) return explicitId; + + const subject = entry.querySelector('.subject')?.textContent?.trim() || ''; + const sender = entry.querySelector('.from')?.textContent?.trim() || ''; + const dateText = entry.querySelector('.date')?.textContent?.trim() || ''; + + return `mailbox:${index}:${normalizeText(subject)}|${normalizeText(sender)}|${normalizeText(dateText)}`; +} + +function parseMailboxEntry(entry, index = 0) { + const subject = entry.querySelector('.subject')?.textContent?.trim() || ''; + const sender = entry.querySelector('.from')?.textContent?.trim() || ''; + const dateText = entry.querySelector('.date')?.textContent?.trim() || ''; + const combinedText = [subject, sender, dateText].filter(Boolean).join(' '); + + return { + entry, + dateText, + sender, + mailbox: '', + subject, + unread: entry.classList.contains('unseen'), + combinedText, + mailId: getMailboxEntryId(entry, index), + }; +} + +function getCurrentMailboxIds() { + const ids = new Set(); + Array.from(findMailboxEntries()).forEach((entry, index) => { + ids.add(getMailboxEntryId(entry, index)); + }); + return ids; +} + +async function refreshMailbox() { + const refreshButton = document.querySelector('button[alt="Refresh Mailbox"]'); + if (!refreshButton) return; + + simulateClick(refreshButton); + await sleep(800); +} + +async function openMailboxEntry(entry) { + simulateClick(entry); + + for (let i = 0; i < 20; i++) { + if (entry.classList.contains('selected') || document.querySelector('.message-header, .message-body, .button-bar')) { + return; + } + await sleep(150); + } +} + +async function deleteCurrentMailboxMessage(step) { + try { + const deleteButton = await waitForElement('.button-bar button.danger', 5000); + simulateClick(deleteButton); + log(`Step ${step}: Deleted mailbox message`, 'ok'); + await sleep(1200); + } catch (err) { + log(`Step ${step}: Failed to delete mailbox message: ${err.message}`, 'warn'); + } +} + +async function handleMailboxPollEmail(step, payload) { + const { + senderFilters = [], + subjectFilters = [], + maxAttempts = 20, + intervalMs = 3000, + } = payload || {}; + + log(`Step ${step}: Starting email poll on Inbucket mailbox page (max ${maxAttempts} attempts)`); + + try { + await waitForElement('.message-list, .message-list-entry', 15000); + log(`Step ${step}: Mailbox page loaded`); + } catch { + throw new Error('Inbucket mailbox page did not load. Make sure /m// is open.'); + } + + const existingMailIds = getCurrentMailboxIds(); + log(`Step ${step}: Snapshotted ${existingMailIds.size} existing mailbox messages`); + + const FALLBACK_AFTER = 3; + + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + log(`Polling Inbucket mailbox... attempt ${attempt}/${maxAttempts}`); + + if (attempt > 1) { + await refreshMailbox(); + } + + const entries = Array.from(findMailboxEntries()).map(parseMailboxEntry); + const useFallback = attempt > FALLBACK_AFTER; + const candidates = []; + + for (const mail of entries) { + if (!mail.unread) continue; + if (seenMailIds.has(mail.mailId)) continue; + if (!useFallback && existingMailIds.has(mail.mailId)) continue; + + const match = rowMatchesFilters(mail, senderFilters, subjectFilters, ''); + if (!match.matched) continue; + + candidates.push({ ...mail, code: match.code }); + } + + for (const mail of candidates) { + const code = mail.code || extractVerificationCode(mail.combinedText); + if (!code) continue; + + await openMailboxEntry(mail.entry); + await deleteCurrentMailboxMessage(step); + + seenMailIds.add(mail.mailId); + await persistSeenMailIds(); + + const source = existingMailIds.has(mail.mailId) ? 'fallback' : 'new'; + log( + `Step ${step}: Code found: ${code} (${source}, sender: ${mail.sender || 'unknown'}, subject: ${(mail.subject || '').slice(0, 60)})`, + 'ok' + ); + + return { + ok: true, + code, + emailTimestamp: Date.now(), + mailId: mail.mailId, + }; + } + + if (attempt === FALLBACK_AFTER + 1) { + log(`Step ${step}: No new mailbox messages yet, falling back to older matching messages`, 'warn'); + } + + if (attempt < maxAttempts) { + await sleep(intervalMs); + } + } + + throw new Error( + `No matching verification email found in Inbucket mailbox after ${(maxAttempts * intervalMs / 1000).toFixed(0)}s. ` + + 'Check the mailbox page manually.' + ); +} + +async function handlePollEmail(step, payload) { + if (!location.pathname.startsWith('/m/')) { + throw new Error('Inbucket now only supports mailbox pages like /m//.'); + } + return handleMailboxPollEmail(step, payload); +} + +} // end of isTopFrame else block diff --git a/content/mail-163.js b/content/mail-163.js index 9e8dab3..63762b9 100644 --- a/content/mail-163.js +++ b/content/mail-163.js @@ -20,19 +20,27 @@ if (!isTopFrame) { // Track codes we've already seen — persisted in chrome.storage.session to survive script re-injection let seenCodes = new Set(); -// Load previously seen codes on startup -(async () => { +async function loadSeenCodes() { try { const data = await chrome.storage.session.get('seenCodes'); if (data.seenCodes && Array.isArray(data.seenCodes)) { seenCodes = new Set(data.seenCodes); console.log(MAIL163_PREFIX, `Loaded ${seenCodes.size} previously seen codes`); } - } catch {} -})(); + } catch (err) { + console.warn(MAIL163_PREFIX, 'Session storage unavailable, using in-memory seen codes:', err?.message || err); + } +} + +// Load previously seen codes on startup +loadSeenCodes(); async function persistSeenCodes() { - await chrome.storage.session.set({ seenCodes: [...seenCodes] }); + try { + await chrome.storage.session.set({ seenCodes: [...seenCodes] }); + } catch (err) { + console.warn(MAIL163_PREFIX, 'Could not persist seen codes, continuing in-memory only:', err?.message || err); + } } // ============================================================ diff --git a/content/utils.js b/content/utils.js index 55f0bf3..cfed4bf 100644 --- a/content/utils.js +++ b/content/utils.js @@ -5,6 +5,7 @@ const SCRIPT_SOURCE = (() => { if (url.includes('auth0.openai.com') || url.includes('auth.openai.com') || url.includes('accounts.openai.com')) return 'signup-page'; if (url.includes('mail.qq.com')) return 'qq-mail'; if (url.includes('mail.163.com')) return 'mail-163'; + if (url.includes('inbucket.j2to.de')) return 'inbucket-mail'; if (url.includes('duckduckgo.com/email/settings/autofill')) return 'duck-mail'; if (url.includes('chatgpt.com')) return 'chatgpt'; // VPS panel — detected dynamically since URL is configurable @@ -328,7 +329,7 @@ async function humanPause(min = 250, max = 850) { // Auto-report ready on load // Skip ready signal from child iframes of mail pages to avoid overwriting the top frame's registration -const _isMailChildFrame = (SCRIPT_SOURCE === 'qq-mail' || SCRIPT_SOURCE === 'mail-163') && window !== window.top; +const _isMailChildFrame = (SCRIPT_SOURCE === 'qq-mail' || SCRIPT_SOURCE === 'mail-163' || SCRIPT_SOURCE === 'inbucket-mail') && window !== window.top; if (!_isMailChildFrame) { reportReady(); } diff --git a/manifest.json b/manifest.json index 903132a..235ceb2 100644 --- a/manifest.json +++ b/manifest.json @@ -53,6 +53,14 @@ ], "js": ["content/utils.js", "content/duck-mail.js"], "run_at": "document_idle" + }, + { + "matches": [ + "https://inbucket.j2to.de/*" + ], + "js": ["content/utils.js", "content/inbucket-mail.js"], + "all_frames": true, + "run_at": "document_idle" } ], "action": { diff --git a/sidepanel/sidepanel.css b/sidepanel/sidepanel.css index f35d1e3..5c16ca1 100644 --- a/sidepanel/sidepanel.css +++ b/sidepanel/sidepanel.css @@ -301,6 +301,11 @@ header { padding-inline: 10px; } +#btn-toggle-password { + min-width: 58px; + padding-inline: 10px; +} + .data-select { flex: 1; padding: 7px 10px; diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index 513f963..4d73394 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -53,8 +53,13 @@ +
Email
@@ -62,6 +67,13 @@
+
+ Password +
+ + +
+
OAuth Waiting... diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js index 59e669f..5418975 100644 --- a/sidepanel/sidepanel.js +++ b/sidepanel/sidepanel.js @@ -14,7 +14,9 @@ const displayLocalhostUrl = document.getElementById('display-localhost-url'); const displayStatus = document.getElementById('display-status'); const statusBar = document.getElementById('status-bar'); const inputEmail = document.getElementById('input-email'); +const inputPassword = document.getElementById('input-password'); const btnFetchEmail = document.getElementById('btn-fetch-email'); +const btnTogglePassword = document.getElementById('btn-toggle-password'); const btnStop = document.getElementById('btn-stop'); const btnReset = document.getElementById('btn-reset'); const stepsProgress = document.getElementById('steps-progress'); @@ -24,6 +26,8 @@ const autoContinueBar = document.getElementById('auto-continue-bar'); const btnClearLog = document.getElementById('btn-clear-log'); const inputVpsUrl = document.getElementById('input-vps-url'); const selectMailProvider = document.getElementById('select-mail-provider'); +const rowInbucketMailbox = document.getElementById('row-inbucket-mailbox'); +const inputInbucketMailbox = document.getElementById('input-inbucket-mailbox'); const inputRunCount = document.getElementById('input-run-count'); // ============================================================ @@ -77,12 +81,16 @@ async function restoreState() { if (state.email) { inputEmail.value = state.email; } + syncPasswordField(state); if (state.vpsUrl) { inputVpsUrl.value = state.vpsUrl; } if (state.mailProvider) { selectMailProvider.value = state.mailProvider; } + if (state.inbucketMailbox) { + inputInbucketMailbox.value = state.inbucketMailbox; + } if (state.stepStatuses) { for (const [step, status] of Object.entries(state.stepStatuses)) { @@ -98,11 +106,21 @@ async function restoreState() { updateStatusDisplay(state); updateProgressCounter(); + updateMailProviderUI(); } catch (err) { console.error('Failed to restore state:', err); } } +function syncPasswordField(state) { + inputPassword.value = state.customPassword || state.password || ''; +} + +function updateMailProviderUI() { + const useInbucket = selectMailProvider.value === 'inbucket'; + rowInbucketMailbox.style.display = useInbucket ? '' : 'none'; +} + // ============================================================ // UI Updates // ============================================================ @@ -262,6 +280,10 @@ async function fetchDuckEmail() { } } +function syncPasswordToggleLabel() { + btnTogglePassword.textContent = inputPassword.type === 'password' ? 'Show' : 'Hide'; +} + // ============================================================ // Button Handlers // ============================================================ @@ -286,6 +308,11 @@ btnFetchEmail.addEventListener('click', async () => { await fetchDuckEmail().catch(() => {}); }); +btnTogglePassword.addEventListener('click', () => { + inputPassword.type = inputPassword.type === 'password' ? 'text' : 'password'; + syncPasswordToggleLabel(); +}); + btnStop.addEventListener('click', async () => { btnStop.disabled = true; await chrome.runtime.sendMessage({ type: 'STOP_FLOW', source: 'sidepanel', payload: {} }); @@ -355,13 +382,30 @@ inputVpsUrl.addEventListener('change', async () => { } }); +inputPassword.addEventListener('change', async () => { + await chrome.runtime.sendMessage({ + type: 'SAVE_SETTING', + source: 'sidepanel', + payload: { customPassword: inputPassword.value }, + }); +}); + selectMailProvider.addEventListener('change', async () => { + updateMailProviderUI(); await chrome.runtime.sendMessage({ type: 'SAVE_SETTING', source: 'sidepanel', payload: { mailProvider: selectMailProvider.value }, }); }); +inputInbucketMailbox.addEventListener('change', async () => { + await chrome.runtime.sendMessage({ + type: 'SAVE_SETTING', + source: 'sidepanel', + payload: { inbucketMailbox: inputInbucketMailbox.value.trim() }, + }); +}); + // ============================================================ // Listen for Background broadcasts // ============================================================ @@ -381,6 +425,7 @@ chrome.runtime.onMessage.addListener((message) => { chrome.runtime.sendMessage({ type: 'GET_STATE', source: 'sidepanel' }).then(updateStatusDisplay); if (status === 'completed') { chrome.runtime.sendMessage({ type: 'GET_STATE', source: 'sidepanel' }).then(state => { + syncPasswordField(state); if (state.oauthUrl) { displayOauthUrl.textContent = state.oauthUrl; displayOauthUrl.classList.add('has-value'); @@ -415,6 +460,9 @@ chrome.runtime.onMessage.addListener((message) => { if (message.payload.email) { inputEmail.value = message.payload.email; } + if (message.payload.password !== undefined) { + inputPassword.value = message.payload.password || ''; + } if (message.payload.oauthUrl) { displayOauthUrl.textContent = message.payload.oauthUrl; displayOauthUrl.classList.add('has-value'); @@ -490,5 +538,6 @@ btnTheme.addEventListener('click', () => { initTheme(); restoreState().then(() => { + syncPasswordToggleLabel(); updateButtonStates(); });