> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kleep.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 모바일 SDK - React Native

**Github Repository:** [https://github.com/KlipFit/kleep-rn](https://github.com/KlipFit/kleep-rn)

`drawer.kleep.ai`를 감싸는 경량 WebView 래퍼입니다. SDK는 하나의 컴포넌트와 두 개의 메서드를 제공하며, iOS 인터페이스와 동일한 구조를 갖습니다.

## 설치

***

각 릴리스는 `cdn.kleep.ai`에 npm 타볼 형태로 게시됩니다. 두 가지 URL 형식을 사용할 수 있습니다:

**최신 안정 버전** (새로운 안정 릴리스마다 자동 업데이트 — 프리릴리스는 이 포인터를 이동시키지 않습니다):

```bash theme={null}
npm install https://cdn.kleep.ai/react-native-sdk/latest.tgz \
  react-native-webview \
  @react-native-async-storage/async-storage
```

**특정 버전 고정** (프로덕션 권장 — 완전히 불변):

```bash theme={null}
npm install https://cdn.kleep.ai/react-native-sdk/releases/v1.0.0/kleep-react-native-1.0.0.tgz \
  react-native-webview \
  @react-native-async-storage/async-storage
```

`v1.0.0` 및 `1.0.0`을 원하는 릴리스 태그로 교체하세요. 위에 나열된 두 패키지는 피어 의존성입니다 — SDK가 필요로 하지만 버전은 직접 제어할 수 있습니다.

게시된 타볼에는 설치 전 무결성 검증을 원할 경우 사용할 수 있는 SHA-256 파일(`<tarball>.sha256` / `latest.tgz.sha256`)이 함께 포함됩니다.

### Expo

두 피어 의존성 모두 Expo Go (SDK 54+)에 사전 번들되어 있습니다. 개발 환경에서 별도 설정이 필요하지 않습니다. 프로덕션 빌드의 경우 `expo prebuild`가 자동으로 처리합니다.

### Bare React Native

```bash theme={null}
cd ios && pod install
```

## 권한

***

신발 플로우(카메라 스캔)의 경우 `ios/<App>/Info.plist`에 다음을 추가합니다:

```xml theme={null}
<key>NSCameraUsageDescription</key>
<string>Used to scan your shoe size</string>
```

그리고 `android/app/src/main/AndroidManifest.xml`에 다음을 추가합니다:

```xml theme={null}
<uses-permission android:name="android.permission.CAMERA" />
```

## 앱 시작 시 한 번 설정

***

```tsx theme={null}
import { Kleep } from '@kleep/react-native';

Kleep.configure({
  publicId: 'YOUR_KLEEP_PUBLIC_ID',  // UUID provided by Kleep
  language: 'fr',                      // optional, default UI language
});
```

| 필드         | 필수 여부 | 설명                                                                                                                     |
| ---------- | ----- | ---------------------------------------------------------------------------------------------------------------------- |
| `publicId` | yes   | 리테일러를 식별하는 UUID (Kleep에서 제공)                                                                                           |
| `language` | no    | `'fr' \| 'en' \| 'de' \| 'it' \| 'es' \| 'nl' \| 'pt' \| 'ja' \| 'ko' \| 'pl' \| 'br' \| 'dk' \| 'fi' \| 'se' \| 'gb'` |

## 사용법

***

### 메서드 1: `Kleep.checkProduct`

상품 페이지 (PDP) 마운트 시 호출합니다. 반환값은 "내 사이즈 찾기" CTA의 렌더링 여부와 표시할 레이블을 결정합니다.

| 파라미터        | 우선순위 | 설명            |
| ----------- | ---- | ------------- |
| `productId` | *필수* | 리테일러에서의 상품 ID |

**반환값**

```ts theme={null}
{
  recommendable: boolean;
  productFound: boolean;
  category?: 'clothing' | 'lingerie' | 'footwear' | 'children';
  recommendedSize?: string;   // e.g. "M", "38"
}
```

**로직 스키마**

| `recommendable` | `recommendedSize` | 렌더링 내용                           |
| --------------- | ----------------- | -------------------------------- |
| `false`         | —                 | **CTA 숨기기** (Kleep 지원 불가 상품)     |
| `true`          | 없음                | CTA: **"Trouver ma taille"**     |
| `true`          | `"M"`             | CTA: **"Taille recommandée: M"** |

SDK는 `(publicId, productId)` 키 기반의 게이트와 `(publicId, productId, mid)` 키 기반의 추천 사이즈에 대해 결과를 5분간 메모리 캐시에 저장합니다. 상품 페이지 (PDP)를 다시 렌더링하거나 뒤로 이동해도 추가 비용이 발생하지 않습니다.

**구현 예시**

```tsx theme={null}
import { useEffect, useState } from 'react';
import { Pressable, Text } from 'react-native';
import { Kleep, KleepFindSizeView, type KleepCheckProductResult } from '@kleep/react-native';

function ProductPage({ product }) {
  const [check, setCheck] = useState<KleepCheckProductResult | null>(null);
  const [open, setOpen] = useState(false);

  useEffect(() => {
    Kleep.checkProduct({ productId: product.id }).then(setCheck);
  }, [product.id]);

  if (!check?.recommendable) return <ProductContent />;

  return (
    <>
      <ProductContent />
      <Pressable onPress={() => setOpen(true)}>
        <Text>
          {check.recommendedSize
            ? `Taille recommandée : ${check.recommendedSize}`
            : 'Trouver ma taille'}
        </Text>
      </Pressable>
      <KleepFindSizeView
        visible={open}
        productId={product.id}
        variantId={selectedVariant?.id}
        onAddToCart={(e) => cart.add(e.variantId)}
        onSelectSize={(e) => pdp.setSize(e.size)}
        onDismiss={() => setOpen(false)}
      />
    </>
  );
}
```

### 메서드 2: `<KleepFindSizeView>`

이 컴포넌트를 마운트하면 전체 화면 Modal+WebView에서 사이즈 찾기 드로어가 열립니다. 제어 컴포넌트 패턴으로 동작합니다: 호스트가 `visible` 상태를 소유하며, SDK는 `onDismiss`를 통해 닫기를 요청합니다.

| prop           | 우선순위    | 설명                                                                                                         |
| -------------- | ------- | ---------------------------------------------------------------------------------------------------------- |
| `visible`      | *필수*    | 모달 표시 여부를 제어합니다                                                                                            |
| `productId`    | *필수*    | `checkProduct`와 동일                                                                                         |
| `onDismiss`    | *필수*    | 사용자가 드로어를 닫을 때 발생합니다 (X / 스와이프 / 뒤로 가기). 호스트는 반드시 `visible`을 `false`로 설정해야 합니다                             |
| `variantId`    | *선택 사항* | 추천을 위한 변형을 미리 선택합니다                                                                                        |
| `customerId`   | *선택 사항* | 세션 간 연결을 위한 CRM 식별자                                                                                        |
| `language`     | *선택 사항* | 이 열기에 대한 SDK 수준 언어를 재정의합니다                                                                                 |
| `countryCode`  | *선택 사항* | 예: `"FR"`, `"US"` — 단위 시스템 및 브라 사이즈 기본값을 결정합니다                                                             |
| `stocks`       | *선택 사항* | `{ [variantId]: number \| boolean }` — 드로어에 품절/부분 재고 UI가 렌더링됩니다                                            |
| `mock`         | *선택 사항* | `true` → 드로어가 실제 추천 API 호출을 건너뜁니다 (QA 전용)                                                                  |
| `forceState`   | *선택 사항* | `'outOfRange' \| 'unavailable' \| 'error' \| 'qrcode'` — 최종 상태를 직접 렌더링하는 QA 해치                             |
| `warmRestore`  | *선택 사항* | `{ mid, uid }` — 기존 측정값 미리 로드 (인트로 플로우 건너뜀). 동일 기기 복원은 AsyncStorage를 통해 자동으로 처리됩니다                         |
| `extraParams`  | *선택 사항* | 드로어 URL에 추가되는 원시 `Record<string, string>` (이스케이프 해치)                                                       |
| `onAddToCart`  | *선택 사항* | `(event: { variantId, size? }) => void` — 사용자가 드로어 내 "장바구니에 추가" CTA를 탭한 경우. **호스트가 해당 변형을 장바구니에 추가해야 합니다** |
| `onSelectSize` | *선택 사항* | `(event: { size }) => void` — 사용자가 결과 화면에서 사이즈를 선택한 경우. **호스트는 상품 페이지 (PDP) 사이즈 선택기와 동기화해야 합니다**           |
| `onMessage`    | *선택 사항* | 디버그 훅 — 드로어에서 파싱된 모든 인바운드 postMessage에 대해 발생합니다                                                            |
| `style`        | *선택 사항* | `StyleProp<ViewStyle>` — 컨테이너 스타일 재정의                                                                      |
| `webViewProps` | *선택 사항* | 고급 맞춤 설정을 위해 `react-native-webview`로 전달됩니다                                                                 |

**SDK가 자동으로 처리하는 항목 (별도 설정 필요 없음):**

* 드로어의 iframe 스타일 `window.parent.postMessage`를 React Native 브리지에 연결합니다
* 드로어가 `mid` / `uid`를 전달할 때 AsyncStorage에 유지합니다
* 드로어의 `getMid` / `getUid` / `getSizes` postMessage에 응답합니다
* 열릴 때 `Kleep.checkProduct`를 호출하여 플로우(clothing / lingerie / footwear / children)를 결정합니다 — CTA 호출과 동일한 5분 캐시를 사용하므로 이미 `checkProduct`를 호출했다면 추가 비용 없이 처리됩니다

### 메서드 3: `Kleep.track`

비동기 분석 이벤트 전송. 예외를 발생시키지 않습니다.

| 파라미터                 | 우선순위    | 설명                                      |
| -------------------- | ------- | --------------------------------------- |
| `eventName`          | *필수*    | 이벤트 이름                                  |
| `options.customerId` | *선택 사항* | CRM 식별자                                 |
| `options.parameters` | *선택 사항* | `Record<string, unknown>` — 임의의 이벤트 데이터 |

```tsx theme={null}
import { Kleep } from '@kleep/react-native';

await Kleep.track('product_viewed', {
  parameters: { productId: product.id },
});

await Kleep.track('product_added_to_cart', {
  customerId: user.id,
  parameters: {
    productId: product.id,
    variantId: selectedVariant.id,
    cart: cart.items,
  },
});

await Kleep.track('checkout_completed', {
  customerId: user.id,
  parameters: { orderId: order.id, cart: order.items },
});
```

3가지 이벤트를 추적합니다:

| eventName               | 트리거               |
| ----------------------- | ----------------- |
| `product_viewed`        | 상품 페이지 (PDP) 조회 시 |
| `product_added_to_cart` | 장바구니에 상품 추가 시     |
| `checkout_completed`    | 결제 후 주문 확인 시      |

**`product_viewed` 예시**

```jsx theme={null}
{
  productId: "123ABC456"
}
```

**`product_added_to_cart` 예시**

```jsx theme={null}
{
  productId: "123ABC456",
  variantId: "123ABC456-00R",
  cart: [
    {
      productId: "123ABC456",
      variantId: "123ABC456-00R",
      sku: "XYZ",
      size: "S",
      quantity: 2,
      price: { amount: "50", currencyCode: "EUR" }
    }
  ]
}
```

**`checkout_completed` 예시**

```jsx theme={null}
{
  orderId: "000001",
  cart: [
    {
      lineItemId: "000001#1",
      productId: "123ABC456",
      variantId: "123ABC456-00R",
      sku: "XYZ",
      size: "S",
      quantity: 2,
      price: { amount: "50", currencyCode: "EUR" }
    }
  ]
}
```

## 캐시 무효화

***

SDK는 두 개의 인메모리 캐시를 유지합니다 (각 TTL 5분):

* **상품 게이트** — `(publicId, productId) → { recommendable, category, productFound }`
* **추천 사이즈** — `(publicId, productId, mid) → size label`

두 캐시 모두 자동으로 만료됩니다. 강제 새로고침이 필요한 경우 (사용자 로그아웃, 리테일러 전환, 수동 새로고침 버튼):

```tsx theme={null}
Kleep.clearCheckProductCache();
```
