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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@realtimexsco/live-chat-react-native

v1.1.6

Published

RealtimeX Live Chat SDK for React Native — embeddable chat UI with configurable fonts, theme, styles, and replaceable components

Readme

@realtimexsco/live-chat-react-native

Drop-in real-time chat UI for React Native — DMs, groups, reactions, replies, forwarding, pinned/starred messages, plus audio/video calling, Picture-in-Picture, and screen share. Built on Socket.IO and a configurable fonts / theme / UI system.

npm version npm downloads License: MIT

| | | | ------------ | ------------------------------------------------------------------------------------------------------------ | | Package | @realtimexsco/live-chat-react-native | | Requires | React Native ≥ 0.78, React ≥ 18 (see peerDependencies) | | Native | host-setup/NATIVE_SETUP.md — calls, PiP, screen share | | Web twin | @realtimexsco/live-chat | | Docs | Package documentation | | License | MIT |


Install

yarn add @realtimexsco/live-chat-react-native
# npm install @realtimexsco/live-chat-react-native

Peer dependencies

Peers are not installed automatically. Do not nest a second react / react-native under this package.

Chat (minimum):

yarn add \
  @react-native-async-storage/async-storage@^3.1.1 \
  @react-native-clipboard/clipboard@^1.16.3 \
  @react-native-community/netinfo@^12.0.1 \
  @react-native-community/push-notification-ios@^1.12.0 \
  @react-native-documents/picker@^12.0.1 \
  @react-native-firebase/app@^24.0.0 \
  @react-native-firebase/messaging@^24.0.0 \
  @react-native-masked-view/masked-view@^0.3.2 \
  @react-navigation/bottom-tabs@^7.18.13 \
  @react-navigation/native@^7.3.13 \
  @react-navigation/stack@^7.10.16 \
  @shopify/flash-list@^2.3.2 \
  react-native-config@^1.6.1 \
  react-native-device-info@^15.0.2 \
  react-native-gesture-handler@^3.1.0 \
  react-native-heic-converter@^1.3.3 \
  react-native-image-crop-picker@^0.51.1 \
  react-native-keyboard-aware-scroll-view@^0.9.5 \
  react-native-permissions@^5.6.0 \
  react-native-reanimated@^4.5.2 \
  react-native-responsive-screen@^1.4.2 \
  react-native-safe-area-context@^5.8.0 \
  react-native-screens@^4.15.0 \
  react-native-svg@^15.15.5 \
  react-native-toast-message@^2.4.0 \
  react-native-webview@^14.0.1 \
  react-native-worklets@^0.12.0 \
  socket.io-client@^4.8.3

Calling (audio / video / PiP / screen share):

yarn add \
  @videosdk.live/react-native-sdk@^0.6.0 \
  @videosdk.live/react-native-incallmanager@^0.1.3
# @videosdk.live/react-native-webrtc is transitive

Skip packages you already satisfy. Then:

cd ios && pod install && cd ..

Native setup (calls / PiP / screen share / push)

| Platform | Package provides | Host still does | |----------|------------------|-----------------| | Android | Autolink: call + push permissions, notification channels, MediaProjection FGS, CallPipModule (self-hooks the activity) | google-services.json + Gradle plugin, supportsPictureInPicture on MainActivity | | iOS | Autolink pod RealtimexLiveChat: CallPipModule, VideosdkRPK, RealtimexVoip (PushKit → CallKit at launch, no AppDelegate code) | GoogleService-Info.plist + FirebaseApp.configure(), Push capability, Info.plist usage strings + background modes, Broadcast Upload Extension target, App Group |

Step by step: Native configuration in the full guide · Reference: host-setup/NATIVE_SETUP.md

Also call VideoSDK register() once in index.js before any meeting UI.

For monorepo / portal: / file: installs, add the package path to Metro watchFolders.


Quick start

  1. Call configureChat({ clientId, accessToken, apiUrl, socketUrl, fonts?, theme?, ui? }) once at app boot.
  2. Wrap chat in <ChatProvider> (auto-connects Socket.IO when credentials are set).
  3. Render <ChatNavigator /> — it owns the conversation list → room stack (no manual screen wiring).
