From f554559be919c584529f6b6641f6dbb4f7670034 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Wed, 24 Jun 2026 21:24:12 +0900 Subject: [PATCH] installer: stop treating ifconfig.co failure as 'port closed' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 포트 1차 점검(probePortFromOutside) 오탐 수정. - 임시 리스너에 인바운드가 안 왔다는 이유만으로 false(닫힘) 단정하던 로직 제거. ifconfig.co 가 타임아웃/레이트리밋으로 실패하면 외부에서 연결 시도 자체가 없었던 것이라 '리스너 미도달'은 무의미하고, 인바운드 수신 주체가 마크 서버가 아니라 설치기 프로세스라 Windows 방화벽이 설치기만 막아도 미도달이 된다. 외부 판정이 없으면 reachable=null(확인 불가)로 남겨 UPnP 시도/안내로 넘긴다. - ifconfig.co 일시 실패 대비 1회 재시도 추가(fetchIfconfigCoPortOnce + 래퍼). v0.3.14. Co-Authored-By: Claude Opus 4 --- package.json | 2 +- src/installer/main.ts | 26 +++++++++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index eb8fe61..47a9c92 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minecraft-music-quiz-installer", - "version": "0.3.13", + "version": "0.3.14", "description": "마인크래프트 음악퀴즈 간편설치기 + 관리 사이트", "main": "dist/installer/main.js", "scripts": { diff --git a/src/installer/main.ts b/src/installer/main.ts index 1e44988..e390bc1 100644 --- a/src/installer/main.ts +++ b/src/installer/main.ts @@ -1063,8 +1063,14 @@ async function probePortFromOutside( details.push(t('log.detailIfconfigFail', { error: (externalResult as { error: string }).error })) } - // 임시 리스너가 떴고 외부 서비스도 닿지 않았다면 명확한 false. - if (reachable === null && listenerBound && !gotInboundConnection) reachable = false + // '닫힘(false)' 판정은 외부 점검 서비스(ifconfig.co)가 명시적으로 reachable=false 를 + // 돌려준 경우에만 인정한다(위 분기에서 처리). 임시 리스너에 인바운드가 안 왔다는 사실만으로는 + // false 로 단정하지 않는다: + // - ifconfig.co 가 타임아웃/레이트리밋으로 실패하면 애초에 외부에서 연결을 시도한 적이 없으므로 + // '리스너 미도달'은 아무 의미가 없다(과거엔 이 경우를 false 로 오탐했다). + // - 인바운드를 받는 주체가 마크 서버가 아니라 설치기(node/electron) 프로세스라, Windows 방화벽이 + // 설치기 인바운드만 막아도 포워딩이 정상이어도 '리스너 미도달'이 된다. + // 따라서 외부 판정이 없으면 reachable=null(확인 불가)로 남겨 UPnP 시도/안내 단계로 넘긴다. return { reachable, @@ -1073,7 +1079,21 @@ async function probePortFromOutside( } } -function fetchIfconfigCoPort(port: number): Promise<{ ok: true; reachable: boolean | null; ip: string } | { ok: false; error: string }> { +type IfconfigPortResult = { ok: true; reachable: boolean | null; ip: string } | { ok: false; error: string } + +// ifconfig.co 는 간헐적으로 타임아웃/레이트리밋(429)을 낸다. 실패를 곧장 '확인 불가'로 +// 넘기기 전에 한 번 더 시도해서 일시적 실패로 인한 오탐/미확인을 줄인다. +async function fetchIfconfigCoPort(port: number): Promise { + let last: IfconfigPortResult = { ok: false, error: 'no attempt' } + for (let attempt = 0; attempt < 2; attempt++) { + last = await fetchIfconfigCoPortOnce(port) + if (last.ok) return last + if (attempt === 0) await sleep(1500) + } + return last +} + +function fetchIfconfigCoPortOnce(port: number): Promise { return new Promise((resolve) => { const target = new URL(`https://ifconfig.co/port/${port}`) const req = https.get(target, {