387e177e00
- Remove dead chatgpt.js (unused — all auth flows go through signup-page.js) - Remove chatgpt.com from manifest content_scripts - Fix resetState() to preserve vpsUrl and mailProvider on manual reset - Add 30s timeout to all tabs.onUpdated listeners (prevent hanging) - Fix misleading "All runs finished" log when loop breaks early - Remove stale TODO comment in signup-page.js - Add disabled styles for .btn-primary and .run-count-input - Remove unused `indicator` variable in sidepanel.js - Fix trailing comma in manifest.json
427 lines
15 KiB
JavaScript
427 lines
15 KiB
JavaScript
// sidepanel/sidepanel.js — Side Panel logic
|
|
|
|
const STATUS_ICONS = {
|
|
pending: '',
|
|
running: '',
|
|
completed: '\u2713', // ✓
|
|
failed: '\u2717', // ✗
|
|
};
|
|
|
|
const logArea = document.getElementById('log-area');
|
|
const displayOauthUrl = document.getElementById('display-oauth-url');
|
|
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 btnReset = document.getElementById('btn-reset');
|
|
const stepsProgress = document.getElementById('steps-progress');
|
|
const btnAutoRun = document.getElementById('btn-auto-run');
|
|
const btnAutoContinue = document.getElementById('btn-auto-continue');
|
|
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 inputRunCount = document.getElementById('input-run-count');
|
|
|
|
// ============================================================
|
|
// Toast Notifications
|
|
// ============================================================
|
|
|
|
const toastContainer = document.getElementById('toast-container');
|
|
|
|
const TOAST_ICONS = {
|
|
error: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>',
|
|
warn: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',
|
|
success: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>',
|
|
info: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>',
|
|
};
|
|
|
|
function showToast(message, type = 'error', duration = 4000) {
|
|
const toast = document.createElement('div');
|
|
toast.className = `toast toast-${type}`;
|
|
toast.innerHTML = `${TOAST_ICONS[type] || ''}<span class="toast-msg">${escapeHtml(message)}</span><button class="toast-close">×</button>`;
|
|
|
|
toast.querySelector('.toast-close').addEventListener('click', () => dismissToast(toast));
|
|
toastContainer.appendChild(toast);
|
|
|
|
if (duration > 0) {
|
|
setTimeout(() => dismissToast(toast), duration);
|
|
}
|
|
}
|
|
|
|
function dismissToast(toast) {
|
|
if (!toast.parentNode) return;
|
|
toast.classList.add('toast-exit');
|
|
toast.addEventListener('animationend', () => toast.remove());
|
|
}
|
|
|
|
// ============================================================
|
|
// State Restore on load
|
|
// ============================================================
|
|
|
|
async function restoreState() {
|
|
try {
|
|
const state = await chrome.runtime.sendMessage({ type: 'GET_STATE', source: 'sidepanel' });
|
|
|
|
if (state.oauthUrl) {
|
|
displayOauthUrl.textContent = state.oauthUrl;
|
|
displayOauthUrl.classList.add('has-value');
|
|
}
|
|
if (state.localhostUrl) {
|
|
displayLocalhostUrl.textContent = state.localhostUrl;
|
|
displayLocalhostUrl.classList.add('has-value');
|
|
}
|
|
if (state.email) {
|
|
inputEmail.value = state.email;
|
|
}
|
|
if (state.vpsUrl) {
|
|
inputVpsUrl.value = state.vpsUrl;
|
|
}
|
|
if (state.mailProvider) {
|
|
selectMailProvider.value = state.mailProvider;
|
|
}
|
|
|
|
if (state.stepStatuses) {
|
|
for (const [step, status] of Object.entries(state.stepStatuses)) {
|
|
updateStepUI(Number(step), status);
|
|
}
|
|
}
|
|
|
|
if (state.logs) {
|
|
for (const entry of state.logs) {
|
|
appendLog(entry);
|
|
}
|
|
}
|
|
|
|
updateStatusDisplay(state);
|
|
updateProgressCounter();
|
|
} catch (err) {
|
|
console.error('Failed to restore state:', err);
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// UI Updates
|
|
// ============================================================
|
|
|
|
function updateStepUI(step, status) {
|
|
const statusEl = document.querySelector(`.step-status[data-step="${step}"]`);
|
|
const row = document.querySelector(`.step-row[data-step="${step}"]`);
|
|
|
|
if (statusEl) statusEl.textContent = STATUS_ICONS[status] || '';
|
|
if (row) {
|
|
row.className = `step-row ${status}`;
|
|
}
|
|
|
|
updateButtonStates();
|
|
updateProgressCounter();
|
|
}
|
|
|
|
function updateProgressCounter() {
|
|
let completed = 0;
|
|
document.querySelectorAll('.step-row').forEach(row => {
|
|
if (row.classList.contains('completed')) completed++;
|
|
});
|
|
stepsProgress.textContent = `${completed} / 9`;
|
|
}
|
|
|
|
function updateButtonStates() {
|
|
const statuses = {};
|
|
document.querySelectorAll('.step-row').forEach(row => {
|
|
const step = Number(row.dataset.step);
|
|
if (row.classList.contains('completed')) statuses[step] = 'completed';
|
|
else if (row.classList.contains('running')) statuses[step] = 'running';
|
|
else if (row.classList.contains('failed')) statuses[step] = 'failed';
|
|
else statuses[step] = 'pending';
|
|
});
|
|
|
|
const anyRunning = Object.values(statuses).some(s => s === 'running');
|
|
|
|
for (let step = 1; step <= 9; step++) {
|
|
const btn = document.querySelector(`.step-btn[data-step="${step}"]`);
|
|
if (!btn) continue;
|
|
|
|
if (anyRunning) {
|
|
btn.disabled = true;
|
|
} else if (step === 1) {
|
|
btn.disabled = false;
|
|
} else {
|
|
const prevStatus = statuses[step - 1];
|
|
const currentStatus = statuses[step];
|
|
btn.disabled = !(prevStatus === 'completed' || currentStatus === 'failed' || currentStatus === 'completed');
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateStatusDisplay(state) {
|
|
if (!state || !state.stepStatuses) return;
|
|
|
|
statusBar.className = 'status-bar';
|
|
|
|
const running = Object.entries(state.stepStatuses).find(([, s]) => s === 'running');
|
|
if (running) {
|
|
displayStatus.textContent = `Step ${running[0]} running...`;
|
|
statusBar.classList.add('running');
|
|
return;
|
|
}
|
|
|
|
const failed = Object.entries(state.stepStatuses).find(([, s]) => s === 'failed');
|
|
if (failed) {
|
|
displayStatus.textContent = `Step ${failed[0]} failed`;
|
|
statusBar.classList.add('failed');
|
|
return;
|
|
}
|
|
|
|
const lastCompleted = Object.entries(state.stepStatuses)
|
|
.filter(([, s]) => s === 'completed')
|
|
.map(([k]) => Number(k))
|
|
.sort((a, b) => b - a)[0];
|
|
|
|
if (lastCompleted === 9) {
|
|
displayStatus.textContent = 'All steps completed!';
|
|
statusBar.classList.add('completed');
|
|
} else if (lastCompleted) {
|
|
displayStatus.textContent = `Step ${lastCompleted} done`;
|
|
} else {
|
|
displayStatus.textContent = 'Ready';
|
|
}
|
|
}
|
|
|
|
function appendLog(entry) {
|
|
const time = new Date(entry.timestamp).toLocaleTimeString('en-US', { hour12: false });
|
|
const levelLabel = entry.level.toUpperCase();
|
|
const line = document.createElement('div');
|
|
line.className = `log-line log-${entry.level}`;
|
|
|
|
const stepMatch = entry.message.match(/Step (\d)/);
|
|
const stepNum = stepMatch ? stepMatch[1] : null;
|
|
|
|
let html = `<span class="log-time">${time}</span> `;
|
|
html += `<span class="log-level log-level-${entry.level}">${levelLabel}</span> `;
|
|
if (stepNum) {
|
|
html += `<span class="log-step-tag step-${stepNum}">S${stepNum}</span>`;
|
|
}
|
|
html += `<span class="log-msg">${escapeHtml(entry.message)}</span>`;
|
|
|
|
line.innerHTML = html;
|
|
logArea.appendChild(line);
|
|
logArea.scrollTop = logArea.scrollHeight;
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
// ============================================================
|
|
// Button Handlers
|
|
// ============================================================
|
|
|
|
document.querySelectorAll('.step-btn').forEach(btn => {
|
|
btn.addEventListener('click', async () => {
|
|
const step = Number(btn.dataset.step);
|
|
if (step === 3) {
|
|
const email = inputEmail.value.trim();
|
|
if (!email) {
|
|
showToast('Please paste email address first', 'warn');
|
|
return;
|
|
}
|
|
await chrome.runtime.sendMessage({ type: 'EXECUTE_STEP', source: 'sidepanel', payload: { step, email } });
|
|
} else {
|
|
await chrome.runtime.sendMessage({ type: 'EXECUTE_STEP', source: 'sidepanel', payload: { step } });
|
|
}
|
|
});
|
|
});
|
|
|
|
// Auto Run
|
|
btnAutoRun.addEventListener('click', async () => {
|
|
const totalRuns = parseInt(inputRunCount.value) || 1;
|
|
btnAutoRun.disabled = true;
|
|
inputRunCount.disabled = true;
|
|
btnAutoRun.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg> Running...';
|
|
await chrome.runtime.sendMessage({ type: 'AUTO_RUN', source: 'sidepanel', payload: { totalRuns } });
|
|
});
|
|
|
|
btnAutoContinue.addEventListener('click', async () => {
|
|
const email = inputEmail.value.trim();
|
|
if (!email) {
|
|
showToast('Please paste DuckDuckGo email first!', 'warn');
|
|
return;
|
|
}
|
|
autoContinueBar.style.display = 'none';
|
|
await chrome.runtime.sendMessage({ type: 'RESUME_AUTO_RUN', source: 'sidepanel', payload: { email } });
|
|
});
|
|
|
|
// Reset
|
|
btnReset.addEventListener('click', async () => {
|
|
if (confirm('Reset all steps and data?')) {
|
|
await chrome.runtime.sendMessage({ type: 'RESET', source: 'sidepanel' });
|
|
displayOauthUrl.textContent = 'Waiting...';
|
|
displayOauthUrl.classList.remove('has-value');
|
|
displayLocalhostUrl.textContent = 'Waiting...';
|
|
displayLocalhostUrl.classList.remove('has-value');
|
|
inputEmail.value = '';
|
|
displayStatus.textContent = 'Ready';
|
|
statusBar.className = 'status-bar';
|
|
logArea.innerHTML = '';
|
|
document.querySelectorAll('.step-row').forEach(row => row.className = 'step-row');
|
|
document.querySelectorAll('.step-status').forEach(el => el.textContent = '');
|
|
btnAutoRun.disabled = false;
|
|
btnAutoRun.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg> Auto';
|
|
autoContinueBar.style.display = 'none';
|
|
updateButtonStates();
|
|
updateProgressCounter();
|
|
}
|
|
});
|
|
|
|
// Clear log
|
|
btnClearLog.addEventListener('click', () => {
|
|
logArea.innerHTML = '';
|
|
});
|
|
|
|
// Save settings on change
|
|
inputEmail.addEventListener('change', async () => {
|
|
const email = inputEmail.value.trim();
|
|
if (email) {
|
|
await chrome.runtime.sendMessage({ type: 'SAVE_EMAIL', source: 'sidepanel', payload: { email } });
|
|
}
|
|
});
|
|
|
|
inputVpsUrl.addEventListener('change', async () => {
|
|
const vpsUrl = inputVpsUrl.value.trim();
|
|
if (vpsUrl) {
|
|
await chrome.runtime.sendMessage({ type: 'SAVE_SETTING', source: 'sidepanel', payload: { vpsUrl } });
|
|
}
|
|
});
|
|
|
|
selectMailProvider.addEventListener('change', async () => {
|
|
await chrome.runtime.sendMessage({
|
|
type: 'SAVE_SETTING', source: 'sidepanel',
|
|
payload: { mailProvider: selectMailProvider.value },
|
|
});
|
|
});
|
|
|
|
// ============================================================
|
|
// Listen for Background broadcasts
|
|
// ============================================================
|
|
|
|
chrome.runtime.onMessage.addListener((message) => {
|
|
switch (message.type) {
|
|
case 'LOG_ENTRY':
|
|
appendLog(message.payload);
|
|
if (message.payload.level === 'error') {
|
|
showToast(message.payload.message, 'error');
|
|
}
|
|
break;
|
|
|
|
case 'STEP_STATUS_CHANGED': {
|
|
const { step, status } = message.payload;
|
|
updateStepUI(step, status);
|
|
chrome.runtime.sendMessage({ type: 'GET_STATE', source: 'sidepanel' }).then(updateStatusDisplay);
|
|
if (status === 'completed') {
|
|
chrome.runtime.sendMessage({ type: 'GET_STATE', source: 'sidepanel' }).then(state => {
|
|
if (state.oauthUrl) {
|
|
displayOauthUrl.textContent = state.oauthUrl;
|
|
displayOauthUrl.classList.add('has-value');
|
|
}
|
|
if (state.localhostUrl) {
|
|
displayLocalhostUrl.textContent = state.localhostUrl;
|
|
displayLocalhostUrl.classList.add('has-value');
|
|
}
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'AUTO_RUN_RESET': {
|
|
// Full UI reset for next run
|
|
displayOauthUrl.textContent = 'Waiting...';
|
|
displayOauthUrl.classList.remove('has-value');
|
|
displayLocalhostUrl.textContent = 'Waiting...';
|
|
displayLocalhostUrl.classList.remove('has-value');
|
|
inputEmail.value = '';
|
|
displayStatus.textContent = 'Ready';
|
|
statusBar.className = 'status-bar';
|
|
logArea.innerHTML = '';
|
|
document.querySelectorAll('.step-row').forEach(row => row.className = 'step-row');
|
|
document.querySelectorAll('.step-status').forEach(el => el.textContent = '');
|
|
updateProgressCounter();
|
|
break;
|
|
}
|
|
|
|
case 'DATA_UPDATED': {
|
|
if (message.payload.oauthUrl) {
|
|
displayOauthUrl.textContent = message.payload.oauthUrl;
|
|
displayOauthUrl.classList.add('has-value');
|
|
}
|
|
if (message.payload.localhostUrl) {
|
|
displayLocalhostUrl.textContent = message.payload.localhostUrl;
|
|
displayLocalhostUrl.classList.add('has-value');
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'AUTO_RUN_STATUS': {
|
|
const { phase, currentRun, totalRuns } = message.payload;
|
|
const runLabel = totalRuns > 1 ? ` (${currentRun}/${totalRuns})` : '';
|
|
switch (phase) {
|
|
case 'waiting_email':
|
|
autoContinueBar.style.display = 'flex';
|
|
btnAutoRun.innerHTML = `Paused${runLabel}`;
|
|
break;
|
|
case 'running':
|
|
btnAutoRun.innerHTML = `Running${runLabel}`;
|
|
break;
|
|
case 'complete':
|
|
btnAutoRun.disabled = false;
|
|
inputRunCount.disabled = false;
|
|
btnAutoRun.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg> Auto';
|
|
autoContinueBar.style.display = 'none';
|
|
break;
|
|
case 'stopped':
|
|
btnAutoRun.disabled = false;
|
|
inputRunCount.disabled = false;
|
|
btnAutoRun.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg> Auto';
|
|
autoContinueBar.style.display = 'none';
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
// ============================================================
|
|
// Theme Toggle
|
|
// ============================================================
|
|
|
|
const btnTheme = document.getElementById('btn-theme');
|
|
|
|
function setTheme(theme) {
|
|
document.documentElement.setAttribute('data-theme', theme);
|
|
localStorage.setItem('multipage-theme', theme);
|
|
}
|
|
|
|
function initTheme() {
|
|
const saved = localStorage.getItem('multipage-theme');
|
|
if (saved) {
|
|
setTheme(saved);
|
|
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
setTheme('dark');
|
|
}
|
|
}
|
|
|
|
btnTheme.addEventListener('click', () => {
|
|
const current = document.documentElement.getAttribute('data-theme');
|
|
setTheme(current === 'dark' ? 'light' : 'dark');
|
|
});
|
|
|
|
// ============================================================
|
|
// Init
|
|
// ============================================================
|
|
|
|
initTheme();
|
|
restoreState().then(() => {
|
|
updateButtonStates();
|
|
});
|