import {
  configureChat,
  ChatProvider,
  ChatNavigator,
} from '@realtimexsco/live-chat-react-native';

configureChat({
  apiUrl: 'https://api.example.com/api/v1',
  socketUrl: 'https://api.example.com',
  clientId: 'your-client-id',
  accessToken: () => getHostAccessToken(), // or a static string
  fonts: {
    Regular: 'MyApp-Regular',
    SemiBold: 'MyApp-SemiBold',
    Bold: 'MyApp-Bold',
  },
  theme: {
    colorMode: 'light',
    // Built-in: 'green' | 'teal' | 'blue' | 'indigo' | 'purple' | 'pink' | 'orange' | 'red'
    // Or pass your brand hex — the SDK derives header, bubbles, and badge:
    accent: '#124232',
  },
  ui: {
    conversationList: {
      title: 'Messages',
    },
    chatRoom: {
      header: {
        showVideoCall: true,
        showVoiceCall: true,
        showSearch: true,
      },
    },
  },
});

/** Drop-in chat — list taps open ChatRoom automatically. */
export function ChatTab() {
  return (
    <ChatProvider>
      <ChatNavigator />
    </ChatProvider>
  );
}

You can also pass config as a prop (recommended for package hosts — config + connect run together):

<ChatProvider
  config={{
    apiUrl: 'https://api.example.com/api/v1',
    socketUrl: 'https://api.example.com',
    clientId: 'your-client-id',
    accessToken: () => getHostAccessToken(),
  }}
>
  <ChatNavigator />
</ChatProvider>

| ChatProvider prop | Default | Purpose | | ------------------- | ------- | ------- | | config | — | Same options as configureChat | | autoConnect | true | Restore / bootstrap session and connect Socket.IO | | withSafeArea | true | Wrap with SafeAreaProvider | | withGestureRoot | true | Wrap with GestureHandlerRootView | | withToast | true | Mount Toast host |

If the host already provides SafeAreaProvider / gesture root / Toast:

<ChatProvider withSafeArea={false} withGestureRoot={false} withToast={false}>
  <ChatNavigator />
</ChatProvider>

Set autoConnect={false} only when you call bootstrapChatSession() / ensureChatSession() yourself.

Nesting inside the host navigator

ChatNavigator includes its own NavigationContainer by default (safe inside tabs/stacks via NavigationIndependentTree). To nest as a normal child stack instead:

<Stack.Screen name="Messages">
  {() => <ChatNavigator withContainer={false} />}
</Stack.Screen>

Configure

Boot once with configureChat. Omitting optional sections keeps SDK defaults.

| Option | Purpose | | ------------- | --------------------------------------------------------------------------------------- | | apiUrl | REST base URL | | socketUrl | Socket.IO server URL | | clientId | App / tenant client id | | accessToken | Static string or getter refreshed per request | | fonts | Host-registered font family names | | theme | Light/dark, brand accent hex / built-in accent, palette overrides, avatar / bubble appearance | | ui | Feature flags, copy, style overrides, chat info, and component replacements |

Fonts (host responsibility)

The SDK only consumes font family names. Register .ttf / .otf in your iOS and Android projects, then pass those names into configureChat({ fonts }). Empty values fall back to the system font.

configureChat({
  fonts: {
    Regular: 'MyApp-Regular',
    SemiBold: 'MyApp-SemiBold',
    Bold: 'MyApp-Bold',
    // also: Black, Light, Italic, SemiBold_Italic, …
  },
});

Theme

Light / dark is separate. For brand color, just pass a hex:

configureChat({
  theme: {
    colorMode: 'light', // or 'dark'
    accent: '#124232',  // your brand color
  },
});

That hex applies to headers (light), buttons, FABs, links, checks, badges, outgoing bubbles, and tinted icons. Related shades (header / bubbles) are derived automatically.

| Option | Purpose | | ------------------------------ | ----------------------------------------------------------- | | colorMode | 'light' | 'dark' | | accent | Brand hex ('#124232') or built-in id ('blue', …) | | colors.light / colors.dark | Optional fine-tuned palette overrides | | avatarShape / messageStyle | Appearance defaults |

Built-in ids (optional): green · teal · blue · indigo · purple · pink · orange · red

<ChatProvider config={{ theme: { accent: '#124232' } }}>
  <ChatNavigator />
</ChatProvider>

Full UI customization

Pass ui to:

  1. Show / hide chrome and actions
  2. Override copy
  3. Override styles (conversation list, room header, sender/receiver bubbles, composer, chat info)
  4. Swap components (header, list row, bubble, composer, media, permissions, …)
  5. Customise Chat Info for group and individual (buttons, icons, labels, sections)

Omit ui and behaviour / look stay at SDK defaults.

configureChat({
  // ...api / fonts / theme
  ui: {
    conversationList: {
      title: 'Messages',
      showSearch: true,
      showOverflowMenu: true,
      showNewChatFab: true,
      showGlobalMessageSearch: true,
      // Height of host UI floating over the list bottom (e.g. an absolute tab bar).
      // Lifts the new-chat FAB and list padding by this amount. Default 0.
      fabBottomInset: 0,
    },
    chatRoom: {
      header: {
        showBack: true,
        showAvatar: true,
        showSubtitle: true,
        openInfoOnHeaderPress: true,
        showSearch: true,
        showVideoCall: false,
        showVoiceCall: false,
        showOverflowMenu: true,
        // onVideoCallPress: ({ conversationId }) => { startCall(...); return true; },
      },
      view: {
        showTypingIndicator: true,
        showReactions: true,
        showSenderNamesInGroups: true,
        showSenderAvatarsInGroups: true, // profile image in groups
        showSenderNamesInDm: true, // name in 1:1
        showSenderAvatarsInDm: true, // profile image in 1:1
        // showOwnSenderInfo: false,     // also on your own bubbles
        // senderAvatarSize: 28,
        emptyStateText: 'Say hello!',
      },
      composer: {
        placeholder: 'Type a message',
        showEmojiButton: true,
        showAttachButton: true,
        showCameraShortcut: true,
        showMicPlaceholder: false,
        attachments: {
          camera: true,
          gallery: true,
          document: true,
          audio: false,
        },
      },
      messageActions: {
        reply: true,
        react: true,
        forward: true,
        star: true,
        pin: true,
        edit: true,
        delete: true,
      },
      roomOptions: {
        info: true,
        media: true,
        search: true,
        clear: true,
        block: true,
        report: false,
        exit: true,
        delete: true,
      },
    },

    // Chat info (group + DM) — sections, icons, labels, call handlers
    chatInfo: {
      groupTitle: 'Group info',
      dmTitle: 'Contact info',
      aboutLabel: 'About',
      header: {
        showBack: true,
        showEdit: true,
        showSearch: true,
        icons: { back: 'arrow-back', edit: 'create-outline', search: 'search' },
      },
      quickActions: {
        message: { show: true, icon: 'chatbubble', label: 'Message' },
        audio: { show: true, icon: 'call', label: 'Audio' },
        video: { show: false }, // hide video on info screen
        search: { show: true, icon: 'search', label: 'Search' },
      },
      sections: {
        description: true,
        createdBy: true,
        media: true,
        permissions: true,
        members: true,
        danger: true,
      },
      memberToolbar: {
        showAdd: true,
        showSearch: true,
        icons: {
          add: 'person-add-outline',
          search: 'search',
          memberMenu: 'ellipsis-vertical',
        },
      },
      dangerActions: {
        clear: { show: true, icon: 'trash-outline', label: 'Clear chat' },
        exit: { show: true, icon: 'exit-outline', label: 'Exit group' },
        deleteGroup: {
          show: true,
          icon: 'close-circle-outline',
          label: 'Delete group',
        },
        block: { show: true, icon: 'hand-left-outline', label: 'Block' },
        unblock: { show: true, icon: 'lock-open-outline', label: 'Unblock' },
        deleteChat: { show: true, icon: 'trash-outline', label: 'Delete chat' },
        report: { show: false },
      },
      memberSheet: {
        makeAdmin: {
          show: true,
          icon: 'shield-checkmark-outline',
          label: 'Make group admin',
        },
        dismissAdmin: {
          show: true,
          icon: 'shield-outline',
          label: 'Dismiss as admin',
        },
        remove: {
          show: true,
          icon: 'person-remove-outline',
          label: 'Remove from group',
        },
      },
      // onAudioPress / onVideoPress: return true if you handled the call
      // onAudioPress: ({ conversationId }) => { startCall(...); return true; },
    },

    // Style overrides (any RN ViewStyle / TextStyle)
    styles: {
      conversationList: {
        header: { paddingTop: 12 },
        title: { fontSize: 24, fontWeight: '800' },
        row: { paddingVertical: 14 },
        name: { fontSize: 16 },
        preview: { fontSize: 13 },
        fabWrap: { right: 24, bottom: 32 },
        fab: { borderRadius: 16 },
      },
      chatRoom: {
        header: { minHeight: 60 },
        headerTitle: { fontSize: 18 },
        list: { backgroundColor: '#F7F7F7' },
      },
      message: {
        // Own / outgoing
        sender: {
          bubble: { backgroundColor: '#DCF8C6', borderRadius: 12 },
          text: { fontSize: 15, color: '#111' },
          time: { fontSize: 10 },
        },
        // Peer / incoming
        receiver: {
          bubble: { backgroundColor: '#FFFFFF', borderRadius: 12 },
          text: { fontSize: 15, color: '#111' },
          time: { fontSize: 10 },
        },
        senderName: { fontSize: 12, fontWeight: '700' },
      },
      composer: {
        container: { paddingBottom: 8 },
        inputWrap: { borderRadius: 24 },
        input: { fontSize: 16 },
        sendButton: { borderRadius: 22 },
      },
      chatInfo: {
        header: { minHeight: 56 },
        heroName: { fontSize: 24 },
        actionIcon: { borderRadius: 16 },
        dangerText: { fontWeight: '600' },
      },
    },

    // Full component replacements (same props as the built-ins)
    // components: {
    //   MessageComposer: MyComposer,
    //   MessageBubble: MyBubble,
    //   ConversationListItem: MyListRow,
    //   ConversationListHeader: MyListHeader,
    //   ChatRoomHeader: MyRoomHeader,
    //   SharedMediaSection: MyMedia,
    //   GroupPermissionsEditor: MyPermissions,
    // },
  },
});

Feature flags

| Area | Flags / options | | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Conversation list | title, showSearch, showOverflowMenu, showNewChatFab, showGlobalMessageSearch, fabBottomInset | | Chat room header | showBack, showAvatar, showSubtitle, openInfoOnHeaderPress, showSearch, showVideoCall, showVoiceCall, showOverflowMenu, call handlers | | Chat view | showTypingIndicator, showReactions, showSenderNamesInGroups, showSenderAvatarsInGroups, showSenderNamesInDm, showSenderAvatarsInDm, showOwnSenderInfo, senderAvatarSize, emptyStateText | | Composer | placeholder, showEmojiButton, showAttachButton, showCameraShortcut, showMicPlaceholder, richText (web rich-text editor; false = plain input), attachments.* | | Message actions | reply, react, forward, star, pin, edit, delete | | Room options | info, media, search, clear, block, report, exit, delete | | Chat info | See Chat info (group + DM) below |

Sender name & avatar (group + DM)

Control peer (and optionally own) identity next to bubbles via ui.chatRoom.view:

| Flag | Default | Purpose | | --------------------------- | ------- | ----------------------------------------------- | | showSenderNamesInGroups | true | Name above bubbles in groups | | showSenderAvatarsInGroups | false | Profile image beside bubbles in groups | | showSenderNamesInDm | false | Name above bubbles in 1:1 | | showSenderAvatarsInDm | false | Profile image beside bubbles in 1:1 | | showOwnSenderInfo | false | Also show name/avatar on your outgoing messages | | senderAvatarSize | 28 | Avatar diameter when avatars are on |

configureChat({
  ui: {
    chatRoom: {
      view: {
        showSenderNamesInGroups: true,
        showSenderAvatarsInGroups: true,
        showSenderNamesInDm: true,
        showSenderAvatarsInDm: true,
        senderAvatarSize: 32,
      },
    },
  },
});

Chat info (group + DM)

Configure via ui.chatInfo. Works for group and individual chats.

| Area | Options | | -------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | Titles / copy | groupTitle, dmTitle, aboutLabel, noDescriptionText, adminBadgeLabel, descriptionSectionLabel, permissionsSectionLabel | | Header | showBack, showEdit, showSearch + icons.back / edit / search (SVG icon names) | | Quick actions | message, audio, video, search — each { show, icon, label }; onAudioPress / onVideoPress handlers | | Sections | description, createdBy, media, permissions, members, danger | | Member toolbar | showAdd, showSearch + icons.add / search / memberMenu | | Danger actions | clear, exit, deleteGroup (group); block, unblock, deleteChat (DM); report — each { show, icon, label } | | Member sheet | makeAdmin, dismissAdmin, remove — each { show, icon, label } |

Group-only UI (description, permissions, exit, delete group, member admin sheet) only renders in groups. DM-only UI (block / unblock / delete chat) only renders in 1:1 chats. Shared controls (clear, report, media, quick actions) apply to both.

Style overrides (ui.styles)

| Key | What it styles | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | conversationList | container, header, title, row, name, preview, time, badge, badgeText, fab | | chatRoom | container, header, headerTitle, headerSubtitle, list, emptyState, emptyStateText | | message.sender | Own bubble — bubble, text, time, meta | | message.receiver | Peer bubble — bubble, text, time, meta | | message | container, senderName, senderAvatar, senderRow (group sender label) | | composer | container, inputWrap, input, sendButton | | chatInfo | container, header, headerTitle, hero, heroName, actionButton, actionIcon, actionLabel, card, dangerRow, dangerText, memberRow |

Styles merge on top of built-ins. Theme palette (theme.colors) still drives defaults; use styles for layout / typography / per-side bubble look.

Component replacements (ui.components)

| Slot | Replaces | Props type (exported) | | ------------------------ | ------------------------------------- | --------------------------------- | | MessageComposer | Input / attach / send row | MessageComposerProps | | MessageBubble | Sender & receiver bubbles | MessageBubbleProps | | ConversationListItem | Conversation row | ConversationListItemProps | | ConversationListHeader | List screen top bar | receives search / title callbacks | | ChatRoomHeader | Room top bar | ChatRoomHeaderProps | | SharedMediaSection | Media / docs / links on chat info | { messages } | | GroupPermissionsEditor | Group permission toggles on chat info | editor props |

Built-ins are also exported so you can wrap or partially reuse them:

import {
  MessageComposer,
  MessageBubble,
  ConversationListItem,
  ChatRoomHeader,
} from '@realtimexsco/live-chat-react-native';

Defaults & helpers

Resolved defaults live in DEFAULT_CHAT_UI.

| Key | Default | | ------------------------------- | --------------------------------------------------- | | conversationList.title | "RealTime Chat" | | chatRoom.view.emptyStateText | "Messages are end-to-end encrypted. Say hello 👋" | | chatRoom.composer.placeholder | "Message" | | Feature flags | all true |

import {
  getUiConfig, // fully resolved (defaults + host overrides)
  getUiConfigPartial, // raw host overrides only
  DEFAULT_CHAT_UI,
  resolveChatUiConfig,
} from '@realtimexsco/live-chat-react-native';

Per-screen composer overrides

<MessageComposer
  {...props}
  ui={{ showEmojiButton: false, attachments: { audio: false } }}
/>

Auth / session / Socket.IO

Hosts supply accessToken (string or getter) and clientId via configureChat. The package then:

  1. Exchanges the host token for a chat session (bootstrapChatSession / auto from ChatProvider)
  2. Connects Socket.IO with the chat token (fresh Manager each connect — does not stay idle on re-configure)
  3. Owns emit/listen for DM + group messaging (events are queued until connected, then flushed)
