UNPKG

react-peer-chat

Version:

An easy to use react component for impleting peer-to-peer chatting.

357 lines (300 loc) 12.2 kB
# 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)