npm package: @getecho-ai/react-native-sdk
Description: Echo AI Chat SDK for React Native - AI-powered e-commerce assistant
Author: Echo AI
Latest version: 1.1.3

# Echo React Native SDK

AI-powered chat and e-commerce SDK for React Native apps. Add an intelligent shopping assistant to your app in minutes.

## Installation

```bash
npm install @getecho-ai/react-native-sdk react-native-webview @react-native-async-storage/async-storage
```

**Expo users:**

```bash
npx expo install @getecho-ai/react-native-sdk react-native-webview @react-native-async-storage/async-storage
```

## Quick Start (< 5 minutes)

The fastest way to get started - uses the built-in `useSimpleCart` hook for automatic cart management:

```tsx
import { EchoProvider, EchoChat, useSimpleCart } from '@getecho-ai/react-native-sdk';

function App() {
  const { cart, callbacks } = useSimpleCart();

  return (
    <EchoProvider config={{ apiKey: 'your-api-key', callbacks }}>
      <YourApp cart={cart} />
      <EchoChat />
    </EchoProvider>
  );
}
```

That's it! The AI assistant can now add products to cart and show cart contents.

## Production Setup (Custom Cart)

For production apps with existing cart logic:

```tsx
import { EchoProvider, EchoChat } from '@getecho-ai/react-native-sdk';

function App() {
  return (
    <EchoProvider
      config={{
        apiKey: 'your-api-key',
        callbacks: {
          // Required: Handle add to cart
          onAddToCart: async (product, quantity = 1) => {
            await yourCartService.add(product.id, quantity);
            return {
              success: true,
              cartItemCount: yourCartService.getItemCount()
            };
          },

          // Required: Return current cart
          onGetCart: async () => {
            const cart = await yourCartService.getCart();
            return {
              success: true,
              cart: {
                items: cart.items.map(item => ({
                  productId: item.id,
                  quantity: item.qty,
                  product: item.product,
                })),
                itemCount: cart.totalItems,
                total: cart.totalPrice,
                currency: 'TRY',
              }
            };
          },

          // Optional: Navigate to product detail
          onNavigateToProduct: (productId) => {
            navigation.navigate('ProductDetail', { id: productId });
          },

          // Optional: Navigate to checkout
          onNavigateToCheckout: () => {
            navigation.navigate('Checkout');
          },

          // Optional: Track order status
          onTrackOrderState: async ({ orderId }) => {
            const res = await fetch(`/api/orders/${orderId}`);
            const order = await res.json();
            return { success: res.ok, status: order.status, details: order };
          },

          // Optional: Get order history
          onGetOrders: async () => {
            const res = await fetch('/api/orders');
            const data = await res.json();
            return { success: res.ok, orders: data.orders };
          },
        },
      }}
    >
      <YourApp />
      <EchoChat />
    </EchoProvider>
  );
}
```

## Local Development

When developing locally with the Echo backend:

```tsx
<EchoProvider
  config={{
    apiKey: 'your-api-key',
    apiUrl: 'http://localhost:3000', // Auto-converts to 10.0.2.2 on Android emulator
    callbacks,
  }}
>
```

The SDK automatically handles Android emulator's localhost quirk (converts `localhost` to `10.0.2.2`).

For real devices, use your machine's local IP:

```tsx
apiUrl: 'http://192.168.1.100:3000'
```

## Components

### `<EchoProvider>`

Wrap your app with this provider. Required props:

| Prop | Type | Description |
|------|------|-------------|
| `config.apiKey` | `string` | Your Echo API key |
| `config.callbacks` | `EchoCallbacks` | Cart and navigation callbacks |

Optional props:

| Prop | Type | Description |
|------|------|-------------|
| `config.apiUrl` | `string` | Override API URL (default: production) |
| `config.userId` | `string` | Pre-set user ID |
| `config.userEmail` | `string` | User email for identification |
| `config.userIdentifier` | `string` | Custom user identifier |
| `config.theme` | `EchoTheme` | UI customization |
| `config.uiSettings` | `UISettings` | Control UI elements visibility |
| `config.onEvent` | `(events: EchoEvent[]) => void` | Callback fired when event batches flush |

### `<EchoChat>`

The main chat component. Supports both modal (overlay) and inline (embedded) modes.

```tsx
// Modal mode (default) - floating button + full-screen overlay
<EchoChat />
<EchoChat floating={false} />
<EchoChat position="bottom-left" />

// Inline mode - embedded directly in your layout
<EchoChat mode="inline" />
<EchoChat
  mode="inline"
  inlineProps={{
    showHeader: true,
    headerTitle: "Support Chat",
    onClose: () => closeChat()
  }}
/>
```

