// Cache-busting comment - Updated to fix ES6 syntax issues - v2.0 const fetchWithRetry = async (url, options, maxRetries = 3) => { for (let i = 0; i < maxRetries; i++) { try { const response = await fetch(url, options); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const text = await response.text(); if (!text) { throw new Error('Empty response received'); } try { return JSON.parse(text); } catch (e) { console.error('JSON Parse Error:', e, 'Response:', text); throw e; } } catch (error) { console.warn(`Attempt ${i + 1} failed:`, error); if (i === maxRetries - 1) throw error; await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); // Exponential backoff } } }; // Helper function for getServiceAccountToken async function getGoogleSettings() { return new Promise((resolve) => { chrome.storage.local.get(['googleSettings'], (result) => { // console.log('Retrieved Google settings:', result.googleSettings); resolve(result.googleSettings || null); }); }); } async function getServiceAccountToken() { try { const googleSettings = await getGoogleSettings(); if (!googleSettings || !googleSettings.clientEmail || !googleSettings.privateKey) { throw new Error('Google credentials not configured.'); } const now = Math.floor(Date.now() / 1000); const jwtHeader = { alg: 'RS256', typ: 'JWT' }; const jwtPayload = { iss: googleSettings.clientEmail, sub: googleSettings.clientEmail, aud: 'https://oauth2.googleapis.com/token', scope: 'https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive.file', exp: now + 3600, iat: now }; const base64Header = btoa(JSON.stringify(jwtHeader)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); const base64Payload = btoa(JSON.stringify(jwtPayload)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); const signInput = `${base64Header}.${base64Payload}`; const pemHeader = "-----BEGIN PRIVATE KEY-----"; const pemFooter = "-----END PRIVATE KEY-----"; const pemContents = googleSettings.privateKey .replace(/\\n/g, '\n') .replace(pemHeader, '') .replace(pemFooter, '') .replace(/\s/g, ''); const binaryKey = Uint8Array.from(atob(pemContents), c => c.charCodeAt(0)); const cryptoKey = await window.crypto.subtle.importKey( 'pkcs8', binaryKey, { name: 'RSASSA-PKCS1-v1_5', hash: { name: 'SHA-256' }, }, false, ['sign'] ); const signature = await window.crypto.subtle.sign( 'RSASSA-PKCS1-v1_5', cryptoKey, new TextEncoder().encode(signInput) ); const jwt = `${signInput}.${btoa(String.fromCharCode(...new Uint8Array(signature))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')}`; const tokenResponse = await fetch('https://oauth2.googleapis.com/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', assertion: jwt }) }); const tokenData = await tokenResponse.json(); if (!tokenResponse.ok) { console.error('Token Error:', tokenData); throw new Error(`Token error: ${tokenData.error_description || tokenData.error}`); } return tokenData.access_token; } catch (error) { console.error('Detailed token error:', error); throw error; } } async function handlePageAction(pageType, releaseId, displayContainerId, updateButtonId, dataAction, updateFunction) { try { const tabs = await chrome.tabs.query({ active: true, currentWindow: true }); await chrome.scripting.executeScript({ target: { tabId: tabs[0].id }, files: ['content.js'] }); await new Promise(resolve => setTimeout(resolve, 100)); const response = await chrome.tabs.sendMessage(tabs[0].id, { action: dataAction, releaseId: releaseId }); const displayContainer = document.getElementById(displayContainerId); if (response) { updateFunction(response, displayContainer, updateButtonId, releaseId); } else { displayContainer.innerHTML = `