From f03fd3490722e9ee3b737214e0838096abd079e5 Mon Sep 17 00:00:00 2001 From: SH010526 Date: Sat, 3 Jan 2026 01:48:18 +0900 Subject: [PATCH] Harden API base URL for HTTPS and add fallback notices --- online_class_platform_v4/script.js | 64 +++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/online_class_platform_v4/script.js b/online_class_platform_v4/script.js index e426e51..bb09f5f 100644 --- a/online_class_platform_v4/script.js +++ b/online_class_platform_v4/script.js @@ -37,16 +37,36 @@ function ensureSupabaseClient() { } ensureSupabaseClient(); -// OTP 백엔드 API +// OTP 백엔드 API (HTTPS 환경에서도 강제로 HTTP를 섞지 않도록 보정) +function normalizeBaseUrl(url) { + if (!url) return ""; + try { + const u = new URL(url, window?.location?.href || ""); + // 페이지가 HTTPS라면 HTTP를 HTTPS로 자동 승격 + const protocol = u.protocol === "http:" && window?.location?.protocol === "https:" ? "https:" : u.protocol; + return `${protocol}//${u.host}`; + } catch (_) { + return url; + } +} + const API_BASE_URL = (() => { - if (typeof window !== "undefined" && window.location) { - const origin = window.location.origin || ""; + const hasWindow = typeof window !== "undefined"; + + if (hasWindow) { + // URL 쿼리(api_base)나 글로벌 변수로 API 주소를 강제 지정 가능 (테스트/배포 대응) + const search = new URLSearchParams(window.location.search || ""); + const override = window.API_BASE_URL || window.__API_BASE_URL__ || search.get("api_base"); + if (override) return normalizeBaseUrl(override); + + const origin = window.location?.origin || ""; // 프로덕션(railway/custom 도메인)은 동일 origin 사용 - if (origin.includes("railway.app") || origin.includes("lessonbay")) return origin; + if (origin.includes("railway.app") || origin.includes("lessonbay")) return normalizeBaseUrl(origin); // 로컬 5500(정적)에서 백엔드 3000으로 우회 - if (origin.includes("127.0.0.1:5500") || origin.includes("localhost:5500")) return "http://localhost:3000"; - return origin || "http://localhost:3000"; + if (origin.includes("127.0.0.1:5500") || origin.includes("localhost:5500")) return normalizeBaseUrl("http://localhost:3000"); + return normalizeBaseUrl(origin || "http://localhost:3000"); } + return "http://localhost:3000"; })(); @@ -744,19 +764,43 @@ function normalizeCurrentUserInStorage() { /* no-op: localStorage 미사용 */ } // ? SEED // --------------------------- async function loadLocalSampleClasses() { + const builtin = [ + { + id: "c_demo_korean_1", + title: "영어 회화 입문", + teacher: "이승훈", + category: "영어", + description: "왕초보도 바로 따라올 수 있는 실전 표현과 발음 교정.", + weeklyPrice: 19000, + monthlyPrice: 59000, + thumb: "https://images.unsplash.com/photo-1523240795612-9a054b0db644?auto=format&fit=crop&w=1400&q=60", + }, + { + id: "c_demo_math_1", + title: "고등 수학 확률과 통계", + teacher: "이승훈", + category: "수학", + description: "개념부터 기출, 모의고사까지 확률·통계 핵심 정리와 실전 연습.", + weeklyPrice: 25000, + monthlyPrice: 79000, + thumb: "https://images.unsplash.com/photo-1522202176988-66273c2fd55f?auto=format&fit=crop&w=1400&q=60", + }, + ]; + try { const res = await fetch("data/classes.json", { cache: "no-cache" }); - if (!res.ok) return []; + if (!res.ok) return builtin; const list = await res.json(); - return (list || []).map(c => ({ + const normalized = (list || []).map(c => ({ ...c, teacher: c.teacher || c.teacherName || "-", teacherId: c.teacherId || "", thumb: c.thumb || FALLBACK_THUMB, })); + return normalized.length ? normalized : builtin; } catch (err) { console.warn("local sample classes load failed", err); - return []; + return builtin; } } @@ -775,12 +819,14 @@ async function ensureSeedData() { thumb: c.thumbUrl || c.thumb || FALLBACK_THUMB, })); if (normalized.length === 0) { + showToast("API 수업 목록이 비어 있어 데모 데이터를 표시합니다.", "warn", 4500); setClasses(await loadLocalSampleClasses()); } else { setClasses(normalized); } } catch (e) { console.error("classes fetch failed", e); + showToast("HTTPS 환경에서 API 접근에 실패해 데모 데이터를 표시합니다.", "warn", 4500); setClasses(await loadLocalSampleClasses()); }