**Props:**

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `mode` | `"modal" \| "inline"` | `"modal"` | Display mode |
| `floating` | `boolean` | `true` | Show floating button (modal only) |
| `position` | `"bottom-right" \| "bottom-left"` | `"bottom-right"` | Button position |
| `inlineProps` | `EchoChatInlineProps` | - | Props for inline mode |

### `<EchoChatInline>`

Inline chat component for embedding in screens or tabs. Does not use a modal wrapper.

```tsx
import { useEffect } from 'react';
import { EchoChatInline, useEcho } from '@getecho-ai/react-native-sdk';

function ChatScreen() {
  const { openChat, closeChat } = useEcho();

  useEffect(() => {
    openChat();
    return closeChat;
  }, [openChat, closeChat]);

  return (
    <View style={{ flex: 1 }}>
      <EchoChatInline
        showHeader
        headerTitle="Support Chat"
        onClose={closeChat}
        style={{ backgroundColor: '#f5f5f5' }}
      />
    </View>
  );
}
```

**Props:**

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `showHeader` | `boolean` | `false` | Show header bar |
| `headerTitle` | `string` | `"AI Asistan"` | Header title text |
| `onClose` | `() => void` | - | Close button callback (shown when `showHeader=true`) |
| `style` | `ViewStyle` | - | Container style |

### `<EchoChatModal>`

Full-screen modal chat component. Reads visibility from `useEcho()` context. Use `openChat()` / `closeChat()` to control it.

```tsx
<EchoChatModal />
```

### `<EchoChatButton>`

Standalone chat button for custom layouts:

```tsx
// Basic usage
<EchoChatButton
  onPress={() => setModalVisible(true)}
  position="bottom-left"
/>

// Custom icon (emoji)
<EchoChatButton
  onPress={() => setModalVisible(true)}
  icon="🤖"
  iconSize={28}
  color="#FF5722"
/>

// Custom icon (Image)
<EchoChatButton
  onPress={() => setModalVisible(true)}
  icon={<Image source={require('./chat-icon.png')} style={{ width: 32, height: 32 }} />}
  color="#6366f1"
/>

// Custom icon (Vector Icon)
import Icon from 'react-native-vector-icons/MaterialIcons';

<EchoChatButton
  onPress={() => setModalVisible(true)}
  icon={<Icon name="chat" size={28} color="#fff" />}
  color="#1e88e5"
/>
```

**Props:**
- `onPress: () => void` - Button press handler
- `position?: "bottom-right" | "bottom-left"` - Button position (default: "bottom-right")
- `color?: string` - Background color (default: "#007AFF")
- `icon?: React.ReactNode | string` - Custom icon (emoji string or React component) (default: "💬")
- `iconSize?: number` - Icon size for text/emoji icons (default: 24)
- `unreadCount?: number` - Show unread message badge

## Hooks

### `useEcho()`

Access Echo context anywhere in your app:

```tsx
const {
  // State
  config,               // EchoConfig
  userId,               // string
  chatId,               // string
  isReady,              // boolean
  isChatOpen,           // boolean

  // Chat Control
  sendMessage,          // (message: string) => void
  searchProducts,       // (query: string) => void
  openChat,             // () => void
  closeChat,            // () => void

  // User Identity
  identify,             // (params: IdentifyParams) => Promise<IdentifyResult>
  logout,               // () => Promise<void>

  // Recommendations
  getRecommendations,   // (request: RecommendationRequest) => Promise<RecommendationResult>

  // Event Tracking
  trackPageView,        // (data: { url: string; title?: string }) => void
  viewedProduct,        // (data: { productId: string }) => void
  showRecommendation,   // (productId: string) => void
  clickRecommendation,  // (productId: string) => void
  trackAddToCart,       // (data: AddToCartEventData) => void
  trackPurchase,        // (data: PurchaseEventData) => void
  trackEvent,           // (eventType: string, eventData?: Record<string, unknown>) => void
} = useEcho();
```

### `useSimpleCart(options?)`

Built-in cart management for quick prototyping:

```tsx
const {
  cart,           // Current cart state
  setCart,        // Direct cart setter
  clearCart,      // Clear all items
  removeItem,     // Remove by productId
  updateQuantity, // Update item quantity
  callbacks,      // Ready-to-use EchoCallbacks
} = useSimpleCart({
  initialCart: { items: [], itemCount: 0 },
  onCartChange: (cart) => console.log('Cart updated:', cart),
  onNavigateToProduct: (id) => navigation.navigate('Product', { id }),
  onNavigateToCheckout: () => navigation.navigate('Checkout'),
  onAuthRequired: () => navigation.navigate('Login'),
  onTrackOrderState: async ({ orderId }) => {
    const res = await fetch(`/api/orders/${orderId}`);
    const order = await res.json();
    return { success: res.ok, status: order.status, details: order };
  },
  onGetOrders: async () => {
    const res = await fetch('/api/orders');
    const data = await res.json();
    return { success: res.ok, orders: data.orders };
  },
});
```