configureChat({
  apiUrl: 'https://api.example.com/api/v1',
  // Origin only — `/api/v1` is stripped if you paste the REST URL by mistake
  socketUrl: 'https://api.example.com',
  clientId: 'your-client-id',
  accessToken: () => getHostAccessToken(),
});

// Preferred: pass config so session + socket connect on mount
<ChatProvider config={/* same options */}>
  <ChatNavigator />
</ChatProvider>

If the host JWT has no email claim, pass identity explicitly:

import { bootstrapChatSession } from '@realtimexsco/live-chat-react-native';

await bootstrapChatSession({
  email: user.email,
  name: user.name,
  hostAccessToken: token,
});

Demo-only Splash/Login screens are not required in host apps.

Session helpers

| Export | Purpose | | ------ | ------- | | ensureChatSession(options?) | Restore persisted session or bootstrap login + connect socket | | bootstrapChatSession(options?) | Alias of ensureChatSession | | connectChatSocket(chatToken, clientId?) | Connect / reconnect Socket.IO with an existing chat token | | selectIsAuthenticate / selectIsSessionReady | Redux selectors for session state | | selectSdkChatToken / selectLoggedUserId | Chat token and current user id | | authActions | Advanced: loginRequest, logout, bootstrapSession, … |

options for bootstrap: { name?, email?, clientId?, hostAccessToken? }.

Socket API (exported for hosts)

Built-in screens already use the socket. Export these when you build custom UI or need to emit/listen from the host:

import {
  socketService,
  SOCKET_EVENTS,
  connectChatSocket,
  selectSocketStatus,
  selectOnlineUsers,
} from '@realtimexsco/live-chat-react-native';

// Status: 'idle' | 'connecting' | 'connected' | 'disconnected' | 'error'
const status = useSelector(selectSocketStatus);

// Emit (safe before connect — queued and flushed on connect)
socketService.emitDmSend({ /* … */ });
socketService.emitGroupMessageSend({ /* … */ });
socketService.emitTypingStart({ conversationId, userId }, isGroup);
socketService.emitMarkAsRead({ conversationId, sender, receiver });

// Listen (safe before connect — attached when the socket is created)
const off = socketService.on(SOCKET_EVENTS.DM_RECEIVE, payload => {
  // …
});
// later: off();

// Manual reconnect after you already have a chat token
connectChatSocket(chatToken, clientId);
// or
socketService.connectWithSession(chatToken, clientId);
socketService.ensureConnected();

| socketService method | Notes | | ---------------------- | ----- | | connected / isConnected() / status | Connection state | | connectWithSession(token, clientId) | Preferred connect for package hosts | | ensureConnected() | Reconnect using last config | | emit(event, payload?) | Low-level emit (queued until connected) | | on(event, handler) / off(event, handler?) | Subscribe / unsubscribe | | emitDmSend / emitDmEdit / emitDmDelete / … | DM helpers | | emitGroupJoin / emitGroupMessageSend / … | Group helpers | | emitTypingStart / emitTypingStop | Typing (pass isGroup as 2nd arg) | | emitMarkAsRead / emitGroupMarkRead | Read receipts | | disconnect() | Logout / tear down |

SOCKET_EVENTS lists every server event name (dm_send, dm_receive, group_message_send, group_join, …). Prefer the typed emit* helpers when possible.

Socket behaviour (package)

  • Auto-connect on configureChat / ChatProvider when apiUrl, clientId, and accessToken are set
  • Reconnect if the user is already authenticated but the socket is idle or disconnected
  • Emit queue — calls before connect are not dropped; they flush on connect
  • Listener queue — on() before connect is retained and attached to the live socket
  • forceNew: true — avoids reusing a dead Socket.IO Manager (common cause of status stuck on idle in host apps)

