@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/bin/validate-storefront.mjs +52 -23
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/llms.txt +46 -6
- package/package.json +1 -1
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 은 매 교환마다 회전. 둘 다
|
|
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
|
-
읽기 메서드는
|
|
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
|
-
-
|
|
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
|
|
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/` 루트 절대
|