react-peer-chat
Version:
An easy to use react component for impleting peer-to-peer chatting.
357 lines (300 loc) • 12.2 kB
Markdown
# react-peer-chat
A simple-to-use React component for implementing peer-to-peer chatting, powered by [peerjs](https://peerjs.com/).
## Features
- Peer-to-peer chat without requiring knowledge of [WebRTC](https://webrtc.org/)
- Easy integration
- Supports persistent text chat across page reloads
- Recovers old chats upon reconnection
- Option to clear chat on command
- Supports audio/voice chat
- Multiple peer connections. See [multi-peer usage](#Multi-Peer-Usage)
- Fully customizable. See [usage with FaC](#Full-Customization)
## Installation
To install `react-peer-chat`:
```bash
# with npm:
npm install react-peer-chat --save
# with yarn:
yarn add react-peer-chat
# with pnpm:
pnpm add react-peer-chat
# with bun:
bun add react-peer-chat
```
## Usage
`react-peer-chat` provides two primary methods to integrate chat functionality into your React apps: through the `<Chat>` component and the `useSpeech` hook.
It also exports a `clearChat` function that clears the text chat from the browser's session storage when called.
### Chat Component
The default export of `react-peer-chat` is the `<Chat>` component, which offers the easiest integration and is fully configurable. When using the `<Chat>` component, the user will initially see two buttons (SVG icons) - one for text chat and the other for voice chat.
#### Basic Usage
```jsx
import React from "react";
import Chat, { clearChat } from "react-peer-chat";
export default function App() {
return (
<div>
<Chat name="John Doe" peerId="my-unique-id" remotePeerId="remote-unique-id" />
{/* Text chat will be cleared when following button is clicked. */}
<button onClick={clearChat}>Clear Chat</button>
</div>
);
}
```
#### Multi Peer Usage
```jsx
import React from "react";
import Chat, { clearChat } from "react-peer-chat";
export default function App() {
return (
<div>
<Chat
name="John Doe"
peerId="my-unique-id"
remotePeerId={["remote-unique-id-1", "remote-unique-id-2", "remote-unique-id-3"]} // Array of remote peer ids
/>
{/* Text chat will be cleared when following button is clicked. */}
<button onClick={clearChat}>Clear Chat</button>
</div>
);
}
```
#### Partial Customization
Use the props provided by the `<Chat>` component for customization.
```jsx
import React from "react";
import Chat from "react-peer-chat";
export default function App() {
return (
<Chat
name="John Doe"
peerId="my-unique-id"
remotePeerId="remote-unique-id"
dialogOptions={{
position: "left",
style: { padding: "4px" },
}}
props={{ title: "React Peer Chat Component" }}
onError={() => console.error("Browser not supported!")}
onMicError={() => console.error("Microphone not accessible!")}
/>
);
}
```
#### Full Customization
Use Function as Children (FaC) to fully customize the `<Chat>` component.
```jsx
import React from 'react'
import Chat from 'react-peer-chat'
export default function App() {
return <Chat
name='John Doe'
peerId='my-unique-id'
remotePeerId='remote-unique-id'
onError={() => console.error('Browser not supported!')}
onMicError={() => console.error('Microphone not accessible!')}
>
{({ remotePeers, messages, sendMessage, audio, setAudio }) => (
<YourCustomComponent>
{...}
</YourCustomComponent>
)}
</Chat>
}
```
#### Custom ICE Servers
You can also provide custom ICE servers to avoid connectivity issues if the free TURN server provided by `react-peer-chat` expires.
```jsx
import React from "react";
import Chat from "react-peer-chat";
export default function App() {
return (
<Chat
name="John Doe"
peerId="my-unique-id"
remotePeerId="remote-unique-id"
peerOptions={{
config: {
iceServers: [
{ urls: "stun:stun-server.example.com:19302" },
{
urls: "turn:turn-server.example.com:19403",
username: "optional-username",
credential: "auth-token",
},
],
},
// other peerjs options (optional)
}}
/>
);
}
```
### useChat Hook
The `useChat` hook is ideal when you want to completely redesign the Chat UI or handle the audio stream differently, instead of using traditional playback methods.
```jsx
import React, { useEffect, useRef, useState } from "react";
import { clearChat, useChat } from "react-peer-chat";
import { BiSolidMessageDetail, BiSolidMessageX, BsFillMicFill, BsFillMicMuteFill, GrSend } from "react-peer-chat/icons";
function Chat({ text = true, audio = true, onMessageReceived, dialogOptions, props = {}, children, ...hookProps }) {
const { peerId, audioStreamRef, ...childrenOptions } = useChat({
text,
audio,
onMessageReceived: modifiedOnMessageReceived,
...hookProps,
});
const { remotePeers, messages, sendMessage, audio: audioEnabled, setAudio } = childrenOptions;
const containerRef = useRef(null);
const [dialog, setDialog] = useState(false);
const dialogRef = useRef(null);
const inputRef = useRef(null);
const [notification, setNotification] = useState(false);
function modifiedOnMessageReceived(message) {
if (!dialogRef.current?.open) setNotification(true);
onMessageReceived?.(message);
}
useEffect(() => {
if (dialog) dialogRef.current?.show();
else dialogRef.current?.close();
}, [dialog]);
useEffect(() => {
const container = containerRef.current;
if (container) container.scrollTop = container.scrollHeight;
}, [dialog, remotePeers, messages]);
return (
<div className="rpc-main rpc-font" {...props}>
{typeof children === "function" ? (
children(childrenOptions)
) : (
<>
{text && (
<div className="rpc-dialog-container">
{dialog ? (
<BiSolidMessageX title="Close chat" onClick={() => setDialog(false)} />
) : (
<div className="rpc-notification">
<BiSolidMessageDetail
title="Open chat"
onClick={() => {
setNotification(false);
setDialog(true);
}}
/>
{notification && <span className="rpc-badge" />}
</div>
)}
<dialog ref={dialogRef} className={`${dialog ? "rpc-dialog" : ""} rpc-position-${dialogOptions?.position || "center"}`} style={dialogOptions?.style}>
<div className="rpc-heading">Chat</div>
<hr className="rpc-hr" />
<div>
<div ref={containerRef} className="rpc-message-container">
{messages.map(({ id, text }, i) => (
<div key={i}>
<strong>{id === peerId ? "You" : remotePeers[id]}: </strong>
<span>{text}</span>
</div>
))}
</div>
<hr className="rpc-hr" />
<form
className="rpc-input-container"
onSubmit={(e) => {
e.preventDefault();
const text = inputRef.current?.value;
if (text) {
inputRef.current.value = "";
sendMessage({ id: peerId, text });
}
}}
>
<input ref={inputRef} className="rpc-input rpc-font" placeholder="Enter a message" />
<button type="submit" className="rpc-button">
<GrSend title="Send message" />
</button>
</form>
</div>
</dialog>
</div>
)}
{audio && (
<button>
{audioEnabled ? <BsFillMicFill title="Turn mic off" onClick={() => setAudio(false)} /> : <BsFillMicMuteFill title="Turn mic on" onClick={() => setAudio(true)} />}
</button>
)}
</>
)}
{audio && (
<button className="rpc-button" onClick={() => setAudio(!audioEnabled)}>
{audioEnabled ? <BsFillMicFill title="Turn mic off" /> : <BsFillMicMuteFill title="Turn mic on" />}
</button>
)}
</div>
);
}
export default function App() {
return (
<div>
<Chat name="John Doe" peerId="my-unique-id" remotePeerId="remote-unique-id" />
{/* Text chat will be cleared when following button is clicked. */}
<button onClick={clearChat}>Clear Chat</button>
</div>
);
}
```
#### Basic Usage
## API Reference
### useChat Hook
Here is the full API for the `useChat` hook, these options can be passed as paramerters to the hook:
| Parameter | Type | Required | Default | Description |
| - | - | - | - | - |
| `name` | `string` | No | Anonymous User | Name of the peer which will be shown to the remote peer. |
| `peerId` | `string` | Yes | - | It is the unique id that is alloted to a peer. It uniquely identifies a peer from other peers. |
| `remotePeerId` | `string \| string[]` | No | - | It is the unique id (or array of unique ids) of the remote peer(s). If provided, the peer will try to connect to the remote peer(s). |
| `text` | `boolean` | No | `true` | Text chat will be enabled if this property is set to true. |
| `recoverChat` | `boolean` | No | `false` | Old chats will be recovered upon reconnecting with the same peer(s). |
| `audio` | `boolean` | No | `true` | Voice chat will be enabled if this property is set to true. |
| `peerOptions` | [`PeerOptions`](#PeerOptions) | No | - | Options to customize peerjs Peer instance. |
| `onError` | `Function` | No | `() => alert('Browser not supported! Try some other browser.')` | Function to be executed if browser doesn't support `WebRTC` |
| `onMicError` | `Function` | No | `() => alert('Microphone not accessible!')` | Function to be executed when microphone is not accessible. |
| `onMessageSent` | `Function` | No | - | Function to be executed when a text message is sent to other peers. |
| `onMessageReceived` | `Function` | No | - | Function to be executed when a text message is received from other peers. |
### Chat Component
Here is the full API for the `<Chat>` component, these properties can be set on an instance of `<Chat>`. It contains all the parameters
that are listed in [useChat Hook API Reference](#usechat-hook-1) along with the following parameters:
| Parameter | Type | Required | Default | Description |
| - | - | - | - | - |
| `dialogOptions` | [`DialogOptions`](#DialogOptions) | No | { position: 'center' } | Options to customize text dialog box styling. |
| `props` | `React.DetailedHTMLProps` | No | - | Props to customize the `<Chat>` component. |
| `children` | [`Children`](#Children) | No | - | See [usage with FaC](#Full-Customization) |
### Types
#### PeerOptions
```typescript
import { PeerOptions } from "peerjs";
```
#### DialogOptions
```typescript
import { CSSProperties } from "react";
type DialogPosition = "left" | "center" | "right";
type DialogOptions = {
position: DialogPosition;
style: CSSProperties;
};
```
#### Children
```typescript
import { ReactNode } from "react";
type RemotePeers = { [id: string]: string };
type Message = {
id: string;
text: string;
};
type ChildrenOptions = {
remotePeers?: RemotePeers;
messages?: Message[];
sendMessage?: (message: Message) => void;
audio?: boolean;
setAudio?: (audio: boolean) => void;
};
type Children = (childrenOptions: ChildrenOptions) => ReactNode;
```
## Author
[Sahil Aggarwal](https://www.github.com/SahilAggarwal2004)