From 20d81a2b4dff988dbef9a5cf634edbf55aecfb50 Mon Sep 17 00:00:00 2001 From: haopengzhan Date: Sun, 23 Aug 2026 13:47:05 -0700 Subject: [PATCH] fix: resolve video download CORS via host_permissions, unify records in activities.json, and add exporter_job_info.json --- dashboard/dashboard.js | 69 ++++++++++++++++++++++++++-------- dashboard/viewer_template.html | 11 ++++-- manifest.json | 5 ++- 3 files changed, 65 insertions(+), 20 deletions(-) diff --git a/dashboard/dashboard.js b/dashboard/dashboard.js index 838ea7c..eda0b4b 100644 --- a/dashboard/dashboard.js +++ b/dashboard/dashboard.js @@ -12,6 +12,7 @@ const state = { isCancelled: false, activities: [], mediaQueue: [], + executionLogs: [], stats: { activitiesCount: 0, mediaTotal: 0, @@ -79,10 +80,19 @@ function initDOMElements() { } function log(message, type = 'info') { + const timestamp = new Date().toISOString(); + const timeFormatted = new Date().toLocaleTimeString(); + + state.executionLogs.push({ + timestamp, + time: timeFormatted, + level: type, + message: message + }); + const line = document.createElement('div'); line.className = `log-line ${type}`; - const timestamp = new Date().toLocaleTimeString(); - line.textContent = `[${timestamp}] ${message}`; + line.textContent = `[${timeFormatted}] ${message}`; el.terminalLog.appendChild(line); el.terminalLog.scrollTop = el.terminalLog.scrollHeight; } @@ -970,14 +980,16 @@ async function downloadAllMediaFiles() { async function fetchMediaBlob(url) { let retries = 0; - while (retries < 3) { + const maxRetries = 3; + while (retries < maxRetries) { try { - const res = await fetch(url, { credentials: 'omit' }); - if (!res.ok) throw new Error(`HTTP ${res.status}`); + const res = await fetch(url, { method: 'GET' }); + if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`); return await res.blob(); } catch (err) { retries++; - if (retries >= 3) throw err; + if (retries >= maxRetries) throw err; + log(`Retrying media download in ${retries}s (${err.message})...`, 'warn'); await sleep(1000 * retries); } } @@ -1007,29 +1019,54 @@ async function buildAndDownloadZip(studentProfile) { } } - // 2. Add JSON Manifest + // 2. Single Source of Truth for Data Records: activities.json if (el.fmtJson.checked) { - const manifestData = { + const exportData = { export_date: new Date().toISOString(), student: studentProfile, total_activities: state.activities.length, activities: state.activities }; - rootFolder.file('manifest.json', JSON.stringify(manifestData, null, 2)); - - const rawList = state.activities.map(a => a.raw).filter(Boolean); - if (rawList.length > 0) { - rootFolder.file('raw_activities.json', JSON.stringify(rawList, null, 2)); - } + rootFolder.file('activities.json', JSON.stringify(exportData, null, 2)); } - // 3. Add CSV Spreadsheet + // 3. Exporter Job Information & Debug Logs: exporter_job_info.json + const jobInfo = { + job_id: `bw_export_${Date.now()}`, + export_date: new Date().toISOString(), + exporter_version: chrome.runtime?.getManifest?.()?.version || '1.0.0', + browser_environment: { + user_agent: navigator.userAgent, + platform: navigator.platform, + language: navigator.language, + screen_resolution: `${window.screen?.width || 0}x${window.screen?.height || 0}`, + extension_id: chrome.runtime?.id || null + }, + student: { + id: state.selectedStudentId, + name: `${studentProfile.first_name || ''} ${studentProfile.last_name || ''}`.trim(), + school: studentProfile.school_name || null + }, + statistics: { + total_activities_scraped: state.activities.length, + media_total: state.stats.mediaTotal, + photos_downloaded: state.mediaQueue.filter(m => !m.isVideo && m.status === 'done').length, + videos_downloaded: state.mediaQueue.filter(m => m.isVideo && m.status === 'done').length, + media_failed: state.stats.mediaFailed, + total_bytes_downloaded: state.stats.bytesDownloaded, + retry_count: state.stats.retryCount + }, + execution_logs: state.executionLogs + }; + rootFolder.file('exporter_job_info.json', JSON.stringify(jobInfo, null, 2)); + + // 4. Add CSV Spreadsheet if (el.fmtCsv.checked) { const csvContent = generateCSV(state.activities); rootFolder.file('activities.csv', csvContent); } - // 4. Add Standalone Interactive Offline Viewer + // 5. Add Standalone Interactive Offline Viewer if (el.fmtViewer.checked) { try { const templateRes = await fetch(chrome.runtime.getURL('dashboard/viewer_template.html')); diff --git a/dashboard/viewer_template.html b/dashboard/viewer_template.html index d6656d7..2763edc 100644 --- a/dashboard/viewer_template.html +++ b/dashboard/viewer_template.html @@ -263,17 +263,22 @@ async function init() { if (!ARCHIVE_DATA) { try { - const res = await fetch('manifest.json'); + const res = await fetch('activities.json'); if (res.ok) { ARCHIVE_DATA = await res.json(); } } catch (e) { - console.warn('Could not fetch manifest.json, checking global', e); + try { + const fallbackRes = await fetch('manifest.json'); + if (fallbackRes.ok) ARCHIVE_DATA = await fallbackRes.json(); + } catch (e2) { + console.warn('Could not fetch activities.json or manifest.json', e2); + } } } if (!ARCHIVE_DATA || !ARCHIVE_DATA.activities) { - document.getElementById('feedGrid').innerHTML = '
No activities found in manifest.json. Ensure manifest.json is in the same directory.
'; + document.getElementById('feedGrid').innerHTML = '
No activities found in activities.json. Ensure activities.json is in the same directory.
'; return; } diff --git a/manifest.json b/manifest.json index ab89cb0..ce9e4b0 100644 --- a/manifest.json +++ b/manifest.json @@ -34,8 +34,11 @@ ], "host_permissions": [ "https://schools.mybrightwheel.com/*", + "https://*.mybrightwheel.com/*", "https://*.brightwheel.com/*", "https://*.cloudfront.net/*", - "https://*.s3.amazonaws.com/*" + "https://*.amazonaws.com/*", + "https://*.s3.amazonaws.com/*", + "" ] }