diff --git a/public/editor.js b/public/editor.js index b6e04f5..b588f56 100644 --- a/public/editor.js +++ b/public/editor.js @@ -123,22 +123,31 @@ }) function pollJob(jobId, videoId) { - fetch('/op/job/' + encodeURIComponent(jobId)).then(function (r) { return r.json() }).then(function (j) { - if (!j.ok) { - probeInfo.textContent = j.message || '작업을 찾을 수 없음' - return - } - var job = j.job - dlProgress.value = job.progress - probeInfo.textContent = job.message - if (job.status === 'done') { - location.href = '/op/folder/' + encodeURIComponent(folder) + '/video/editor?id=' + encodeURIComponent(videoId) - } else if (job.status === 'error') { - probeInfo.textContent = '실패: ' + (job.error || '') - } else { - setTimeout(function () { pollJob(jobId, videoId) }, 1500) - } - }) + // cache: 'no-store' 로 304 가 나지 않게 강제. 304 면 body 가 비어 + // r.json() 이 reject → 폴링 중단되는 문제 방지. + fetch('/op/job/' + encodeURIComponent(jobId), { cache: 'no-store' }) + .then(function (r) { return r.json() }) + .then(function (j) { + if (!j.ok) { + probeInfo.textContent = j.message || '작업을 찾을 수 없음' + return + } + var job = j.job + dlProgress.value = job.progress + probeInfo.textContent = job.message + if (job.status === 'done') { + location.href = '/op/folder/' + encodeURIComponent(folder) + '/video/editor?id=' + encodeURIComponent(videoId) + } else if (job.status === 'error') { + probeInfo.textContent = '실패: ' + (job.error || '') + } else { + setTimeout(function () { pollJob(jobId, videoId) }, 1500) + } + }) + .catch(function (err) { + // 네트워크 일시 오류로 폴링이 영구 중단되지 않도록 짧게 백오프 후 재시도. + console.warn('[pollJob] fetch 실패, 재시도:', err) + setTimeout(function () { pollJob(jobId, videoId) }, 2000) + }) } // ── 타임라인 트림 (lossless-cut 류 핸들 UI) ───────────────────────── diff --git a/src/routes/op.ts b/src/routes/op.ts index 242b3ef..a32fcd5 100644 --- a/src/routes/op.ts +++ b/src/routes/op.ts @@ -291,6 +291,10 @@ opRouter.post('/op/folder/:name/video/youtube/start', requireAuth, async (req, r }) opRouter.get('/op/job/:id', requireAuth, (req, res) => { + // 폴링 응답이 304 로 캐싱되면 브라우저가 body 없는 응답을 돌려줘서 + // 클라이언트의 r.json() 이 reject → 폴링이 중단된다. 항상 신선한 응답. + res.set('Cache-Control', 'no-store, no-cache, must-revalidate') + res.set('Pragma', 'no-cache') const job = getJob(req.params.id) if (!job) { res.status(404).json({ ok: false, message: '작업을 찾을 수 없습니다.' })