From 9a4a6e59b25c4a3c8100d6c4659638b564f68302 Mon Sep 17 00:00:00 2001 From: seo Date: Tue, 21 Jul 2026 18:19:41 +0900 Subject: [PATCH] Improve mobile asset reload compatibility --- README.md | 2 +- app/views/header.php | 2 +- public/assets/asset-reload.js | 113 ++++++++++++++++++++++++++++++++-- public/login.php | 2 +- public/register.php | 2 +- 5 files changed, 111 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index bf8344e..fafd108 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Financial은 개인 금융 데이터를 서버 렌더링 PHP 화면에서 관리 - `public/assets/asset-reload.js`: 열린 브라우저에서 파일 묶음 hash가 달라졌는지 확인하고 새로 여는 스크립트 - `public/assets/pwa.js`, `public/sw.js`: PWA 동작 -`asset-version.php`는 `app`의 PHP 파일과 `public`의 PHP/HTML/JS/CSS/manifest/icon 파일을 읽어 크기, 수정 시각, sha256 hash를 묶은 버전을 만듭니다. 공통 헤더, 로그인, 회원가입 화면에서 `asset-reload.js`를 불러오며, 파일 묶음 hash가 달라지면 Cache Storage를 비우고 현재 화면을 다시 엽니다. +`asset-version.php`는 `app`의 PHP 파일과 `public`의 PHP/HTML/JS/CSS/manifest/icon 파일을 읽어 크기, 수정 시각, sha256 hash를 묶은 버전을 만듭니다. 공통 헤더, 로그인, 회원가입 화면에서 `asset-reload.js`를 불러오며, 파일 묶음 hash가 달라지면 Cache Storage를 비우고 현재 화면을 다시 엽니다. 모바일/삼성 브라우저 호환을 위해 화면 복귀, 온라인 복귀, 터치/클릭 복귀 이벤트도 확인하고, Web Storage 실패 시 쿠키 fallback으로 이전 버전을 비교합니다. ## 데이터/저장소 diff --git a/app/views/header.php b/app/views/header.php index 30a03f8..fdaf10c 100644 --- a/app/views/header.php +++ b/app/views/header.php @@ -37,7 +37,7 @@ - + diff --git a/public/assets/asset-reload.js b/public/assets/asset-reload.js index 12a6a6a..6008abd 100644 --- a/public/assets/asset-reload.js +++ b/public/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); @@ -79,17 +155,26 @@ var token = version.slice(0, 12); var marker = token + ':' + Math.floor(Date.now() / 10000); - if (sessionStorage.getItem(reloadKey) === marker) return; + if (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 +189,39 @@ }).then(function (data) { if (!data || data.result !== 'ok' || !data.version) return; - var previous = localStorage.getItem(storageKey); - localStorage.setItem(storageKey, data.version); + var previous = storeGet(storageKey); + storeSet(storageKey, data.version); 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/public/login.php b/public/login.php index 1584c9c..100d637 100644 --- a/public/login.php +++ b/public/login.php @@ -78,7 +78,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { - + diff --git a/public/register.php b/public/register.php index aa8b416..0a18dea 100644 --- a/public/register.php +++ b/public/register.php @@ -87,7 +87,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { - +