npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

react-native-webrtc-call

v1.2.0

Published

A complete WebRTC calling solution for React Native with UI components, Context API integration, and signaling support

Downloads

2,370

Readme

react-native-webrtc-call

A complete WebRTC calling solution for React Native with UI components, Context API integration, and signaling support. This package provides everything you need to implement audio and video calling in your React Native application.

Features

  • P2P & Group Calling - Support for both one-on-one and group calls (default: P2P)
  • Audio & Video - Full support for both audio and video calls
  • Complete WebRTC Implementation - Full peer-to-peer audio/video calling
  • Context API Integration - Built-in React Context for state management
  • Signaling Support - Socket.io-based signaling server integration
  • ICE Server Management - Automatic TURN/STUN server configuration
  • Call Controls - Mute, video toggle, speaker control
  • Call UI Components - Ready-to-use UI components for calls
  • TypeScript Support - Full TypeScript definitions
  • Cross-platform - Works on both iOS and Android

Installation

npm install react-native-webrtc-call
# or
yarn add react-native-webrtc-call

Peer Dependencies

Make sure you have these peer dependencies installed:

npm install react-native-webrtc react-native-incall-manager socket.io-client react-native-flash-message @react-native-async-storage/async-storage react-native-vector-icons

Note: react-native-vector-icons is used for UI icons. If you're using Expo, you can use @expo/vector-icons instead (which is included by default in Expo projects).

Repository Layout (TypeScript + Compiled JS)

This package ships both the original TypeScript sources under src/ and the compiled JavaScript output under lib/. Consumers rely on the lib/ bundle when the package is published, while contributors typically edit the files in src/ and then run the build to regenerate lib/. Seeing duplicate filenames in both folders is expected—avoid editing only one side or your changes may be lost the next time the build runs.

iOS Setup

For iOS, you may need to add permissions to Info.plist:

<key>NSCameraUsageDescription</key>
<string>We need access to your camera for video calls</string>
<key>NSMicrophoneUsageDescription</key>
<string>We need access to your microphone for audio calls</string>

Android Setup

For Android, add permissions to AndroidManifest.xml:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

Quick Start

1. Setup Signaling Server

Before initializing the WebRTC service, you need to set up a signaling server. Here's a complete Express/Socket.io signaling server implementation:

Server Setup

Create a new file server.js in your project:

const express = require("express");
const http = require("http");
const socketIo = require("socket.io");
const cors = require("cors");
require("dotenv").config();
const twilio = require("twilio");

const app = express();
const server = http.createServer(app);

app.use(cors());
app.use(express.json());

const io = socketIo(server, {
  cors: {
    origin: "*",
    methods: ["GET", "POST"],
  },
});

// Store connected users
const users = new Map();
const activeRooms = new Map();

// ICE configuration cache
let cachedIceConfig = null;
let cachedIceExpiresAt = 0;

function isTwilioConfigured() {
  return (
    Boolean(process.env.TWILIO_ACCOUNT_SID && process.env.TWILIO_AUTH_TOKEN) ||
    Boolean(process.env.TWILIO_API_KEY_SID && process.env.TWILIO_API_KEY_SECRET)
  );
}

function createTwilioClient() {
  const accountSid = process.env.TWILIO_ACCOUNT_SID;
  const authToken = process.env.TWILIO_AUTH_TOKEN;
  const apiKeySid = process.env.TWILIO_API_KEY_SID;
  const apiKeySecret = process.env.TWILIO_API_KEY_SECRET;

  if (apiKeySid && apiKeySecret && accountSid) {
    return twilio(apiKeySid, apiKeySecret, { accountSid });
  }
  if (apiKeySid && apiKeySecret) {
    return twilio(apiKeySid, apiKeySecret);
  }
  if (accountSid && authToken) {
    return twilio(accountSid, authToken);
  }
  return null;
}

async function fetchTwilioIceServers() {
  const ttlSeconds = Number(process.env.ICE_TTL_SECONDS || 3600);
  const client = createTwilioClient();
  if (!client) return null;
  const token = await client.tokens.create({ ttl: ttlSeconds });
  return {
    iceServers: token.iceServers || [],
    ttl: ttlSeconds,
    provider: "twilio",
  };
}

async function getIceConfig() {
  const now = Date.now();
  // Refresh 30 seconds before expiry
  const refreshBufferMs = 30 * 1000;
  if (cachedIceConfig && now < cachedIceExpiresAt - refreshBufferMs) {
    return cachedIceConfig;
  }

  if (isTwilioConfigured()) {
    try {
      const twilioConfig = await fetchTwilioIceServers();
      if (
        twilioConfig &&
        twilioConfig.iceServers &&
        twilioConfig.iceServers.length
      ) {
        cachedIceConfig = twilioConfig;
        cachedIceExpiresAt = now + (twilioConfig.ttl || 3600) * 1000;
        return cachedIceConfig;
      }
    } catch (err) {
      console.error("❌ Failed to fetch Twilio ICE servers:", err.message);
    }
  }

  // Fallback to public Twilio STUN only (no TURN)
  cachedIceConfig = {
    iceServers: [{ urls: ["stun:global.stun.twilio.com:3478"] }],
    ttl: 3600,
    provider: "static-stun",
    note: "Using public STUN only. Configure TWILIO_* env vars to enable TURN for NAT traversal.",
  };
  cachedIceExpiresAt = now + cachedIceConfig.ttl * 1000;
  return cachedIceConfig;
}

// Health check endpoint
app.get("/health", (req, res) => {
  res.json({
    status: "ok",
    connectedUsers: users.size,
    activeRooms: activeRooms.size,
    timestamp: new Date().toISOString(),
    iceProvider: cachedIceConfig
      ? cachedIceConfig.provider
      : isTwilioConfigured()
      ? "twilio"
      : "static-stun",
  });
});

// Endpoint to retrieve ICE configuration for clients
app.get("/ice", async (req, res) => {
  try {
    const config = await getIceConfig();
    res.json({
      ...config,
      fetchedAt: new Date().toISOString(),
      expiresAt: new Date(cachedIceExpiresAt).toISOString(),
    });
  } catch (error) {
    console.error("❌ /ice error:", error);
    res.status(500).json({ error: "Failed to load ICE configuration" });
  }
});

// Get connected users (for debugging)
app.get("/users", (req, res) => {
  const userList = Array.from(users.entries()).map(([userId, userData]) => ({
    userId,
    userType: userData.userType,
    connected: true,
  }));
  res.json(userList);
});

io.on("connection", (socket) => {
  console.log("🔌 User connected:", socket.id);

  // Register user with their profile information
  socket.on("register", (userData) => {
    try {
      users.set(userData.userId, {
        socketId: socket.id,
        userType: userData.userType,
        connectedAt: new Date().toISOString(),
        ...userData,
      });

      console.log("👤 User registered:", {
        userId: userData.userId,
        userType: userData.userType,
        socketId: socket.id,
      });

      // Confirm registration with ICE configuration
      getIceConfig()
        .then((iceConfig) => {
          socket.emit("registered", {
            success: true,
            userId: userData.userId,
            connectedUsers: users.size,
            iceServers: iceConfig.iceServers,
            iceProvider: iceConfig.provider,
          });
        })
        .catch(() => {
          socket.emit("registered", {
            success: true,
            userId: userData.userId,
            connectedUsers: users.size,
            iceServers: [{ urls: ["stun:global.stun.twilio.com:3478"] }],
            iceProvider: "static-stun",
          });
        });
    } catch (error) {
      console.error("❌ Registration error:", error);
      socket.emit("registered", { success: false, error: error.message });
    }
  });

  // Handle all signaling messages
  socket.on("signaling-message", (message) => {
    try {
      const senderId = getUserIdBySocketId(socket.id);
      const recipient = users.get(message.to);

      console.log("📡 Signaling message:", {
        type: message.type,
        from: senderId,
        to: message.to,
        callId: message.callId,
        recipientFound: !!recipient,
      });

      if (recipient) {
        // Forward the message to the recipient
        io.to(recipient.socketId).emit("signaling-message", {
          ...message,
          from: senderId,
          timestamp: new Date().toISOString(),
        });

        // Handle room management for calls
        if (message.type === "call-request") {
          activeRooms.set(message.callId, {
            caller: senderId,
            callee: message.to,
            startTime: new Date().toISOString(),
            status: "ringing",
          });
        } else if (message.type === "call-accept") {
          const room = activeRooms.get(message.callId);
          if (room) {
            room.status = "active";
            room.acceptTime = new Date().toISOString();
          }
        } else if (
          message.type === "call-end" ||
          message.type === "call-reject"
        ) {
          const room = activeRooms.get(message.callId);
          if (room) {
            room.status = message.type === "call-end" ? "ended" : "rejected";
            room.endTime = new Date().toISOString();
            // Clean up room after 5 minutes
            setTimeout(() => {
              activeRooms.delete(message.callId);
            }, 5 * 60 * 1000);
          }
        }

        console.log("✅ Message forwarded successfully");
      } else {
        console.log("❌ Recipient not found:", message.to);

        // Notify sender that recipient is not available
        socket.emit("signaling-error", {
          type: "recipient-not-found",
          message: "The person you are trying to call is not available",
          originalMessage: message,
        });
      }
    } catch (error) {
      console.error("❌ Signaling error:", error);
      socket.emit("signaling-error", {
        type: "server-error",
        message: "An error occurred while processing your request",
        error: error.message,
      });
    }
  });

  // Handle user status updates
  socket.on("update-status", (status) => {
    const userId = getUserIdBySocketId(socket.id);
    if (userId && users.has(userId)) {
      const userData = users.get(userId);
      userData.status = status;
      userData.lastStatusUpdate = new Date().toISOString();
      users.set(userId, userData);

      console.log("📊 Status updated:", { userId, status });
    }
  });

  // Handle ping/pong for connection health
  socket.on("ping", () => {
    socket.emit("pong", { timestamp: new Date().toISOString() });
  });

  // Handle disconnect
  socket.on("disconnect", (reason) => {
    const userId = getUserIdBySocketId(socket.id);

    if (userId) {
      // Clean up any active calls
      for (const [callId, room] of activeRooms.entries()) {
        if (room.caller === userId || room.callee === userId) {
          room.status = "disconnected";
          room.endTime = new Date().toISOString();

          // Notify the other party
          const otherUserId =
            room.caller === userId ? room.callee : room.caller;
          const otherUser = users.get(otherUserId);
          if (otherUser) {
            io.to(otherUser.socketId).emit("signaling-message", {
              type: "call-end",
              callId: callId,
              from: userId,
              reason: "peer-disconnected",
            });
          }
        }
      }

      users.delete(userId);
      console.log("👋 User disconnected:", {
        userId,
        socketId: socket.id,
        reason,
        remainingUsers: users.size,
      });
    } else {
      console.log("👋 Unknown user disconnected:", socket.id);
    }
  });

  // Handle errors
  socket.on("error", (error) => {
    console.error("🔥 Socket error:", error);
  });
});

// Helper function to get user ID by socket ID
function getUserIdBySocketId(socketId) {
  for (const [userId, userData] of users.entries()) {
    if (userData.socketId === socketId) {
      return userId;
    }
  }
  return null;
}

// Cleanup inactive rooms periodically
setInterval(() => {
  const now = new Date();
  const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000);

  for (const [callId, room] of activeRooms.entries()) {
    const roomTime = new Date(room.endTime || room.startTime);
    if (roomTime < oneHourAgo) {
      activeRooms.delete(callId);
    }
  }
}, 10 * 60 * 1000); // Run every 10 minutes

