93 lines
3.3 KiB
JavaScript
93 lines
3.3 KiB
JavaScript
import React from 'react'
|
||
import ReactDOM from 'react-dom/client'
|
||
import App from './App.jsx'
|
||
|
||
// ── Auto-Reload bei neuem Build ───────────────────────────────────────────────
|
||
// __BUILD_TIME__ wird von Vite zur Build-Zeit aus version.txt gebacken.
|
||
// /api/build-time liefert denselben Wert vom Server.
|
||
// Wenn sie abweichen → neues Build wurde deployed → Reload.
|
||
// Kein localStorage, keine Race-Condition.
|
||
const MY_BUILD_TIME = __BUILD_TIME__;
|
||
window.__newBuildAvailable = false;
|
||
|
||
function isUserTyping() {
|
||
const el = document.activeElement;
|
||
if (!el) return false;
|
||
const tag = el.tagName;
|
||
return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || el.isContentEditable;
|
||
}
|
||
|
||
function hasUnsavedInput() {
|
||
const fields = document.querySelectorAll('input, textarea');
|
||
for (const f of fields) {
|
||
const isText = f.tagName === 'TEXTAREA' ||
|
||
['text','search','url','email','number','password','tel'].includes(f.type);
|
||
if (isText && f.value && f.value.trim() !== '') return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
async function getBuildTime() {
|
||
try {
|
||
const r = await fetch('/api/build-time', { cache: 'no-store' });
|
||
const { buildTime } = await r.json();
|
||
return buildTime;
|
||
} catch { return null; }
|
||
}
|
||
|
||
// Prüft ob Server-Build != Bundle-Build → Flag setzen
|
||
// reloadNow: sofort laden wenn kein Input aktiv (für App-Fokus)
|
||
async function checkBuild({ reloadNow = false } = {}) {
|
||
const serverBt = await getBuildTime();
|
||
if (!serverBt) return;
|
||
if (serverBt !== MY_BUILD_TIME) {
|
||
window.__newBuildAvailable = true;
|
||
if (reloadNow && !isUserTyping() && !hasUnsavedInput()) {
|
||
window.location.reload();
|
||
}
|
||
}
|
||
}
|
||
|
||
// Sicherer Reload bei Seitenwechsel (aufgerufen aus App.jsx setActive)
|
||
window.__safeReloadIfNewBuild = function () {
|
||
if (!window.__newBuildAvailable) return false;
|
||
if (isUserTyping() || hasUnsavedInput()) return false;
|
||
window.location.reload();
|
||
return true;
|
||
};
|
||
|
||
// Beim Start prüfen (kein sofortiger Reload – Seite lädt gerade erst)
|
||
checkBuild();
|
||
// Im Hintergrund alle 60s
|
||
setInterval(() => checkBuild(), 60_000);
|
||
|
||
// App kommt in den Vordergrund → prüfen und bei Bedarf sofort laden
|
||
// (PWA vom Homescreen öffnen / aus dem Hintergrund holen)
|
||
document.addEventListener('visibilitychange', () => {
|
||
if (document.visibilityState === 'visible') {
|
||
checkBuild({ reloadNow: true });
|
||
}
|
||
});
|
||
|
||
// ── Service Worker ────────────────────────────────────────────────────────────
|
||
if ('serviceWorker' in navigator) {
|
||
navigator.serviceWorker.addEventListener('message', (e) => {
|
||
if (e.data?.type === 'SW_ACTIVATED') window.__newBuildAvailable = true;
|
||
});
|
||
|
||
navigator.serviceWorker.register('/sw.js').then(reg => {
|
||
setInterval(() => reg.update().catch(()=>{}), 60_000);
|
||
if (reg.waiting) reg.waiting.postMessage('SKIP_WAITING');
|
||
reg.addEventListener('updatefound', () => {
|
||
const nw = reg.installing;
|
||
nw?.addEventListener('statechange', () => {
|
||
if (nw.state === 'installed') nw.postMessage('SKIP_WAITING');
|
||
});
|
||
});
|
||
}).catch(()=>{});
|
||
}
|
||
|
||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||
<React.StrictMode><App /></React.StrictMode>
|
||
)
|