Socket stuck on idle?

  1. Confirm configureChat / ChatProvider config includes apiUrl, socketUrl, clientId, and a non-empty accessToken.
  2. If the host JWT has no email, call bootstrapChatSession({ email, name, hostAccessToken }).
  3. Prefer socketUrl as the origin only (e.g. https://api.example.com) — trailing /api/v1 is stripped automatically.
  4. Call ensureChatSession() or connectChatSocket(chatToken, clientId) after login if you use autoConnect={false}.
  5. Watch selectSocketStatus — it should move idle → connecting → connected.

Audio & Video Calling

Calling uses RealtimeX call_* signaling and VideoSDK for media. <ChatNavigator /> already mounts CallProvider and ActiveCallScreen.

Native wiring is required. Follow host-setup/NATIVE_SETUP.md for Android/iOS permissions, PiP, and screen share. Without it, JS calling UI alone is not enough for PiP or iOS screen share.

JS setup

import {
  configureChat,
  configureCalling,
  ChatProvider,
  ChatNavigator,
} from '@realtimexsco/live-chat-react-native';

configureCalling({ enabled: true });

configureChat({
  apiUrl: 'https://api.example.com/api/v1',
  socketUrl: 'https://api.example.com',
  clientId: 'your-client-id',
  accessToken: () => getHostAccessToken(),
  // Call REST base (rooms at `${callUri}/calls/rooms`). String or getter.
  callUri: 'https://api.example.com/api/v2',
  ui: {
    chatRoom: {
      header: {
        showVoiceCall: true,
        showVideoCall: true,
      },
    },
  },
});

export function ChatTab() {
  return (
    <ChatProvider>
      <ChatNavigator />
    </ChatProvider>
  );
}

index.js (once at startup):

import { register } from '@videosdk.live/react-native-sdk';
register();

Programmatic calls:

import { callManager } from '@realtimexsco/live-chat-react-native';

await callManager.startOutgoingCall({
  conversationId,
  mode: 'video', // or 'audio'
  peer: { id: userId, name: displayName },
  isGroupCall: false,
});

If you do not use ChatNavigator, mount CallProvider and register ActiveCallScreen yourself (pass navigationRef).

In-call: mute, camera, More menu (flip camera, screen share, whiteboard, recording when allowed, raise hand, chat, reactions, people). The local self-view tile is draggable and stays above the control bar.

Permissions (host)

iOS Info.plist: NSMicrophoneUsageDescription, NSCameraUsageDescription, RTCAppGroupIdentifier, RTCScreenSharingExtension, UIBackgroundModes → audio, voip, picture-in-picture.

Android Manifest: RECORD_AUDIO, CAMERA, MODIFY_AUDIO_SETTINGS, BLUETOOTH / BLUETOOTH_CONNECT and the screen-share FGS are merged from android-screenshare/ when autolinked — nothing to add.

Snippets: host-setup/android/AndroidManifest.snippet.xml, host-setup/ios/snippets/.

Screen sharing

| Platform | Setup | |----------|--------| | Android | Autolinked (android-screenshare/). Fallback: host-setup/android/AndroidManifest.snippet.xml. | | iOS | VideosdkRPK is autolinked. Create a Broadcast Upload Extension target, use the 3 templates in host-setup/ios/screen-share/Broadcast/, and add pod RealtimexLiveChatBroadcast to that target in your Podfile. Details: ios/SCREEN_SHARE_HOST_SETUP.md. |

When a remote participant is sharing, PiP shows the screen share (not their camera). Otherwise PiP shows their camera, or avatar if the camera is off.

Picture-in-Picture

| Platform | Setup | |----------|--------| | Android | CallPipModule is autolinked and hooks the activity itself. Add android:supportsPictureInPicture="true" to MainActivity. No MainActivity code. | | iOS | CallPipModule is autolinked (pod install). Enable picture-in-picture background mode. Device only (not simulator). Remove any old copies from your app target. |

Without CallPip, calls still work; PiP is skipped. Controls (mic / camera / end) stay hidden while PiP is active.

Camera during PiP / iPad multitasking (iOS)

By default iOS pauses the camera when the app enters PiP, Split View or Slide Over. To keep the local video live, apply the WebRTC patch shipped at host-setup/patches/@videosdk.live+react-native-webrtc+0.0.22.patch with patch-package. Copy it into your app's patches/ folder and add "postinstall": "patch-package". The patch enables AVCaptureSession.multitaskingCameraAccessEnabled when isMultitaskingCameraAccessSupported is true, before startRunning(), and restarts the capture session once CallPipModule reports that PiP started. CallPipModule starts PiP on UIApplicationWillResignActiveNotification, because iOS keeps the camera only if PiP is already active when the app leaves the foreground. It also contains the Android rounded self-view (cornerRadius) change.

To check on a device, filter the Xcode console for RTCCameraVideoCapturer and CallPipModule. You should see Multitasking camera access supported=1 enabled=1 and willResignActive: starting PiP before backgrounding.

| iOS version | Requirement | |-------------|-------------| | 18+ | voip in UIBackgroundModes (already required for calls). No entitlement. | | 16–17, iPad with Stage Manager | Nothing extra | | 16–17, PiP / Split View / Slide Over | com.apple.developer.avfoundation.multitasking-camera-access entitlement. Apple must grant it first (see host-setup/ios/snippets/Entitlements.snippet.plist) | | 15 | Not available; the camera pauses in the background and resumes on return |

Common native issues

| Symptom | Fix | |---------|-----| | Android 14+ crash after accepting screen share | FGS MediaProjection missing — check autolink | | iOS screen share never starts | App Group / RTCScreenSharingExtension mismatch | | CallPipModule undefined | Android: clean rebuild; iOS: pod install (check RealtimexLiveChat in Podfile.lock) | | iOS duplicate symbol CallPipModule / VideosdkRPK | Delete old copies from the app target | | Square self-view on Android | Optional WebRTC cornerRadius patch (see Troubleshooting below) | | iOS: remote side sees your video freeze in PiP / Split View | Apply host-setup/patches/ WebRTC patch; check voip background mode (iOS 18+) or the entitlement (iOS 16–17) |


Embed screens

Preferred: mount <ChatNavigator /> inside <ChatProvider>. It registers the chat stack (conversation list, room, new chat, group create, chat info, global search) so taps redirect without host route setup. Session + Socket.IO connect automatically when autoConnect is enabled (default).

Advanced: individual screens are still exported if you build a custom navigator: ConversationListScreen, ChatRoomScreen, CreateGroupScreen, SelectUserScreen, SelectGroupMembersScreen, ChatInfoScreen, GlobalMessageSearchScreen, ProfileScreen.

Also exported for custom layouts: MessageComposer, MessageBubble, ConversationListItem, ChatRoomHeader (and their prop types).

Route names: AppScreen.*. Param types: ChatStackParamList, RootStackParamList, ChatRoomParams, etc.


Features

DMs · groups · Socket.IO (queued emit / listen) · reactions · replies · forward · edit/delete · pin/star · attachments · search · typing · read receipts · audio and video calls · light/dark · dynamic brand accent (hex) · configurable fonts, styles, chat info & replaceable UI components


Demo app

This repository also runs as a full React Native app:

yarn install
yarn start
yarn ios   # or yarn android

Demo boot calls configureChat from .env via src/configureDemo.ts.

Complete the Set Up Your Environment guide first.

iOS pods

bundle install
bundle exec pod install

Troubleshooting

| Issue | What to check | | ----- | ------------- | | Socket status stays idle | apiUrl / socketUrl / clientId / accessToken set; JWT has email or you passed email to bootstrapChatSession; see Socket stuck on idle? | | Emits do nothing | Wait for selectSocketStatus === 'connected', or rely on the emit queue after session bootstrap | | Nested RN / Metro codegen errors | Single react / react-native tree — see Peer dependencies | | General RN issues | React Native Troubleshooting |


More information

| Topic | Link | | ----------------- | ---------------------------------------------------------------------------------------------------------------------------- | | Full setup & docs | https://realtimex.softwareco.com/documentations/package | | Web package | npmjs.com/package/@realtimexsco/live-chat | | npm package | npmjs.com/package/@realtimexsco/live-chat-react-native | | License | MIT |


License

The MIT License (MIT)

Copyright (c) 2026 RealtimeX

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.