// Graceful shutdown
process.on("SIGTERM", () => {
  console.log("🛑 SIGTERM received, shutting down gracefully");
  server.close(() => {
    console.log("✅ Server closed");
    process.exit(0);
  });
});

process.on("SIGINT", () => {
  console.log("🛑 SIGINT received, shutting down gracefully");
  server.close(() => {
    console.log("✅ Server closed");
    process.exit(0);
  });
});

const PORT = process.env.PORT || 3001;
server.listen(PORT, () => {
  console.log(`🚀 PlugEx Signaling Server running on port ${PORT}`);
  console.log(`📊 Health check available at http://localhost:${PORT}/health`);
  console.log(`👥 Users endpoint available at http://localhost:${PORT}/users`);
});

Server Dependencies

Install the required dependencies:

npm install express socket.io cors dotenv twilio
# or
yarn add express socket.io cors dotenv twilio

Environment Variables

Create a .env file in your server directory:

PORT=3001
ICE_TTL_SECONDS=3600

# Optional: Twilio Configuration (for TURN servers)
# Option 1: Using Account SID and Auth Token
TWILIO_ACCOUNT_SID=your_account_sid
TWILIO_AUTH_TOKEN=your_auth_token

# Option 2: Using API Key (recommended)
TWILIO_API_KEY_SID=your_api_key_sid
TWILIO_API_KEY_SECRET=your_api_key_secret

Running the Server

node server.js

The server will start on port 3001 (or the port specified in your .env file).

2. Wrap Your App with WebRTCCallManagerWrapper

Wrap your app (or the part that needs calling functionality) with WebRTCCallManagerWrapper and pass the configuration as props:

import React from "react";
import { WebRTCCallManagerWrapper } from "react-native-webrtc-call";
import AsyncStorage from "@react-native-async-storage/async-storage";

const App = () => {
  return (
    <WebRTCCallManagerWrapper
      config={{
        // Use http:// or https:// (not ws:// or wss://)
        // The service will automatically convert to WebSocket protocol
        signalingServerUrl: "http://localhost:3001", // For development
        // signalingServerUrl: 'https://your-signaling-server.com', // For production

        // Optional: If your server provides ICE servers via /ice endpoint
        turnEndpoint: "http://localhost:3001/ice", // Optional

        icePolicy: "all", // or 'relay'
        defaultCallType: "p2p", // Default to P2P, can be 'group'

        // Optional: Provide custom token retrieval
        getUserToken: async () => {
          return await AsyncStorage.getItem("userToken");
        },

        // Optional: Provide custom user profile
        getUserProfile: () => {
          return {
            _id: "user123",
            firstName: "John",
            lastName: "Doe",
            userType: "user",
            profilePicture: "https://...",
          };
        },
      }}
      userId="user123" // Required for user registration
      userName="John Doe" // Optional - for display purposes
      userAvatar="https://..." // Optional - for display purposes
      onEvents={(event) => {
        // Optional: Handle background events
        console.log("WebRTC Event:", event);
      }}
    >
      {/* Your app components */}
    </WebRTCCallManagerWrapper>
  );
};

export default App;

Important Notes:

  • Use http:// or https:// for signalingServerUrl (not ws:// or wss://)
  • The service automatically converts the protocol to WebSocket internally
  • For local development, use http://localhost:3001
  • For production, use your deployed server URL with https://
  • userId is required for user registration with the signaling server
  • If getUserProfile is not provided, it will be built from userId, userName, and userAvatar props

3. Use in Your Components

When using WebRTCCallManagerWrapper, the service is created internally. For components that need to initiate calls, you have a few options:

Option A: Create a Service Instance for Direct Access

If you need direct access to the service methods, create a separate service instance with the same configuration:

// services/webRTCService.ts
import { WebRTCService, WebRTCConfig } from "react-native-webrtc-call";
import AsyncStorage from "@react-native-async-storage/async-storage";

const config: WebRTCConfig = {
  signalingServerUrl: "http://localhost:3001",
  turnEndpoint: "http://localhost:3001/ice",
  icePolicy: "all",
  defaultCallType: "p2p",
  getUserToken: async () => {
    return await AsyncStorage.getItem("userToken");
  },
  getUserProfile: () => {
    return {
      _id: "user123",
      firstName: "John",
      lastName: "Doe",
      userType: "user",
    };
  },
};

export const webRTCService = new WebRTCService(config);

Then use it in your components:

import { useWebRTC } from "react-native-webrtc-call";
import { webRTCService } from "./services/webRTCService";

const MyComponent = () => {
  const { initiateCall } = useWebRTC();

  const handleCall = async () => {
    try {
      // P2P call (default)
      const callId = await webRTCService.initiateCall(
        "recipient-id",
        false // video: false for audio, true for video
      );

      initiateCall(
        {
          id: "recipient-id",
          name: "Recipient Name",
          avatar: "https://...",
          userType: "user",
        },
        callId
      );
    } catch (error) {
      console.error("Call failed:", error);
    }
  };

  return <Button onPress={handleCall} title="Call" />;
};

Option B: Using CallButton Component

import { CallButton } from "react-native-webrtc-call";
import { webRTCService } from "./services/webRTCService";

const MyComponent = () => {
  return (
    <CallButton
      recipientId="recipient-id"
      recipientName="Recipient Name"
      webRTCService={webRTCService}
      onInitiateCall={(callId) => {
        // Optional: Handle call initiation
        console.log("Call initiated:", callId);
      }}
    />
  );
};

Initiate a Group Call

import { useWebRTC } from "react-native-webrtc-call";
import { webRTCService } from "./services/webRTCService";

const MyComponent = () => {
  const { initiateCall, setCallType } = useWebRTC();

  const handleGroupCall = async () => {
    try {
      const participantIds = ["user1", "user2", "user3"];

      // Group call
      const callId = await webRTCService.initiateGroupCall(
        participantIds,
        true // video: true for video, false for audio
      );

      setCallType("group");
      initiateCall(
        {
          id: "group",
          name: "Group Call",
        },
        callId
      );
    } catch (error) {
      console.error("Group call failed:", error);
    }
  };

  return <Button onPress={handleGroupCall} title="Start Group Call" />;
};

Accept/Reject Incoming Calls

The WebRTCCallManagerWrapper automatically displays the IncomingCallScreen when there's an incoming call. You can also use the IncomingCallScreen component directly:

import { IncomingCallScreen } from "react-native-webrtc-call";
import { webRTCService } from "./services/webRTCService";

const MyApp = () => {
  return <IncomingCallScreen webRTCService={webRTCService} />;
};

Or handle it manually:

import { useWebRTC } from "react-native-webrtc-call";
import { webRTCService } from "./services/webRTCService";

const IncomingCallHandler = () => {
  const { state, acceptCall, rejectCall } = useWebRTC();
  const { isIncomingCall, caller } = state;

  const handleAccept = async () => {
    acceptCall();
    await webRTCService.acceptCall(false); // false for audio, true for video
  };

  const handleReject = () => {
    rejectCall();
    webRTCService.rejectCall();
  };

  if (!isIncomingCall) return null;

  return (
    <View>
      <Text>Incoming call from {caller?.name}</Text>
      <Button onPress={handleAccept} title="Accept" />
      <Button onPress={handleReject} title="Reject" />
    </View>
  );
};

Call Controls

import { webRTCService } from "./services/webRTCService";

const CallControls = () => {
  const handleMute = () => {
    webRTCService.toggleMute();
  };

  const handleVideo = () => {
    webRTCService.toggleVideo();
  };

  const handleSpeaker = () => {
    webRTCService.toggleSpeaker();
  };

  const handleEndCall = () => {
    webRTCService.endCall();
  };

  return (
    <View>
      <Button onPress={handleMute} title="Mute" />
      <Button onPress={handleVideo} title="Video" />
      <Button onPress={handleSpeaker} title="Speaker" />
      <Button onPress={handleEndCall} title="End Call" />
    </View>
  );
};

API Reference

WebRTCService

Methods

  • initiateCall(calleeId: string, video?: boolean, callIdOverride?: string): Promise<string>

    • Initiates a P2P call to the specified user (default)
    • Returns the call ID
  • initiateP2PCall(calleeId: string, video?: boolean, callIdOverride?: string): Promise<string>

    • Explicitly initiates a P2P call
    • Returns the call ID
  • initiateGroupCall(participantIds: string[], video?: boolean, callIdOverride?: string): Promise<string>

    • Initiates a group call with multiple participants
    • Returns the call ID
  • acceptCall(video?: boolean): Promise<void>

    • Accepts an incoming call
  • rejectCall(): void

    • Rejects an incoming call
  • endCall(reason?: string): void

    • Ends the current call
  • toggleMute(): void

    • Toggles microphone mute state
  • toggleVideo(): Promise<void>

    • Toggles video on/off
  • toggleSpeaker(): void

    • Toggles speakerphone on/off
  • getFormattedCallDuration(seconds: number): string

    • Formats call duration as MM:SS
  • cleanup(): void

    • Cleans up all resources
  • getState(): Partial<CallState>

    • Returns current call state

Context API

The package provides a useWebRTC hook that gives you access to:

  • State: All call state (isCallActive, isIncomingCall, caller, callee, etc.)
  • Actions: All state management functions
import { useWebRTC } from "react-native-webrtc-call";

const MyComponent = () => {
  const {
    state,
    initiateCall,
    acceptCall,
    rejectCall,
    endCall,
    toggleMute,
    toggleVideo,
    toggleSpeaker,
  } = useWebRTC();

  // Access state
  const { isCallActive, caller, callee } = state;

  // Use actions
  const handleCall = () => {
    initiateCall(callee, callId);
  };
};

Call State

interface CallState {
  isCallActive: boolean;
  isIncomingCall: boolean;
  isOutgoingCall: boolean;
  callId: string | null;
  callType: "p2p" | "group"; // Call type
  localStream: any;
  remoteStream: any; // For P2P calls
  remoteParticipants: RemoteParticipant[]; // For group calls
  isMuted: boolean;
  isVideoEnabled: boolean;
  isSpeakerEnabled: boolean;
  callStartTime: number | null;
  callDuration: number;
  caller: CallParticipant | null;
  callee: CallParticipant | null;
  participants: CallParticipant[]; // All participants (for group calls)
  connectionState: "connecting" | "connected" | "disconnected" | "failed";
  isCallMinimized: boolean;
  incomingWantsVideo: boolean | null;
}

Signaling Server Requirements

Your signaling server should handle the following Socket.io events:

Client → Server

  • register - Register user with signaling server

    {
      userId: string;
      userType: string;
      token?: string;
    }
  • signaling-message - Send signaling messages

    {
      type: "offer" | "answer" | "ice-candidate" | "call-request" | "call-accept" | "call-reject" | "call-end";
      data?: any;
      callId: string;
      to: string;
    }

Server → Client

  • registered - Registration confirmation with ICE servers

    {
      success: boolean;
      iceServers: RTCIceServer[];
      iceProvider?: string;
    }
  • signaling-message - Receive signaling messages

ICE Server Configuration

The package supports both STUN and TURN servers. TURN servers are required for calls to work across different networks (NAT traversal).

You can provide ICE servers in two ways:

  1. Via signaling server registration response - The server returns ICE servers when the client registers
  2. Via TURN endpoint - Configure a turnEndpoint in the config that returns ICE servers

Example TURN endpoint response:

{
  "iceServers": [
    {
      "urls": "turn:your-turn-server.com:3478",
      "username": "username",
      "credential": "password"
    }
  ]
}

Testing

Testing Signaling Server Connection

You can test your signaling server setup using the following methods:

Automated Test Script

Run the included test script to verify all endpoints:

