npm package: @nzmedet/voice-sdk
Description: Production-grade WebRTC voice calling SDK for React Native with Firebase signaling, CallKeep UI, VoIP push, metered billing, and auto-reconnect.
Latest version: 1.0.20

# voice-sdk

Production-grade WebRTC voice calling SDK for React Native with Firebase signaling, CallKeep UI, VoIP push, metered billing, and auto-reconnect.

## ⚠️ Breaking Change: Context Provider Required

**As of v1.0.6+, the SDK uses React Context instead of global variables.** You must wrap your app with `VoiceSDKProvider` after initialization. See the [Setup](#setup) section below for details.

## Installation

```bash
npm install @nzmedet/voice-sdk
```

### Peer Dependencies

This package requires the following peer dependencies:

```bash
npm install react react-native @react-native-firebase/app @react-native-firebase/firestore react-native-webrtc react-native-callkeep react-native-pushkit
```

## Setup

### 1. Initialize the SDK

```typescript
import { VoiceSDK, VoiceSDKProvider } from 'voice-sdk';
// Initialize @react-native-firebase/app first
// initializeApp();

// Initialize the SDK
await VoiceSDK.init({
  appName: 'MyApp',
  turnServers: [
    { urls: 'stun:stun.l.google.com:19302' },
    {
      urls: 'turn:your-turn-server.com:3478',
      username: 'your-username',
      credential: 'your-password',
    },
  ],
  callbacks: {
    // Optional: Store push tokens in your database
    onTokenUpdate: async (platform, token) => {
      // Store token in your backend
    },
    // Optional: Client-side call event handlers
    onCallStarted: async (callId, callerId, calleeId) => {
      // Client-side logic when call starts
    },
    onCallStateChanged: async (callId, state) => {
      // Client-side logic when call state changes
    },
    onCallEnded: async (callId, startTime, endTime) => {
      // Client-side logic when call ends
      // Note: For server-side billing, implement Cloud Functions
    },
  },
});
```

### 2. Wrap Your App with VoiceSDKProvider

**Important:** The SDK uses React Context to provide functionality to hooks. You must wrap your app with `VoiceSDKProvider` after initialization:

```typescript
import React from 'react';
import { VoiceSDK, VoiceSDKProvider } from 'voice-sdk';

function App() {
  const [initialized, setInitialized] = React.useState(false);

  React.useEffect(() => {
    VoiceSDK.init({
      appName: 'MyApp',
      turnServers: [/* ... */],
    })
      .then(() => {
        setInitialized(true);
        VoiceSDK.enableDebugMode(); // Optional
      })
      .catch((error) => {
        console.error('Failed to initialize VoiceSDK:', error);
      });
  }, []);

  if (!initialized) {
    return <LoadingScreen />;
  }

  // Wrap your app with VoiceSDKProvider
  return (
    <VoiceSDKProvider value={VoiceSDK.getContextValue()}>
      <YourAppContent />
    </VoiceSDKProvider>
  );
}
```

**Why?** The SDK uses React Context instead of global variables for better React patterns, type safety, and testability. The `useCall()` and `useIncomingCall()` hooks require this provider to function.

### 3. Enable Debug Mode (Optional)

```typescript
VoiceSDK.enableDebugMode();
```

### 4. Token Management

The SDK automatically handles VoIP push token registration (iOS) and FCM token registration (Android). Tokens are automatically received and passed to your `onTokenUpdate` callback, where you should store them in your database.

## Usage

### Starting a Call

**Note:** Components using hooks must be within a `VoiceSDKProvider`.

```typescript
import { useCall } from 'voice-sdk';

function MyComponent() {
  // This component must be rendered within <VoiceSDKProvider>
  const { startCall, callState, isConnected, endCall } = useCall();

  const handleCall = async () => {
    try {
      await startCall('callee-user-id');
    } catch (error) {
      console.error('Failed to start call:', error);
    }
  };

  return (
    <>
      <Button title="Call" onPress={handleCall} />
      <Button title="End Call" onPress={endCall} />
      <Text>Status: {callState}</Text>
      <Text>Connected: {isConnected ? 'Yes' : 'No'}</Text>
    </>
  );
}
```

### Handling Incoming Calls

**Note:** Components using hooks must be within a `VoiceSDKProvider`.

```typescript
import { useIncomingCall } from 'voice-sdk';

function IncomingCallComponent() {
  // This component must be rendered within <VoiceSDKProvider>
  const { incomingCall, answer, decline, getCallMetadata } = useIncomingCall();

  if (incomingCall) {
    // Access participant info
    const caller = incomingCall.caller;
    const callee = incomingCall.callee;
    
    // Or get full metadata including any backend-provided fields
    const metadata = getCallMetadata(incomingCall.callId);
    
    return (
      <View>
        <Text>{caller.displayName} is calling...</Text>
        {caller.photoURL && <Image source={{ uri: caller.photoURL }} />}
        <Button title="Answer" onPress={answer} />
        <Button title="Decline" onPress={decline} />
      </View>
    );
  }

  return null;
}
```

### Using UI Components

```typescript
import { IncomingCallScreen, ActiveCallScreen } from 'voice-sdk';

// In your navigation
<Stack.Screen
  name="IncomingCall"
  component={IncomingCallScreen}
/>

<Stack.Screen
  name="ActiveCall"
  component={ActiveCallScreen}
/>
```

## Firebase Setup

### Firestore Rules

Deploy the included Firestore rules:

```bash
firebase deploy --only firestore:rules
```

### Cloud Functions

**Important:** This SDK does NOT include complete Cloud Functions. You must implement your own Cloud Functions to handle:

- **Call Initiation**: Creating call records, sending push notifications, validation
- **Call Termination**: Processing ended calls, calculating duration
- **Billing**: Calculating costs, updating user balances, processing payments (Stripe, etc.)
- **Cleanup**: Removing stale calls, handling abandoned calls

The SDK provides reusable helper functions that you can import into your own Cloud Functions. See `firebase/functions/README.md` for documentation and usage examples.

**You are responsible for:**
- Implementing your own Cloud Functions based on your requirements
- Setting up your billing logic (Stripe, in-app purchases, subscription models, etc.)
- Defining your database schema and collection structure
- Implementing authentication, authorization, and security rules
- Customizing push notification delivery

Copy the helper utilities to your Firebase Functions project:
```bash
mkdir -p your-firebase-project/functions/utils
cp firebase/functions/utils/callHelpers.ts your-firebase-project/functions/utils/
```

## Push Notification Payload Format

When sending push notifications to trigger incoming calls, use this structure:

### iOS (VoIP Push)

```json
{
  "callId": "call-uuid-123",
  "caller": {
    "id": "user-123",
    "displayName": "John Doe",
    "photoURL": "https://example.com/photo.jpg"
    // Backend can add additional fields here
  },
  "callee": {
    "id": "user-456",
    "displayName": "Jane Smith",
    "photoURL": "https://example.com/jane.jpg"
  },
  // Optional: additional metadata
  "metadata": {
    "customField": "value"
  }
}
```

### Android (FCM)

The `caller` and `callee` objects can be sent as:
- Direct JSON objects in the data payload
- JSON strings that will be parsed

```json
{
  "callId": "call-uuid-123",
  "caller": "{\"id\":\"user-123\",\"displayName\":\"John Doe\",\"photoURL\":\"https://example.com/photo.jpg\"}",
  "callee": "{\"id\":\"user-456\",\"displayName\":\"Jane Smith\"}"
}
```

**Important:** The SDK expects exactly `id`, `displayName`, and `photoURL` (optional) fields in `CallParticipant` objects. No backward compatibility for flattened fields like `callerId`, `callerName`, etc.

## Features

- ✅ WebRTC voice calling with auto-reconnect
- ✅ Firebase Cloud Firestore signaling
- ✅ CallKeep integration for native call UI
- ✅ VoIP push notifications (iOS)
- ✅ FCM push notifications (Android)
- ✅ Call metadata storage (no backend refetch needed)
- ✅ Billing helpers (you implement your own billing logic)
- ✅ TypeScript support
- ✅ React hooks API

## License

MIT