playreel/frontend/lib/prefs.ts
2026-09-08 13:48:20 +09:00

54 lines
2.4 KiB
TypeScript

"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<Record<AutoApprovable, boolean>>;
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);
}