> ## 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.

# Mobile SDK - React Native

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

A thin WebView wrapper around `drawer.kleep.ai`. The SDK exposes one component + two methods, mirroring the iOS surface.

## Installation

***

Each release is published as an npm tarball on `cdn.kleep.ai`. Two URL shapes are available:

**Latest stable** (auto-updates with every new stable release — pre-releases never move this pointer):

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

**Pin a specific version** (recommended for production — fully immutable):

```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
```

Replace `v1.0.0` and `1.0.0` with the desired release tag. The two listed packages are peer dependencies — the SDK requires them but lets you control the version.

The published tarball includes a SHA-256 alongside (`<tarball>.sha256` / `latest.tgz.sha256`) if you want to verify integrity before installing.

### Expo

Both peer deps are pre-bundled in Expo Go (SDK 54+). No extra setup needed in dev. For production builds, `expo prebuild` picks them up automatically.

### Bare React Native

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

## Permissions

***

For the footwear flow (camera scan), add to `ios/<App>/Info.plist`:

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

And to `android/app/src/main/AndroidManifest.xml`:

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

## Configure once at app boot

***

```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
});
```

| Field      | Required | Description                                                                                                            |
| ---------- | -------- | ---------------------------------------------------------------------------------------------------------------------- |
| `publicId` | yes      | UUID identifying your retailer (provided by Kleep)                                                                     |
| `language` | no       | `'fr' \| 'en' \| 'de' \| 'it' \| 'es' \| 'nl' \| 'pt' \| 'ja' \| 'ko' \| 'pl' \| 'br' \| 'dk' \| 'fi' \| 'se' \| 'gb'` |

## Usage

***

### Method 1: `Kleep.checkProduct`

Call this on PDP mount. The result drives the "Find my size" CTA — whether to render it, and what label to show.

| parameter   | priority   | description                     |
| ----------- | ---------- | ------------------------------- |
| `productId` | *required* | Your product ID at the retailer |

**Returns**

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

**Logic schema**

| `recommendable` | `recommendedSize` | What to render                                    |
| --------------- | ----------------- | ------------------------------------------------- |
| `false`         | —                 | **Hide the CTA** (product not eligible for Kleep) |
| `true`          | absent            | CTA: **"Trouver ma taille"**                      |
| `true`          | `"M"`             | CTA: **"Taille recommandée: M"**                  |

The SDK caches the result for 5 min in memory, keyed on `(publicId, productId)` for the gate and `(publicId, productId, mid)` for the recommended size. Re-rendering the PDP or navigating back is free.

**Implementation example**

```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)}
      />
    </>
  );
}
```

### Method 2: `<KleepFindSizeView>`

Mount this component to open the size-finder drawer in a fullscreen Modal+WebView. Controlled-component pattern: you own the `visible` state, the SDK requests close via `onDismiss`.

| prop           | priority   | description                                                                                                                    |
| -------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `visible`      | *required* | Controls Modal visibility                                                                                                      |
| `productId`    | *required* | Same as in `checkProduct`                                                                                                      |
| `onDismiss`    | *required* | Fired when the user closes the drawer (X / swipe / back). Host MUST flip `visible` to `false`                                  |
| `variantId`    | *optional* | Pre-selects a variant for the recommendation                                                                                   |
| `customerId`   | *optional* | CRM identifier for cross-session linking                                                                                       |
| `language`     | *optional* | Overrides the SDK-level language for this open                                                                                 |
| `countryCode`  | *optional* | e.g. `"FR"`, `"US"` — drives unit system + bra-size defaults                                                                   |
| `stocks`       | *optional* | `{ [variantId]: number \| boolean }` — drawer renders unavailable / partial-stock UI                                           |
| `mock`         | *optional* | `true` → drawer skips real recommendation API calls (QA only)                                                                  |
| `forceState`   | *optional* | `'outOfRange' \| 'unavailable' \| 'error' \| 'qrcode'` — QA hatch to render an end-state directly                              |
| `warmRestore`  | *optional* | `{ mid, uid }` — pre-load an existing measurement (skip intro flow). Same-device restore is already automatic via AsyncStorage |
| `extraParams`  | *optional* | Raw `Record<string, string>` appended to the drawer URL (escape hatch)                                                         |
| `onAddToCart`  | *optional* | `(event: { variantId, size? }) => void` — user tapped the in-drawer "Add to cart" CTA. **Host adds the variant to its cart**   |
| `onSelectSize` | *optional* | `(event: { size }) => void` — user picked a size on the Result screen. **Host should sync its PDP size picker**                |
| `onMessage`    | *optional* | Debug hook — fires for every inbound postMessage parsed from the drawer                                                        |
| `style`        | *optional* | `StyleProp<ViewStyle>` — container style override                                                                              |
| `webViewProps` | *optional* | Forwarded to `react-native-webview` for advanced customisation                                                                 |

**Auto-wired by the SDK (you don't need to do anything):**

* Bridges the drawer's iframe-style `window.parent.postMessage` to the React Native bridge
* Persists `mid` / `uid` to AsyncStorage when the drawer pushes them
* Responds to `getMid` / `getUid` / `getSizes` postMessages from the drawer
* Calls `Kleep.checkProduct` on open to resolve the flow (clothing / lingerie / footwear / children) — uses the same 5-min cache as your CTA call, so it's free if you've already invoked `checkProduct`

### Method 3: `Kleep.track`

Fire-and-forget analytics. Never throws.

| parameter            | priority   | description                                      |
| -------------------- | ---------- | ------------------------------------------------ |
| `eventName`          | *required* | Name of the event                                |
| `options.customerId` | *optional* | CRM identifier                                   |
| `options.parameters` | *optional* | `Record<string, unknown>` — arbitrary event data |

```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 },
});
```

We want to track 3 events:

| eventName               | Trigger                                   |
| ----------------------- | ----------------------------------------- |
| `product_viewed`        | Upon PDP viewed                           |
| `product_added_to_cart` | Upon product added to the cart            |
| `checkout_completed`    | Upon order confirmation after the payment |

**`product_viewed` example**

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

**`product_added_to_cart` example**

```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` example**

```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" }
    }
  ]
}
```

## Cache invalidation

***

The SDK keeps two in-memory caches (5 min TTL each):

* **Product gate** — `(publicId, productId) → { recommendable, category, productFound }`
* **Recommended size** — `(publicId, productId, mid) → size label`

Both invalidate automatically. If you need to force-refresh (user logged out, retailer switched, manual refresh button):

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