@ph-cms/client-sdk 0.1.15 → 0.1.17
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 +125 -1
- package/bin/cli.js +6 -0
- package/dist/hooks/useContent.d.ts +2 -2
- package/dist/hooks/useFirebaseAuthSync.js +9 -3
- package/dist/hooks/useStampTour.d.ts +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -382,12 +382,16 @@ function AuthComponent() {
|
|
|
382
382
|
login, // (data: LoginRequest) => Promise<AuthResponse>
|
|
383
383
|
loginWithFirebase, // (data: FirebaseExchangeRequest) => Promise<AuthResponse>
|
|
384
384
|
register, // (data: RegisterRequest) => Promise<AuthResponse>
|
|
385
|
+
loginAnonymous, // (data?: AnonymousLoginRequest) => Promise<AuthResponse>
|
|
386
|
+
upgradeAnonymous, // (data: { email, password, display_name?, username? }) => Promise<UserDto>
|
|
385
387
|
logout, // () => Promise<void>
|
|
386
388
|
|
|
387
389
|
// 뮤테이션 상태 (isPending, error 등)
|
|
388
390
|
loginStatus,
|
|
389
391
|
loginWithFirebaseStatus,
|
|
390
392
|
registerStatus,
|
|
393
|
+
loginAnonymousStatus,
|
|
394
|
+
upgradeAnonymousStatus,
|
|
391
395
|
logoutStatus,
|
|
392
396
|
} = useAuth();
|
|
393
397
|
}
|
|
@@ -457,6 +461,114 @@ function FirebaseLoginButton() {
|
|
|
457
461
|
}
|
|
458
462
|
```
|
|
459
463
|
|
|
464
|
+
#### 익명 회원가입
|
|
465
|
+
|
|
466
|
+
이메일·비밀번호 없이 임시 계정을 생성합니다. 생성된 계정은 나중에 이메일 계정으로 전환할 수 있으며, 그 동안 쌓인 활동 히스토리(좋아요, 스탬프 등)는 그대로 유지됩니다.
|
|
467
|
+
|
|
468
|
+
두 가지 방식이 있으며 **결과는 동일**합니다.
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
**방식 1 — 순수 익명 (Firebase 없이)**
|
|
473
|
+
|
|
474
|
+
서버가 임시 계정(`provider: anonymous`)을 직접 생성합니다.
|
|
475
|
+
|
|
476
|
+
```tsx
|
|
477
|
+
function GuestButton() {
|
|
478
|
+
const { loginAnonymous, loginAnonymousStatus } = useAuth();
|
|
479
|
+
|
|
480
|
+
return (
|
|
481
|
+
<button
|
|
482
|
+
onClick={() => loginAnonymous()}
|
|
483
|
+
disabled={loginAnonymousStatus.isPending}
|
|
484
|
+
>
|
|
485
|
+
{loginAnonymousStatus.isPending ? '입장 중...' : '게스트로 시작하기'}
|
|
486
|
+
</button>
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
`channelUid`는 `PHCMSProvider`에 설정된 값이 자동으로 사용됩니다. 직접 지정하려면 인자로 전달합니다:
|
|
492
|
+
|
|
493
|
+
```ts
|
|
494
|
+
await loginAnonymous({ channelUid: 'channel-uid', displayName: '손님1' });
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
**방식 2 — Firebase 익명 (`signInAnonymously` 연동)**
|
|
500
|
+
|
|
501
|
+
Firebase의 `signInAnonymously()`로 익명 인증을 먼저 수행한 뒤, 발급된 ID 토큰을 `loginAnonymous()`에 전달합니다. 서버는 이를 검증하여 `provider: firebase:anonymous` 계정을 생성합니다.
|
|
502
|
+
|
|
503
|
+
이 방식은 Firebase 익명 계정을 나중에 `linkWithCredential()` 등으로 실계정에 연결하는 Firebase 네이티브 플로우와 함께 사용할 때 적합합니다.
|
|
504
|
+
|
|
505
|
+
```tsx
|
|
506
|
+
import { getAuth, signInAnonymously } from 'firebase/auth';
|
|
507
|
+
|
|
508
|
+
function FirebaseGuestButton() {
|
|
509
|
+
const { loginAnonymous, loginAnonymousStatus } = useAuth();
|
|
510
|
+
|
|
511
|
+
const handleAnonymous = async () => {
|
|
512
|
+
const firebaseAuth = getAuth();
|
|
513
|
+
const { user } = await signInAnonymously(firebaseAuth);
|
|
514
|
+
const firebaseIdToken = await user.getIdToken();
|
|
515
|
+
|
|
516
|
+
await loginAnonymous({ firebaseIdToken });
|
|
517
|
+
// 성공 → 자동으로 me() 호출 → 인증 상태 갱신
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
return (
|
|
521
|
+
<button onClick={handleAnonymous} disabled={loginAnonymousStatus.isPending}>
|
|
522
|
+
{loginAnonymousStatus.isPending ? '입장 중...' : '게스트로 시작하기'}
|
|
523
|
+
</button>
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
> **주의:** Firebase 익명 계정은 `POST /auth/firebase/exchange`(토큰 교환)로는 등록되지 않습니다.
|
|
529
|
+
> 신규 익명 유저는 반드시 `loginAnonymous({ firebaseIdToken })`을 통해 등록해야 합니다.
|
|
530
|
+
> `FirebaseAuthSync`를 사용하는 경우 이 분기가 자동으로 처리됩니다 ([Firebase Auth Sync](#firebase-auth-sync) 참고).
|
|
531
|
+
|
|
532
|
+
---
|
|
533
|
+
|
|
534
|
+
**익명 계정 → 이메일 계정 전환**
|
|
535
|
+
|
|
536
|
+
`upgradeAnonymous()`를 호출하면 user ID를 유지한 채로 정식 이메일 계정으로 전환됩니다.
|
|
537
|
+
|
|
538
|
+
```tsx
|
|
539
|
+
function UpgradeForm() {
|
|
540
|
+
const { upgradeAnonymous, upgradeAnonymousStatus, user } = useAuth();
|
|
541
|
+
|
|
542
|
+
const handleUpgrade = async (email: string, password: string, displayName: string) => {
|
|
543
|
+
await upgradeAnonymous({ email, password, display_name: displayName });
|
|
544
|
+
// 성공 → role에서 'anonymous' 제거됨 → 기존 히스토리 유지
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
if (!user?.role.includes('anonymous')) return null;
|
|
548
|
+
|
|
549
|
+
return (
|
|
550
|
+
<form onSubmit={e => {
|
|
551
|
+
e.preventDefault();
|
|
552
|
+
const fd = new FormData(e.currentTarget);
|
|
553
|
+
handleUpgrade(
|
|
554
|
+
fd.get('email') as string,
|
|
555
|
+
fd.get('password') as string,
|
|
556
|
+
fd.get('display_name') as string,
|
|
557
|
+
);
|
|
558
|
+
}}>
|
|
559
|
+
<input name="email" type="email" placeholder="이메일" />
|
|
560
|
+
<input name="password" type="password" placeholder="비밀번호" />
|
|
561
|
+
<input name="display_name" placeholder="이름" />
|
|
562
|
+
<button type="submit" disabled={upgradeAnonymousStatus.isPending}>
|
|
563
|
+
{upgradeAnonymousStatus.isPending ? '전환 중...' : '계정 만들기'}
|
|
564
|
+
</button>
|
|
565
|
+
</form>
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
---
|
|
571
|
+
|
|
460
572
|
#### 회원가입
|
|
461
573
|
|
|
462
574
|
```tsx
|
|
@@ -645,7 +757,15 @@ Firebase onAuthStateChanged
|
|
|
645
757
|
│
|
|
646
758
|
├─ fbUser 존재 + PH-CMS 비인증 상태
|
|
647
759
|
│ → fbUser.getIdToken()
|
|
648
|
-
│
|
|
760
|
+
│ │
|
|
761
|
+
│ ├─ fbUser.isAnonymous === true
|
|
762
|
+
│ │ → client.auth.loginAnonymous({ channelUid, firebaseIdToken })
|
|
763
|
+
│ │ ※ POST /auth/anonymous (등록 또는 기존 계정 토큰 재발급)
|
|
764
|
+
│ │
|
|
765
|
+
│ └─ fbUser.isAnonymous === false (Google, 이메일 등)
|
|
766
|
+
│ → client.auth.loginWithFirebase({ idToken })
|
|
767
|
+
│ ※ POST /auth/firebase/exchange (기존 유저 로그인 전용)
|
|
768
|
+
│
|
|
649
769
|
│ → provider.setTokens(...)
|
|
650
770
|
│ → refreshUser() ← me() 호출하여 프로필 로드
|
|
651
771
|
│
|
|
@@ -654,6 +774,10 @@ Firebase onAuthStateChanged
|
|
|
654
774
|
→ refreshUser() ← 상태 초기화
|
|
655
775
|
```
|
|
656
776
|
|
|
777
|
+
> `POST /auth/firebase/exchange`는 **기존 유저 로그인 전용**입니다.
|
|
778
|
+
> 신규 익명 유저를 이 엔드포인트로 생성하려 하면 서버가 에러를 반환합니다.
|
|
779
|
+
> `FirebaseAuthSync`를 사용하면 익명/비익명 분기가 자동으로 처리됩니다.
|
|
780
|
+
|
|
657
781
|
#### `<FirebaseAuthSync>` 컴포넌트
|
|
658
782
|
|
|
659
783
|
`<PHCMSProvider>` 안에서 사용합니다:
|
package/bin/cli.js
CHANGED
|
@@ -59,6 +59,12 @@ const { data: terms } = useChannelTerms();
|
|
|
59
59
|
const { mutate: agree } = useAgreeTerms();
|
|
60
60
|
\`\`\`
|
|
61
61
|
|
|
62
|
+
## 📚 Detailed Documentation
|
|
63
|
+
For the complete API reference, advanced configuration, and comprehensive examples, always refer to the full documentation:
|
|
64
|
+
- **Location**: \`node_modules/@ph-cms/client-sdk/README.md\`
|
|
65
|
+
|
|
66
|
+
If you need specific details about a module (e.g., \`ContentModule\`, \`AuthModule\`), read that file to ensure correct parameter usage.
|
|
67
|
+
|
|
62
68
|
## ⚠️ Common Pitfalls
|
|
63
69
|
- **Manual channelUid passing**: Avoid \`useContentList({ channelUid: '...', ... })\` unless necessary. Rely on the provider.
|
|
64
70
|
- **Hook-less API calls**: When using \`client.content.list()\` directly, you MUST provide \`channelUid\` in the params as there is no context available.
|
|
@@ -8,8 +8,8 @@ export declare const contentKeys: {
|
|
|
8
8
|
status?: string | undefined;
|
|
9
9
|
type?: string | undefined;
|
|
10
10
|
channelUid?: string | undefined;
|
|
11
|
-
tags?: string[] | undefined;
|
|
12
11
|
channelSlug?: string | undefined;
|
|
12
|
+
tags?: string[] | undefined;
|
|
13
13
|
parentUid?: string | undefined;
|
|
14
14
|
authorUid?: string | undefined;
|
|
15
15
|
uids?: string[] | undefined;
|
|
@@ -45,6 +45,7 @@ export declare const useCreateContent: () => import("@tanstack/react-query").Use
|
|
|
45
45
|
title: string;
|
|
46
46
|
status?: string | undefined;
|
|
47
47
|
channelUid?: string | undefined;
|
|
48
|
+
channelSlug?: string | undefined;
|
|
48
49
|
geometry?: {
|
|
49
50
|
type: "Point" | "LineString" | "Polygon" | "MultiPoint" | "MultiLineString" | "MultiPolygon" | "GeometryCollection" | "Feature" | "FeatureCollection";
|
|
50
51
|
coordinates?: any[] | undefined;
|
|
@@ -59,7 +60,6 @@ export declare const useCreateContent: () => import("@tanstack/react-query").Use
|
|
|
59
60
|
summary?: string | null | undefined;
|
|
60
61
|
slug?: string | null | undefined;
|
|
61
62
|
tags?: string[] | undefined;
|
|
62
|
-
channelSlug?: string | undefined;
|
|
63
63
|
parentUid?: string | undefined;
|
|
64
64
|
sortOrder?: number | undefined;
|
|
65
65
|
mediaAttachments?: string[] | undefined;
|
|
@@ -106,9 +106,15 @@ const useFirebaseAuthSync = (options) => {
|
|
|
106
106
|
setIsSyncing(true);
|
|
107
107
|
try {
|
|
108
108
|
const idToken = await fbUser.getIdToken();
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
if (fbUser.isAnonymous) {
|
|
110
|
+
// 익명 유저는 POST /auth/anonymous 로 등록/로그인
|
|
111
|
+
await client.auth.loginAnonymous({ channelUid, firebaseIdToken: idToken });
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// 일반 Firebase 유저는 토큰 교환 (기존 유저 로그인)
|
|
115
|
+
await client.auth.loginWithFirebase({ idToken, channelUid });
|
|
116
|
+
}
|
|
117
|
+
// Refresh the context so `isAuthenticated` and `user` update.
|
|
112
118
|
await refreshUser();
|
|
113
119
|
onSyncSuccessRef.current?.();
|
|
114
120
|
}
|
|
@@ -11,6 +11,7 @@ export declare const useCreateStampTour: () => import("@tanstack/react-query").U
|
|
|
11
11
|
markerUids: string[];
|
|
12
12
|
status?: string | undefined;
|
|
13
13
|
channelUid?: string | undefined;
|
|
14
|
+
channelSlug?: string | undefined;
|
|
14
15
|
geometry?: {
|
|
15
16
|
type: "Point" | "LineString" | "Polygon" | "MultiPoint" | "MultiLineString" | "MultiPolygon" | "GeometryCollection" | "Feature" | "FeatureCollection";
|
|
16
17
|
coordinates?: any[] | undefined;
|
|
@@ -22,7 +23,6 @@ export declare const useCreateStampTour: () => import("@tanstack/react-query").U
|
|
|
22
23
|
image?: string | null | undefined;
|
|
23
24
|
summary?: string | null | undefined;
|
|
24
25
|
tags?: string[] | undefined;
|
|
25
|
-
channelSlug?: string | undefined;
|
|
26
26
|
startsAt?: string | null | undefined;
|
|
27
27
|
endsAt?: string | null | undefined;
|
|
28
28
|
}, unknown>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ph-cms/client-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"description": "Unified PH-CMS Client SDK (React + Core)",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"LICENSE"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@ph-cms/api-contract": "^0.1.
|
|
28
|
+
"@ph-cms/api-contract": "^0.1.7",
|
|
29
29
|
"@tanstack/react-query": "^5.0.0",
|
|
30
30
|
"axios": "^1.6.0",
|
|
31
31
|
"zod": "^3.22.4"
|