npm package: @rematter/pylon-react-native
Description: Pylon Chat SDK for React Native
Author: Pylon
Latest version: 0.1.11

# Pylon Chat SDK for React Native

Add Pylon's chat widget to your React Native application to enable in-app customer support.

## Requirements

- React Native >= 0.70.0
- iOS >= 13.0
- Android minSdkVersion >= 24

**Note:** This package uses native modules. For Expo projects, you'll need a [development build](https://docs.expo.dev/develop/development-builds/introduction/).

---

## Installation

This SDK is distributed as source code and installed from a local directory.

### Step 1: Clone the Repository

Clone the SDK repository anywhere on your machine, but preferably forked into your own repository:

```bash
git clone https://github.com/pylon-labs/sdk-mobile.git
```

### Step 2: Install the Package

From your React Native project root, install from the local path:

```bash
npm install /path/to/sdk-mobile/react-native
# or
yarn add file:/path/to/sdk-mobile/react-native
```

**What happens automatically:**

- npm runs the `prepare` script (compiles TypeScript, copies native code)
- Package is installed to `node_modules/@pylon/react-native-chat/`

### Step 3: Link Native Code

The SDK includes native Swift (iOS) and Kotlin (Android) code that needs to be linked to your app.

**For Expo projects:**

Expo apps don't have native folders by default. Generate them:

```bash
npx expo install expo-dev-client
npx expo prebuild
```

This creates `ios/` and `android/` folders with the SDK's native code linked.

**For bare React Native projects:**

iOS requires CocoaPods linking:

```bash
cd ios && pod install && cd ..
```

Android auto-links automatically (no action needed).

### Step 4: Get Your Pylon App ID