## Messaging

### `sendMessage(message)`

Send a message to the chat programmatically. Opens the chat if closed:

```tsx
const { sendMessage } = useEcho();
sendMessage('I need help finding a gift');
```

### `searchProducts(query)`

Trigger a product search in the chat. Opens the chat if closed:

```tsx
const { searchProducts } = useEcho();
searchProducts('red summer dress');
```

## Events & Analytics

The SDK automatically tracks user interactions and provides methods for custom event tracking. Events are GA4-compatible and batched for performance.

### Automatic Events

| Event | Trigger |
|-------|---------|
| `widget_open` | Chat opened |
| `widget_close` | Chat closed |
| `new_chat` | New chat session started |
| `product_view` | User taps a product in chat |
| `product_click` | User taps to visit a product URL |

### Manual Tracking

```tsx
const {
  trackPageView,
  viewedProduct,
  showRecommendation,
  clickRecommendation,
  trackAddToCart,
  trackPurchase,
  trackEvent,
} = useEcho();

// Track screen views
trackPageView({ url: 'app://products/123', title: 'Product Detail' });

// Track product views
viewedProduct({ productId: 'SKU_123' });

// Track recommendations
showRecommendation('SKU_456');
clickRecommendation('SKU_456');

// GA4-compatible add to cart
trackAddToCart({
  currency: 'USD',
  value: 114.00,
  items: [{ item_id: 'SKU_123', price: 114.00, item_name: 'Product Name', quantity: 1 }]
});

// GA4-compatible purchase
trackPurchase({
  transaction_id: 'T_12345',
  value: 129.99,
  currency: 'USD',
  items: [{ item_id: 'SKU_123', price: 114.00 }]
});

// Custom events
trackEvent('share_product', { productId: 'SKU_123', method: 'whatsapp' });
```

### Listening to Events

Forward events to your analytics provider:

```tsx
<EchoProvider
  config={{
    apiKey: 'your-api-key',
    callbacks,
    onEvent: (events) => {
      events.forEach(event => {
        analytics.logEvent(event.eventType, event.eventData);
      });
    }
  }}
>
```

Events are batched: flushed after 5s of inactivity, when 10 events accumulate, or when the app backgrounds.

## Recommendations

Fetch AI-powered product recommendations:

```tsx
const { getRecommendations } = useEcho();

const result = await getRecommendations({
  productIds: ['SKU_123'],
  strategy: 'similar',  // 'similar' | 'complementary' | 'bought_together' | 'cart'
  limit: 8,
});

result.recommendations.forEach(product => {
  console.log(product.title, product.primaryImage, product.priceAmount);
});
```

## UI Settings

Control visibility of UI elements:

```tsx
<EchoProvider
  config={{
    apiKey: 'your-key',
    callbacks,
    uiSettings: {
      showSidebar: false,       // Hide conversation history sidebar
      showExpandButton: false,  // Hide expand/collapse button
      showCartButton: true,     // Show cart button in header
      showHistoryButton: false, // Hide history/new chat button
      showCloseButton: true,    // Show close button
    },
  }}
>
```

## User Authentication

Echo SDK supports three user states:

1. **Anonymous** - Default state. A random UUID is generated and persisted automatically.
2. **Identified** - User is linked to an email or identifier. Chat history migrates from anonymous to identified user.
3. **Logged out** - User is reset to a new anonymous state with a fresh UUID.

### Identifying Users

**Option 1: Via config (automatic)**

Pass `userEmail` or `userIdentifier` in the provider config. The SDK auto-identifies on mount:

```tsx
<EchoProvider
  config={{
    apiKey: 'your-key',
    callbacks,
    userEmail: user.email,
    userIdentifier: user.id,
  }}
>
```

**Option 2: Via hook (imperative)**

Call `identify()` after login for full control:

```tsx
const { identify } = useEcho();

const result = await identify({
  email: 'user@example.com',
  userIdentifier: 'user-123',
  firstName: 'John',
  lastName: 'Doe',
  phone: '+905551234567',
  traits: { plan: 'premium' },
});
// result: { success: true, userId: '...', userIdChanged: true }
```

### Logging Out

Call `logout()` to reset to anonymous state. This generates a new anonymous UUID and clears chat history:

```tsx
const { logout } = useEcho();

await logout();
```

## Utilities

### `resolveApiUrl(url?)`

Platform-aware URL resolution for development:

```tsx
import { resolveApiUrl } from '@getecho-ai/react-native-sdk';

const url = resolveApiUrl('http://localhost:3000');
// Android emulator: 'http://10.0.2.2:3000'
// iOS simulator: 'http://localhost:3000'
// Production URL: unchanged
```

