feat(import): use WebSocket push for progress updates with polling fallback

This commit is contained in:
HotSwapp
2025-09-04 14:50:14 -05:00
parent 48ca876123
commit 032baf6e3e
2 changed files with 195 additions and 6 deletions

View File

@@ -882,7 +882,7 @@ function showProgress(show, message = '', percent = null) {
// Batch progress monitoring
// -----------------------------
const TERMINAL_BATCH_STATUSES = new Set(['success', 'completed_with_errors', 'failed']);
let batchProgress = { timer: null, auditId: null };
let batchProgress = { timer: null, auditId: null, wsMgr: null };
async function fetchCurrentBatch() {
try {
@@ -899,6 +899,8 @@ function stopBatchProgressPolling() {
batchProgress.timer = null;
}
batchProgress.auditId = null;
try { if (batchProgress.wsMgr) { batchProgress.wsMgr.close(); } } catch (_) {}
batchProgress.wsMgr = null;
}
async function pollBatchProgressOnce(auditId) {
@@ -924,9 +926,48 @@ async function pollBatchProgressOnce(auditId) {
function startBatchProgressPolling(auditId) {
stopBatchProgressPolling();
batchProgress.auditId = auditId;
// immediate + interval polling
pollBatchProgressOnce(auditId);
batchProgress.timer = setInterval(() => pollBatchProgressOnce(auditId), 1500);
// Try WebSocket first; fallback to polling
try {
const mgr = new (window.notifications && window.notifications.NotificationManager ? window.notifications.NotificationManager : null)({
getUrl: () => `/api/import/batch-progress/ws/${encodeURIComponent(auditId)}`,
onMessage: (msg) => {
if (!msg || !msg.type) return;
if (msg.type === 'progress') {
const p = msg.data || {};
const percent = Number(p.percent || 0);
const total = Number(p.total_files || 0);
const processed = Number(p.processed_files || 0);
const status = String(p.status || 'running');
const statusNice = status.replaceAll('_', ' ');
const msgText = total > 0
? `Processing ${processed}/${total} (${percent.toFixed(1)}%) · ${statusNice}`
: `Processing… ${statusNice}`;
showProgress(true, msgText, percent);
if (TERMINAL_BATCH_STATUSES.has(status)) {
stopBatchProgressPolling();
}
}
},
onStateChange: (state) => {
if (state === 'error' || state === 'closed' || state === 'offline') {
// fallback to polling
if (!batchProgress.timer) {
pollBatchProgressOnce(auditId);
batchProgress.timer = setInterval(() => pollBatchProgressOnce(auditId), 1500);
}
}
},
autoConnect: true,
debug: false
});
batchProgress.wsMgr = mgr;
// Safety: also do an immediate HTTP fetch as first snapshot
pollBatchProgressOnce(auditId);
} catch (_) {
// immediate + interval polling
pollBatchProgressOnce(auditId);
batchProgress.timer = setInterval(() => pollBatchProgressOnce(auditId), 1500);
}
}
async function ensureAuditIdWithRetry(maxAttempts = 10, delayMs = 500) {