@ph-cms/client-sdk 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +488 -64
- package/dist/auth/firebase-provider.d.ts +2 -1
- package/dist/auth/firebase-provider.js +3 -0
- package/dist/auth/interfaces.d.ts +6 -0
- package/dist/auth/local-provider.d.ts +1 -0
- package/dist/auth/local-provider.js +3 -0
- package/dist/client.d.ts +4 -2
- package/dist/client.js +6 -2
- package/dist/context.d.ts +4 -2
- package/dist/context.js +45 -14
- package/dist/hooks/useAuth.d.ts +25 -1
- package/dist/hooks/useAuth.js +12 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,6 +8,8 @@ This package provides:
|
|
|
8
8
|
- Auth provider interfaces and implementations
|
|
9
9
|
- React context and hooks for consuming the client in UI code
|
|
10
10
|
- **Integrated React Query support** (from v0.1.1)
|
|
11
|
+
- **Automatic Auth State Management** (from v0.1.3)
|
|
12
|
+
- **Conditional profile fetching & guaranteed post-login profile load** (from v0.1.4)
|
|
11
13
|
|
|
12
14
|
## Installation
|
|
13
15
|
|
|
@@ -15,45 +17,226 @@ This package provides:
|
|
|
15
17
|
npm install @ph-cms/client-sdk
|
|
16
18
|
```
|
|
17
19
|
|
|
18
|
-
> **Note:** `@tanstack/react-query` is a direct dependency
|
|
20
|
+
> **Note:** `@tanstack/react-query` is a direct dependency. You no longer need to install it manually unless you use it in your own application code.
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Authentication Architecture
|
|
25
|
+
|
|
26
|
+
### Overview
|
|
27
|
+
|
|
28
|
+
The SDK's authentication system is composed of three layers:
|
|
29
|
+
|
|
30
|
+
| Layer | Component | Role |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| **Provider** | `LocalAuthProvider` / `FirebaseAuthProvider` | 토큰 저장·조회·삭제를 담당하는 저수준 어댑터 |
|
|
33
|
+
| **Module** | `AuthModule` (`client.auth`) | 서버 API 호출 (`login`, `loginWithFirebase`, `me`, `refresh`, `logout`) |
|
|
34
|
+
| **Hook / Context** | `PHCMSProvider`, `useAuth` | React 상태 관리 — 토큰 유무에 따른 조건부 프로필 조회, 로그인·로그아웃 액션 |
|
|
21
35
|
|
|
22
|
-
```
|
|
23
|
-
|
|
36
|
+
```
|
|
37
|
+
┌─────────────────────────────────────────────────────────┐
|
|
38
|
+
│ React Component Tree │
|
|
39
|
+
│ │
|
|
40
|
+
│ PHCMSProvider │
|
|
41
|
+
│ └─ PHCMSInternalProvider │
|
|
42
|
+
│ ├─ useQuery(['auth','me']) │
|
|
43
|
+
│ │ enabled: authProvider.hasToken() │
|
|
44
|
+
│ │ → 토큰 없으면 호출하지 않음 (401 방지) │
|
|
45
|
+
│ │ → 토큰 있으면 자동으로 me() 호출 │
|
|
46
|
+
│ └─ context value: { user, isAuthenticated, ... } │
|
|
47
|
+
│ │
|
|
48
|
+
│ useAuth() │
|
|
49
|
+
│ ├─ login() ──→ AuthModule.login() │
|
|
50
|
+
│ │ ├─ POST /api/auth/login │
|
|
51
|
+
│ │ └─ provider.setTokens() │
|
|
52
|
+
│ │ ──→ refetchQueries(['auth','me']) │
|
|
53
|
+
│ │ └─ GET /api/auth/me ← 프로필 보장 │
|
|
54
|
+
│ │ │
|
|
55
|
+
│ ├─ loginWithFirebase() ──→ AuthModule.loginWithFirebase() │
|
|
56
|
+
│ │ ├─ POST /api/auth/firebase/exchange │
|
|
57
|
+
│ │ └─ provider.setTokens() │
|
|
58
|
+
│ │ ──→ refetchQueries(['auth','me']) │
|
|
59
|
+
│ │ └─ GET /api/auth/me ← 프로필 보장 │
|
|
60
|
+
│ │ │
|
|
61
|
+
│ ├─ register() ──→ AuthModule.register() │
|
|
62
|
+
│ │ ──→ refetchQueries(['auth','me']) │
|
|
63
|
+
│ │ │
|
|
64
|
+
│ └─ logout() ──→ AuthModule.logout() │
|
|
65
|
+
│ ──→ setQueryData(['auth','me'], null) │
|
|
66
|
+
└─────────────────────────────────────────────────────────┘
|
|
24
67
|
```
|
|
25
68
|
|
|
26
|
-
|
|
69
|
+
### Authentication Lifecycle
|
|
70
|
+
|
|
71
|
+
#### 1. 초기 마운트 (비인증 상태)
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
App Mount
|
|
75
|
+
→ PHCMSProvider mount
|
|
76
|
+
→ authProvider.hasToken() ← false (localStorage에 토큰 없음)
|
|
77
|
+
→ useQuery enabled: false ← me() 호출하지 않음
|
|
78
|
+
→ user: null, isAuthenticated: false
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
서버에 불필요한 401 요청을 보내지 않습니다.
|
|
82
|
+
|
|
83
|
+
#### 2. 초기 마운트 (기존 세션 복원)
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
App Mount
|
|
87
|
+
→ PHCMSProvider mount
|
|
88
|
+
→ authProvider.hasToken() ← true (localStorage에 토큰 존재)
|
|
89
|
+
→ useQuery enabled: true ← me() 자동 호출
|
|
90
|
+
→ GET /api/auth/me
|
|
91
|
+
→ user: { ... }, isAuthenticated: true
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
페이지 새로고침이나 재방문 시 기존 세션이 자동으로 복원됩니다.
|
|
95
|
+
|
|
96
|
+
#### 3. 로그인 (이메일/비밀번호)
|
|
27
97
|
|
|
28
|
-
```
|
|
29
|
-
|
|
98
|
+
```
|
|
99
|
+
user calls login({ email, password })
|
|
100
|
+
→ POST /api/auth/login
|
|
101
|
+
→ 서버 응답: { accessToken, refreshToken }
|
|
102
|
+
→ provider.setTokens(accessToken, refreshToken) ← localStorage 저장
|
|
103
|
+
→ refetchQueries(['auth', 'me'])
|
|
104
|
+
→ GET /api/auth/me
|
|
105
|
+
→ user: { ... }, isAuthenticated: true
|
|
30
106
|
```
|
|
31
107
|
|
|
32
|
-
|
|
108
|
+
#### 4. 로그인 (Firebase)
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
user calls loginWithFirebase({ idToken })
|
|
112
|
+
→ POST /api/auth/firebase/exchange
|
|
113
|
+
→ 서버 응답: { accessToken, refreshToken }
|
|
114
|
+
→ provider.setTokens(accessToken, refreshToken) ← localStorage 저장
|
|
115
|
+
→ refetchQueries(['auth', 'me'])
|
|
116
|
+
→ GET /api/auth/me
|
|
117
|
+
→ user: { ... }, isAuthenticated: true
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Firebase ID 토큰을 PH-CMS 자체 토큰으로 교환한 뒤, 동일한 프로필 조회 흐름을 따릅니다.
|
|
121
|
+
|
|
122
|
+
#### 5. 로그아웃
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
user calls logout()
|
|
126
|
+
→ provider.logout() ← localStorage 토큰 삭제
|
|
127
|
+
→ POST /api/auth/logout ← 서버 세션 무효화 (실패해도 무시)
|
|
128
|
+
→ setQueryData(['auth','me'], null)
|
|
129
|
+
→ user: null, isAuthenticated: false
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### `refetchQueries` vs `invalidateQueries`
|
|
133
|
+
|
|
134
|
+
로그인 성공 후 프로필 조회에는 `invalidateQueries` 대신 `refetchQueries`를 사용합니다.
|
|
135
|
+
|
|
136
|
+
- `invalidateQueries`는 쿼리를 stale로 표시만 하고, `enabled: true`인 쿼리만 자동 refetch합니다.
|
|
137
|
+
- 로그인 직후에는 `provider.setTokens()`로 토큰이 저장되지만, React 리렌더가 아직 발생하지 않아 `hasToken()`의 반환값이 context에 반영되지 않은 상태입니다.
|
|
138
|
+
- 따라서 `enabled`가 여전히 `false`일 수 있고, `invalidateQueries`만으로는 `me()` 호출이 보장되지 않습니다.
|
|
139
|
+
- `refetchQueries`는 `enabled` 상태와 무관하게 즉시 네트워크 요청을 강제하므로, 토큰 저장 직후 프로필 조회를 확실히 보장합니다.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Auth Providers
|
|
144
|
+
|
|
145
|
+
### `LocalAuthProvider`
|
|
146
|
+
|
|
147
|
+
이메일/비밀번호 기반 인증에 사용합니다. 토큰을 `localStorage`에 저장합니다.
|
|
33
148
|
|
|
34
149
|
```ts
|
|
35
|
-
import { PHCMSClient } from '@ph-cms/client-sdk';
|
|
150
|
+
import { LocalAuthProvider, PHCMSClient } from '@ph-cms/client-sdk';
|
|
151
|
+
|
|
152
|
+
const authProvider = new LocalAuthProvider('my_app_');
|
|
36
153
|
|
|
37
154
|
const client = new PHCMSClient({
|
|
38
155
|
baseURL: 'https://api.example.com',
|
|
156
|
+
auth: authProvider,
|
|
39
157
|
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
| 생성자 인자 | 타입 | 기본값 | 설명 |
|
|
161
|
+
|---|---|---|---|
|
|
162
|
+
| `storageKeyPrefix` | `string` | `'ph_cms_'` | `localStorage` 키 접두사. `{prefix}access_token`, `{prefix}refresh_token` 형태로 저장됩니다. |
|
|
163
|
+
|
|
164
|
+
#### 주요 메서드
|
|
165
|
+
|
|
166
|
+
| 메서드 | 설명 |
|
|
167
|
+
|---|---|
|
|
168
|
+
| `hasToken()` | `accessToken` 또는 `refreshToken`이 존재하면 `true` 반환 |
|
|
169
|
+
| `getToken()` | 현재 `accessToken` 반환 (`Promise<string \| null>`) |
|
|
170
|
+
| `setTokens(access, refresh)` | 토큰 저장 (login 성공 시 SDK가 자동 호출) |
|
|
171
|
+
| `getRefreshToken()` | 현재 `refreshToken` 반환 |
|
|
172
|
+
| `logout()` | 토큰 삭제 |
|
|
40
173
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
174
|
+
### `FirebaseAuthProvider`
|
|
175
|
+
|
|
176
|
+
Firebase Authentication과 연동하여 사용합니다. Firebase ID 토큰을 PH-CMS 서버 토큰으로 교환하는 방식입니다.
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import { FirebaseAuthProvider, PHCMSClient } from '@ph-cms/client-sdk';
|
|
180
|
+
import { initializeApp } from 'firebase/app';
|
|
181
|
+
import { getAuth } from 'firebase/auth';
|
|
182
|
+
|
|
183
|
+
const firebaseApp = initializeApp({ /* Firebase config */ });
|
|
184
|
+
const firebaseAuth = getAuth(firebaseApp);
|
|
185
|
+
|
|
186
|
+
const authProvider = new FirebaseAuthProvider(firebaseAuth, 'my_app_fb_');
|
|
187
|
+
|
|
188
|
+
const client = new PHCMSClient({
|
|
189
|
+
baseURL: 'https://api.example.com',
|
|
190
|
+
auth: authProvider,
|
|
45
191
|
});
|
|
46
192
|
```
|
|
47
193
|
|
|
48
|
-
|
|
194
|
+
| 생성자 인자 | 타입 | 기본값 | 설명 |
|
|
195
|
+
|---|---|---|---|
|
|
196
|
+
| `auth` | `firebase/auth.Auth` | (필수) | Firebase Auth 인스턴스 |
|
|
197
|
+
| `storageKeyPrefix` | `string` | `'ph_cms_fb_'` | `localStorage` 키 접두사 |
|
|
198
|
+
|
|
199
|
+
#### 주요 메서드
|
|
200
|
+
|
|
201
|
+
| 메서드 | 설명 |
|
|
202
|
+
|---|---|
|
|
203
|
+
| `hasToken()` | PH-CMS 교환 토큰이 존재하면 `true` 반환 |
|
|
204
|
+
| `getToken()` | PH-CMS `accessToken` → Firebase ID 토큰 순으로 fallback 반환 |
|
|
205
|
+
| `getIdToken()` | Firebase ID 토큰을 직접 반환 (토큰 교환 시 사용) |
|
|
206
|
+
| `setTokens(access, refresh)` | 교환된 PH-CMS 토큰 저장 (SDK가 자동 호출) |
|
|
207
|
+
| `logout()` | PH-CMS 토큰 삭제 + `firebase.auth.signOut()` 호출 |
|
|
208
|
+
|
|
209
|
+
### `AuthProvider` Interface
|
|
210
|
+
|
|
211
|
+
커스텀 인증 프로바이더를 구현하려면 아래 인터페이스를 구현합니다:
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
interface AuthProvider {
|
|
215
|
+
type: 'FIREBASE' | 'LOCAL';
|
|
216
|
+
getToken(): Promise<string | null>;
|
|
217
|
+
hasToken(): boolean;
|
|
218
|
+
onTokenExpired(callback: () => Promise<void>): void;
|
|
219
|
+
logout(): Promise<void>;
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
`hasToken()`은 **동기** 메서드여야 합니다. React 렌더 사이클에서 `useQuery`의 `enabled` 조건으로 사용되므로, 비동기 호출은 허용되지 않습니다.
|
|
224
|
+
|
|
225
|
+
---
|
|
49
226
|
|
|
50
|
-
|
|
227
|
+
## React Usage
|
|
51
228
|
|
|
52
229
|
### Basic Setup
|
|
53
230
|
|
|
54
231
|
```tsx
|
|
55
|
-
import { PHCMSClient, PHCMSProvider } from '@ph-cms/client-sdk';
|
|
56
|
-
|
|
232
|
+
import { PHCMSClient, LocalAuthProvider, PHCMSProvider } from '@ph-cms/client-sdk';
|
|
233
|
+
|
|
234
|
+
const authProvider = new LocalAuthProvider('my_app_');
|
|
235
|
+
|
|
236
|
+
const client = new PHCMSClient({
|
|
237
|
+
baseURL: 'https://api.example.com',
|
|
238
|
+
auth: authProvider,
|
|
239
|
+
});
|
|
57
240
|
|
|
58
241
|
export function App() {
|
|
59
242
|
return (
|
|
@@ -64,73 +247,263 @@ export function App() {
|
|
|
64
247
|
}
|
|
65
248
|
```
|
|
66
249
|
|
|
67
|
-
|
|
250
|
+
`PHCMSProvider`는 내부적으로 `QueryClientProvider`를 포함하므로, 별도로 추가할 필요가 없습니다.
|
|
251
|
+
외부에서 직접 관리하는 `QueryClient`를 사용하려면 `queryClient` prop으로 전달합니다:
|
|
68
252
|
|
|
69
253
|
```tsx
|
|
70
|
-
import {
|
|
254
|
+
import { QueryClient } from '@tanstack/react-query';
|
|
71
255
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
256
|
+
const queryClient = new QueryClient();
|
|
257
|
+
|
|
258
|
+
<PHCMSProvider client={client} queryClient={queryClient}>
|
|
259
|
+
...
|
|
260
|
+
</PHCMSProvider>
|
|
261
|
+
```
|
|
75
262
|
|
|
76
|
-
|
|
263
|
+
### Authentication with `useAuth`
|
|
264
|
+
|
|
265
|
+
`useAuth`는 인증 상태와 액션을 통합 제공하는 메인 훅입니다.
|
|
266
|
+
|
|
267
|
+
```tsx
|
|
268
|
+
import { useAuth } from '@ph-cms/client-sdk';
|
|
269
|
+
|
|
270
|
+
function AuthComponent() {
|
|
271
|
+
const {
|
|
272
|
+
// 상태
|
|
273
|
+
user, // UserDto | null — 현재 로그인한 사용자 프로필
|
|
274
|
+
isAuthenticated, // boolean
|
|
275
|
+
isLoading, // boolean — 프로필 로딩 중 여부
|
|
276
|
+
|
|
277
|
+
// 액션 (모두 Promise 반환)
|
|
278
|
+
login, // (data: LoginRequest) => Promise<AuthResponse>
|
|
279
|
+
loginWithFirebase, // (data: FirebaseExchangeRequest) => Promise<AuthResponse>
|
|
280
|
+
register, // (data: RegisterRequest) => Promise<AuthResponse>
|
|
281
|
+
logout, // () => Promise<void>
|
|
282
|
+
|
|
283
|
+
// 뮤테이션 상태 (isPending, error 등)
|
|
284
|
+
loginStatus,
|
|
285
|
+
loginWithFirebaseStatus,
|
|
286
|
+
registerStatus,
|
|
287
|
+
logoutStatus,
|
|
288
|
+
} = useAuth();
|
|
289
|
+
|
|
290
|
+
// ...
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
#### 이메일/비밀번호 로그인
|
|
295
|
+
|
|
296
|
+
```tsx
|
|
297
|
+
function LoginForm() {
|
|
298
|
+
const { login, loginStatus, isAuthenticated, user } = useAuth();
|
|
299
|
+
const [email, setEmail] = useState('');
|
|
300
|
+
const [password, setPassword] = useState('');
|
|
301
|
+
|
|
302
|
+
if (isAuthenticated) {
|
|
303
|
+
return <p>Welcome, {user?.displayName}</p>;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
307
|
+
e.preventDefault();
|
|
308
|
+
try {
|
|
309
|
+
await login({ email, password });
|
|
310
|
+
// 성공 시 자동으로 me() 호출 → user, isAuthenticated 갱신
|
|
311
|
+
} catch (error) {
|
|
312
|
+
console.error('Login failed:', error);
|
|
313
|
+
}
|
|
314
|
+
};
|
|
77
315
|
|
|
78
316
|
return (
|
|
79
|
-
<
|
|
80
|
-
<
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
</
|
|
86
|
-
</
|
|
317
|
+
<form onSubmit={handleSubmit}>
|
|
318
|
+
<input type="email" value={email} onChange={e => setEmail(e.target.value)} />
|
|
319
|
+
<input type="password" value={password} onChange={e => setPassword(e.target.value)} />
|
|
320
|
+
<button type="submit" disabled={loginStatus.isPending}>
|
|
321
|
+
{loginStatus.isPending ? '로그인 중...' : '로그인'}
|
|
322
|
+
</button>
|
|
323
|
+
{loginStatus.error && <p>{(loginStatus.error as Error).message}</p>}
|
|
324
|
+
</form>
|
|
87
325
|
);
|
|
88
326
|
}
|
|
89
327
|
```
|
|
90
328
|
|
|
91
|
-
|
|
329
|
+
#### Firebase 로그인
|
|
92
330
|
|
|
93
|
-
|
|
331
|
+
Firebase 로그인은 두 단계로 이루어집니다:
|
|
332
|
+
1. Firebase SDK로 인증하여 ID 토큰을 획득
|
|
333
|
+
2. `loginWithFirebase()`로 PH-CMS 서버에 토큰 교환 요청
|
|
94
334
|
|
|
95
335
|
```tsx
|
|
96
|
-
import {
|
|
97
|
-
import {
|
|
336
|
+
import { FirebaseAuthProvider } from '@ph-cms/client-sdk';
|
|
337
|
+
import { getAuth, signInWithPopup, GoogleAuthProvider } from 'firebase/auth';
|
|
98
338
|
|
|
99
|
-
|
|
100
|
-
const
|
|
339
|
+
// firebaseProvider는 앱 초기화 시 생성
|
|
340
|
+
declare const firebaseProvider: FirebaseAuthProvider;
|
|
341
|
+
|
|
342
|
+
function FirebaseLoginButton() {
|
|
343
|
+
const { loginWithFirebase, loginWithFirebaseStatus } = useAuth();
|
|
344
|
+
|
|
345
|
+
const handleFirebaseLogin = async () => {
|
|
346
|
+
// 1) Firebase 인증 (Google 팝업 예시)
|
|
347
|
+
const auth = getAuth();
|
|
348
|
+
await signInWithPopup(auth, new GoogleAuthProvider());
|
|
349
|
+
|
|
350
|
+
// 2) Firebase ID 토큰 획득
|
|
351
|
+
const idToken = await firebaseProvider.getIdToken();
|
|
352
|
+
if (!idToken) return;
|
|
353
|
+
|
|
354
|
+
// 3) PH-CMS 서버에 토큰 교환 → 자동으로 me() 호출
|
|
355
|
+
await loginWithFirebase({ idToken });
|
|
356
|
+
};
|
|
101
357
|
|
|
102
|
-
export function App() {
|
|
103
358
|
return (
|
|
104
|
-
<
|
|
105
|
-
{
|
|
106
|
-
|
|
107
|
-
</PHCMSProvider>
|
|
359
|
+
<button onClick={handleFirebaseLogin} disabled={loginWithFirebaseStatus.isPending}>
|
|
360
|
+
{loginWithFirebaseStatus.isPending ? 'Firebase 로그인 중...' : 'Google 로그인'}
|
|
361
|
+
</button>
|
|
108
362
|
);
|
|
109
363
|
}
|
|
110
364
|
```
|
|
111
365
|
|
|
112
|
-
|
|
366
|
+
#### 회원가입
|
|
367
|
+
|
|
368
|
+
```tsx
|
|
369
|
+
function RegisterForm() {
|
|
370
|
+
const { register, registerStatus } = useAuth();
|
|
371
|
+
|
|
372
|
+
const handleRegister = async (formData: {
|
|
373
|
+
email: string;
|
|
374
|
+
password: string;
|
|
375
|
+
display_name: string;
|
|
376
|
+
username?: string;
|
|
377
|
+
}) => {
|
|
378
|
+
await register(formData);
|
|
379
|
+
// 성공 시 자동으로 me() 호출 → 즉시 인증 상태로 전환
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
// ...
|
|
383
|
+
}
|
|
384
|
+
```
|
|
113
385
|
|
|
114
|
-
|
|
386
|
+
#### 로그아웃
|
|
115
387
|
|
|
116
388
|
```tsx
|
|
117
|
-
|
|
118
|
-
|
|
389
|
+
function LogoutButton() {
|
|
390
|
+
const { logout } = useAuth();
|
|
391
|
+
|
|
392
|
+
return <button onClick={() => logout()}>로그아웃</button>;
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Context API (`usePHCMSContext`)
|
|
397
|
+
|
|
398
|
+
`useAuth` 대신 context에 직접 접근할 수도 있습니다.
|
|
399
|
+
|
|
400
|
+
```tsx
|
|
401
|
+
import { usePHCMSContext } from '@ph-cms/client-sdk';
|
|
402
|
+
|
|
403
|
+
function MyComponent() {
|
|
404
|
+
const {
|
|
405
|
+
client, // PHCMSClient 인스턴스
|
|
406
|
+
user, // UserDto | null
|
|
407
|
+
isAuthenticated, // boolean
|
|
408
|
+
isLoading, // boolean
|
|
409
|
+
refreshUser, // () => Promise<void> — 수동으로 프로필 다시 조회
|
|
410
|
+
} = usePHCMSContext();
|
|
411
|
+
|
|
412
|
+
// 프로필 정보가 변경된 후 수동 갱신
|
|
413
|
+
const handleProfileUpdate = async () => {
|
|
414
|
+
await client.auth.me(); // or your update API
|
|
415
|
+
await refreshUser();
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Standalone (Non-React) Usage
|
|
421
|
+
|
|
422
|
+
React 없이 `PHCMSClient`와 `AuthModule`을 직접 사용할 수 있습니다.
|
|
423
|
+
|
|
424
|
+
```ts
|
|
425
|
+
import { PHCMSClient, LocalAuthProvider } from '@ph-cms/client-sdk';
|
|
426
|
+
|
|
427
|
+
const authProvider = new LocalAuthProvider('my_app_');
|
|
428
|
+
const client = new PHCMSClient({
|
|
429
|
+
baseURL: 'https://api.example.com',
|
|
430
|
+
auth: authProvider,
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
// 로그인
|
|
434
|
+
const authResponse = await client.auth.login({
|
|
435
|
+
email: 'user@example.com',
|
|
436
|
+
password: 'password',
|
|
437
|
+
});
|
|
438
|
+
// → authProvider에 토큰이 자동 저장됨
|
|
439
|
+
|
|
440
|
+
// 프로필 조회
|
|
441
|
+
const me = await client.auth.me();
|
|
442
|
+
console.log(me.email);
|
|
443
|
+
|
|
444
|
+
// 토큰 갱신
|
|
445
|
+
const refreshToken = authProvider.getRefreshToken();
|
|
446
|
+
if (refreshToken) {
|
|
447
|
+
const newTokens = await client.auth.refresh(refreshToken);
|
|
448
|
+
authProvider.setTokens(newTokens.accessToken, newTokens.refreshToken);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// 로그아웃
|
|
452
|
+
await client.auth.logout();
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### Legacy Hooks
|
|
456
|
+
|
|
457
|
+
하위 호환성을 위해 개별 훅도 제공됩니다. 새 코드에서는 `useAuth`를 권장합니다.
|
|
458
|
+
|
|
459
|
+
| Hook | 설명 |
|
|
460
|
+
|---|---|
|
|
461
|
+
| `useUser()` | `{ data: UserDto \| null, isLoading, isAuthenticated }` |
|
|
462
|
+
| `useLogin()` | `{ mutateAsync: login }` |
|
|
463
|
+
| `useLogout()` | `{ mutateAsync: logout }` |
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
## Using Data Hooks
|
|
468
|
+
|
|
469
|
+
### Content
|
|
470
|
+
|
|
471
|
+
```tsx
|
|
472
|
+
import { useContentList, useContentDetail } from '@ph-cms/client-sdk';
|
|
473
|
+
|
|
474
|
+
function ContentList() {
|
|
475
|
+
const { data, isLoading } = useContentList({ limit: 10 });
|
|
476
|
+
|
|
477
|
+
if (isLoading) return <div>Loading...</div>;
|
|
119
478
|
|
|
120
|
-
export function App() {
|
|
121
479
|
return (
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
|
|
480
|
+
<ul>
|
|
481
|
+
{data?.content.map(item => (
|
|
482
|
+
<li key={item.uid}>{item.title}</li>
|
|
483
|
+
))}
|
|
484
|
+
</ul>
|
|
125
485
|
);
|
|
126
486
|
}
|
|
127
487
|
```
|
|
128
488
|
|
|
129
|
-
|
|
489
|
+
### Channel
|
|
490
|
+
|
|
491
|
+
```tsx
|
|
492
|
+
import { useChannelList } from '@ph-cms/client-sdk';
|
|
493
|
+
|
|
494
|
+
function ChannelList() {
|
|
495
|
+
const { data, isLoading } = useChannelList({ limit: 20 });
|
|
496
|
+
// ...
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
## Media & File Upload
|
|
130
503
|
|
|
131
|
-
Uploading media files follows a 3-step process: Request a Ticket
|
|
504
|
+
Uploading media files follows a 3-step process: Request a Ticket → Upload to S3 → Create/Update Content.
|
|
132
505
|
|
|
133
|
-
###
|
|
506
|
+
### Simple Workflow Example
|
|
134
507
|
|
|
135
508
|
```tsx
|
|
136
509
|
import { useMediaUploadTickets, useUploadToS3, useCreateContent } from '@ph-cms/client-sdk';
|
|
@@ -149,8 +522,6 @@ function MediaUploader() {
|
|
|
149
522
|
filename: file.name,
|
|
150
523
|
contentType: file.type,
|
|
151
524
|
fileSize: file.size,
|
|
152
|
-
// width: 1024, (optional)
|
|
153
|
-
// height: 768 (optional)
|
|
154
525
|
}]);
|
|
155
526
|
|
|
156
527
|
const { mediaUid, uploadUrl } = tickets[0];
|
|
@@ -160,7 +531,6 @@ function MediaUploader() {
|
|
|
160
531
|
|
|
161
532
|
// Step 3: Use the mediaUid to create content
|
|
162
533
|
await createContent({
|
|
163
|
-
channelSlug: 'my-channel',
|
|
164
534
|
type: 'post',
|
|
165
535
|
title: 'Post with image',
|
|
166
536
|
mediaAttachments: [mediaUid]
|
|
@@ -171,14 +541,68 @@ function MediaUploader() {
|
|
|
171
541
|
}
|
|
172
542
|
```
|
|
173
543
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
544
|
+
---
|
|
545
|
+
|
|
546
|
+
## Error Handling
|
|
547
|
+
|
|
548
|
+
SDK는 세 종류의 에러를 throw합니다:
|
|
549
|
+
|
|
550
|
+
| Error Class | 상황 | 주요 필드 |
|
|
551
|
+
|---|---|---|
|
|
552
|
+
| `ValidationError` | Zod 스키마 검증 실패 (클라이언트 측) | `errors: ZodIssue[]` |
|
|
553
|
+
| `ApiError` | 서버가 2xx 이외의 응답을 반환 | `statusCode: number`, `originalError: any` |
|
|
554
|
+
| `PHCMSError` | 네트워크 오류 등 기타 에러 | `message: string` |
|
|
555
|
+
|
|
556
|
+
```tsx
|
|
557
|
+
import { ApiError } from '@ph-cms/client-sdk';
|
|
558
|
+
|
|
559
|
+
try {
|
|
560
|
+
await login({ email, password });
|
|
561
|
+
} catch (error) {
|
|
562
|
+
if (error instanceof ApiError) {
|
|
563
|
+
if (error.statusCode === 401) {
|
|
564
|
+
console.error('잘못된 인증 정보입니다.');
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
---
|
|
571
|
+
|
|
572
|
+
## API Reference
|
|
573
|
+
|
|
574
|
+
### `PHCMSClient`
|
|
575
|
+
|
|
576
|
+
```ts
|
|
577
|
+
const client = new PHCMSClient({
|
|
578
|
+
baseURL: string; // 서버 URL (필수)
|
|
579
|
+
apiPrefix?: string; // API 경로 접두사 (기본값: '/api')
|
|
580
|
+
auth?: AuthProvider; // 인증 프로바이더
|
|
581
|
+
timeout?: number; // 요청 타임아웃 ms (기본값: 10000)
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
client.authProvider // AuthProvider | undefined — 인증 프로바이더 접근
|
|
585
|
+
client.axiosInstance // AxiosInstance — 내부 axios 인스턴스 직접 접근
|
|
586
|
+
client.auth // AuthModule
|
|
587
|
+
client.content // ContentModule
|
|
588
|
+
client.channel // ChannelModule
|
|
589
|
+
client.terms // TermsModule
|
|
590
|
+
client.media // MediaModule
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
### `AuthModule` (`client.auth`)
|
|
594
|
+
|
|
595
|
+
| 메서드 | 설명 |
|
|
596
|
+
|---|---|
|
|
597
|
+
| `login(data: LoginRequest)` | 이메일/비밀번호 로그인 → `AuthResponse` |
|
|
598
|
+
| `loginWithFirebase(data: FirebaseExchangeRequest)` | Firebase ID 토큰 교환 → `AuthResponse` |
|
|
599
|
+
| `register(data: RegisterRequest)` | 회원가입 → `AuthResponse` |
|
|
600
|
+
| `me()` | 현재 사용자 프로필 조회 → `UserDto` |
|
|
601
|
+
| `refresh(refreshToken: string)` | 토큰 갱신 → `{ accessToken, refreshToken }` |
|
|
602
|
+
| `logout()` | 로그아웃 (프로바이더 토큰 삭제 + 서버 세션 무효화) |
|
|
603
|
+
|
|
604
|
+
---
|
|
181
605
|
|
|
182
606
|
## License
|
|
183
607
|
|
|
184
|
-
MIT
|
|
608
|
+
MIT
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AuthProvider } from "./interfaces";
|
|
2
1
|
import type { Auth } from "firebase/auth";
|
|
2
|
+
import { AuthProvider } from "./interfaces";
|
|
3
3
|
export declare class FirebaseAuthProvider implements AuthProvider {
|
|
4
4
|
private readonly auth;
|
|
5
5
|
private readonly storageKeyPrefix;
|
|
@@ -8,6 +8,7 @@ export declare class FirebaseAuthProvider implements AuthProvider {
|
|
|
8
8
|
private refreshToken;
|
|
9
9
|
private onExpiredCallback;
|
|
10
10
|
constructor(auth: Auth, storageKeyPrefix?: string);
|
|
11
|
+
hasToken(): boolean;
|
|
11
12
|
setTokens(accessToken: string, refreshToken: string): void;
|
|
12
13
|
getToken(): Promise<string | null>;
|
|
13
14
|
onTokenExpired(callback: () => Promise<void>): void;
|
|
@@ -14,6 +14,9 @@ class FirebaseAuthProvider {
|
|
|
14
14
|
this.refreshToken = localStorage.getItem(`${this.storageKeyPrefix}refresh_token`);
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
+
hasToken() {
|
|
18
|
+
return this.accessToken !== null || this.refreshToken !== null;
|
|
19
|
+
}
|
|
17
20
|
setTokens(accessToken, refreshToken) {
|
|
18
21
|
this.accessToken = accessToken;
|
|
19
22
|
this.refreshToken = refreshToken;
|
|
@@ -5,6 +5,12 @@ export interface AuthProvider {
|
|
|
5
5
|
* Should handle refreshing if necessary (or return null/throw if expired and can't refresh).
|
|
6
6
|
*/
|
|
7
7
|
getToken(): Promise<string | null>;
|
|
8
|
+
/**
|
|
9
|
+
* Returns true if the provider currently holds a token (access or refresh).
|
|
10
|
+
* This is a synchronous check used to decide whether to attempt API calls
|
|
11
|
+
* that require authentication (e.g. /auth/me).
|
|
12
|
+
*/
|
|
13
|
+
hasToken(): boolean;
|
|
8
14
|
/**
|
|
9
15
|
* Sets a callback to be called when the token is known to be expired by the external world (e.g. 401 response).
|
|
10
16
|
*/
|
|
@@ -11,6 +11,7 @@ export declare class LocalAuthProvider implements AuthProvider {
|
|
|
11
11
|
refreshToken: string;
|
|
12
12
|
}>) | undefined);
|
|
13
13
|
setTokens(accessToken: string, refreshToken: string): void;
|
|
14
|
+
hasToken(): boolean;
|
|
14
15
|
getToken(): Promise<string | null>;
|
|
15
16
|
onTokenExpired(callback: () => Promise<void>): void;
|
|
16
17
|
logout(): Promise<void>;
|
|
@@ -22,6 +22,9 @@ class LocalAuthProvider {
|
|
|
22
22
|
localStorage.setItem(`${this.storageKeyPrefix}refresh_token`, refreshToken);
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
+
hasToken() {
|
|
26
|
+
return this.accessToken !== null || this.refreshToken !== null;
|
|
27
|
+
}
|
|
25
28
|
async getToken() {
|
|
26
29
|
// Ideally check expiration here using jwt-decode, but for now return what we have.
|
|
27
30
|
// If it's expired, the API will return 401, triggering the interceptor which calls onTokenExpired.
|
package/dist/client.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { AxiosInstance } from 'axios';
|
|
2
2
|
import { AuthProvider } from './auth/interfaces';
|
|
3
3
|
import { AuthModule } from './modules/auth';
|
|
4
|
-
import { ContentModule } from './modules/content';
|
|
5
4
|
import { ChannelModule } from './modules/channel';
|
|
6
|
-
import {
|
|
5
|
+
import { ContentModule } from './modules/content';
|
|
7
6
|
import { MediaModule } from './modules/media';
|
|
7
|
+
import { TermsModule } from './modules/terms';
|
|
8
8
|
export interface PHCMSClientConfig {
|
|
9
9
|
baseURL: string;
|
|
10
10
|
apiPrefix?: string;
|
|
@@ -19,5 +19,7 @@ export declare class PHCMSClient {
|
|
|
19
19
|
readonly channel: ChannelModule;
|
|
20
20
|
readonly terms: TermsModule;
|
|
21
21
|
readonly media: MediaModule;
|
|
22
|
+
/** Exposes the auth provider so UI layers can check token state synchronously. */
|
|
23
|
+
get authProvider(): AuthProvider | undefined;
|
|
22
24
|
constructor(config: PHCMSClientConfig);
|
|
23
25
|
}
|
package/dist/client.js
CHANGED
|
@@ -7,11 +7,15 @@ exports.PHCMSClient = void 0;
|
|
|
7
7
|
const axios_1 = __importDefault(require("axios"));
|
|
8
8
|
const errors_1 = require("./errors");
|
|
9
9
|
const auth_1 = require("./modules/auth");
|
|
10
|
-
const content_1 = require("./modules/content");
|
|
11
10
|
const channel_1 = require("./modules/channel");
|
|
12
|
-
const
|
|
11
|
+
const content_1 = require("./modules/content");
|
|
13
12
|
const media_1 = require("./modules/media");
|
|
13
|
+
const terms_1 = require("./modules/terms");
|
|
14
14
|
class PHCMSClient {
|
|
15
|
+
/** Exposes the auth provider so UI layers can check token state synchronously. */
|
|
16
|
+
get authProvider() {
|
|
17
|
+
return this.config.auth;
|
|
18
|
+
}
|
|
15
19
|
constructor(config) {
|
|
16
20
|
this.config = config;
|
|
17
21
|
const normalizedApiPrefix = `/${(config.apiPrefix || '/api').replace(/^\/+|\/+$/g, '')}`;
|
package/dist/context.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { UserDto } from '@ph-cms/api-contract';
|
|
2
2
|
import { QueryClient } from '@tanstack/react-query';
|
|
3
|
+
import React, { ReactNode } from 'react';
|
|
3
4
|
import { PHCMSClient } from './client';
|
|
4
|
-
import { UserDto } from '@ph-cms/api-contract';
|
|
5
5
|
export interface PHCMSContextType {
|
|
6
6
|
client: PHCMSClient;
|
|
7
7
|
user: UserDto | null;
|
|
8
8
|
isAuthenticated: boolean;
|
|
9
9
|
isLoading: boolean;
|
|
10
|
+
/** Manually trigger a refetch of the current user profile. */
|
|
11
|
+
refreshUser: () => Promise<void>;
|
|
10
12
|
}
|
|
11
13
|
export interface PHCMSProviderProps {
|
|
12
14
|
client: PHCMSClient;
|
package/dist/context.js
CHANGED
|
@@ -34,40 +34,71 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.usePHCMS = exports.usePHCMSContext = exports.PHCMSProvider = void 0;
|
|
37
|
-
const react_1 = __importStar(require("react"));
|
|
38
37
|
const react_query_1 = require("@tanstack/react-query");
|
|
38
|
+
const react_1 = __importStar(require("react"));
|
|
39
39
|
const PHCMSContext = (0, react_1.createContext)(null);
|
|
40
|
+
const AUTH_ME_QUERY_KEY = ['auth', 'me'];
|
|
40
41
|
/**
|
|
41
42
|
* Internal component to handle auth state and provide to context
|
|
42
43
|
*/
|
|
43
44
|
const PHCMSInternalProvider = ({ client, children }) => {
|
|
44
|
-
const
|
|
45
|
-
|
|
45
|
+
const queryClient = (0, react_query_1.useQueryClient)();
|
|
46
|
+
// Track token presence in state to trigger re-renders when it changes.
|
|
47
|
+
// Initial value from provider.
|
|
48
|
+
const [hasToken, setHasToken] = (0, react_1.useState)(() => client.authProvider?.hasToken() ?? false);
|
|
49
|
+
// The 'me' query is only enabled if we have a token.
|
|
50
|
+
const { data: user, isLoading, isError, refetch } = (0, react_query_1.useQuery)({
|
|
51
|
+
queryKey: [...AUTH_ME_QUERY_KEY],
|
|
46
52
|
queryFn: () => client.auth.me(),
|
|
53
|
+
enabled: hasToken,
|
|
47
54
|
retry: false,
|
|
48
55
|
staleTime: 1000 * 60 * 5,
|
|
49
56
|
});
|
|
57
|
+
// Function to update token state and optionally refetch
|
|
58
|
+
const refreshUser = (0, react_1.useCallback)(async () => {
|
|
59
|
+
const currentHasToken = client.authProvider?.hasToken() ?? false;
|
|
60
|
+
setHasToken(currentHasToken);
|
|
61
|
+
if (currentHasToken) {
|
|
62
|
+
await refetch();
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// If no token, clear the query data
|
|
66
|
+
queryClient.setQueryData([...AUTH_ME_QUERY_KEY], null);
|
|
67
|
+
}
|
|
68
|
+
}, [client.authProvider, refetch, queryClient]);
|
|
69
|
+
// Effect to sync state if provider changes externally (rare but possible)
|
|
70
|
+
(0, react_1.useEffect)(() => {
|
|
71
|
+
const interval = setInterval(() => {
|
|
72
|
+
const currentHasToken = client.authProvider?.hasToken() ?? false;
|
|
73
|
+
if (currentHasToken !== hasToken) {
|
|
74
|
+
setHasToken(currentHasToken);
|
|
75
|
+
}
|
|
76
|
+
}, 2000); // Check every 2s as a fallback
|
|
77
|
+
return () => clearInterval(interval);
|
|
78
|
+
}, [client.authProvider, hasToken]);
|
|
50
79
|
const value = (0, react_1.useMemo)(() => ({
|
|
51
80
|
client,
|
|
52
81
|
user: user || null,
|
|
53
|
-
isAuthenticated: !!user && !isError,
|
|
54
|
-
isLoading,
|
|
55
|
-
|
|
56
|
-
|
|
82
|
+
isAuthenticated: !!user && !isError && hasToken,
|
|
83
|
+
isLoading: hasToken ? isLoading : false,
|
|
84
|
+
refreshUser,
|
|
85
|
+
}), [client, user, isError, isLoading, hasToken, refreshUser]);
|
|
86
|
+
return react_1.default.createElement(PHCMSContext.Provider, { value: value }, children);
|
|
57
87
|
};
|
|
58
88
|
/**
|
|
59
89
|
* Root Provider for PH-CMS
|
|
60
90
|
* Automatically includes QueryClientProvider
|
|
61
91
|
*/
|
|
62
92
|
const PHCMSProvider = ({ client, queryClient, children }) => {
|
|
63
|
-
const internalQueryClient = (0, react_1.useMemo)(() => queryClient ??
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
93
|
+
const internalQueryClient = (0, react_1.useMemo)(() => queryClient ??
|
|
94
|
+
new react_query_1.QueryClient({
|
|
95
|
+
defaultOptions: {
|
|
96
|
+
queries: {
|
|
97
|
+
refetchOnWindowFocus: false,
|
|
98
|
+
retry: false,
|
|
99
|
+
},
|
|
68
100
|
},
|
|
69
|
-
},
|
|
70
|
-
}), [queryClient]);
|
|
101
|
+
}), [queryClient]);
|
|
71
102
|
return (react_1.default.createElement(react_query_1.QueryClientProvider, { client: internalQueryClient },
|
|
72
103
|
react_1.default.createElement(PHCMSInternalProvider, { client: client }, children)));
|
|
73
104
|
};
|
package/dist/hooks/useAuth.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unified Auth Hook
|
|
3
|
-
* Returns both the current auth status and the actions
|
|
3
|
+
* Returns both the current auth status and the actions.
|
|
4
4
|
*/
|
|
5
5
|
export declare const useAuth: () => {
|
|
6
6
|
user: {
|
|
@@ -128,6 +128,30 @@ export declare const useAuth: () => {
|
|
|
128
128
|
email: string;
|
|
129
129
|
password: string;
|
|
130
130
|
}, unknown>;
|
|
131
|
+
loginWithFirebaseStatus: import("@tanstack/react-query").UseMutationResult<{
|
|
132
|
+
refreshToken: string;
|
|
133
|
+
user: {
|
|
134
|
+
uid: string;
|
|
135
|
+
email: string;
|
|
136
|
+
username: string | null;
|
|
137
|
+
display_name: string;
|
|
138
|
+
avatar_url: string | null;
|
|
139
|
+
phone_number: string | null;
|
|
140
|
+
email_verified_at: string | null;
|
|
141
|
+
phone_verified_at: string | null;
|
|
142
|
+
locale: string;
|
|
143
|
+
timezone: string;
|
|
144
|
+
status: string;
|
|
145
|
+
role: string[];
|
|
146
|
+
profile_data: Record<string, any>;
|
|
147
|
+
last_login_at: string | null;
|
|
148
|
+
created_at: string;
|
|
149
|
+
updated_at: string;
|
|
150
|
+
};
|
|
151
|
+
accessToken: string;
|
|
152
|
+
}, Error, {
|
|
153
|
+
idToken: string;
|
|
154
|
+
}, unknown>;
|
|
131
155
|
registerStatus: import("@tanstack/react-query").UseMutationResult<{
|
|
132
156
|
refreshToken: string;
|
|
133
157
|
user: {
|
package/dist/hooks/useAuth.js
CHANGED
|
@@ -3,36 +3,37 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.useLogout = exports.useLogin = exports.useUser = exports.useAuth = void 0;
|
|
4
4
|
const react_query_1 = require("@tanstack/react-query");
|
|
5
5
|
const context_1 = require("../context");
|
|
6
|
+
const AUTH_ME_QUERY_KEY = ['auth', 'me'];
|
|
6
7
|
/**
|
|
7
8
|
* Unified Auth Hook
|
|
8
|
-
* Returns both the current auth status and the actions
|
|
9
|
+
* Returns both the current auth status and the actions.
|
|
9
10
|
*/
|
|
10
11
|
const useAuth = () => {
|
|
11
|
-
const { user, isAuthenticated, isLoading } = (0, context_1.usePHCMSContext)();
|
|
12
|
+
const { user, isAuthenticated, isLoading, refreshUser } = (0, context_1.usePHCMSContext)();
|
|
12
13
|
const client = (0, context_1.usePHCMS)();
|
|
13
14
|
const queryClient = (0, react_query_1.useQueryClient)();
|
|
14
15
|
const loginMutation = (0, react_query_1.useMutation)({
|
|
15
16
|
mutationFn: (data) => client.auth.login(data),
|
|
16
|
-
onSuccess: () => {
|
|
17
|
-
|
|
17
|
+
onSuccess: async () => {
|
|
18
|
+
await refreshUser();
|
|
18
19
|
},
|
|
19
20
|
});
|
|
20
21
|
const loginWithFirebaseMutation = (0, react_query_1.useMutation)({
|
|
21
22
|
mutationFn: (data) => client.auth.loginWithFirebase(data),
|
|
22
|
-
onSuccess: () => {
|
|
23
|
-
|
|
23
|
+
onSuccess: async () => {
|
|
24
|
+
await refreshUser();
|
|
24
25
|
},
|
|
25
26
|
});
|
|
26
27
|
const registerMutation = (0, react_query_1.useMutation)({
|
|
27
28
|
mutationFn: (data) => client.auth.register(data),
|
|
28
|
-
onSuccess: () => {
|
|
29
|
-
|
|
29
|
+
onSuccess: async () => {
|
|
30
|
+
await refreshUser();
|
|
30
31
|
},
|
|
31
32
|
});
|
|
32
33
|
const logoutMutation = (0, react_query_1.useMutation)({
|
|
33
34
|
mutationFn: () => client.auth.logout(),
|
|
34
|
-
onSuccess: () => {
|
|
35
|
-
|
|
35
|
+
onSuccess: async () => {
|
|
36
|
+
await refreshUser(); // This will clear state since hasToken will be false
|
|
36
37
|
queryClient.invalidateQueries();
|
|
37
38
|
},
|
|
38
39
|
});
|
|
@@ -46,6 +47,7 @@ const useAuth = () => {
|
|
|
46
47
|
logout: logoutMutation.mutateAsync,
|
|
47
48
|
// Access to mutation objects for loading/error states if needed
|
|
48
49
|
loginStatus: loginMutation,
|
|
50
|
+
loginWithFirebaseStatus: loginWithFirebaseMutation,
|
|
49
51
|
registerStatus: registerMutation,
|
|
50
52
|
logoutStatus: logoutMutation,
|
|
51
53
|
};
|