@zalkera/client 0.21.9 → 0.21.11

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/llms.txt CHANGED
@@ -150,11 +150,33 @@ visitorIp(req.headers, { trustedHops: 2 }); // 예: CDN + 로드밸런서
150
150
  `rescheduleBooking(accessToken, bookingCode, newSlotId)` — 전부 **bookingCode** 로(숫자 id 아님).
151
151
 
152
152
  ### 커머스 — 고객 인증(소셜)
153
- - `socialLogin({ provider, code, redirectUri? })` → `AuthTokens { accessToken, refreshToken, customer }`
153
+ - `socialLogin({ provider, code, redirectUri?, consents? })` → `AuthTokens { accessToken, refreshToken, tokenType, expiresIn, customer }`
154
154
  - provider: `KAKAO`|`NAVER`|`GOOGLE`. 소셜 리다이렉트로 받은 `code` 를 넘기면 백엔드가 교환.
155
+ - ⚠ **신규 가입이면 `consents` 가 필수다.** `TERMS`·`PRIVACY`·`OVER_14` 셋이 모두 `granted: true`
156
+ 로 있어야 하고, 없으면 400 `CONSENT_REQUIRED` 로 **가입이 거부된다**. 이 필드는 선택 타입이라
157
+ 컴파일은 통과한다 — 개발 중에는 개발자 계정이 이미 있어 안 드러나고, 개시 후 **첫 실고객**에게서
158
+ 처음 드러난다. 기존 고객 로그인에는 필요 없다.
159
+
160
+ ```ts
161
+ const tokens = await zalkera.socialLogin({
162
+ provider: "KAKAO",
163
+ code,
164
+ redirectUri,
165
+ // 신규 가입 화면에서 받은 값. 셋 다 없으면 400 이다.
166
+ consents: [
167
+ {type: "TERMS", granted: true},
168
+ {type: "PRIVACY", granted: true},
169
+ {type: "OVER_14", granted: true},
170
+ // 선택: MARKETING_EMAIL · MARKETING_SMS
171
+ ],
172
+ });
173
+ ```
155
174
  - `refreshSession(refreshToken)` → 새 토큰(회전). `logout(accessToken)`. `getMe(accessToken)`.
156
175
  - `getConsents(accessToken)` → `ConsentStatus[]` · `updateConsents(accessToken, consents)` → 확인 메시지 (마케팅 수신 토글 등·append).
157
- - **accessToken 은 15분**, refreshToken 은 매 교환마다 회전. 둘 다 httpOnly 쿠키에 저장 권장.
176
+ - **accessToken 은 15분**, refreshToken 은 매 교환마다 회전. 둘 다 쿠키에 저장하되 플래그를
177
+ **넷 다** 준다: `{httpOnly: true, secure: true, sameSite: "lax", path: "/"}`.
178
+ ⚠ `httpOnly` 만으로는 부족하다 — Firefox·Safari 는 `SameSite` 를 Lax 로 기본화하지 않으므로,
179
+ `sameSite` 없는 토큰 쿠키와 가드 없는 변이 라우트가 만나면 위조 경로가 완성된다.
158
180
 
159
181
  ### 커머스 — 장바구니 (session = { accessToken? , cartSessionKey? })
160
182
  - `getCart(session)` · `addToCart(variantId, qty, session)` · `updateCartItem(variantId, qty, session)`
@@ -207,7 +229,12 @@ visitorIp(req.headers, { trustedHops: 2 }); // 예: CDN + 로드밸런서
207
229
 
208
230
  ### ISR 캐시 태그 (`ReadOptions`)
209
231
 
210
- 읽기 메서드는 마지막 인자로 `{ tags: [...] }` 받는다. 넘기면 그 fetch 에 `next.tags` 가 실려
232
+ **`ReadOptions` 를 받는 읽기 메서드는 일곱이다** `getSiteConfig` · `getProduct` · `listProducts` ·
233
+ `listProductCategories` · `listProductReviews` · `getProductReviewSummary` · `availability`.
234
+ 나머지(`getPost`·`listPosts`·`listCategories`·`getMediaUrl`·`listMyOrders`·`getCart` 등)에 넘기면
235
+ **TypeScript 가 `TS2554` 로 막고**, JS 레포에서는 인자가 조용히 버려진다.
236
+
237
+ 이 일곱은 마지막 인자로 `{ tags: [...] }` 를 받는다. 넘기면 그 fetch 에 `next.tags` 가 실려
211
238
  **백엔드가 `revalidateTag` 로 온디맨드 무효화**한다 — 테넌트가 콘솔에서 고친 것이 즉시 반영되는 경로다.
212
239
 
