/** * Background script - handles cookie operations and API calls */ // Store for captured Discord token let discordToken = null; let discordTokenCapturedAt = null; // Track initialization state let initialized = false; /** * Ensure extension is initialized */ async function ensureInitialized() { if (!initialized) { console.log('Initializing extension...'); try { await api.init(); await loadDiscordToken(); initialized = true; console.log('Extension initialized successfully'); } catch (error) { console.error('Failed to initialize extension:', error); throw error; } } } // Initialize API on startup browser.runtime.onInstalled.addListener(async () => { console.log('GallerySubscriber extension installed'); await ensureInitialized(); }); browser.runtime.onStartup.addListener(async () => { console.log('GallerySubscriber extension startup'); await ensureInitialized(); }); // Also initialize immediately in case background wakes up from idle ensureInitialized().catch(err => console.error('Initial init failed:', err)); /** * Load Discord token from storage */ async function loadDiscordToken() { const stored = await browser.storage.local.get(['discordToken', 'discordTokenCapturedAt']); if (stored.discordToken) { discordToken = stored.discordToken; discordTokenCapturedAt = stored.discordTokenCapturedAt; console.log('Loaded Discord token from storage (captured:', discordTokenCapturedAt, ')'); } } /** * Save Discord token to storage */ async function saveDiscordToken(token) { discordToken = token; discordTokenCapturedAt = new Date().toISOString(); await browser.storage.local.set({ discordToken: token, discordTokenCapturedAt: discordTokenCapturedAt }); console.log('Discord token captured and saved'); } /** * Listen for Discord API requests to capture Authorization header */ browser.webRequest.onSendHeaders.addListener( (details) => { // Look for Authorization header const authHeader = details.requestHeaders.find( h => h.name.toLowerCase() === 'authorization' ); if (authHeader && authHeader.value) { // Discord user tokens don't have "Bearer " prefix // Bot tokens have "Bot " prefix - we want user tokens const token = authHeader.value; // Only capture if it looks like a user token (not a bot token) if (!token.startsWith('Bot ') && token.length > 50) { // Only save if it's different from what we have if (token !== discordToken) { saveDiscordToken(token); } } } }, { urls: ['*://discord.com/api/*', '*://*.discord.com/api/*'] }, ['requestHeaders'] ); // Handle messages from popup and options pages browser.runtime.onMessage.addListener((message, sender, sendResponse) => { handleMessage(message) .then(sendResponse) .catch(error => { console.error('Message handler error:', error); sendResponse({ error: error.message }); }); return true; // Keep channel open for async response }); /** * Route messages to appropriate handlers * @param {object} message - Message object with type property * @returns {Promise} - Response data */ async function handleMessage(message) { console.log('Handling message:', message.type); // Ensure initialized for operations that need it if (['EXPORT_COOKIES', 'EXPORT_ALL_COOKIES', 'GET_PLATFORM_STATUS', 'TEST_CONNECTION'].includes(message.type)) { await ensureInitialized(); } switch (message.type) { case 'EXPORT_COOKIES': return await exportCookies(message.platform); case 'EXPORT_ALL_COOKIES': return await exportAllCookies(); case 'GET_PLATFORM_STATUS': return await getPlatformStatus(); case 'TEST_CONNECTION': return await testConnection(); case 'GET_CONFIG': return await getConfig(); case 'SAVE_CONFIG': return await saveConfig(message.config); default: throw new Error(`Unknown message type: ${message.type}`); } } /** * Export cookies for a single platform to the backend * @param {string} platformKey - Platform identifier * @returns {Promise} - Result object */ async function exportCookies(platformKey) { await api.init(); const platform = PLATFORMS[platformKey]; if (!platform) { throw new Error(`Unknown platform: ${platformKey}`); } let credentialData; let credentialType; if (platform.authType === 'token') { // Discord - use captured token from webRequest listener if (platformKey === 'discord') { if (!discordToken) { throw new Error(`No Discord token captured. Please open Discord in this browser and interact with it (e.g., switch channels) to capture the token.`); } credentialData = discordToken; credentialType = 'token'; // Send to backend const result = await api.uploadCredentials(platformKey, credentialType, credentialData); return { success: true, platform: platformKey, tokenCapturedAt: discordTokenCapturedAt, message: result.message || 'Token exported successfully' }; } // Other token-based platforms require manual entry throw new Error(`${platform.name} requires manual token entry in the web UI.`); } // Cookie-based auth const cookies = await extractCookiesForPlatform(platformKey); if (cookies.length === 0) { throw new Error(`No cookies found for ${platform.name}. Please ensure you are logged in.`); } credentialData = toNetscapeFormat(cookies); credentialType = 'cookies'; // Send to backend const result = await api.uploadCredentials(platformKey, credentialType, credentialData); return { success: true, platform: platformKey, cookieCount: cookies.length, message: result.message || 'Credentials exported successfully' }; } /** * Export cookies for all cookie-based platforms * @returns {Promise} - Results for each platform */ async function exportAllCookies() { await api.init(); const results = {}; const platforms = Object.keys(PLATFORMS); for (const platformKey of platforms) { const platform = PLATFORMS[platformKey]; // Skip token-based platforms (except Discord if we have a token) if (platform.authType === 'token' && platformKey !== 'discord') { results[platformKey] = { success: false, platform: platformKey, skipped: true, message: 'Token-based auth requires manual entry' }; continue; } // Skip Discord if no token captured if (platformKey === 'discord' && !discordToken) { results[platformKey] = { success: false, platform: platformKey, skipped: true, message: 'No token captured - open Discord in browser first' }; continue; } try { results[platformKey] = await exportCookies(platformKey); } catch (error) { results[platformKey] = { success: false, platform: platformKey, error: error.message }; } } return results; } /** * Get status information for all platforms * @returns {Promise} - Status for each platform */ async function getPlatformStatus() { const status = {}; for (const [key, platform] of Object.entries(PLATFORMS)) { try { if (platform.authType === 'token') { // Token-based platforms if (key === 'discord') { status[key] = { name: platform.name, authType: 'token', hasToken: !!discordToken, tokenCapturedAt: discordTokenCapturedAt, hasCookies: false, cookieCount: 0, note: discordToken ? `Token captured ${new Date(discordTokenCapturedAt).toLocaleString()}` : 'Open Discord in browser to capture token' }; } else { status[key] = { name: platform.name, authType: 'token', hasCookies: false, cookieCount: 0, note: platform.note || 'Requires manual token entry' }; } } else { // Cookie-based platforms const cookies = await extractCookiesForPlatform(key); status[key] = { name: platform.name, authType: 'cookies', hasCookies: cookies.length > 0, cookieCount: cookies.length }; } } catch (error) { status[key] = { name: platform.name, authType: platform.authType, hasCookies: false, cookieCount: 0, error: error.message }; } } return status; } /** * Test connection to the backend * @returns {Promise} - Connection status */ async function testConnection() { await api.init(); if (!api.isConfigured()) { return { connected: false, error: 'Not configured' }; } try { await api.testConnection(); return { connected: true }; } catch (error) { return { connected: false, error: error.message }; } } /** * Get current configuration * @returns {Promise} - Config object */ async function getConfig() { const config = await browser.storage.local.get(['apiUrl', 'apiKey']); return config; } /** * Save configuration * @param {object} config - Config object with apiUrl and apiKey * @returns {Promise} - Success status */ async function saveConfig(config) { await browser.storage.local.set(config); await api.init(); return { success: true }; }