"use client"; /** * 브라우저 로컬 환경설정 — 게이트 자동 승인. * 키 `playreel.autoApprove` = { fetch_confirm?: true, narration_confirm?: true } * 대상은 크레딧이 들지 않고 되돌릴 수 있는 게이트(①·③)뿐. ②·④·⑤는 GATE_META.credits/비가역이라 제외. * 서버에는 저장하지 않는다(사용자별 계정 개념이 아직 없음). 서버 `approve` 자동 호출은 /playreel/[id] 에서. */ import { useSyncExternalStore } from "react"; import type { GateKey } from "@/lib/playreel"; export const AUTO_APPROVABLE = ["fetch_confirm", "narration_confirm"] as const satisfies readonly GateKey[]; export type AutoApprovable = (typeof AUTO_APPROVABLE)[number]; export type AutoApprovePrefs = Partial>; const KEY = "playreel.autoApprove"; const EVT = "playreel:prefs"; const EMPTY: AutoApprovePrefs = {}; let cache: { raw: string | null; value: AutoApprovePrefs } = { raw: null, value: EMPTY }; export function readAutoApprove(): AutoApprovePrefs { if (typeof window === "undefined") return EMPTY; let raw: string | null = null; try { raw = window.localStorage.getItem(KEY); } catch { return EMPTY; } if (raw === cache.raw) return cache.value; // useSyncExternalStore 는 참조 안정성이 필요 let value: AutoApprovePrefs = EMPTY; try { const parsed = raw ? JSON.parse(raw) : {}; value = Object.fromEntries(AUTO_APPROVABLE.filter((g) => parsed?.[g] === true).map((g) => [g, true])); } catch { /* 깨진 값은 비활성으로 */ } cache = { raw, value }; return value; } export function setAutoApprove(gate: AutoApprovable, on: boolean) { if (typeof window === "undefined") return; const next = { ...readAutoApprove() }; if (on) next[gate] = true; else delete next[gate]; try { window.localStorage.setItem(KEY, JSON.stringify(next)); } catch { /* 프라이빗 모드 등 — 이 세션만 유지 안 됨 */ } window.dispatchEvent(new Event(EVT)); } function subscribe(cb: () => void) { window.addEventListener(EVT, cb); window.addEventListener("storage", cb); // 다른 탭에서 바꾼 경우 return () => { window.removeEventListener(EVT, cb); window.removeEventListener("storage", cb); }; } /** 렌더 중 읽는 훅. SSR·하이드레이션 첫 렌더는 항상 비활성(EMPTY)으로 맞춘다. */ export function useAutoApprove(): AutoApprovePrefs { return useSyncExternalStore(subscribe, readAutoApprove, () => EMPTY); }