From 6447b1cb78f8fca07622b5adb5563f47e26c2f97 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Wed, 20 May 2026 10:20:49 +0900 Subject: [PATCH] terms: block install on terms list fetch failure (retry UI) Reviewer caught that v0.3.4 was bypassing the agreement step entirely on network/server errors, letting users install without ever seeing terms. Now only the explicit empty-list response (terms:[]) skips the step. Network errors, 404s, and IPC failures render an error page with Back/Retry buttons; no next button is exposed. Co-Authored-By: Claude Opus 4.7 --- installer-rp/renderer.js | 27 +++++++++++++++++++++++---- installer/renderer.js | 30 +++++++++++++++++++++++++----- locales/installer-rp/ko-kr.json | 4 +++- locales/installer/ko-kr.json | 4 +++- 4 files changed, 54 insertions(+), 11 deletions(-) diff --git a/installer-rp/renderer.js b/installer-rp/renderer.js index 57b2fe0..db5fd80 100644 --- a/installer-rp/renderer.js +++ b/installer-rp/renderer.js @@ -141,7 +141,8 @@ function renderStep1() { } // 약관 동의 페이지: 1단계 직후, 2단계 설치 진입 전에 노출. -// v0.3.4~ : 사이트의 visibility 토글에 따라 표시할 약관이 결정된다. 목록이 비면 단계를 건너뛴다. +// v0.3.4~ : 사이트의 visibility 토글에 따라 표시할 약관이 결정된다. 명시적으로 빈 목록(terms:[]) +// 정상 응답일 때만 단계를 건너뛰고, 네트워크/서버 오류는 차단 후 다시 시도 UI를 보여준다. function renderAgreement() { setActiveStep(1) clearPage() @@ -153,7 +154,7 @@ function renderAgreement() { api.getTermsList().then(function (res) { if (!res || !res.ok) { - renderStep2() + showAgreementError((res && res.message) || 'unknown') return } var terms = (res.terms || []).map(function (t) { @@ -165,11 +166,29 @@ function renderAgreement() { } clearPage() renderAgreementWithKinds(terms) - }).catch(function () { - renderStep2() + }).catch(function (err) { + showAgreementError(err && err.message ? err.message : 'unknown') }) } +// 약관 목록을 못 받아왔을 때: 사용자에게 오류 + 다시 시도 옵션. 동의 없이 설치 단계로 +// 자동 진입하지 않도록 next 버튼을 두지 않는다. +function showAgreementError(message) { + clearPage() + var section = document.createElement('section') + section.className = 'page' + section.innerHTML = + '

' + escapeHtml(tt('agreement.heading')) + '

' + + '

' + escapeHtml(tt('agreement.listLoadFailed', { message: message })) + '

' + + '
' + + '' + + '' + + '
' + pageHost.appendChild(section) + section.querySelector('#back').addEventListener('click', renderStep1) + section.querySelector('#retry').addEventListener('click', renderAgreement) +} + function renderAgreementWithKinds(KINDS) { var section = document.createElement('section') section.className = 'page' diff --git a/installer/renderer.js b/installer/renderer.js index 634bc69..13b2437 100644 --- a/installer/renderer.js +++ b/installer/renderer.js @@ -150,7 +150,9 @@ function renderStep1() { // 약관 동의 페이지: 음악퀴즈 선택 직후, 싱글/멀티 선택(step2) 진입 전에 노출. // v0.3.4~ : 어떤 약관을 표시할지는 사이트(/manifest/terms//index.json) 가 -// 결정. 메인 인스톨러용으로 표시 토글된 항목만 받아 탭을 만든다. 목록이 비면 약관 단계를 건너뛴다. +// 결정. 메인 인스톨러용으로 표시 토글된 항목만 받아 탭을 만든다. 목록이 비어 있는 (terms:[]) +// 정상 응답일 때만 단계 자체를 건너뛴다. 네트워크 오류/404/서버 오류는 사용자가 약관 동의 +// 없이 설치로 넘어가는 것을 막기 위해 오류 화면 + 다시 시도 버튼으로 차단한다. function renderAgreement() { setActiveStep(1) clearPage() @@ -162,24 +164,42 @@ function renderAgreement() { installerApi.getTermsList().then(function (res) { if (!res || !res.ok) { - // 목록 조회 실패면 약관 단계를 건너뛴다 (서버가 구버전일 수도 있으므로 차단보다 통과 선호). - renderStep2() + showAgreementError((res && res.message) || 'unknown') return } var terms = (res.terms || []).map(function (t) { return { id: t.kind, tab: t.label } }) if (terms.length === 0) { + // 명시적으로 표시 대상이 0개라고 서버가 알려준 정상 응답 → 약관 단계 스킵. renderStep2() return } clearPage() renderAgreementWithKinds(terms) - }).catch(function () { - renderStep2() + }).catch(function (err) { + showAgreementError(err && err.message ? err.message : 'unknown') }) } +// 약관 목록을 못 받아왔을 때: 사용자에게 오류 + 다시 시도/뒤로 가기 옵션을 보여준다. +// 동의 없이 설치 단계로 넘어가지 않도록 next 버튼을 두지 않는다. +function showAgreementError(message) { + clearPage() + var section = document.createElement('section') + section.className = 'page' + section.innerHTML = + '

' + tt('agreement.heading') + '

' + + '

' + tt('agreement.listLoadFailed', { message: message }) + '

' + + '
' + + '' + + '' + + '
' + pageHost.appendChild(section) + section.querySelector('#back').addEventListener('click', renderStep1) + section.querySelector('#retry').addEventListener('click', renderAgreement) +} + function renderAgreementWithKinds(KINDS) { var section = document.createElement('section') section.className = 'page' diff --git a/locales/installer-rp/ko-kr.json b/locales/installer-rp/ko-kr.json index 07436e3..a451077 100644 --- a/locales/installer-rp/ko-kr.json +++ b/locales/installer-rp/ko-kr.json @@ -38,7 +38,9 @@ "tabInstaller": "리소스팩 설치기 약관", "loading": "약관을 불러오는 중...", "loadFailed": "약관 로드 실패: {{message}}", - "agreeAll": "위 모든 약관(리소스팩·설치기)에 동의합니다.", + "listLoadFailed": "약관 목록을 불러오지 못했습니다: {{message}}\n네트워크 상태를 확인하고 다시 시도해 주세요.", + "retry": "다시 시도", + "agreeAll": "위 모든 약관에 동의합니다.", "agreeRequired": "약관에 동의해야 다음 단계로 진행할 수 있습니다.", "cancelling": "취소 중…" }, diff --git a/locales/installer/ko-kr.json b/locales/installer/ko-kr.json index 81b63a0..bb2d6a0 100644 --- a/locales/installer/ko-kr.json +++ b/locales/installer/ko-kr.json @@ -38,7 +38,9 @@ "tabInstaller": "설치기 약관", "loading": "약관을 불러오는 중...", "loadFailed": "약관 로드 실패: {{message}}", - "agreeAll": "위 모든 약관(맵·모드·설치기)에 동의합니다.", + "listLoadFailed": "약관 목록을 불러오지 못했습니다: {{message}}\n네트워크 상태를 확인하고 다시 시도해 주세요.", + "retry": "다시 시도", + "agreeAll": "위 모든 약관에 동의합니다.", "agreeRequired": "약관에 동의해야 다음 단계로 진행할 수 있습니다." }, "step1": {