Make user sync configurable and visible

Add optional apiBase for /api endpoints and surface sync failures so admins know when ncue_user writes are not reaching the backend.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dsyoon
2026-02-07 21:13:01 +09:00
parent 1a423ae2ef
commit a1e37759cc

View File

@@ -446,10 +446,12 @@
function getAuthConfig() {
const cfg = globalThis.AUTH_CONFIG && typeof globalThis.AUTH_CONFIG === "object" ? globalThis.AUTH_CONFIG : {};
const allowEndUserConfig = Boolean(cfg.allowEndUserConfig);
const apiBase = String(cfg.apiBase || "").trim(); // optional, e.g. https://api.ncue.net
const auth0 = cfg.auth0 && typeof cfg.auth0 === "object" ? cfg.auth0 : {};
const allowedEmails = Array.isArray(cfg.allowedEmails) ? cfg.allowedEmails : [];
const base = {
allowEndUserConfig,
apiBase,
auth0: {
domain: String(auth0.domain || "").trim(),
clientId: String(auth0.clientId || "").trim(),
@@ -466,6 +468,7 @@
// override가 있으면 우선 적용 (서버 재배포 없이 테스트 가능)
return {
allowEndUserConfig,
apiBase,
auth0: {
domain: override.auth0.domain || base.auth0.domain,
clientId: override.auth0.clientId || base.auth0.clientId,
@@ -479,6 +482,17 @@
};
}
function apiUrl(pathname) {
const cfg = getAuthConfig();
const base = cfg.apiBase;
if (!base) return pathname; // same-origin
try {
return new URL(pathname, base).toString();
} catch {
return pathname;
}
}
function currentUrlNoQuery() {
// Auth0 callback 후 URL 정리용
const u = new URL(location.href);
@@ -649,7 +663,7 @@
async function syncUserToServerWithIdToken(idToken) {
try {
const cfg = getAuthConfig();
const r = await fetch("/api/auth/sync", {
const r = await fetch(apiUrl("/api/auth/sync"), {
method: "POST",
headers: {
Authorization: `Bearer ${idToken}`,
@@ -657,11 +671,18 @@
"X-Auth0-ClientId": cfg.auth0.clientId,
},
});
if (!r.ok) return null;
if (!r.ok) {
toastOnce(
"syncfail",
`사용자 저장(API)이 실패했습니다. (${r.status}) 정적 호스팅이면 /api 를 서버로 프록시하거나 apiBase를 설정해야 합니다.`
);
return null;
}
const data = await r.json();
if (data && data.ok) return Boolean(data.canManage);
return null;
} catch {
toastOnce("syncerr", "사용자 저장(API)에 연결하지 못했습니다. /api 서버 연결을 확인하세요.");
return null;
}
}
@@ -673,14 +694,14 @@
// Prefer sendBeacon to survive navigation
try {
const blob = new Blob([payload], { type: "application/json" });
const ok = navigator.sendBeacon("/api/auth/logout", blob);
const ok = navigator.sendBeacon(apiUrl("/api/auth/logout"), blob);
if (ok) return;
} catch {
// ignore
}
// Fallback fetch keepalive (best-effort)
try {
fetch("/api/auth/logout", {
fetch(apiUrl("/api/auth/logout"), {
method: "POST",
headers: {
Authorization: `Bearer ${idToken}`,