import { defineStore } from 'pinia' import { ref, computed } from 'vue' import { Preferences } from '@capacitor/preferences' import { authApi } from '@/api/authApi' import { exitDemo } from '@/demo' // 세션 토큰은 Capacitor Preferences(네이티브 영속 저장 / 웹은 localStorage)로 보관한다. // http.js 요청 인터셉터가 동기로 읽을 수 있도록 localStorage 에도 미러링한다. const TOKEN_KEY = 'token' const USER_KEY = 'auth_user' export const useAuthStore = defineStore('auth', () => { const token = ref('') const user = ref(null) const ready = ref(false) const isAuthenticated = computed(() => !!token.value) const isAdmin = computed(() => user.value?.role === 'ADMIN') // 유료(PREMIUM) 멤버십 여부. 관리자는 항상 유료 기능 사용 가능. const isPremium = computed(() => isAdmin.value || user.value?.plan === 'PREMIUM') // localStorage 동기화 (http.js 가 동기로 토큰을 읽음) function mirrorLocal() { if (token.value) localStorage.setItem(TOKEN_KEY, token.value) else localStorage.removeItem(TOKEN_KEY) if (user.value) localStorage.setItem(USER_KEY, JSON.stringify(user.value)) else localStorage.removeItem(USER_KEY) } async function persist() { // localStorage 미러는 항상 먼저(동기) — Preferences 실패해도 토큰 유실 없음 mirrorLocal() try { if (token.value) await Preferences.set({ key: TOKEN_KEY, value: token.value }) else await Preferences.remove({ key: TOKEN_KEY }) if (user.value) await Preferences.set({ key: USER_KEY, value: JSON.stringify(user.value) }) else await Preferences.remove({ key: USER_KEY }) } catch { // Preferences 사용 불가(일부 모바일 웹/시크릿 모드) — localStorage 미러로 충분 } } // 앱/웹 시작 시 저장된 세션 복원 (자동 로그인). main.js 에서 mount 전 await. async function restore() { // 1) localStorage 우선 — 웹에서 항상 동작하고 http.js 의 토큰 소스와 동일. // (Preferences.get 가 모바일 웹에서 throw 해도 토큰을 잃지 않도록 먼저 읽는다) let t = localStorage.getItem(TOKEN_KEY) || '' let uRaw = localStorage.getItem(USER_KEY) // 2) 네이티브(Preferences)에만 있고 localStorage 에 없으면 보강. 실패해도 무시. try { if (!t) t = (await Preferences.get({ key: TOKEN_KEY })).value || '' if (!uRaw) uRaw = (await Preferences.get({ key: USER_KEY })).value || null } catch { // Preferences 사용 불가(일부 모바일 웹/시크릿 모드) — localStorage 값으로 진행 } token.value = t user.value = safeParse(uRaw) mirrorLocal() ready.value = true } async function login(loginId, password, rememberMe = false) { const res = await authApi.login({ loginId, password, rememberMe }) token.value = res.token user.value = res.member exitDemo() await persist() return res } // 구글 로그인 — GIS ID 토큰으로 세션 발급 async function googleLogin(idToken, rememberMe = true) { const res = await authApi.googleLogin({ idToken, rememberMe }) token.value = res.token user.value = res.member exitDemo() await persist() return res } async function signup(payload) { return authApi.signup(payload) } // 서버 세션 기준으로 현재 사용자 정보 동기화 (토큰 유효성 확인 용도) async function fetchMe() { const me = await authApi.me() user.value = { ...(user.value || {}), ...me } await persist() return me } async function logout() { try { await authApi.logout() } catch { // 서버 세션이 이미 만료됐어도 클라이언트는 정리 } await clear() } // 가입정보 변경 등으로 바뀐 사용자 필드를 로컬 세션에 병합 (헤더 이름 등 즉시 반영) async function applyUser(partial) { user.value = { ...(user.value || {}), ...partial } await persist() } async function clear() { exitDemo() token.value = '' user.value = null await persist() } return { token, user, ready, isAuthenticated, isAdmin, isPremium, restore, login, googleLogin, signup, fetchMe, applyUser, logout, clear } }) function safeParse(value) { try { return value ? JSON.parse(value) : null } catch { return null } }