- VPS URL: now an input field in Side Panel, no longer hardcoded - Dynamic script injection via chrome.scripting.executeScript - host_permissions changed to <all_urls> for flexibility - 163 Mail: new content script (mail-163.js) with actual selectors - Mail items: div[sign="letter"], sender: .nui-user, subject: span.da0 - Supports aria-label fallback for matching - Mail provider selector: dropdown to switch between QQ Mail and 163 Mail - Background routes steps 4/7 to correct mail content script - Settings persist in chrome.storage.session
20 KiB
Multi-Page Automation Chrome Extension - Design Spec
Overview
A Chrome extension that automates a multi-step OAuth account registration and verification workflow. The extension operates across multiple websites (VPS panel, OpenAI auth, QQ Mail, ChatGPT) using a Side Panel as the control center and Content Scripts for DOM manipulation.
User Story
The user needs to repeatedly perform a multi-step workflow involving:
- Getting an OAuth link from a VPS panel
- Registering a new account on the linked auth page (multi-page: email/password → verify code → name/birthday)
- Receiving email verification codes (twice: registration + login)
- Logging into ChatGPT with the new account
- Completing OAuth callback verification
The extension automates DOM interactions at each step, with the user triggering steps manually via the Side Panel (semi-automatic mode, with future upgrade to full automation).
Architecture
┌─────────────────────────────────────────────────┐
│ Chrome Extension │
├──────────┬──────────────┬───────────────────────┤
│ Side Panel │ Background │ Content Scripts │
│ (Control) │ (Service │ (Injected per site) │
│ │ Worker) │ │
│ - Step list│ - Tab mgmt │ - vps-panel.js │
│ - Buttons │ - Msg relay │ - signup-page.js │
│ - Status │ - State store│ - qq-mail.js │
│ - Logs │ - Orchestrate│ - chatgpt.js │
│ - Reset │ tab switch │ - utils.js (shared) │
└──────────┴──────────────┴───────────────────────┘
Components
- Side Panel: Persistent right-side panel showing all steps with action buttons, status indicators, shared data display (OAuth URL, email, etc.), debug log area, and a reset button. On open/reload, restores full state from
chrome.storage.session. - Background Service Worker: Central dispatcher and orchestrator. Manages tab creation/switching, stores flow state in
chrome.storage.session(NOT in-memory, as MV3 service workers can be terminated after 30s of inactivity), relays and routes messages between Side Panel and Content Scripts, and handles tab-switching choreography between steps. - Content Scripts: Per-domain scripts injected into target pages, each responsible for DOM operations (reading, filling forms, clicking buttons). All share
utils.jsfor common utilities.
Communication — Unified Message Protocol
All messages between components use a standard format:
{
type: "ACTION_NAME", // e.g. "FILL_SIGNUP", "CODE_FOUND", "STEP_COMPLETE", "STEP_ERROR", "LOG"
source: "qq-mail", // sender identifier
target: "signup-page", // intended receiver (optional, Background uses for routing)
step: 4, // which workflow step this relates to
payload: { ... }, // data
error: null // error message string (if any)
}
Message routing:
- Side Panel <-> Background:
chrome.runtime.sendMessage - Background -> Content Script:
chrome.tabs.sendMessage(only after content script reports ready) - Content Script -> Background:
chrome.runtime.sendMessage - Background is the central router: it receives all messages and forwards to the correct target based on
targetfield andtabRegistry.
Content Script Readiness Protocol
Background must NOT send messages to a content script immediately after opening/navigating a tab. Instead:
- Content script sends
{ type: "CONTENT_SCRIPT_READY", source: "vps-panel" }message on load - Background registers the tab as ready in the
tabRegistry - Only then does Background send action commands to that tab
- If Background needs to send a command but the script isn't ready yet, it queues the command and sends it when the ready signal arrives (with a 15s timeout → error)
Tab Registry
Background maintains a tabRegistry in chrome.storage.session:
{
"vps-panel": { "tabId": 123, "ready": true },
"signup": { "tabId": 124, "ready": true },
"qq-mail": { "tabId": 125, "ready": false },
"chatgpt": { "tabId": 126, "ready": true }
}
Before operating on a tab, Background checks if the tab still exists via chrome.tabs.get(). If the tab was closed, it reopens and waits for the ready signal.
Tab Switching Orchestration
When a step requires cross-tab coordination (e.g., step 4: get code from QQ Mail → fill into signup page), the orchestration is always handled by Background:
- Content script A (e.g., qq-mail.js) sends result to Background:
{ type: "CODE_FOUND", payload: { code: "123456" } } - Background stores the data in
chrome.storage.session - Background activates target tab via
chrome.tabs.update(tabId, { active: true }) - Background sends command to Content script B:
{ type: "FILL_CODE", payload: { code: "123456" } } - Content script B executes and reports
STEP_COMPLETEorSTEP_ERROR
Content scripts NEVER communicate directly with each other.
Workflow Steps
| Step | Button | Executor | Action |
|---|---|---|---|
| 1 | Get OAuth Link | vps-panel.js |
Check VPS login state → Click OAuth login → Click Codex login → Read auth URL → Send to Background |
| 2 | Open Signup & Click Register | Background + signup-page.js |
Background opens auth URL in new tab → signup-page.js loads and clicks "Register" button |
| 3 | Fill Email & Password | signup-page.js |
Read email from Side Panel input (via Background) → Fill email + password (mimashisha0.0) → Submit |
| 4 | Get Signup Verification Code | qq-mail.js → Background → signup-page.js |
Background opens QQ Mail tab → qq-mail.js polls for new email from OpenAI → Extracts code → Background switches to signup tab → signup-page.js fills code and confirms |
| 5 | Fill Name & Birthday | signup-page.js |
After code verification, page transitions to profile form → Fill random English name + random birthday (age 19-25) → Click complete registration |
| 6 | Login ChatGPT | Background + chatgpt.js |
Background opens chatgpt.com in new tab → chatgpt.js clicks login → Enters email → If password field appears, fills password; otherwise waits for OTP flow → Submit |
| 7 | Get Login Verification Code | qq-mail.js → Background → chatgpt.js |
Background switches to QQ Mail tab → qq-mail.js polls for email newer than step 4's → Extracts code → Background switches to ChatGPT tab → chatgpt.js fills code → Login complete |
| 8 | Complete OAuth | Background (webNavigation) + chatgpt.js |
chatgpt.js navigates to step 1's oauthUrl → Background captures localhost redirect via webNavigation listener → Stores localhostUrl |
| 9 | VPS Verify | vps-panel.js |
Background switches to VPS panel tab → vps-panel.js pastes localhostUrl into input → Clicks verify button |
Content Script URL Matching
| Script | Match Patterns | all_frames |
|---|---|---|
utils.js |
(included by all below) | — |
vps-panel.js |
http://154.26.182.181:8317/* |
false |
signup-page.js |
https://auth0.openai.com/*, https://auth.openai.com/*, https://accounts.openai.com/* |
false |
qq-mail.js |
https://mail.qq.com/*, https://wx.mail.qq.com/* |
true |
chatgpt.js |
https://chatgpt.com/* |
false |
Note: qq-mail.js uses all_frames: true because old QQ Mail (mail.qq.com) renders inbox inside iframes.
Content Script Details
utils.js (shared)
Loaded before every content script. Provides:
// Wait for a DOM element to appear, with timeout
async function waitForElement(selector, timeout = 10000)
// React-compatible form filling — triggers proper event chain
function fillInput(el, value) {
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype, 'value'
).set;
nativeInputValueSetter.call(el, value);
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new Event('change', { bubbles: true }));
}
// Send log message to Side Panel (via Background)
function log(message, level = 'info')
// Send step result to Background
function reportComplete(step, data = {})
function reportError(step, errorMessage)
// Standard ready signal
function reportReady(source)
vps-panel.js
- Step 1: Check login state (look for known logged-in DOM indicator); if not logged in →
reportError(1, "VPS panel not logged in, please log in first"). Then: locate OAuth login button → click → wait for Codex login button to appear → click → wait for auth URL to appear in DOM → read it →reportComplete(1, { oauthUrl }) - Step 9:
waitForElementfor URL input field →fillInputwithlocalhostUrl→ click verify button →reportComplete(9)
signup-page.js
- Step 2: On ready, detect if current page has a "Register" / "Sign up" button → click it →
reportComplete(2) - Step 3:
waitForElementfor email input →fillInputemail →fillInputpassword (mimashisha0.0) → click submit →reportComplete(3) - Step 4 (receiving end):
waitForElementfor verification code input →fillInputcode → click confirm →reportComplete(4) - Step 5:
waitForElementfor name/birthday fields (page transition after code verification) →fillInputrandom first name, last name →fillInputrandom birthday → click complete →reportComplete(5) - All DOM operations use
waitForElementbefore acting - All input filling uses
fillInput(React-compatible)
qq-mail.js
- Check login state first; if not logged in →
reportError(step, "QQ Mail not logged in, please log in first") - QQ Mail version handling:
- Old version (
mail.qq.com): Content is inside iframes. Withall_frames: true, the script runs in each frame. Only the frame containing the inbox list should act. - New version (
wx.mail.qq.com): SPA, no iframes. All content in main frame with dynamic DOM updates.
- Old version (
- Polling strategy (no page refresh):
- Click inbox refresh button (or equivalent DOM trigger) every 3 seconds
- Observe email list DOM for new entries matching sender filter (e.g.,
openai,noreply) or subject filter (e.g.,verify,verification,code) - Extract verification code from email preview/snippet if visible, or click into email and extract from body
- Log each poll attempt:
log("Polling QQ Mail... attempt 3/20")
- Step 4: Find email newer than flow start time → extract 6-digit code via regex →
reportComplete(4, { code, emailTimestamp }) - Step 7: Find email newer than step 4's
emailTimestamp→ extract 6-digit code →reportComplete(7, { code }) - Timeout after 60 seconds (20 attempts) →
reportError(step, "No matching email found after 60s")
chatgpt.js
- Step 6:
waitForElementfor login button → click →waitForElementfor email input →fillInputemail → submit → detect next state:- If password field appears →
fillInputpassword → submit →reportComplete(6) - If no password field (OTP flow) →
reportComplete(6, { needsOTP: true })(code will come from step 7)
- If password field appears →
- Step 7 (receiving end):
waitForElementfor code input →fillInputcode → submit →reportComplete(7) - Step 8: Navigate to
oauthUrl(from storage) → page will redirect →log("Navigating to OAuth URL, waiting for redirect...")→ (localhost capture handled by Background)
Data Flow
Data persisted in chrome.storage.session (survives service worker termination), passed between steps:
Step 1 → oauthUrl (authorization link)
Step 3 → email, password (from Side Panel input + hardcoded)
Step 4 → lastEmailTimestamp (to distinguish step 7's email)
Step 8 → localhostUrl (OAuth callback URL)
Additionally stored:
currentStep: which step is active (for UI restore)stepStatuses:{ 1: "completed", 2: "completed", 3: "running", ... }(for UI restore)tabRegistry: tab ID mapping (for tab management)logs: array of log entries (for UI restore)flowStartTime: timestamp when flow began (for email filtering)
Random Data Generation (in Background)
- English names: Built-in list of common first names + last names, randomly combined
- Birthday: Current year minus 19-25 years, random month/day, formatted per the target form's expected format
Side Panel UI
┌──────────────────────────────────┐
│ Multi-Page Automation [Reset]│
├──────────────────────────────────┤
│ OAuth URL: [显示/未获取] │
│ Email: [________粘贴邮箱______] │
│ Status: Step 3 running... │
├──────────────────────────────────┤
│ 1 [Get OAuth Link] ✅ │
│ 2 [Open Signup] ✅ │
│ 3 [Fill Email/Password] ⏳ │
│ 4 [Get Signup Code] ⬚ 禁用 │
│ 5 [Fill Name/Birthday] ⬚ 禁用 │
│ 6 [Login ChatGPT] ⬚ 禁用 │
│ 7 [Get Login Code] ⬚ 禁用 │
│ 8 [Complete OAuth] ⬚ 禁用 │
│ 9 [VPS Verify] ⬚ 禁用 │
├──────────────────────────────────┤
│ Log: │
│ 10:23:01 [INFO] Step 1 started │
│ 10:23:03 [INFO] Found OAuth btn │
│ 10:23:05 [OK] Step 1 done │
│ 10:23:05 [INFO] oauthUrl saved │
│ 10:23:08 [INFO] Step 2 started │
│ 10:23:10 [INFO] Tab opened │
│ 10:23:12 [OK] Register clicked│
│ 10:23:15 [ERR] Step 3 failed: │
│ email input not found │
│ 10:23:15 [INFO] Retry step 3... │
└──────────────────────────────────┘
UI behaviors:
- Step interlock: When a step is running, all other buttons are disabled. Next step button stays disabled until previous step succeeds. Failed steps show ❌ and can be retried. Completed steps show ✅ and can be re-run.
- State restore: On Side Panel open/reload, read
currentStep,stepStatuses,logs, and all data fields fromchrome.storage.sessionand render. - Reset button: Clears all
chrome.storage.sessiondata, resets all steps to pending, clears logs. Does NOT close open tabs (user may want them). - Email input: Editable text field. User pastes DuckDuckGo-generated email here before step 3.
- Log area: Scrollable, auto-scrolls to bottom. Shows timestamp, level (INFO/OK/WARN/ERR), and message. Each content script action logs what it's about to do and the result.
Debugging & Observability
Every step must be fully debuggable. The user should never be stuck wondering "what went wrong".
Log Everything
Each content script logs at these checkpoints:
- Step start:
"Step N started" - Waiting for element:
"Waiting for selector: #email-input..." - Element found/not found:
"Found #email-input"or"Timeout waiting for #email-input after 10s" - Action taken:
"Filled email input with xxx@duck.com","Clicked submit button" - Page transition:
"Page URL changed to https://..." - Polling progress:
"Polling QQ Mail... attempt 5/20, no match yet" - Data extracted:
"Verification code found: 123456" - Step result:
"Step N completed successfully"or"Step N failed: [reason]"
Error Recovery Guide
When a step fails, the error message should include actionable guidance:
| Error | Guidance shown in log |
|---|---|
| VPS not logged in | "Please log in to VPS panel at http://154.26.182.181:8317 and retry" |
| QQ Mail not logged in | "Please log in to QQ Mail and retry" |
| Element not found (timeout) | "Could not find [selector] on [url]. Page may have changed layout. Check DevTools." |
| Email not received (timeout) | "No matching email after 60s. Check QQ Mail manually. Email may be in spam." |
| Tab was closed | "Tab [name] was closed. Will reopen on retry." |
| Content script not ready | "Content script on [url] did not respond in 15s. Try refreshing the tab and retry." |
| Unknown error | "Unexpected error: [message]. Check DevTools console on [tab name] for details." |
DevTools Integration
- Each content script logs to the browser console with a prefix:
[MultiPage:vps-panel],[MultiPage:qq-mail], etc. - Background service worker also logs with
[MultiPage:bg]prefix. - All chrome.storage writes are logged:
[MultiPage:bg] storage.set: stepStatuses = {...} - This allows using Chrome DevTools on any tab or the service worker to see detailed execution trace.
Robustness
Login State Detection
- VPS panel (step 1): Check for known logged-in DOM indicator before operating; if absent,
reportErrorwith guidance and pause. - QQ Mail (steps 4, 7): Check for inbox DOM indicator; if absent,
reportErrorwith guidance and pause.
DOM Element Waiting
All content script DOM operations use waitForElement(selector, timeout) from utils.js. Default timeout: 10 seconds. On timeout, reportError with the selector and URL for debugging.
Email Polling
- Poll QQ Mail inbox every 3 seconds for matching email (filter by sender/subject keywords)
- Step 7 only accepts emails newer than step 4's
lastEmailTimestampto avoid duplicates - Each poll attempt is logged with attempt count
- Timeout after 60 seconds (20 attempts);
reportErrorwith guidance to check spam folder
Localhost Redirect Capture
chrome.webNavigation.onBeforeNavigatelistener is registered in Background only when step 8 starts- Listener matches URL starting with
http://localhost - On capture: store
localhostUrl, remove listener,reportComplete(8) - If not captured within 30 seconds:
reportErrorwith guidance - Listener is always removed when step 8 ends (success or failure)
Page Transitions
- After form submissions,
signup-page.jsandchatgpt.jsmust handle page navigation/SPA route changes - Use
waitForElementon the NEXT expected element after submission, not just submit and hope - If URL changes (full navigation), the content script re-injects and sends a new READY signal; Background re-sends the pending command
Permissions (manifest.json)
{
"permissions": [
"sidePanel",
"tabs",
"webNavigation",
"storage",
"scripting",
"activeTab"
],
"host_permissions": [
"http://154.26.182.181:8317/*",
"https://auth0.openai.com/*",
"https://auth.openai.com/*",
"https://accounts.openai.com/*",
"https://mail.qq.com/*",
"https://wx.mail.qq.com/*",
"https://chatgpt.com/*",
"http://localhost/*"
]
}
Note: scripting permission enables chrome.scripting.executeScript for dynamic injection when the auth flow redirects to unexpected domains.
Technical Decisions
- Manifest V3: Required for current Chrome extension development
- Side Panel API:
chrome.sidePanelfor persistent control panel - Pure Content Script approach: No external dependencies, direct DOM manipulation
- State in
chrome.storage.session: MV3 service workers can terminate after 30s idle; all flow state must be persisted, not held in memory - React-compatible form filling: Use native setter + event dispatch pattern for all React-based target pages
- Content script readiness protocol: Scripts report ready before receiving commands; commands queued if not ready
- Unified message protocol: All inter-component messages use same format with type/source/target/step/payload/error
- Shared utils.js: Common utilities loaded by all content scripts to avoid duplication
- Background as sole orchestrator: All cross-tab coordination goes through Background; content scripts never talk to each other
- webNavigation listener scoped to step 8: Registered on step start, removed on step end, avoids false triggers
- Semi-automatic first: Each step triggered manually, upgrade to full automation later
- DuckDuckGo email generation: Manual step (user clicks DuckDuckGo panel), user pastes email into Side Panel input
Future Enhancements
- Full automation mode (single button to run all steps)
- DuckDuckGo API integration for automatic email generation
- Batch execution support
- Error recovery and auto-retry logic
- Export logs for troubleshooting