fix: resolve video download CORS via host_permissions, unify records in activities.json, and add exporter_job_info.json

This commit is contained in:
2026-08-23 13:47:05 -07:00
parent cedaba4ca6
commit 20d81a2b4d
3 changed files with 65 additions and 20 deletions
+53 -16
View File
@@ -12,6 +12,7 @@ const state = {
isCancelled: false, isCancelled: false,
activities: [], activities: [],
mediaQueue: [], mediaQueue: [],
executionLogs: [],
stats: { stats: {
activitiesCount: 0, activitiesCount: 0,
mediaTotal: 0, mediaTotal: 0,
@@ -79,10 +80,19 @@ function initDOMElements() {
} }
function log(message, type = 'info') { 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'); const line = document.createElement('div');
line.className = `log-line ${type}`; line.className = `log-line ${type}`;
const timestamp = new Date().toLocaleTimeString(); line.textContent = `[${timeFormatted}] ${message}`;
line.textContent = `[${timestamp}] ${message}`;
el.terminalLog.appendChild(line); el.terminalLog.appendChild(line);
el.terminalLog.scrollTop = el.terminalLog.scrollHeight; el.terminalLog.scrollTop = el.terminalLog.scrollHeight;
} }
@@ -970,14 +980,16 @@ async function downloadAllMediaFiles() {
async function fetchMediaBlob(url) { async function fetchMediaBlob(url) {
let retries = 0; let retries = 0;
while (retries < 3) { const maxRetries = 3;
while (retries < maxRetries) {
try { try {
const res = await fetch(url, { credentials: 'omit' }); const res = await fetch(url, { method: 'GET' });
if (!res.ok) throw new Error(`HTTP ${res.status}`); if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`);
return await res.blob(); return await res.blob();
} catch (err) { } catch (err) {
retries++; 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); 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) { if (el.fmtJson.checked) {
const manifestData = { const exportData = {
export_date: new Date().toISOString(), export_date: new Date().toISOString(),
student: studentProfile, student: studentProfile,
total_activities: state.activities.length, total_activities: state.activities.length,
activities: state.activities activities: state.activities
}; };
rootFolder.file('manifest.json', JSON.stringify(manifestData, null, 2)); rootFolder.file('activities.json', JSON.stringify(exportData, 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));
}
} }
// 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) { if (el.fmtCsv.checked) {
const csvContent = generateCSV(state.activities); const csvContent = generateCSV(state.activities);
rootFolder.file('activities.csv', csvContent); rootFolder.file('activities.csv', csvContent);
} }
// 4. Add Standalone Interactive Offline Viewer // 5. Add Standalone Interactive Offline Viewer
if (el.fmtViewer.checked) { if (el.fmtViewer.checked) {
try { try {
const templateRes = await fetch(chrome.runtime.getURL('dashboard/viewer_template.html')); const templateRes = await fetch(chrome.runtime.getURL('dashboard/viewer_template.html'));
+8 -3
View File
@@ -263,17 +263,22 @@
async function init() { async function init() {
if (!ARCHIVE_DATA) { if (!ARCHIVE_DATA) {
try { try {
const res = await fetch('manifest.json'); const res = await fetch('activities.json');
if (res.ok) { if (res.ok) {
ARCHIVE_DATA = await res.json(); ARCHIVE_DATA = await res.json();
} }
} catch (e) { } 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) { if (!ARCHIVE_DATA || !ARCHIVE_DATA.activities) {
document.getElementById('feedGrid').innerHTML = '<div class="empty-state">No activities found in manifest.json. Ensure manifest.json is in the same directory.</div>'; document.getElementById('feedGrid').innerHTML = '<div class="empty-state">No activities found in activities.json. Ensure activities.json is in the same directory.</div>';
return; return;
} }
+4 -1
View File
@@ -34,8 +34,11 @@
], ],
"host_permissions": [ "host_permissions": [
"https://schools.mybrightwheel.com/*", "https://schools.mybrightwheel.com/*",
"https://*.mybrightwheel.com/*",
"https://*.brightwheel.com/*", "https://*.brightwheel.com/*",
"https://*.cloudfront.net/*", "https://*.cloudfront.net/*",
"https://*.s3.amazonaws.com/*" "https://*.amazonaws.com/*",
"https://*.s3.amazonaws.com/*",
"<all_urls>"
] ]
} }