From cf2c90e4ced69e74a3c509cceddc7ec895490bf4 Mon Sep 17 00:00:00 2001 From: seo Date: Tue, 21 Jul 2026 19:15:34 +0900 Subject: [PATCH] =?UTF-8?q?=EC=B0=A8=EB=9F=89=20=EB=AA=A8=EB=8B=88?= =?UTF-8?q?=ED=84=B0=20=EC=97=90=EC=85=8B=20=EA=B0=B1=EC=8B=A0=20=EB=B3=B4?= =?UTF-8?q?=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- assets/asset-reload.js | 126 ++++++++++++++++++++++++++++++++++++++--- monitor.php | 4 +- sw.js | 22 +------ 4 files changed, 125 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index c6552b4..fd3923e 100644 --- a/README.md +++ b/README.md @@ -64,9 +64,9 @@ Car는 차량 모뎀/게이트웨이에서 받은 상태를 수집해 DB에 저 - `sw.js`: service worker - `assets/`: 아이콘과 정적 자산 -`asset-version.php`는 차량 모니터의 PHP/JS/CSS/manifest/icon 파일을 읽어 크기, 수정 시각, sha256 hash를 묶은 버전을 만듭니다. `assets/asset-reload.js`는 열린 모니터 화면에서 이 값을 확인하고, 달라지면 Cache Storage를 비운 뒤 현재 화면을 다시 엽니다. +`asset-version.php`는 차량 모니터의 PHP/JS/CSS/manifest/icon 파일을 읽어 크기, 수정 시각, sha256 hash를 묶은 버전을 만듭니다. `assets/asset-reload.js`는 열린 모니터 화면에서 이 값을 확인하고, 달라지면 Cache Storage를 비운 뒤 현재 화면을 다시 엽니다. 현재 URL의 `assetReload` 토큰이 최신 파일 묶음 hash와 다르면 첫 진입도 토큰을 붙인 URL로 다시 열어 낡은 JS/CSS가 먼저 실행되는 상황을 줄입니다. 모바일/삼성 브라우저 호환을 위해 화면 복귀, 온라인 복귀, 터치/클릭 복귀 이벤트도 확인하고, Web Storage 실패 시 쿠키 fallback으로 이전 버전을 비교합니다. -`sw.js`는 아이콘과 manifest 같은 정적 자산만 캐시합니다. `monitor.php`, 인증 요청, AJAX/usage 같은 PHP 응답은 인증 쿠키와 실시간 상태가 엮이므로 service worker 캐시 대상에서 제외합니다. +`sw.js`는 기존 정적 캐시를 정리한 뒤 요청을 네트워크로 넘깁니다. `monitor.php`, 인증 요청, AJAX/usage 같은 PHP 응답은 인증 쿠키와 실시간 상태가 엮이므로 service worker 캐시 대상에서 제외합니다. ## 데이터/저장소 diff --git a/assets/asset-reload.js b/assets/asset-reload.js index 12a6a6a..580fad1 100644 --- a/assets/asset-reload.js +++ b/assets/asset-reload.js @@ -19,6 +19,82 @@ var storageKey = 'assetVersion:' + service; var reloadKey = 'assetReload:' + service; var running = false; + var memoryStore = {}; + var lastCheckAt = 0; + + function cookieName(key) { + return key.replace(/[^A-Za-z0-9_-]/g, '_'); + } + + function getCookie(key) { + var name = cookieName(key) + '='; + return (document.cookie || '').split(';').map(function (part) { + return part.trim(); + }).reduce(function (found, part) { + if (found || part.indexOf(name) !== 0) return found; + return decodeURIComponent(part.slice(name.length)); + }, ''); + } + + function setCookie(key, value) { + document.cookie = cookieName(key) + '=' + encodeURIComponent(value) + '; Path=/; Max-Age=31536000; SameSite=Lax'; + } + + function storeGet(key) { + try { + return localStorage.getItem(key) || getCookie(key) || memoryStore[key] || ''; + } catch (e) { + return getCookie(key) || memoryStore[key] || ''; + } + } + + function storeSet(key, value) { + memoryStore[key] = value; + try { + localStorage.setItem(key, value); + } catch (e) {} + try { + setCookie(key, value); + } catch (e) {} + } + + function sessionGet(key) { + try { + return sessionStorage.getItem(key) || memoryStore[key] || ''; + } catch (e) { + return memoryStore[key] || ''; + } + } + + function sessionSet(key, value) { + memoryStore[key] = value; + try { + sessionStorage.setItem(key, value); + } catch (e) {} + } + + function withTimeout(promise, timeout) { + return new Promise(function (resolve) { + var done = false; + var timer = setTimeout(function () { + if (done) return; + done = true; + resolve(); + }, timeout); + + Promise.resolve(promise).then(function () { + if (done) return; + done = true; + clearTimeout(timer); + resolve(); + }).catch(function () { + if (done) return; + done = true; + clearTimeout(timer); + resolve(); + }); + }); + } function addQuery(url, key, value) { var parsed = new URL(url, window.location.href); @@ -57,7 +133,11 @@ }); } - var currentAssetToken = new URL(window.location.href).searchParams.get('assetReload') || ''; + function assetTokenFromLocation() { + return new URL(window.location.href).searchParams.get('assetReload') || ''; + } + + var currentAssetToken = assetTokenFromLocation(); if (currentAssetToken) { applyAssetToken(currentAssetToken); new MutationObserver(function () { @@ -79,17 +159,27 @@ var token = version.slice(0, 12); var marker = token + ':' + Math.floor(Date.now() / 10000); - if (sessionStorage.getItem(reloadKey) === marker) return; + if (assetTokenFromLocation() === token) return; + if (assetTokenFromLocation() && sessionGet(reloadKey) === marker) return; - sessionStorage.setItem(reloadKey, marker); - deleteCaches().then(function () { + sessionSet(reloadKey, marker); + withTimeout(deleteCaches(), 2500).then(function () { + if (navigator.serviceWorker && navigator.serviceWorker.getRegistrations) { + navigator.serviceWorker.getRegistrations().then(function (registrations) { + registrations.forEach(function (registration) { + if (registration && typeof registration.update === 'function') registration.update(); + }); + }).catch(function () {}); + } window.location.replace(addQuery(window.location.href, 'assetReload', token)); }); } function check(force) { if (running || (!force && document.hidden)) return; + if (force && Date.now() - lastCheckAt < 1000) return; + lastCheckAt = Date.now(); running = true; fetch(addQuery(endpoint, '_', String(Date.now())), { cache: 'no-store', @@ -104,23 +194,45 @@ }).then(function (data) { if (!data || data.result !== 'ok' || !data.version) return; - var previous = localStorage.getItem(storageKey); - localStorage.setItem(storageKey, data.version); + var token = data.version.slice(0, 12); + var previous = storeGet(storageKey); + storeSet(storageKey, data.version); + + if (assetTokenFromLocation() !== token) { + reloadWith(data.version); + return; + } if (previous && previous !== data.version) { reloadWith(data.version); } - }).catch(function () {}).finally(function () { + }).then(function () { + running = false; + }, function () { running = false; }); } window.addEventListener('pageshow', function () { check(true); + setTimeout(function () { check(true); }, 250); + setTimeout(function () { check(true); }, 1500); }); window.addEventListener('focus', function () { check(true); }); + window.addEventListener('online', function () { + check(true); + }); + window.addEventListener('orientationchange', function () { + setTimeout(function () { check(true); }, 500); + }); + ['pointerdown', 'touchstart', 'click'].forEach(function (eventName) { + window.addEventListener(eventName, function () { + if (document.hidden || Date.now() - lastCheckAt < 5000) return; + check(true); + }, { passive: true }); + }); document.addEventListener('visibilitychange', function () { if (!document.hidden) check(true); }); diff --git a/monitor.php b/monitor.php index 24bf608..d215f8d 100644 --- a/monitor.php +++ b/monitor.php @@ -1202,7 +1202,7 @@ $carMonitorSession = car_monitor_launcher_session(); - + @@ -3350,7 +3350,7 @@ $carMonitorSession = car_monitor_launcher_session();