@sayren/storefront-sdk 0.2.1 → 0.3.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sayren/storefront-sdk",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "main": "./dist/index.mjs",
5
5
  "types": "./dist/index.d.mts",
6
6
  "exports": {
@@ -13,6 +13,11 @@
13
13
  "development": "./src/analytics/index.ts",
14
14
  "types": "./dist/analytics/index.d.mts",
15
15
  "default": "./dist/analytics/index.mjs"
16
+ },
17
+ "./auth": {
18
+ "development": "./src/auth/index.ts",
19
+ "types": "./dist/auth/index.d.mts",
20
+ "default": "./dist/auth/index.mjs"
16
21
  }
17
22
  },
18
23
  "dependencies": {
@@ -0,0 +1,74 @@
1
+ import { createStorefrontClient } from "../client";
2
+ import { ApiError } from "../http";
3
+ import type { AnonymousSession, LoginRequest, SignupRequest, TokenPair } from "../schemas/auth";
4
+
5
+ /** 실패는 모두 이 클래스다 — 메인 엔트리의 `ApiError`와 같은 클래스라 어느 쪽으로 `instanceof`해도 된다 */
6
+ export { ApiError } from "../http";
7
+ export type { AnonymousSession, LoginRequest, SignupRequest, TokenPair } from "../schemas/auth";
8
+
9
+ export interface StorefrontAuthOptions {
10
+ /** 예: https://api.sayren.app/storefront/v1 */
11
+ baseUrl: string;
12
+ /** 테넌트 스토어 코드 — `X-Store-Code` 헤더로 보낸다 */
13
+ storeCode: string;
14
+ fetch?: typeof globalThis.fetch;
15
+ }
16
+
17
+ /**
18
+ * 구매자 인증 — 화면은 스토어프런트가 그리고, 계정 저장과 인증 처리는 sayren이 한다.
19
+ * 어느 방식으로 로그인해도 결과는 같은 구매자 토큰(`TokenPair`)이고 `/me/*`·장바구니·주문 API에 그대로 쓴다.
20
+ *
21
+ * 토큰 보관은 앱 몫이다. 서버(loader·action)에서 호출하고 httpOnly 쿠키에 두기를 권한다.
22
+ *
23
+ * ```ts
24
+ * const auth = createStorefrontAuth({ baseUrl, storeCode });
25
+ * const guest = await auth.anonymous(); // 비회원: 장바구니 토큰
26
+ * const tokens = await auth.session({ email, password, cartToken: guest.cartToken });
27
+ * ```
28
+ */
29
+ export function createStorefrontAuth(options: StorefrontAuthOptions) {
30
+ const client = (accessToken?: string) =>
31
+ createStorefrontClient({
32
+ baseUrl: options.baseUrl,
33
+ storeCode: options.storeCode,
34
+ accessToken,
35
+ fetch: options.fetch,
36
+ });
37
+
38
+ return {
39
+ /**
40
+ * 비회원 익명 세션 — 구매자 계정을 만들지 않는다. `cartToken`을 `X-Cart-Token`으로 보내면 장바구니·
41
+ * 비회원 주문이 쌓이고, `session()`·`signUp()`에 실으면 회원 장바구니로 병합된 뒤 닫힌다.
42
+ * 마지막 활동 후 30일이 지나면 만료된다.
43
+ */
44
+ anonymous(): Promise<AnonymousSession> {
45
+ return client().auth.anonymous();
46
+ },
47
+ /** 이메일·비밀번호 로그인. `cartToken`을 주면 비회원 장바구니를 병합한다 */
48
+ session(body: LoginRequest): Promise<TokenPair> {
49
+ return client().auth.login(body);
50
+ },
51
+ /** 회원가입 — 가입과 동시에 로그인 토큰을 받는다. `cartToken`을 주면 장바구니를 병합한다 */
52
+ signUp(body: SignupRequest): Promise<TokenPair> {
53
+ return client().auth.signup(body);
54
+ },
55
+ /** 토큰 갱신 — refreshToken은 한 번 쓰면 폐기되고 새 쌍이 온다(회전) */
56
+ refresh(refreshToken: string): Promise<TokenPair> {
57
+ return client().auth.refresh(refreshToken);
58
+ },
59
+ /**
60
+ * 로그아웃 — 이 구매자의 refreshToken을 모두 폐기한다. 앱은 보관한 토큰을 지운다.
61
+ * 액세스 토큰이 이미 만료됐으면(401) 서버에서 할 일이 없으므로 성공으로 본다.
62
+ */
63
+ async signOut(accessToken: string): Promise<void> {
64
+ try {
65
+ await client(accessToken).auth.logout();
66
+ } catch (error) {
67
+ if (error instanceof ApiError && error.status === 401) return;
68
+ throw error;
69
+ }
70
+ },
71
+ };
72
+ }
73
+
74
+ export type StorefrontAuth = ReturnType<typeof createStorefrontAuth>;
package/src/client.ts CHANGED
@@ -2,7 +2,12 @@ import { z } from "zod";
2
2
  import { createHttp, path, type Query, resolveToken, type TokenSource } from "./http";
3
3
  import { ANALYTICS_SESSION_HEADER, ANALYTICS_VISITOR_HEADER } from "./schemas/analytics";
4
4
  import type { LoginRequest, SignupRequest } from "./schemas/auth";
5
- import { loginRequestSchema, signupRequestSchema, tokenPairSchema } from "./schemas/auth";
5
+ import {
6
+ anonymousSessionSchema,
7
+ loginRequestSchema,
8
+ signupRequestSchema,
9
+ tokenPairSchema,
10
+ } from "./schemas/auth";
6
11
  import {
7
12
  type AddCartItemRequest,
8
13
  addCartItemRequestSchema,
@@ -163,6 +168,8 @@ export function createStorefrontClient(options: StorefrontClientOptions) {
163
168
  return session.userinfo();
164
169
  },
165
170
  auth: {
171
+ /** 비회원 익명 세션 발급 — 응답의 cartToken을 `cartToken` 옵션(X-Cart-Token)으로 쓴다 */
172
+ anonymous: () => http.request("POST", "/auth/anonymous", anonymousSessionSchema),
166
173
  signup: (body: SignupRequest) =>
167
174
  http.request("POST", "/auth/signup", tokenPairSchema, {
168
175
  body: signupRequestSchema.parse(body),
@@ -28,6 +28,8 @@ export const signupRequestSchema = z.object({
28
28
  privacy: z.literal(true),
29
29
  marketing: z.boolean().default(false),
30
30
  }),
31
+ /** 비회원 장바구니 토큰 — 가입과 함께 회원 장바구니로 옮긴다 */
32
+ cartToken: z.string().optional(),
31
33
  });
32
34
 
33
35
  export type SignupRequest = z.infer<typeof signupRequestSchema>;
@@ -39,3 +41,16 @@ export const loginRequestSchema = z.object({
39
41
  });
40
42
 
41
43
  export type LoginRequest = z.infer<typeof loginRequestSchema>;
44
+
45
+ /**
46
+ * 비회원 익명 세션 — `POST /auth/anonymous` 응답. `cartToken`을 `X-Cart-Token` 헤더로 보내면 장바구니와
47
+ * 비회원 주문이 이 세션에 쌓인다. 구매자 계정이 아니므로 `/me/*`에는 쓸 수 없다.
48
+ * 마지막 활동 후 30일이 지나거나 로그인·가입으로 회원 장바구니에 병합되면 더는 쓸 수 없다.
49
+ */
50
+ export const anonymousSessionSchema = z.object({
51
+ cartToken: z.string(),
52
+ /** 이 시각까지 활동이 없으면 만료(ISO 8601). 쓸 때마다 늘어난다 */
53
+ expiresAt: z.string(),
54
+ });
55
+
56
+ export type AnonymousSession = z.infer<typeof anonymousSessionSchema>;