diff --git a/posadmesh-tools/flasher/app.js b/posadmesh-tools/flasher/app.js index 714335d3..689cb209 100644 --- a/posadmesh-tools/flasher/app.js +++ b/posadmesh-tools/flasher/app.js @@ -4,6 +4,7 @@ const $ = (id) => document.getElementById(id); const logEl = $('log'); let index = null; +let manifestStamp = ''; // generated_at манифеста, который сейчас в памяти let selected = null; // выбранный файл из index.json let ownFile = null; // файл, загруженный пользователем с компьютера let transport = null; @@ -23,6 +24,36 @@ function log(msg, cls = '') { logEl.scrollTop = logEl.scrollHeight; } +// ---------------------------------------------------------------- устаревшая страница + +// Страница, открытая давно, держит манифест в памяти: после выкладки новой +// сборки его ссылки ведут на удалённые файлы и скачивание молча не работает. +// Показываем баннер и предлагаем обновить страницу. +function showStale(msg) { + const box = $('stale'); + if (!box) return; + if (msg) $('stale-text').textContent = msg; + if (box.hidden) { + box.hidden = false; + log('Доступна новая версия прошивальщика — обновите страницу.', 'warn'); + } +} + +// Сверяет generated_at манифеста в памяти со свежим ответом сервера. +async function checkFreshness() { + if (!manifestStamp) return false; + try { + const res = await fetch('index.json', { cache: 'no-store' }); + if (!res.ok) return false; + const fresh = await res.json(); + if (fresh.generated_at && fresh.generated_at !== manifestStamp) { + showStale(); + return true; + } + } catch (e) { /* сеть недоступна — молчим, это не ошибка страницы */ } + return false; +} + function setProgress(ratio, label = '') { const pct = Math.max(0, Math.min(1, ratio || 0)) * 100; $('bar').style.width = pct.toFixed(1) + '%'; @@ -75,12 +106,25 @@ function fileRow(section, board, file) { const btn = document.createElement('button'); btn.className = 'small' + (file.flashable ? '' : ''); btn.textContent = file.flashable ? 'Прошить' : 'Скачать'; - btn.addEventListener('click', (e) => { + btn.addEventListener('click', async (e) => { e.preventDefault(); if (file.flashable) { select(file, row); flash(); } else { + // Перед скачиванием проверяем, что файл ещё на месте: у давно открытой + // страницы в памяти может быть старый манифест со ссылками на удалённые + // файлы. Тогда честно показываем баннер «обновите страницу». + btn.disabled = true; + try { + const head = await fetch(file.url, { method: 'HEAD', cache: 'no-store' }); + if (!head.ok) { + showStale('Файл ' + file.name + ' не найден — на сервере уже новая версия прошивальщика.'); + await checkFreshness(); + return; + } + } catch (err) { /* сеть недоступна — пробуем скачать как раньше */ } + finally { btn.disabled = false; } const a = document.createElement('a'); a.href = file.url; a.download = file.name; @@ -212,6 +256,7 @@ async function loadIndex() { const res = await fetch('index.json', { cache: 'no-store' }); if (!res.ok) throw new Error('HTTP ' + res.status); index = await res.json(); + manifestStamp = index.generated_at || ''; const host = $('sections'); host.textContent = ''; index.sections.forEach((s) => host.appendChild(sectionBlock(s))); @@ -479,3 +524,10 @@ if ('serial' in navigator) { loadIndex(); updateButtons(); + +// Раз в минуту и при возврате на вкладку сверяем манифест: если на сервере уже +// новая сборка, показываем баннер вместо тихих 404 при скачивании файлов. +setInterval(checkFreshness, 60000); +document.addEventListener('visibilitychange', () => { + if (!document.hidden) checkFreshness(); +}); diff --git a/posadmesh-tools/flasher/index.html b/posadmesh-tools/flasher/index.html index 18a8946f..94b7b246 100644 --- a/posadmesh-tools/flasher/index.html +++ b/posadmesh-tools/flasher/index.html @@ -5,7 +5,7 @@