fix: resolve video download CORS via host_permissions, unify records in activities.json, and add exporter_job_info.json
This commit is contained in:
+53
-16
@@ -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'));
|
||||||
|
|||||||
@@ -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
@@ -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>"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user