### `getLocalhostUrl(port?)`

Get the correct localhost URL for current platform:

```tsx
import { getLocalhostUrl } from '@getecho-ai/react-native-sdk';

const url = getLocalhostUrl(3000);
// Android: 'http://10.0.2.2:3000'
// iOS: 'http://localhost:3000'
```

## Troubleshooting

### "Network request failed" on Android emulator

The SDK should auto-convert localhost URLs. If issues persist:

```tsx
// Explicitly use 10.0.2.2
apiUrl: 'http://10.0.2.2:3000'
```

### WebView not loading

Ensure peer dependencies are installed:

```bash
npm ls react-native-webview @react-native-async-storage/async-storage
```

For Expo, run `npx expo install` to get compatible versions.

### Cart not updating

1. Verify `onAddToCart` returns `{ success: true, cartItemCount: N }`
2. Check `onGetCart` returns valid cart structure
3. Add console.log in callbacks to debug

### TypeScript errors

Import types explicitly:

```tsx
import type { Product, Cart, EchoCallbacks } from '@getecho-ai/react-native-sdk';
```

## API Reference

### Types

```tsx
type Product = {
  id: string;
  title: string;
  description?: string;
  priceAmount?: number;
  currency?: string;
  primaryImage?: string;
  images?: string[];
  url?: string;
  category?: string;
  brand?: string;
  inStock?: boolean;
};

type Cart = {
  items: CartItem[];
  total?: number;
  currency?: string;
  itemCount: number;
};

type CartItem = {
  productId: string;
  quantity: number;
  product?: Product;
};

type AddToCartResult = {
  success: boolean;
  cartItemCount?: number;
  error?: string;
};

type GetCartResult = {
  success: boolean;
  cart?: Cart;
  error?: string;
};

type TrackOrderStateResult = {
  success: boolean;
  status?: string;
  details?: any;
  error?: string;
};

type GetOrdersResult = {
  success: boolean;
  orders?: any[];
  error?: string;
};

type EchoCallbacks = {
  onAddToCart: (product: Product, quantity?: number) => Promise<AddToCartResult>;
  onGetCart: () => Promise<GetCartResult>;
  onNavigateToProduct?: (productId: string) => void;
  onNavigateToUrl?: (url: string) => void;
  onNavigateToCheckout?: () => void;
  onAuthRequired?: () => void;
  onTrackOrderState?: (data: { orderId: string | number }) => Promise<TrackOrderStateResult>;
  onGetOrders?: () => Promise<GetOrdersResult>;
};

type UISettings = {
  showSidebar?: boolean;
  showExpandButton?: boolean;
  showCartButton?: boolean;
  showHistoryButton?: boolean;
  showCloseButton?: boolean;
};

type GA4Item = {
  item_id: string;
  price: number;
  item_name?: string;
  quantity?: number;
  item_brand?: string;
  item_category?: string;
  item_variant?: string;
};

type AddToCartEventData = {
  items: GA4Item[];
  currency?: string;
  value?: number;
};

type PurchaseEventData = {
  transaction_id: string;
  items: GA4Item[];
  value?: number;
  currency?: string;
  tax?: number;
  shipping?: number;
  coupon?: string;
  affiliation?: string;
};

type EchoEvent = {
  chatId: string | null;
  eventType: string;
  eventData: Record<string, unknown>;
  timestamp: string;
  userEmail?: string;
  userIdentifier?: string;
};

type IdentifyParams = {
  email?: string;
  userIdentifier?: string;
  firstName?: string;
  lastName?: string;
  phone?: string;
  traits?: Record<string, unknown>;
};

type IdentifyResult = {
  success: boolean;
  userId: string;
  email?: string;
  userIdentifier?: string;
  userIdChanged: boolean;
};

type RecommendationRequest = {
  productIds: string[];
  strategy: 'similar' | 'complementary' | 'bought_together' | 'cart';
  limit?: number;
  threshold?: number;
  minCount?: number;
  weights?: {
    semantic?: number;
    color?: number;
    price?: number;
    category?: number;
  };
};

type RecommendationResult = {
  success: boolean;
  strategy: string;
  recommendations: RecommendedProduct[];
  count: number;
  message?: string;
  error?: string;
};

type RecommendedProduct = {
  id: string;
  productId: string;
  title: string;
  brand?: string;
  url?: string;
  images: string[];
  primaryImage?: string | null;
  categoryBreadcrumb?: string[];
  priceAmount?: string;
  similarity?: number;
  embeddingSimilarity?: number;
  colorSimilarity?: number;
  summary?: string | null;
};
```

## Example App

See the `/example` directory for a complete Expo demo app.

## License

MIT