Android(TWA) 앱 패키징 + 재실행 불가 버그 수정

Cloudflare Pages 가 /index.html 을 / 로 308 리다이렉트하는데, 서비스워커가
그 리다이렉트 응답을 캐시해 두면 navigation 요청에 응답할 수 없어(스펙상 금지)
두 번째 실행부터 ERR_FAILED 로 앱이 아예 열리지 않았다.

- sw.js: navigation 은 네트워크 우선 + 캐시된 './' 오프라인 폴백,
  프리캐시는 리다이렉트 플래그 없는 새 Response 로 복사해 저장 (tetrig-v2)
- manifest.webmanifest: start_url 을 './' 로 바꿔 리다이렉트 자체를 회피
- android/: Bubblewrap TWA 프로젝트 (kr.tkrmagid.tetrig, launchUrl '/')
- .well-known/assetlinks.json: 서명 키 SHA-256 등록 → 주소창 없는 전체화면 실행
This commit is contained in:
2026-09-23 04:30:10 +09:00
parent c9463fe46b
commit 0839705b97
46 changed files with 1177 additions and 8 deletions

32
sw.js
View File

@@ -1,16 +1,27 @@
// TETRIG 서비스 워커 — 오프라인 캐시 (앱처럼 설치/실행)
const CACHE = 'tetrig-v1';
const CACHE = 'tetrig-v2';
const ASSETS = [
'./',
'./index.html',
'./manifest.webmanifest',
'./icon-192.png',
'./icon-512.png',
'./apple-touch-icon.png',
];
// Cloudflare Pages 는 /index.html 을 / 로 308 리다이렉트한다.
// 리다이렉트된 Response 를 그대로 캐시하면 navigation 요청에 응답할 수 없어
// (spec 상 금지) 두 번째 실행부터 ERR_FAILED 가 난다. 새 Response 로 복사해 저장한다.
async function cacheClean(cache, url) {
const res = await fetch(url, { cache: 'reload' });
if (!res.ok) return;
const body = await res.blob();
await cache.put(url, new Response(body, { status: 200, statusText: 'OK', headers: res.headers }));
}
self.addEventListener('install', (e) => {
e.waitUntil(caches.open(CACHE).then((c) => c.addAll(ASSETS)));
e.waitUntil(
caches.open(CACHE).then((c) => Promise.all(ASSETS.map((u) => cacheClean(c, u))))
);
self.skipWaiting();
});
@@ -23,10 +34,17 @@ self.addEventListener('activate', (e) => {
self.clients.claim();
});
// 캐시 우선, 없으면 네트워크 (게임은 정적이라 오프라인 완전 동작)
self.addEventListener('fetch', (e) => {
if (e.request.method !== 'GET') return;
e.respondWith(
caches.match(e.request).then((r) => r || fetch(e.request))
);
// 페이지 진입: 네트워크 우선, 실패하면 캐시된 시작 문서로 오프라인 동작
if (e.request.mode === 'navigate') {
e.respondWith(
fetch(e.request).catch(async () => (await caches.match('./')) || Response.error())
);
return;
}
// 나머지 정적 에셋: 캐시 우선
e.respondWith(caches.match(e.request).then((r) => r || fetch(e.request)));
});