396 lines
12 KiB
JavaScript
396 lines
12 KiB
JavaScript
/**
|
|
* Popup script - handles UI interactions
|
|
*/
|
|
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
try {
|
|
await init();
|
|
} catch (error) {
|
|
console.error('Popup init error:', error);
|
|
// Show error in UI
|
|
document.getElementById('setup-required').classList.remove('hidden');
|
|
const alertDiv = document.querySelector('#setup-required .alert');
|
|
if (alertDiv) {
|
|
alertDiv.textContent = '';
|
|
const strong = document.createElement('strong');
|
|
strong.textContent = 'Error';
|
|
const p = document.createElement('p');
|
|
p.textContent = error.message;
|
|
alertDiv.appendChild(strong);
|
|
alertDiv.appendChild(p);
|
|
alertDiv.className = 'alert alert-error';
|
|
}
|
|
}
|
|
});
|
|
|
|
// Rate limit connection tests (max once per 2 minutes)
|
|
const CONNECTION_TEST_INTERVAL = 2 * 60 * 1000; // 2 minutes
|
|
|
|
/**
|
|
* Initialize the popup
|
|
*/
|
|
async function init() {
|
|
console.log('Popup initializing...');
|
|
|
|
// Check configuration (this is fast - just reads from storage)
|
|
let config;
|
|
try {
|
|
config = await browser.runtime.sendMessage({ type: 'GET_CONFIG' });
|
|
console.log('Got config:', config);
|
|
} catch (error) {
|
|
console.error('Failed to get config:', error);
|
|
throw new Error(`Failed to communicate with background script: ${error.message}`);
|
|
}
|
|
|
|
if (!config || !config.apiUrl || !config.apiKey) {
|
|
console.log('Config missing, showing setup');
|
|
showSetupRequired();
|
|
return;
|
|
}
|
|
|
|
// Show main content IMMEDIATELY - don't wait for connection test or platform status
|
|
document.getElementById('setup-required').classList.add('hidden');
|
|
document.getElementById('main-content').classList.remove('hidden');
|
|
|
|
// Setup event listeners right away
|
|
setupEventListeners();
|
|
|
|
// Show loading state for platforms
|
|
const platformsContainer = document.getElementById('platforms-list');
|
|
platformsContainer.textContent = '';
|
|
const loadingDiv = document.createElement('div');
|
|
loadingDiv.style.cssText = 'text-align: center; padding: 20px; color: var(--text-secondary);';
|
|
loadingDiv.textContent = 'Loading platforms...';
|
|
platformsContainer.appendChild(loadingDiv);
|
|
|
|
// Now do background tasks in parallel (non-blocking)
|
|
// Check if we should test connection (rate limited)
|
|
testConnectionIfNeeded();
|
|
|
|
// Load platform status (this can take time)
|
|
loadPlatformStatus().catch(error => {
|
|
console.error('Failed to load platform status:', error);
|
|
showError(`Failed to load platforms: ${error.message}`);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test connection only if enough time has passed since last test
|
|
*/
|
|
async function testConnectionIfNeeded() {
|
|
try {
|
|
// Check last test time from storage
|
|
const stored = await browser.storage.local.get(['lastConnectionTest', 'lastConnectionStatus']);
|
|
const now = Date.now();
|
|
const lastTest = stored.lastConnectionTest || 0;
|
|
|
|
// Use cached status if recent
|
|
if (now - lastTest < CONNECTION_TEST_INTERVAL && stored.lastConnectionStatus !== undefined) {
|
|
console.log('Using cached connection status');
|
|
updateConnectionIndicator(stored.lastConnectionStatus);
|
|
if (!stored.lastConnectionStatus) {
|
|
showError('Cannot connect to backend (cached status)');
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Test connection in background
|
|
console.log('Testing connection...');
|
|
const connectionStatus = await browser.runtime.sendMessage({ type: 'TEST_CONNECTION' });
|
|
console.log('Connection status:', connectionStatus);
|
|
|
|
// Cache the result
|
|
await browser.storage.local.set({
|
|
lastConnectionTest: now,
|
|
lastConnectionStatus: connectionStatus.connected
|
|
});
|
|
|
|
updateConnectionIndicator(connectionStatus.connected);
|
|
|
|
if (!connectionStatus.connected) {
|
|
showError(`Cannot connect to backend: ${connectionStatus.error}`);
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to test connection:', error);
|
|
updateConnectionIndicator(false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show the setup required message
|
|
*/
|
|
function showSetupRequired() {
|
|
document.getElementById('setup-required').classList.remove('hidden');
|
|
document.getElementById('main-content').classList.add('hidden');
|
|
|
|
document.getElementById('open-settings-btn').addEventListener('click', () => {
|
|
browser.runtime.openOptionsPage();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update the connection status indicator
|
|
* @param {boolean} connected - Whether connected to backend
|
|
*/
|
|
function updateConnectionIndicator(connected) {
|
|
const indicator = document.getElementById('connection-status');
|
|
indicator.classList.toggle('connected', connected);
|
|
indicator.title = connected ? 'Connected to GallerySubscriber' : 'Disconnected';
|
|
}
|
|
|
|
/**
|
|
* Load and display platform status
|
|
*/
|
|
async function loadPlatformStatus() {
|
|
const status = await browser.runtime.sendMessage({ type: 'GET_PLATFORM_STATUS' });
|
|
const container = document.getElementById('platforms-list');
|
|
container.textContent = '';
|
|
|
|
for (const [key, platform] of Object.entries(PLATFORMS)) {
|
|
const platformStatus = status[key] || {};
|
|
const card = createPlatformCard(key, platform, platformStatus);
|
|
container.appendChild(card);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a platform card element
|
|
* @param {string} key - Platform key
|
|
* @param {object} platform - Platform definition
|
|
* @param {object} status - Platform status
|
|
* @returns {HTMLElement}
|
|
*/
|
|
function createPlatformCard(key, platform, status) {
|
|
const card = document.createElement('div');
|
|
card.className = 'platform-card';
|
|
card.dataset.platform = key;
|
|
|
|
// Discord is clickable if we have a token captured
|
|
// Other token-based platforms are disabled
|
|
const isDisabled = platform.authType === 'token' && key !== 'discord';
|
|
const isDiscordWithoutToken = key === 'discord' && !status.hasToken;
|
|
|
|
if (isDisabled || isDiscordWithoutToken) {
|
|
card.classList.add('disabled');
|
|
}
|
|
|
|
const statusText = getStatusText(status, platform, key);
|
|
const statusClass = getStatusClass(status, platform, key);
|
|
|
|
// Build card content using DOM methods (avoids innerHTML security warnings)
|
|
const iconDiv = document.createElement('div');
|
|
iconDiv.className = 'platform-icon';
|
|
iconDiv.style.backgroundColor = platform.color;
|
|
iconDiv.textContent = platform.name.charAt(0);
|
|
|
|
const infoDiv = document.createElement('div');
|
|
infoDiv.className = 'info';
|
|
|
|
const nameDiv = document.createElement('div');
|
|
nameDiv.className = 'name';
|
|
nameDiv.textContent = platform.name;
|
|
|
|
const statusDiv = document.createElement('div');
|
|
statusDiv.className = `status ${statusClass}`;
|
|
statusDiv.textContent = statusText;
|
|
|
|
infoDiv.appendChild(nameDiv);
|
|
infoDiv.appendChild(statusDiv);
|
|
|
|
const actionSpan = document.createElement('span');
|
|
actionSpan.className = 'action-icon';
|
|
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
svg.setAttribute('width', '20');
|
|
svg.setAttribute('height', '20');
|
|
svg.setAttribute('viewBox', '0 0 24 24');
|
|
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
path.setAttribute('fill', 'currentColor');
|
|
path.setAttribute('d', 'M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z');
|
|
svg.appendChild(path);
|
|
actionSpan.appendChild(svg);
|
|
|
|
card.appendChild(iconDiv);
|
|
card.appendChild(infoDiv);
|
|
card.appendChild(actionSpan);
|
|
|
|
if (!isDisabled && !isDiscordWithoutToken) {
|
|
card.addEventListener('click', () => exportPlatformCookies(key, card));
|
|
}
|
|
|
|
return card;
|
|
}
|
|
|
|
/**
|
|
* Get status text for display
|
|
* @param {object} status - Platform status
|
|
* @param {object} platform - Platform definition
|
|
* @param {string} key - Platform key
|
|
* @returns {string}
|
|
*/
|
|
function getStatusText(status, platform, key) {
|
|
// Special handling for Discord token-based auth
|
|
if (key === 'discord') {
|
|
if (status.hasToken) {
|
|
return 'Token captured - ready to export';
|
|
}
|
|
return 'Open Discord in browser to capture token';
|
|
}
|
|
|
|
if (platform.authType === 'token') {
|
|
return 'Manual token entry required';
|
|
}
|
|
if (status.error) {
|
|
return 'Error checking cookies';
|
|
}
|
|
if (!status.hasCookies || status.cookieCount === 0) {
|
|
return 'No cookies found - log in first';
|
|
}
|
|
return `${status.cookieCount} cookies ready`;
|
|
}
|
|
|
|
/**
|
|
* Get status CSS class
|
|
* @param {object} status - Platform status
|
|
* @param {object} platform - Platform definition
|
|
* @param {string} key - Platform key
|
|
* @returns {string}
|
|
*/
|
|
function getStatusClass(status, platform, key) {
|
|
// Special handling for Discord
|
|
if (key === 'discord') {
|
|
return status.hasToken ? 'ready' : 'no-cookies';
|
|
}
|
|
|
|
if (platform.authType === 'token') {
|
|
return 'no-cookies';
|
|
}
|
|
if (status.error) {
|
|
return 'error';
|
|
}
|
|
if (!status.hasCookies || status.cookieCount === 0) {
|
|
return 'no-cookies';
|
|
}
|
|
return 'ready';
|
|
}
|
|
|
|
/**
|
|
* Export cookies for a single platform
|
|
* @param {string} platformKey - Platform key
|
|
* @param {HTMLElement} cardElement - The card element
|
|
*/
|
|
async function exportPlatformCookies(platformKey, cardElement) {
|
|
cardElement.classList.add('loading');
|
|
hideStatusMessage();
|
|
|
|
try {
|
|
const result = await browser.runtime.sendMessage({
|
|
type: 'EXPORT_COOKIES',
|
|
platform: platformKey
|
|
});
|
|
|
|
if (result.error) {
|
|
showError(result.error);
|
|
} else {
|
|
// Handle different response types (cookies vs token)
|
|
const successMsg = result.cookieCount !== undefined
|
|
? `${PLATFORMS[platformKey].name}: ${result.cookieCount} cookies exported!`
|
|
: `${PLATFORMS[platformKey].name}: Token exported successfully!`;
|
|
showSuccess(successMsg);
|
|
// Refresh status
|
|
await loadPlatformStatus();
|
|
}
|
|
} catch (error) {
|
|
showError(error.message);
|
|
} finally {
|
|
cardElement.classList.remove('loading');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Export cookies for all platforms
|
|
*/
|
|
async function exportAllCookies() {
|
|
const btn = document.getElementById('export-all-btn');
|
|
btn.disabled = true;
|
|
btn.textContent = 'Exporting...';
|
|
hideStatusMessage();
|
|
|
|
try {
|
|
const results = await browser.runtime.sendMessage({ type: 'EXPORT_ALL_COOKIES' });
|
|
|
|
const successes = Object.values(results).filter(r => r.success).length;
|
|
const skipped = Object.values(results).filter(r => r.skipped).length;
|
|
const failures = Object.values(results).filter(r => !r.success && !r.skipped).length;
|
|
|
|
if (failures === 0 && successes > 0) {
|
|
showSuccess(`Exported ${successes} platform(s) successfully!`);
|
|
} else if (successes > 0) {
|
|
showWarning(`${successes} succeeded, ${failures} failed`);
|
|
} else if (failures > 0) {
|
|
showError('All exports failed. Are you logged in?');
|
|
} else {
|
|
showWarning('No platforms to export');
|
|
}
|
|
|
|
await loadPlatformStatus();
|
|
} catch (error) {
|
|
showError(error.message);
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.textContent = 'Export All Platforms';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Setup event listeners
|
|
*/
|
|
function setupEventListeners() {
|
|
document.getElementById('export-all-btn').addEventListener('click', exportAllCookies);
|
|
document.getElementById('settings-btn').addEventListener('click', () => {
|
|
browser.runtime.openOptionsPage();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Show success message
|
|
* @param {string} message - Message to display
|
|
*/
|
|
function showSuccess(message) {
|
|
showStatusMessage(message, 'success');
|
|
}
|
|
|
|
/**
|
|
* Show error message
|
|
* @param {string} message - Message to display
|
|
*/
|
|
function showError(message) {
|
|
showStatusMessage(message, 'error');
|
|
}
|
|
|
|
/**
|
|
* Show warning message
|
|
* @param {string} message - Message to display
|
|
*/
|
|
function showWarning(message) {
|
|
showStatusMessage(message, 'warning');
|
|
}
|
|
|
|
/**
|
|
* Show a status message
|
|
* @param {string} message - Message text
|
|
* @param {string} type - Message type (success, error, warning)
|
|
*/
|
|
function showStatusMessage(message, type) {
|
|
const el = document.getElementById('status-message');
|
|
el.textContent = message;
|
|
el.className = `status-message ${type}`;
|
|
el.classList.remove('hidden');
|
|
}
|
|
|
|
/**
|
|
* Hide the status message
|
|
*/
|
|
function hideStatusMessage() {
|
|
document.getElementById('status-message').classList.add('hidden');
|
|
}
|