213
240
  ```ts
@@ -225,8 +252,10 @@ const product = await zalkera.getProduct(slug, { tags: ["products", `product:${s
225
252
 
226
253
  - **`product:{slug}` 는 기대하지 마라.** 백엔드가 일부러 안 붙인다 — 오퍼레이션 페이로드의 상품 참조가
227
254
  고객이 말로 지시한 모호 참조라 slug 와 일치한다는 보장이 없다. 상품 페이지도 `products` 로 받는다.
228
- - **모든 페이지 fetch `site-config` 를 동승시켜라.** 테마·레이아웃은 전 페이지에 영향인데,
229
- 발화 태그는 `site-config` 하나뿐이라 이걸 안 달면 그 페이지만 옛 테마로 남는다.
255
+ - **`ReadOptions` 받는 일곱 곳에는 `site-config` 를 동승시켜라.** 테마·레이아웃은 전 페이지에
256
+ 영향인데 발화 태그는 `site-config` 하나뿐이라, 이걸 안 달면 그 페이지만 옛 테마로 남는다.
257
+ 블로그·미디어처럼 `ReadOptions` 를 안 받는 라우트는 이 경로가 없다 — 그쪽은 `revalidate` 로 두거나
258
+ 라우트 세그먼트 설정(`export const revalidate = N`)을 쓴다.
230
259
 
231
260
  ```ts
232
261
  // 카테고리 라우트 — 자기 태그 + site-config 동승
@@ -273,6 +302,12 @@ const product = await zalkera.getProduct(params.slug);
273
302
  ```ts
274
303
  // app/api/cart/add/route.ts (BFF)
275
304
  export async function POST(req: Request) {
305
+ // ⚠ **변이 라우트의 첫 구문은 교차사이트 위조 가드다.** 이 route handler 는 쿠키로 인증하므로
306
+ // 공격자 페이지가 방문자의 브라우저로 이 주소에 POST 를 보낼 수 있다 — 다치는 건 방문자다.
307
+ // 막힌 요청이 아무 상태도 안 남기려면 **쿠키를 쓰기 전에** 있어야 한다.
308
+ const blocked = assertSameOrigin(req);
309
+ if (blocked) return blocked;
310
+
276
311
  const { variantId, quantity } = await req.json();
277
312
  const session = { accessToken: cookieAccessToken(), cartSessionKey: ensureCartCookie() };
278
313
  const cart = await zalkera.addToCart(variantId, quantity, session);
@@ -280,6 +315,11 @@ export async function POST(req: Request) {
280
315
  }
281
316
  ```
282
317
  - `ensureCartCookie()`: 게스트면 랜덤 키를 만들어 httpOnly 쿠키에 저장하고 그 값을 쓴다.
318
+ - **`assertSameOrigin(req)`** — 변이 메서드(POST·PUT·PATCH·DELETE)를 가진 route handler **전부**에
319
+ 둔다. `Origin`(없으면 `Referer`)이 자기 사이트가 아니면 403 을 돌려주고, 그 외엔 `null` 이다.
320
+ 카탈로그 팩은 `src/lib/crossOrigin.ts` 로 배송한다. 팩이 아닌 레포라면 같은 판정을 직접 두되
321
+ **이름과 위치를 바꾸지 마라** — 소스 검사기의 `[X1]` 이 이 심볼을 찾는다.
322
+ ⚠ 그 검사는 **호출이 있는가만** 본다. 통과가 안전을 뜻하지 않으니 가드의 내용은 사람이 본다.
283
323
 
284
324
  ### 4.3 결제 플로우 (게스트/로그인 공통)
285
325
  1. 카트 확인 → `checkout({ buyerName, buyerPhone, shipTo }, session, idempotencyKey)` → `order.orderNo`.
@@ -711,7 +751,7 @@ export function merchantReturnPolicyJsonLd(config: SiteConfig, windowDays?: numb
711
751
  실은 값**이라 IP 레이트리밋이 한 줄로 우회되고 IP 기록이 오염된다(실측). ✅ `visitorIp(req.headers)` 를 쓰고
712
752
  프록시 홉 수를 선언한다(§2). `x-real-ip` 폴백도 쓰지 마라 — 세우는 주체가 프록시마다 다르고 안 세우면 위조 자유다.
713
753
  검사기(`zalkera-validate`)가 이 형상을 `[I1]` 경고로 잡는다.
714
- - ❌ **콘텐츠 파일 사이트에서 페이지를 라우트로 신설**(`src/app/오시는길/page.tsx` 를 새로 짜기). ✅ `content/pages/<slug>.json`
754
+ - ❌ **콘텐츠 파일 사이트에서 페이지를 라우트로 신설**(`src/app/<slug>/page.tsx` 를 새로 짜기). ✅ `content/pages/<slug>.json`
715
755
  **+ 매니페스트 1행**이면 끝이고 라우팅·sitemap 은 이미 있다(§4.8). 라우트를 새로 짜면 그 페이지만 계약 밖으로
716
756
  나가 다음번 "말로 고치기"가 다시 tsx 탐색이 된다 — 실측된 회귀다.
717
757
  - ❌ **콘텐츠 파일에 숫자 id 적기**(`"assetId": 12`·`"productIds": [3,7]`). ✅ 에셋은 `public/` 루트 절대
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zalkera/client",
3
- "version": "0.21.9",
3
+ "version": "0.21.11",
4
4
  "description": "zalkera 헤드리스 CMS 공개 API 클라이언트 (테넌트 사이트용)",
5
5
  "license": "MIT",
6
6
  "author": "Credium Co., Ltd.",