/** * Brightwheel Exporter - Content Script * Injected on https://schools.mybrightwheel.com/* */ (function () { console.log('[Brightwheel Exporter] Content script initialized.'); // Extract student ID from URL if present function detectCurrentStudentId() { const match = window.location.pathname.match(/\/students\/([a-zA-Z0-9-]+)/); if (match && match[1]) { chrome.storage.local.set({ lastActiveStudentId: match[1] }); return match[1]; } return null; } detectCurrentStudentId(); // Watch for SPA URL changes let lastUrl = location.href; new MutationObserver(() => { const url = location.href; if (url !== lastUrl) { lastUrl = url; detectCurrentStudentId(); } }).observe(document, { subtree: true, childList: true }); // Listen for messages from extension popup or dashboard chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.action === 'GET_PAGE_CONTEXT') { const studentId = detectCurrentStudentId(); sendResponse({ url: window.location.href, studentId: studentId, cookiesPresent: document.cookie.includes('_brightwheel_v2') }); return true; } if (message.action === 'SCRAPE_DOM_FEED') { try { const domActivities = scrapeDomFeedCards(); sendResponse({ success: true, activities: domActivities, htmlSample: document.body.innerHTML.substring(0, 5000) }); } catch (err) { sendResponse({ success: false, error: err.message }); } return true; } if (message.action === 'FETCH_WITH_CREDENTIALS') { fetch(message.url, { method: message.method || 'GET', headers: { 'Accept': 'application/json', ...(message.headers || {}) }, credentials: 'include' }) .then(async (res) => { if (!res.ok) { const errorText = await res.text().catch(() => ''); return sendResponse({ success: false, status: res.status, statusText: res.statusText, body: errorText }); } const json = await res.json(); sendResponse({ success: true, status: res.status, data: json }); }) .catch((err) => { sendResponse({ success: false, error: err.message }); }); return true; // async } }); // DOM Scraper for Brightwheel Web Feed function scrapeDomFeedCards() { const results = []; // Broad selector covering common Brightwheel feed cards const cardSelectors = [ '[data-testid*="activity"]', '[data-testid*="feed"]', '.activity-item', '.feed-item', '.timeline-item', 'article', '[class*="ActivityCard"]', '[class*="feed-card"]', '[class*="FeedItem"]', '[class*="TimelineItem"]' ]; let cards = document.querySelectorAll(cardSelectors.join(',')); if (!cards || cards.length === 0) { // Fallback: look for cards by common structure cards = document.querySelectorAll('div[class*="card"], div[class*="Card"]'); } cards.forEach((card, index) => { const text = card.innerText || ''; if (!text.trim()) return; const cardId = card.getAttribute('data-id') || card.getAttribute('data-activity-id') || card.id || `dom_${index}`; const img = card.querySelector('img[src*="cdn"], img[src*="brightwheel"], img[src*="cloudfront"], img[src*="s3"]'); const video = card.querySelector('video, source, a[href*=".mp4"], a[href*="video"]'); const videoUrl = video?.src || video?.getAttribute('src') || video?.getAttribute('href') || null; const imageUrl = img?.src || null; results.push({ dom_id: cardId, raw_text: text, image_url: imageUrl, video_url: videoUrl, class_name: card.className, html_snippet: card.outerHTML.substring(0, 800) }); }); return results; } // Inject a discreet floating button on Brightwheel page function injectExporterButton() { if (document.getElementById('bw-exporter-floating-btn')) return; const btn = document.createElement('button'); btn.id = 'bw-exporter-floating-btn'; btn.innerHTML = ` Export Brightwheel Data `; Object.assign(btn.style, { position: 'fixed', bottom: '24px', right: '24px', zIndex: '999999', display: 'inline-flex', alignItems: 'center', padding: '10px 18px', background: 'linear-gradient(135deg, #FF6B35 0%, #FF8E53 100%)', color: '#FFFFFF', fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', fontSize: '14px', fontWeight: '600', border: 'none', borderRadius: '30px', boxShadow: '0 4px 16px rgba(255, 107, 53, 0.4), 0 2px 6px rgba(0, 0, 0, 0.1)', cursor: 'pointer', transition: 'all 0.25s ease', }); btn.addEventListener('mouseenter', () => { btn.style.transform = 'translateY(-2px) scale(1.03)'; btn.style.boxShadow = '0 6px 20px rgba(255, 107, 53, 0.5), 0 3px 8px rgba(0, 0, 0, 0.15)'; }); btn.addEventListener('mouseleave', () => { btn.style.transform = 'translateY(0) scale(1)'; btn.style.boxShadow = '0 4px 16px rgba(255, 107, 53, 0.4), 0 2px 6px rgba(0, 0, 0, 0.1)'; }); btn.addEventListener('click', () => { chrome.runtime.sendMessage({ action: 'OPEN_DASHBOARD' }); }); document.body.appendChild(btn); } // Inject when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', injectExporterButton); } else { injectExporterButton(); } })();