react-peer-chat 0.3.4 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,2 +1,143 @@
1
1
  # react-peer-chat
2
- An easy to use react component for impleting peer-to-peer chatting.
2
+ An easy to use react component for impleting peer-to-peer chatting using [peerjs](https://peerjs.com/) under the hood.
3
+
4
+ It is as easy as to import a React component!
5
+ ## Features
6
+ - Peer-to-peer chat without need to have any knowledge about WebRTC
7
+ - Easy to use
8
+ - Supports text chat that persists on page reload
9
+ - Clear text chat on command
10
+ - Supports voice chat
11
+ - Fully Customizable. See [usage with FoC](#Full-Customization)
12
+ ## Installation
13
+ To install react-peer-chat
14
+ ```bash
15
+ # with npm:
16
+ npm install react-peer-chat --save
17
+
18
+ # with yarn:
19
+ yarn add react-peer-chat
20
+
21
+ # with pnpm:
22
+ pnpm add react-peer-chat
23
+
24
+ # with bun:
25
+ bun add react-peer-chat
26
+ ```
27
+ ## Usage
28
+ `react-peer-chat` default exports `<Chat>` component. When you use the `<Chat>` component, initially the user will see 2 buttons (svg icons), one for text chat and other for voice chat.
29
+
30
+ It also exports a `clearChat` function that clears the text chat whenever invoked.
31
+ #### Basic Usage
32
+ ```jsx
33
+ import React from 'react';
34
+ import Chat, { clearChat } from 'react-peer-chat';
35
+ import 'react-peer-chat/build/styles.css';
36
+
37
+ export default function App() {
38
+ return <div>
39
+ <Chat
40
+ name='John Doe'
41
+ peerId='some-unique-id'
42
+ remotePeerId='another-unique-id'
43
+ />
44
+ {/* Text chat will be cleared when following button is clicked. */}
45
+ <button onClick={clearChat}>Clear Chat</button>
46
+ </div>
47
+ }
48
+ ```
49
+ #### Partial Customization
50
+ Use props provided by `<Chat>` component to customize it.
51
+ ```jsx
52
+ import React from 'react';
53
+ import Chat from 'react-peer-chat';
54
+ import 'react-peer-chat/build/styles.css';
55
+
56
+ export default function App() {
57
+ return <Chat
58
+ name='John Doe'
59
+ peerId='some-unique-id'
60
+ remotePeerId='another-unique-id'
61
+ dialogOptions={{
62
+ position: 'left',
63
+ style: { padding: '4px' }
64
+ }}
65
+ props={{ title: 'React Peer Chat Component' }}
66
+ onError={() => {
67
+ console.error('Microphone not accessible!');
68
+ }}
69
+ />
70
+ }
71
+ ```
72
+ #### Full Customization
73
+ Use Function as Children(FoC) to fully customize the `<Chat>` component.
74
+ ```jsx
75
+ import React from 'react'
76
+ import Chat from 'react-peer-chat'
77
+ // import 'react-peer-chat/build/styles.css' (No need to import CSS when using custom component)
78
+
79
+ export default function App() {
80
+ return <Chat
81
+ name='John Doe'
82
+ peerId='some-unique-id'
83
+ remotePeerId='another-unique-id'
84
+ onError={() => {
85
+ console.error('Microphone not accessible!');
86
+ }}
87
+ >
88
+ {({ remotePeerName, messages, addMessage, audio, setAudio }) => (
89
+ <YourCustomComponent>
90
+ {...}
91
+ </YourCustomComponent>
92
+ )}
93
+ </Chat>
94
+ }
95
+ ```
96
+ ## Chat Component API Reference
97
+ Here is the full API for the `<Chat>` component, these properties can be set on an instance of Chat:
98
+ | Parameter | Type | Required | Default | Description |
99
+ | - | - | - | - | - |
100
+ | `name` | `String` | No | Anonymous User | Name of the peer which will be shown to the remote peer. |
101
+ | `peerId` | `String` | Yes | - | It is the unique id that is alloted to a peer. It uniquely identifies a peer from other peers. |
102
+ | `remotePeerId` | `String` | No | - | It is the unique id of the remote peer. If provided, the peer will try to connect to the remote peer. |
103
+ | `text` | `Boolean` | No | `true` | Text chat will be enabled if this property is set to true. |
104
+ | `voice` | `Boolean` | No | `true` | Voice chat will be enabled if this property is set to true. |
105
+ | `peerOptions` | [`PeerOptions`](#PeerOptions) | No | - | Options to customize peerjs Peer instance. |
106
+ | `dialogOptions` | [`DialogOptions`](#DialogOptions) | No | { position: 'center' } | Options to customize text dialog box styling. |
107
+ | `onError` | `Function` | No | `() => alert('Microphone not accessible!')` | Function to be executed when microphone is not accessible. |
108
+ | `props` | `React.DetailedHTMLProps` | No | - | Props to customize the `<Chat>` component. |
109
+ | `children` | [`Children`](#Children) | No | - | Props to customize the `<Chat>` component. |
110
+ ### Types
111
+ #### PeerOptions
112
+ ```typescript
113
+ import { PeerOptions } from 'peerjs'
114
+ ```
115
+ #### DialogOptions
116
+ ```typescript
117
+ import { CSSProperties } from 'react';
118
+ type DialogPosition = 'left' | 'center' | 'right';
119
+ type DialogOptions = {
120
+ position: DialogPosition;
121
+ style: CSSProperties;
122
+ };
123
+ ```
124
+ #### Children
125
+ ```typescript
126
+ import { ReactNode } from 'react';
127
+ type Message = {
128
+ id: string;
129
+ text: string;
130
+ };
131
+ type ChildrenOptions = {
132
+ remotePeerName?: string;
133
+ messages?: Message[];
134
+ addMessage?: (message: Message, sendToRemotePeer?: boolean) => void;
135
+ audio?: boolean;
136
+ setAudio?: (audio: boolean) => void;
137
+ };
138
+ type Children = (childrenOptions: ChildrenOptions) => ReactNode;
139
+ ```
140
+ ## Used By
141
+ - [StarWars](https://starwarsgame.vercel.app/)
142
+ ## Author
143
+ [Sahil Aggarwal](https://www.github.com/SahilAggarwal2004)
package/build/icons.js CHANGED
@@ -1,27 +1,27 @@
1
1
  import React from "react";
2
2
  export function BiSolidMessageDetail(props) {
3
- return React.createElement("span", { ...props },
3
+ return React.createElement("span", { className: 'rpc-icon-container', ...props },
4
4
  React.createElement("svg", { fill: "currentColor", width: '1.5rem', height: '1.5rem', className: 'rpc-scale-down' },
5
5
  React.createElement("path", { d: "M20 2H4c-1.103 0-2 .894-2 1.992v12.016C2 17.106 2.897 18 4 18h3v4l6.351-4H20c1.103 0 2-.894 2-1.992V3.992A1.998 1.998 0 0 0 20 2zm-6 11H7v-2h7v2zm3-4H7V7h10v2z" })));
6
6
  }
7
7
  export function BiSolidMessageX(props) {
8
- return React.createElement("span", { ...props },
8
+ return React.createElement("span", { className: 'rpc-icon-container', ...props },
9
9
  React.createElement("svg", { fill: "currentColor", width: '1.5rem', height: '1.5rem', className: 'rpc-scale-down' },
10
10
  React.createElement("path", { d: "M20 2H4c-1.103 0-2 .894-2 1.992v12.016C2 17.106 2.897 18 4 18h3v4l6.351-4H20c1.103 0 2-.894 2-1.992V3.992A1.998 1.998 0 0 0 20 2zm-3.293 11.293-1.414 1.414L12 11.414l-3.293 3.293-1.414-1.414L10.586 10 7.293 6.707l1.414-1.414L12 8.586l3.293-3.293 1.414 1.414L13.414 10l3.293 3.293z" })));
11
11
  }
12
12
  export function GrSend(props) {
13
- return React.createElement("span", { ...props },
13
+ return React.createElement("span", { className: 'rpc-icon-container', ...props },
14
14
  React.createElement("svg", { fill: "currentColor", width: '1.5rem', height: '1.5rem', className: 'rpc-scale-down rpc-invert' },
15
15
  React.createElement("path", { fill: "none", stroke: "#000", strokeWidth: 2, d: "M22,3 L2,11 L20.5,19 L22,3 Z M10,20.5 L13,16 M15.5,9.5 L9,14 L9.85884537,20.0119176 C9.93680292,20.5576204 10.0751625,20.5490248 10.1651297,20.009222 L11,15 L15.5,9.5 Z" })));
16
16
  }
17
17
  export function BsFillMicFill(props) {
18
- return React.createElement("span", { ...props },
18
+ return React.createElement("span", { className: 'rpc-icon-container', ...props },
19
19
  React.createElement("svg", { fill: "currentColor", width: '1rem', height: '1rem' },
20
20
  React.createElement("path", { d: "M5 3a3 3 0 0 1 6 0v5a3 3 0 0 1-6 0V3z" }),
21
21
  React.createElement("path", { d: "M3.5 6.5A.5.5 0 0 1 4 7v1a4 4 0 0 0 8 0V7a.5.5 0 0 1 1 0v1a5 5 0 0 1-4.5 4.975V15h3a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1h3v-2.025A5 5 0 0 1 3 8V7a.5.5 0 0 1 .5-.5z" })));
22
22
  }
23
23
  export function BsFillMicMuteFill(props) {
24
- return React.createElement("span", { ...props },
24
+ return React.createElement("span", { className: 'rpc-icon-container', ...props },
25
25
  React.createElement("svg", { fill: "currentColor", width: '1rem', height: '1rem' },
26
26
  React.createElement("path", { d: "M13 8c0 .564-.094 1.107-.266 1.613l-.814-.814A4.02 4.02 0 0 0 12 8V7a.5.5 0 0 1 1 0v1zm-5 4c.818 0 1.578-.245 2.212-.667l.718.719a4.973 4.973 0 0 1-2.43.923V15h3a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1h3v-2.025A5 5 0 0 1 3 8V7a.5.5 0 0 1 1 0v1a4 4 0 0 0 4 4zm3-9v4.879L5.158 2.037A3.001 3.001 0 0 1 11 3z" }),
27
27
  React.createElement("path", { d: "M9.486 10.607 5 6.12V8a3 3 0 0 0 4.486 2.607zm-7.84-9.253 12 12 .708-.708-12-12-.708.708z" })));
package/build/index.d.ts CHANGED
@@ -5,7 +5,7 @@ type Message = {
5
5
  text: string;
6
6
  };
7
7
  type ChildrenOptions = {
8
- remotePeer?: string;
8
+ remotePeerName?: string;
9
9
  messages?: Message[];
10
10
  addMessage?: (message: Message, sendToRemotePeer?: boolean) => void;
11
11
  audio?: boolean;
@@ -24,7 +24,7 @@ type Props = {
24
24
  voice?: boolean;
25
25
  peerOptions?: PeerOptions;
26
26
  dialogOptions?: DialogOptions;
27
- onError?: () => void;
27
+ onError?: Function;
28
28
  children?: (childrenOptions: ChildrenOptions) => ReactNode;
29
29
  props?: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
30
30
  };
package/build/index.js CHANGED
@@ -4,7 +4,7 @@ import { BiSolidMessageDetail, BiSolidMessageX, BsFillMicFill, BsFillMicMuteFill
4
4
  export default function Chat({ name, peerId, remotePeerId, peerOptions, text = true, voice = true, dialogOptions, onError = () => alert("Microphone not accessible!"), children, props = {} }) {
5
5
  const [peer, setPeer] = useState();
6
6
  const [notification, setNotification] = useState(false);
7
- const [remotePeer, setRemotePeer] = useStorage('rpc-remote-peer', '', { save: true });
7
+ const [remotePeerName, setRemotePeer] = useStorage('rpc-remote-peer', '', { save: true });
8
8
  const [messages, setMessages] = useStorage('rpc-messages', [], { save: true });
9
9
  const connRef = useRef();
10
10
  const [dialog, setDialog] = useState(false);
@@ -26,11 +26,11 @@ export default function Chat({ name, peerId, remotePeerId, peerOptions, text = t
26
26
  function handleConnection(conn) {
27
27
  connRef.current = conn;
28
28
  conn.on('open', () => {
29
- conn.on('data', ({ type, message, remotePeer, messages }) => {
29
+ conn.on('data', ({ type, message, remotePeerName, messages }) => {
30
30
  if (type === 'message')
31
31
  addMessage(message);
32
32
  else if (type === 'init') {
33
- setRemotePeer(remotePeer || 'Anonymous User');
33
+ setRemotePeer(remotePeerName || 'Anonymous User');
34
34
  setMessages(old => {
35
35
  if (messages.length > old.length)
36
36
  return messages;
@@ -38,7 +38,7 @@ export default function Chat({ name, peerId, remotePeerId, peerOptions, text = t
38
38
  });
39
39
  }
40
40
  });
41
- conn.send({ type: 'init', remotePeer: name, messages });
41
+ conn.send({ type: 'init', remotePeerName: name, messages });
42
42
  });
43
43
  }
44
44
  useEffect(() => {
@@ -115,10 +115,10 @@ export default function Chat({ name, peerId, remotePeerId, peerOptions, text = t
115
115
  const container = containerRef.current;
116
116
  if (container)
117
117
  container.scrollTop = container.scrollHeight;
118
- }, [dialog, remotePeer, messages]);
119
- return React.createElement("div", { className: 'rpc-main', ...props },
120
- typeof children === 'function' ? children({ remotePeer, messages, addMessage, audio, setAudio }) : React.createElement(React.Fragment, null,
121
- text && React.createElement("div", null,
118
+ }, [dialog, remotePeerName, messages]);
119
+ return React.createElement("div", { className: 'rpc-main rpc-font', ...props },
120
+ typeof children === 'function' ? children({ remotePeerName, messages, addMessage, audio, setAudio }) : React.createElement(React.Fragment, null,
121
+ text && React.createElement("div", { className: 'rpc-dialog-container' },
122
122
  dialog ? React.createElement(BiSolidMessageX, { onClick: () => setDialog(false) }) : React.createElement("div", { className: 'rpc-notification' },
123
123
  React.createElement(BiSolidMessageDetail, { onClick: () => {
124
124
  setNotification(false);
@@ -127,14 +127,14 @@ export default function Chat({ name, peerId, remotePeerId, peerOptions, text = t
127
127
  notification && React.createElement("span", { className: 'rpc-badge' })),
128
128
  React.createElement("dialog", { ref: dialogRef, className: `${dialog ? 'rpc-dialog' : ''} rpc-position-${(dialogOptions === null || dialogOptions === void 0 ? void 0 : dialogOptions.position) || 'center'}`, style: dialogOptions === null || dialogOptions === void 0 ? void 0 : dialogOptions.style },
129
129
  React.createElement("div", { className: 'rpc-heading' }, "Chat"),
130
- React.createElement("hr", null),
130
+ React.createElement("hr", { className: 'rpc-hr' }),
131
131
  React.createElement("div", null,
132
132
  React.createElement("div", { ref: containerRef, className: 'rpc-message-container' }, messages.map(({ id, text }, i) => React.createElement("div", { key: i },
133
133
  React.createElement("strong", null,
134
- id === peerId ? 'You' : remotePeer,
134
+ id === peerId ? 'You' : remotePeerName,
135
135
  ": "),
136
136
  React.createElement("span", null, text)))),
137
- React.createElement("hr", null),
137
+ React.createElement("hr", { className: 'rpc-hr' }),
138
138
  React.createElement("form", { className: 'rpc-input-container', onSubmit: e => {
139
139
  var _a;
140
140
  e.preventDefault();
@@ -144,8 +144,8 @@ export default function Chat({ name, peerId, remotePeerId, peerOptions, text = t
144
144
  addMessage({ id: peerId, text }, true);
145
145
  }
146
146
  } },
147
- React.createElement("input", { ref: inputRef, className: 'rpc-input', placeholder: 'Enter a message' }),
148
- React.createElement("button", { type: 'submit' },
147
+ React.createElement("input", { ref: inputRef, className: 'rpc-input rpc-font', placeholder: 'Enter a message' }),
148
+ React.createElement("button", { type: 'submit', className: 'rpc-button' },
149
149
  React.createElement(GrSend, null)))))),
150
150
  voice && React.createElement("div", null, audio ? React.createElement(BsFillMicFill, { title: "Turn mic off", onClick: () => setAudio(false) }) : React.createElement(BsFillMicMuteFill, { title: "Turn mic on", onClick: () => setAudio(true) }))),
151
151
  voice && audio && React.createElement("audio", { ref: streamRef, autoPlay: true, style: { display: 'none' } }));
package/build/styles.css CHANGED
@@ -1,9 +1,17 @@
1
+ .rpc-font {
2
+ font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
3
+ }
4
+
1
5
  .rpc-main {
2
6
  display: flex;
3
7
  align-items: center;
4
8
  column-gap: 0.5rem;
5
9
  }
6
10
 
11
+ .rpc-dialog-container {
12
+ position: relative;
13
+ }
14
+
7
15
  .rpc-notification {
8
16
  position: relative;
9
17
  }
@@ -19,32 +27,35 @@
19
27
  }
20
28
 
21
29
  .rpc-dialog {
22
- display: flex;
23
- flex-direction: column;
24
- row-gap: 0.25rem;
30
+ position: absolute;
25
31
  background-color: black;
26
32
  color: white;
27
- padding-top: 0.25rem;
28
- outline: 2px solid white;
29
- border-radius: 0.25rem;
30
- font-size: smaller;
33
+ padding: 0.4rem 0 0.25rem 0;
34
+ border-radius: 0.4rem;
35
+ font-size: small;
31
36
  }
32
37
 
33
38
  .rpc-position-left {
34
- left: 0.5rem;
39
+ left: 0.6rem;
35
40
  translate: -100%;
36
41
  }
37
42
 
38
43
  .rpc-position-center {
44
+ left: 0.5rem;
39
45
  translate: -50%;
40
46
  }
41
47
 
42
48
  .rpc-position-right {
43
- left: 0.5rem;
49
+ left: 0.3rem;
50
+ }
51
+
52
+ .rpc-hr {
53
+ margin: 0.25rem 0;
54
+ border-color: rgba(255, 255, 255, 0.7);
44
55
  }
45
56
 
46
57
  .rpc-message-container {
47
- height: 6rem;
58
+ height: 8rem;
48
59
  overflow-y: scroll;
49
60
  padding-inline: 0.5rem;
50
61
  margin-bottom: 0.25rem;
@@ -64,29 +75,47 @@
64
75
 
65
76
  .rpc-heading {
66
77
  text-align: center;
78
+ font-size: medium;
67
79
  font-weight: bold;
68
80
  padding-inline: 0.5rem;
69
81
  }
70
82
 
71
83
  .rpc-input-container {
72
84
  display: flex;
85
+ width: 15rem;
86
+ max-width: 90vw;
73
87
  column-gap: 0.25rem;
74
- padding-inline: 0.5rem;
75
- padding-block: 0.4rem;
88
+ padding: 0.25rem 0.5rem;
76
89
  }
77
90
 
78
91
  .rpc-input {
92
+ color: white;
93
+ width: 100%;
79
94
  background-color: black;
80
95
  border: none;
81
96
  outline: 1px solid rgba(255, 255, 255, 0.8);
82
97
  border-radius: 0.25rem;
83
- padding-inline: 0.25rem;
98
+ padding: 0.35rem 0.25rem;
99
+ }
100
+
101
+ .rpc-input::placeholder {
102
+ color: rgba(255, 255, 255, 0.7);
84
103
  }
85
104
 
86
105
  .rpc-input:focus {
87
106
  outline: 2px solid white;
88
107
  }
89
108
 
109
+ .rpc-button {
110
+ all: unset;
111
+ display: flex;
112
+ }
113
+
114
+ .rpc-icon-container {
115
+ display: flex;
116
+ align-items: center;
117
+ }
118
+
90
119
  .rpc-scale-down {
91
120
  scale: calc(2/3);
92
121
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-peer-chat",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "An easy to use react component for impleting peer-to-peer chatting.",
5
5
  "main": "./build/index.js",
6
6
  "type": "module",