1. Login to [app.usepylon.com](https://app.usepylon.com)
2. Go to Settings → Chat Widget
3. Copy your App ID

---

## Quick Start

```tsx
import { PylonChatView, type PylonChatViewRef } from "@pylon/react-native-chat";
import { useRef } from "react";
import { StyleSheet, View } from "react-native";

export default function App() {
  const pylonRef = useRef<PylonChatViewRef>(null);

  const config = {
    appId: "YOUR_APP_ID", // Get from app.usepylon.com/settings/chat-widget
    widgetBaseUrl: "https://widget.usepylon.com",
  };

  const user = {
    email: "user@example.com",
    name: "User Name",
  };

  return (
    <View style={styles.container}>
      {/* Your app content */}
      <Text>My App Content</Text>

      {/* Pylon Chat Widget - renders as overlay */}
      <PylonChatView
        ref={pylonRef}
        config={config}
        user={user}
        style={styles.chat}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  chat: {
    position: "absolute",
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
  },
});
```

**Identity Verification (Strongly Recommended):**

We strongly recommend setting up [Identity Verification](https://docs.usepylon.com/pylon-docs/chat-widget/identity-verification). The `emailHash` needs to be generated by the server responsible for authorizing your mobile app.

```tsx
const user = {
  email: "user@example.com",
  name: "User Name",
  emailHash: "sha256_hash_generated_by_server", // Add this for identity verification
};
```

---

## API Reference

### PylonChatView Props

#### `config` (required)

```tsx
{
  appId: string;           // Your Pylon app ID (required)
  widgetBaseUrl?: string;  // Default: "https://widget.usepylon.com"
  enableLogging?: boolean; // Enable debug logs (default: false)
  debugMode?: boolean;     // Show debug overlay (default: false)
}
```

#### `user` (required)

```tsx
{
  email: string;               // User's email (required)
  name: string;                // User's name (required)
  avatarUrl?: string;          // User's avatar URL
  emailHash?: string;          // SHA-256 hash for identity verification
  accountId?: string;          // Your internal account ID
  accountExternalId?: string;  // External system account ID
}
```

#### `listener` (optional)

Event callbacks for chat interactions:

```tsx
{
  onPylonLoaded?: () => void;
  onPylonInitialized?: () => void;
  onPylonReady?: () => void;
  onChatOpened?: () => void;
  onChatClosed?: (wasOpen: boolean) => void;
  onUnreadCountChanged?: (count: number) => void;
  onMessageReceived?: (message: string) => void;
  onPylonError?: (error: string) => void;
}
```

#### `style` (optional)

Standard React Native `ViewStyle`. Typically used to position the widget:

```tsx
style={{
  position: "absolute",
  top: 0,
  left: 0,
  right: 0,
  bottom: 0,
}}
```

---

### Imperative Methods (via ref)

```tsx
const pylonRef = useRef<PylonChatViewRef>(null);

// Control chat window
pylonRef.current?.openChat();
pylonRef.current?.closeChat();

// Control chat bubble
pylonRef.current?.showChatBubble();
pylonRef.current?.hideChatBubble();

// Send messages programmatically
pylonRef.current?.showNewMessage("Hello from the app!", false);
pylonRef.current?.showNewMessage("<p><strong>HTML</strong> message</p>", true);

// Set custom fields for new issues
pylonRef.current?.setNewIssueCustomFields({
  source: "mobile-app",
  version: "1.2.3",
  platform: Platform.OS,
});

// Show specific forms
pylonRef.current?.showTicketForm("form-id");
pylonRef.current?.showKnowledgeBaseArticle("article-id");

// Pre-fill ticket form fields
pylonRef.current?.setTicketFormFields({
  subject: "Issue from mobile app",
  description: "User reported...",
});

// Identity verification
pylonRef.current?.updateEmailHash("sha256_hash");
```

---

## Usage Patterns

### Basic Setup with Event Listeners

```tsx
export default function App() {
  const pylonRef = useRef<PylonChatViewRef>(null);
  const [unreadCount, setUnreadCount] = useState(0);

  const config = {
    appId: "YOUR_APP_ID",
  };

  const user = {
    email: "user@example.com",
    name: "User Name",
  };

  const listener = {
    onChatOpened: () => console.log("Chat opened"),
    onChatClosed: (wasOpen) => console.log("Chat closed", wasOpen),
    onUnreadCountChanged: (count) => setUnreadCount(count),
    onMessageReceived: (msg) => console.log("New message:", msg),
    onPylonError: (error) => console.error("Pylon error:", error),
  };

  return (
    <View style={{ flex: 1 }}>
      <MyAppContent />

      {unreadCount > 0 && <Badge count={unreadCount} />}

      <PylonChatView
        ref={pylonRef}
        config={config}
        user={user}
        listener={listener}
        style={StyleSheet.absoluteFill}
      />
    </View>
  );
}
```

### With Safe Area Insets

```tsx
import { useSafeAreaInsets } from "react-native-safe-area-context";

export default function App() {
  const insets = useSafeAreaInsets();

  return (
    <View style={{ flex: 1 }}>
      <MyAppContent />

      <PylonChatView
        config={config}
        user={user}
        style={{
          position: "absolute",
          top: 0,
          left: 0,
          right: 0,
          bottom: 0,
          paddingTop: insets.top,
          paddingBottom: insets.bottom,
        }}
      />
    </View>
  );
}
```

### Conditional Rendering (User Login)

```tsx
export default function App() {
  const [currentUser, setCurrentUser] = useState(null);

  return (
    <View style={{ flex: 1 }}>
      <MyAppContent />

      {currentUser && (
        <PylonChatView
          config={{ appId: "YOUR_APP_ID" }}
          user={{
            email: currentUser.email,
            name: currentUser.name,
          }}
          style={StyleSheet.absoluteFill}
        />
      )}
    </View>
  );
}
```

### Custom Fields and Metadata

```tsx
export default function App() {
  const pylonRef = useRef<PylonChatViewRef>(null);

  useEffect(() => {
    // Set custom fields when component mounts
    pylonRef.current?.setNewIssueCustomFields({
      app_version: "1.2.3",
      platform: Platform.OS,
      device_model: DeviceInfo.getModel(),
      user_tier: "premium",
    });
  }, []);

  return (
    <View style={{ flex: 1 }}>
      <MyAppContent />
      <PylonChatView
        ref={pylonRef}
        config={config}
        user={user}
        style={StyleSheet.absoluteFill}
      />
    </View>
  );
}
```

---

## Known Limitations

### Android: Clickable Elements in Chat Widget

**Issue:** On Android React Native specifically, clickable elements associated with an unopened chat widget (such as surveys) besides the chat bubble do not currently work. The chat bubble itself functions correctly for opening and closing the widget.

**Workaround:** This is a known platform limitation related to touch event handling between React Native and Android.

**Need this functionality?** Please contact the Pylon team if your application requires interactive elements like surveys in the Android React Native chat widget. We'd love to hear about your use case and prioritize a smarter React Native Android touch handler.

---

## Troubleshooting

### Android: Java Version Issues

**Error:** `Unsupported class file major version 69`

**Solution:** Install Java 17:

```bash
brew install openjdk@17
export JAVA_HOME=$(/usr/libexec/java_home -v 17)
```

### iOS: CocoaPods Encoding Issues

**Error:** `Unicode Normalization not appropriate for ASCII-8BIT`

**Solution:**

```bash
export LANG=en_US.UTF-8
cd ios && pod install
```

### Expo: Development Build Required

**Error:** `No development build installed`

**Solution:** This package uses native modules and cannot run in Expo Go:

```bash
npx expo install expo-dev-client
npx expo prebuild
npx expo run:ios  # or expo run:android
```

### Module Not Found

**Error:** `Invariant Violation: View config not found for component RNPylonChatView`

**Solution:** Clean and rebuild:

```bash
# Expo
rm -rf ios android .expo
npx expo prebuild --clean
npx expo run:ios

# Bare React Native
cd ios && pod install && cd ..
npx react-native run-ios
```

### Touch Events Not Working

If touches aren't passing through to views behind the chat bubble:

1. Use `position: "absolute"` and cover the full screen
2. Don't wrap `PylonChatView` in additional Views that intercept touches
3. The native SDK handles touch pass-through automatically

```tsx
// ✅ Correct
<View style={{ flex: 1 }}>
  <ScrollView>{/* Your content */}</ScrollView>
  <PylonChatView style={StyleSheet.absoluteFill} {...props} />
</View>

// ❌ Wrong - extra wrapper intercepts touches
<View style={{ flex: 1 }}>
  <ScrollView>{/* Your content */}</ScrollView>
  <View style={StyleSheet.absoluteFill}>
    <PylonChatView style={{ flex: 1 }} {...props} />
  </View>
</View>
```

---

## Architecture

This package wraps the native iOS and Android Pylon Chat SDKs using React Native's native module system:

```
┌─────────────────────────────────────────────────────────────┐
│ React Native (JavaScript/TypeScript)                        │
│ <PylonChatView />                                           │
└───────────────────────┬─────────────────────────────────────┘
                        │ Native Bridge
┌───────────────────────▼─────────────────────────────────────┐
│ Native Module (iOS: Swift, Android: Kotlin)                 │
│ - RNPylonChatView.swift / RNPylonChatView.kt               │
│ - Props → Native config conversion                          │
│ - Event forwarding to JS                                    │
└───────────────────────┬─────────────────────────────────────┘
                        │
┌───────────────────────▼─────────────────────────────────────┐
│ Native SDK (PylonChat)                                      │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ WebView (Pylon Chat Widget)                             │ │
│ │ - Loads from widget.usepylon.com                        │ │
│ │ - JavaScript ↔ Native bridge                            │ │
│ │ - Touch pass-through when collapsed                     │ │
│ │ - Real-time messaging                                   │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```

The widget is WebView-based, providing consistent functionality with the web SDK while native code handles platform-specific integration.

---

## Demo App

See [`demo-app/README.md`](./demo-app/README.md) for a complete example application demonstrating all SDK features.

---

## Support

For issues or questions, please reach out to the Pylon team.