From ea92730bd8c0a3d0d037244f2fee67dbe6de0ca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Thu, 16 Jul 2026 07:00:52 +0400 Subject: [PATCH] =?UTF-8?q?fix:=20XHR=20=E2=86=92=20fetch()=20for=20upload?= =?UTF-8?q?=20=E2=80=94=20drhider=20v0.0.29=20fix=20for=20ERR=5FCONNECTION?= =?UTF-8?q?=5FRESET?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- site/static/files.js | 107 +++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 55 deletions(-) diff --git a/site/static/files.js b/site/static/files.js index de71d1e..02092e3 100644 --- a/site/static/files.js +++ b/site/static/files.js @@ -30,7 +30,7 @@ function statusToHTML(st) { if (!st || !st.kind) return ''; switch (st.kind) { - case 'uploading': return '↑ ' + (st.pct || 0) + '%'; + case 'uploading': return '⏳ загрузка...'; case 'uploaded': return ''; case 'unzipping': return '⏳ распаковка...'; case 'parsing': return '⏳ парсинг...'; @@ -208,64 +208,62 @@ window.toggleClassifyDetail = async function(i) { }; /** - * uploadFile(file, onProgress) — XHR-загрузка одного файла на бэкенд. + * uploadFile(file, onProgress) — загрузка одного файла на бэкенд. + * + * v2.0.1: fetch() вместо XHR — XHR давал ERR_CONNECTION_RESET (drhider v0.0.29) + * Прогресс: без процентов (fetch не поддерживает), только статус. * * Особенности: * - .doc (не .docx!) конвертируется через CONVERT_URL перед загрузкой - * - onProgress(pct) — callback с процентом загрузки (0-100) - * - Возвращает Promise<ответ API> с полями doc_id, contract_id, parsed - * - Таймаут 180с (большие PDF) + * - onProgress(pct) — сохранён для совместимости, вызывается с 0 и 100 + * - Возвращает Promise<ответ API> */ function uploadFile(file, onProgress, zipSource) { - return new Promise(function(resolve, reject) { - // .doc → конвертация в docx (старый формат Word) - var isDoc = file.name.toLowerCase().endsWith('.doc') && !file.name.toLowerCase().endsWith('.docx'); - var uploadFile = file; - var uploadName = file.name; + // .doc → конвертация в docx (старый формат Word) + var isDoc = file.name.toLowerCase().endsWith('.doc') && !file.name.toLowerCase().endsWith('.docx'); - function doUpload() { - var xhr = new XMLHttpRequest(); - var fd = new FormData(); - fd.append('files', uploadFile, uploadName); - if (state.contractId) fd.append('contract_id', state.contractId); - fd.append('batch_id', state.batchId); - if (zipSource) fd.append('zip_source', zipSource); - xhr.open('POST', UPLOAD_URL); - xhr.upload.onprogress = function(e) { - if (e.lengthComputable && onProgress) onProgress(Math.round(e.loaded / e.total * 100)); - }; - xhr.onload = function() { - try { - var r = JSON.parse(xhr.responseText); - if (r.ok) resolve(r); - else reject(new Error(r.error || 'Неизвестная ошибка')); - } catch(e) { reject(new Error('Некорректный ответ')); } - }; - xhr.onerror = function() { reject(new Error('Сеть')); }; - xhr.ontimeout = function() { reject(new Error('Таймаут')); }; - xhr.timeout = 180000; - xhr.send(fd); - } + function doUpload(uploadFile, uploadName) { + if (onProgress) onProgress(0); + var fd = new FormData(); + fd.append('files', uploadFile, uploadName); + if (state.contractId) fd.append('contract_id', state.contractId); + fd.append('batch_id', state.batchId); + if (zipSource) fd.append('zip_source', zipSource); - if (isDoc) { - var xhr = new XMLHttpRequest(); - xhr.open('POST', CONVERT_URL); - xhr.responseType = 'blob'; - xhr.onload = function() { - if (xhr.status === 200 && xhr.response.size > 100) { - uploadFile = xhr.response; - uploadName = file.name.replace(/\.doc$/i, '.docx'); - doUpload(); - } else { - reject(new Error('Конвертация .doc')); + return fetch(UPLOAD_URL + '?_=' + Date.now(), { method: 'POST', body: fd }) + .then(function(r) { + if (!r.ok) throw new Error('HTTP ' + r.status); + return r.json(); + }) + .then(function(data) { + if (data.ok) { + if (onProgress) onProgress(100); + return data; } - }; - xhr.onerror = function() { reject(new Error('Конвертер')); }; - xhr.send(file); - } else { - doUpload(); - } - }); + throw new Error(data.error || 'Неизвестная ошибка'); + }) + .catch(function(e) { + // Перезапись fetch-ошибок в понятные сообщения + if (e.message === 'Failed to fetch' || e.name === 'TypeError') { + throw new Error('Сеть'); + } + throw e; + }); + } + + if (isDoc) { + return fetch(CONVERT_URL, { method: 'POST', body: file }) + .then(function(r) { + if (!r.ok) throw new Error('Конвертация .doc'); + return r.blob(); + }) + .then(function(blob) { + if (blob.size < 100) throw new Error('Конвертация .doc'); + return doUpload(blob, file.name.replace(/\.doc$/i, '.docx')); + }); + } else { + return doUpload(file, file.name); + } } /** @@ -425,7 +423,7 @@ async function addZipFile(file) { */ async function addRegularFile(file, zipSource) { // Создать запись с начальным статусом - var entry = { name: file.name, lastModified: file.lastModified, size: file.size, file: file, status: { kind: 'uploading', pct: 0 }, zip_source: zipSource || null }; + var entry = { name: file.name, lastModified: file.lastModified, size: file.size, file: file, status: { kind: 'uploading' }, zip_source: zipSource || null }; // ДЕДУПЛИКАЦИЯ: ключ = (zip_source, name), чтобы одноимённые файлы из разных ZIP не затирались var dupKey = (zipSource || '') + '/' + file.name; @@ -449,9 +447,8 @@ async function addRegularFile(file, zipSource) { render(state); try { - // XHR-загрузка с прогрессом - var resp = await uploadFile(file, function(pct) { - state.files[rowIdx].status = { kind: 'uploading', pct: pct }; + // fetch-загрузка (v2.0.1: XHR заменён на fetch — drhider v0.0.29) + var resp = await uploadFile(file, function() { render(state); }, zipSource); // Бэкенд возвращает doc_id и contract_id (нижний регистр — Python keys)