> ## 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 仓库：** [https://github.com/KlipFit/kleep-rn](https://github.com/KlipFit/kleep-rn)

这是一个围绕 `drawer.kleep.ai` 的轻量 WebView 封装，SDK 提供一个组件和两个方法，与 iOS 接口保持一致。

## 安装

***

每个版本都以 npm tarball 的形式发布在 `cdn.kleep.ai` 上，提供两种 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 需要它们，但允许您自行控制版本。

发布的 tarball 旁边附有 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` | 是  | 标识您零售商账号的 UUID（由 Kleep 提供）                                                                                             |
| `language` | 否  | `'fr' \| 'en' \| 'de' \| 'it' \| 'es' \| 'nl' \| 'pt' \| 'ja' \| 'ko' \| 'pl' \| 'br' \| 'dk' \| 'fi' \| 'se' \| 'gb'` |

## 使用方法

***

### 方法 1：`Kleep.checkProduct`

在商品详情页加载时调用。返回结果决定"找到我的尺码"CTA 是否显示及显示何种标签。

| parameter   | priority | description  |
| ----------- | -------- | ------------ |
| `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 将结果在内存中缓存 5 分钟，以 `(publicId, productId)` 为键缓存入口条件，以 `(publicId, productId, mid)` 为键缓存推荐尺码。重新渲染商品详情页或返回导航不会产生额外开销。

**实现示例**

```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           | priority | description                                                                     |
| -------------- | -------- | ------------------------------------------------------------------------------- |
| `visible`      | *必填*     | 控制 Modal 的显示状态                                                                  |
| `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`  | *可选*     | 原始 `Record<string, string>`，附加到抽屉 URL（应急逃生口）                                    |
| `onAddToCart`  | *可选*     | `(event: { variantId, size? }) => void`——用户点击抽屉内"加入购物车"CTA 时触发。**宿主需将该变体加入购物车** |
| `onSelectSize` | *可选*     | `(event: { size }) => void`——用户在结果页选择尺码时触发。**宿主应同步更新商品详情页的尺码选择器**               |
| `onMessage`    | *可选*     | 调试钩子——每次从抽屉解析到 postMessage 时触发                                                  |
| `style`        | *可选*     | `StyleProp<ViewStyle>`——容器样式覆盖                                                  |
| `webViewProps` | *可选*     | 转发给 `react-native-webview` 用于高级自定义                                              |

**SDK 自动连接（无需任何操作）：**

* 将抽屉 iframe 风格的 `window.parent.postMessage` 桥接至 React Native 桥
* 当抽屉推送 `mid` / `uid` 时，将其持久化至 AsyncStorage
* 响应来自抽屉的 `getMid` / `getUid` / `getSizes` postMessages
* 在打开时调用 `Kleep.checkProduct` 以确定流程类型（clothing / lingerie / footwear / children）——与您的 CTA 调用共享同一个 5 分钟缓存，若已调用过 `checkProduct` 则无额外开销

### 方法 3：`Kleep.track`

发送即忘的分析数据，永不抛出异常。

| parameter            | priority | description                       |
| -------------------- | -------- | --------------------------------- |
| `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`        | 查看商品详情页时     |
| `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 维护两个内存缓存（各 5 分钟 TTL）：

* **商品入口缓存** — `(publicId, productId) → { recommendable, category, productFound }`
* **推荐尺码缓存** — `(publicId, productId, mid) → 尺码标签`

两者均自动失效。如需强制刷新（用户已登出、切换零售商、手动刷新按钮）：

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