installer: 3-3 자동 다운로드 및 UPnP 안정화
3-3 서버 다운로드도 진입 즉시 자동 시작하도록 변경. "다운로드 시작" 버튼을 제거하고 4-2와 같은 자동 실행 패턴을 적용했다(EULA 모달은 그대로 유지). 실패 시 이전→다음으로 재시도. UPnP 점검 안정화: - openPortViaUpnp에 15초 타임아웃 추가. SSDP 응답 없을 때 영구 hang을 방지한다. - detectExternalIp: ipify 단일 실패 시 ifconfig.me/icanhazip 폴백 후, 최종 폴백으로 UPnP 게이트웨이의 externalIp를 사용. 기존에는 IP를 못 얻으면 UPnP 시도조차 안 했음. - portMapping 성공 후 NAT 상태 전파 지연을 고려해 testPortReachable을 1.5초 간격 3회 재시도. - 각 단계마다 로그를 남겨 라우터 UPnP 비활성/SSDP 차단/이중 NAT 등의 원인을 구분할 수 있게 함. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -260,20 +260,22 @@ function renderSubStep33(host, back, done) {
|
||||
'<h3>3-3. 서버 다운로드 및 설치</h3>' +
|
||||
'<p class="formMessage">선택한 음악퀴즈의 서버 파일을 다운로드합니다. 진행 상황은 하단 로그 뷰어에 표시됩니다.</p>' +
|
||||
'<div class="formMessage" id="downloadStatus">대기 중</div>' +
|
||||
'<button class="primaryBtn" id="startDownload">다운로드 시작</button>' +
|
||||
'<div id="ramSection" hidden style="margin-top:14px;">' +
|
||||
'<h4>램 검사</h4>' +
|
||||
'<div class="formMessage" id="ramMsg">검사 중...</div>' +
|
||||
'</div>' +
|
||||
'<div class="actionRow"><button class="secondaryBtn" id="back">이전</button><button class="primaryBtn" id="next" disabled>다음</button></div>'
|
||||
|
||||
var startBtn = host.querySelector('#startDownload')
|
||||
var statusEl = host.querySelector('#downloadStatus')
|
||||
var ramSection = host.querySelector('#ramSection')
|
||||
var ramMsg = host.querySelector('#ramMsg')
|
||||
var nextBtn = host.querySelector('#next')
|
||||
|
||||
host.querySelector('#back').addEventListener('click', back)
|
||||
nextBtn.addEventListener('click', function () {
|
||||
if (!state.serverInstall.eulaAccepted) return
|
||||
done()
|
||||
})
|
||||
|
||||
// 이미 통과했던 상태 복원: 사용자가 다음→이전으로 돌아왔을 때 재다운로드 강요하지 않는다.
|
||||
if (state.serverInstall.eulaAccepted && state.serverInstall.ram) {
|
||||
@@ -281,10 +283,11 @@ function renderSubStep33(host, back, done) {
|
||||
statusEl.classList.add('success')
|
||||
showRamResult(state.serverInstall.ram)
|
||||
nextBtn.disabled = false
|
||||
return
|
||||
}
|
||||
|
||||
startBtn.addEventListener('click', async function () {
|
||||
startBtn.disabled = true
|
||||
// 페이지 진입 즉시 자동 다운로드
|
||||
;(async function () {
|
||||
state.serverInstall.eulaAccepted = false
|
||||
nextBtn.disabled = true
|
||||
statusEl.classList.remove('success', 'error')
|
||||
@@ -298,9 +301,8 @@ function renderSubStep33(host, back, done) {
|
||||
statusEl.textContent = 'EULA 동의가 필요합니다. 팝업을 확인해 주세요.'
|
||||
var accepted = await openEulaPopup(state.serverInstall.path)
|
||||
if (!accepted) {
|
||||
statusEl.textContent = 'EULA 동의 실패. 다운로드를 취소합니다. "다운로드 시작"으로 다시 시도하세요.'
|
||||
statusEl.textContent = 'EULA 동의 실패. 다운로드를 취소했습니다. 이전→다음으로 다시 시도하세요.'
|
||||
statusEl.classList.add('error')
|
||||
startBtn.disabled = false
|
||||
return
|
||||
}
|
||||
try {
|
||||
@@ -308,7 +310,6 @@ function renderSubStep33(host, back, done) {
|
||||
} catch (err) {
|
||||
statusEl.textContent = 'EULA 저장 실패: ' + err.message
|
||||
statusEl.classList.add('error')
|
||||
startBtn.disabled = false
|
||||
return
|
||||
}
|
||||
state.serverInstall.eulaAccepted = true
|
||||
@@ -320,16 +321,10 @@ function renderSubStep33(host, back, done) {
|
||||
if (ram.decision === 'tooLow') return
|
||||
nextBtn.disabled = false
|
||||
} catch (err) {
|
||||
statusEl.textContent = '다운로드 실패: ' + err.message
|
||||
statusEl.textContent = '다운로드 실패: ' + (err && err.message ? err.message : err)
|
||||
statusEl.classList.add('error')
|
||||
startBtn.disabled = false
|
||||
}
|
||||
})
|
||||
|
||||
nextBtn.addEventListener('click', function () {
|
||||
if (!state.serverInstall.eulaAccepted) return
|
||||
done()
|
||||
})
|
||||
})()
|
||||
|
||||
function showRamResult(result) {
|
||||
ramSection.hidden = false
|
||||
|
||||
Reference in New Issue
Block a user