c9ff605ac9
CI / build (push) Failing after 10m29s
- 하단 내비게이션(뒤로/앞으로/홈/새로고침/설정), 사이드바 홈 제거 - 설정 화면: 계정정보·앱 버전(package.json)·App Data 삭제 - 가입정보 변경: 비밀번호 재인증 → 이름/이메일 수정 - 로그인 후 대시보드(/) 이동, 로그인 전 햄버거·네이버 로그인 버튼 숨김 - Vitest 도입(32): authApi/accountApi 와이어링, auth/ui 스토어, 로그인 플로우 - CI(.gitea): npm test 게이트 추가 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
109 lines
3.7 KiB
JavaScript
109 lines
3.7 KiB
JavaScript
import { defineStore } from 'pinia'
|
|
import { ref, computed } from 'vue'
|
|
import { Preferences } from '@capacitor/preferences'
|
|
import { authApi } from '@/api/authApi'
|
|
|
|
// 세션 토큰은 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)
|
|
|
|
// 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
|
|
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() {
|
|
token.value = ''
|
|
user.value = null
|
|
await persist()
|
|
}
|
|
|
|
return { token, user, ready, isAuthenticated, restore, login, signup, fetchMe, applyUser, logout, clear }
|
|
})
|
|
|
|
function safeParse(value) {
|
|
try {
|
|
return value ? JSON.parse(value) : null
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|