npm run test:server
# or with custom URL
node test/signaling-server-test.js http://localhost:3001

This will test:

  • Health check endpoint (/health)
  • ICE configuration endpoint (/ice)
  • Connected users endpoint (/users)

1. Health Check

Test if your server is running:

curl http://localhost:3001/health

Expected response:

{
  "status": "ok",
  "connectedUsers": 0,
  "activeRooms": 0,
  "timestamp": "2024-01-01T00:00:00.000Z",
  "iceProvider": "static-stun"
}

2. ICE Configuration Endpoint

Test ICE server configuration:

curl http://localhost:3001/ice

Expected response:

{
  "iceServers": [
    {
      "urls": ["stun:global.stun.twilio.com:3478"]
    }
  ],
  "ttl": 3600,
  "provider": "static-stun",
  "fetchedAt": "2024-01-01T00:00:00.000Z",
  "expiresAt": "2024-01-01T01:00:00.000Z"
}

3. Connected Users

Check currently connected users:

curl http://localhost:3001/users

4. Testing signalingServerUrl in Your App

Add this test function to verify your signaling server connection:

import { WebRTCService } from "react-native-webrtc-call";

async function testSignalingServer() {
  const testConfig = {
    signalingServerUrl: "http://localhost:3001", // Your server URL
    getUserProfile: () => ({
      _id: "test-user",
      firstName: "Test",
      lastName: "User",
      userType: "user",
    }),
    onError: (error) => {
      console.error("Test Error:", error);
    },
    onCallStateChange: (state) => {
      console.log("Test State:", state);
    },
  };

  const service = new WebRTCService(testConfig);

  try {
    // Register user (this will connect to signaling server)
    await service.registerUser();
    console.log("✅ Successfully connected to signaling server");

    // Check connection state
    const state = service.getState();
    console.log("Connection state:", state);

    // Cleanup
    service.cleanup();
  } catch (error) {
    console.error("❌ Failed to connect to signaling server:", error);
  }
}

// Call the test function
testSignalingServer();

5. Network Testing

For testing on physical devices or emulators:

  • iOS Simulator: Use http://localhost:3001 if testing on the same machine
  • Android Emulator: Use http://10.0.2.2:3001 (Android emulator's localhost)
  • Physical Devices: Use your computer's local IP address (e.g., http://192.168.1.100:3001)

Find your local IP:

# macOS/Linux
ifconfig | grep "inet " | grep -v 127.0.0.1

# Windows
ipconfig

Troubleshooting

Calls not connecting

  1. Check that your signaling server is running and accessible
    • Test with: curl http://localhost:3001/health
    • Verify the server logs show connections
  2. Verify signalingServerUrl is correctly configured
    • Use http:// or https:// (not ws:// or wss://)
    • For Android emulator, use http://10.0.2.2:3001
    • For physical devices, use your computer's local IP
  3. Verify ICE servers are properly configured (TURN servers required for cross-network calls)
    • Check /ice endpoint returns valid ICE servers
    • Review server logs for Twilio configuration errors
  4. Check device permissions (camera, microphone)
  5. Review connection state using the useWebRTC hook

Signaling Server Connection Issues

  1. Connection refused errors

    • Ensure the server is running: node server.js
    • Check the port matches your configuration
    • Verify firewall settings allow connections
  2. WebSocket connection fails

    • Ensure you're using http:// or https:// in signalingServerUrl
    • Check CORS settings on the server
    • Verify network connectivity between client and server
  3. Registration fails

    • Check server logs for registration errors
    • Verify getUserProfile() returns valid user data
    • Ensure user ID is unique and not empty

Audio/Video not working

  1. Ensure permissions are granted
  2. Check that media tracks are being added to peer connection
  3. Verify local and remote streams using the useWebRTC hook

Connection drops

  1. Check network connectivity
  2. Verify TURN servers are working
  3. Review ICE restart attempts in logs
  4. Check server logs for disconnection reasons

License

MIT

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.