react-peer-chat 0.6.8 → 0.7.0
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/.vscode/settings.json +4 -0
- package/README.md +258 -93
- package/dist/connection.d.ts +2 -0
- package/dist/connection.js +4 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.js +15 -0
- package/dist/hooks.d.ts +13 -0
- package/dist/hooks.js +155 -0
- package/dist/icons.d.ts +1 -1
- package/dist/index.d.ts +6 -42
- package/dist/index.js +27 -145
- package/dist/storage.d.ts +3 -1
- package/dist/storage.js +6 -18
- package/dist/types.d.ts +45 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +1 -0
- package/package.json +29 -22
package/README.md
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
# react-peer-chat
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
A simple-to-use React component for implementing peer-to-peer chatting, powered by [peerjs](https://peerjs.com/).
|
|
3
4
|
|
|
4
5
|
## Features
|
|
5
|
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
6
|
+
|
|
7
|
+
- Peer-to-peer chat without requiring knowledge of [WebRTC](https://webrtc.org/)
|
|
8
|
+
- Easy integration
|
|
9
|
+
- Supports persistent text chat across page reloads
|
|
10
|
+
- Recovers old chats upon reconnection
|
|
11
|
+
- Option to clear chat on command
|
|
12
|
+
- Supports audio/voice chat
|
|
13
|
+
- Multiple peer connections. See [multi-peer usage](#Multi-Peer-Usage)
|
|
14
|
+
- Fully customizable. See [usage with FaC](#Full-Customization)
|
|
15
|
+
|
|
13
16
|
## Installation
|
|
14
|
-
|
|
17
|
+
|
|
18
|
+
To install `react-peer-chat`:
|
|
19
|
+
|
|
15
20
|
```bash
|
|
16
21
|
# with npm:
|
|
17
22
|
npm install react-peer-chat --save
|
|
@@ -25,74 +30,92 @@ To install react-peer-chat
|
|
|
25
30
|
# with bun:
|
|
26
31
|
bun add react-peer-chat
|
|
27
32
|
```
|
|
33
|
+
|
|
28
34
|
## Usage
|
|
29
|
-
`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.
|
|
30
35
|
|
|
31
|
-
|
|
36
|
+
`react-peer-chat` provides two primary methods to integrate chat functionality into your React apps: through the `<Chat>` component and the `useSpeech` hook.
|
|
37
|
+
|
|
38
|
+
It also exports a `clearChat` function that clears the text chat from the browser's session storage when called.
|
|
39
|
+
|
|
40
|
+
### Chat Component
|
|
41
|
+
|
|
42
|
+
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.
|
|
43
|
+
|
|
32
44
|
#### Basic Usage
|
|
45
|
+
|
|
33
46
|
```jsx
|
|
34
|
-
import React from
|
|
35
|
-
import Chat, { clearChat } from
|
|
36
|
-
import
|
|
47
|
+
import React from "react";
|
|
48
|
+
import Chat, { clearChat } from "react-peer-chat";
|
|
49
|
+
import "react-peer-chat/styles.css";
|
|
37
50
|
|
|
38
51
|
export default function App() {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
/>
|
|
45
|
-
{/* Text chat will be cleared when following button is clicked. */}
|
|
46
|
-
<button onClick={clearChat}>Clear Chat</button>
|
|
52
|
+
return (
|
|
53
|
+
<div>
|
|
54
|
+
<Chat name="John Doe" peerId="my-unique-id" remotePeerId="remote-unique-id" />
|
|
55
|
+
{/* Text chat will be cleared when following button is clicked. */}
|
|
56
|
+
<button onClick={clearChat}>Clear Chat</button>
|
|
47
57
|
</div>
|
|
58
|
+
);
|
|
48
59
|
}
|
|
49
60
|
```
|
|
61
|
+
|
|
50
62
|
#### Multi Peer Usage
|
|
63
|
+
|
|
51
64
|
```jsx
|
|
52
|
-
import React from
|
|
53
|
-
import Chat, { clearChat } from
|
|
54
|
-
import
|
|
65
|
+
import React from "react";
|
|
66
|
+
import Chat, { clearChat } from "react-peer-chat";
|
|
67
|
+
import "react-peer-chat/styles.css";
|
|
55
68
|
|
|
56
69
|
export default function App() {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
70
|
+
return (
|
|
71
|
+
<div>
|
|
72
|
+
<Chat
|
|
73
|
+
name="John Doe"
|
|
74
|
+
peerId="my-unique-id"
|
|
75
|
+
remotePeerId={["remote-unique-id-1", "remote-unique-id-2", "remote-unique-id-3"]} // Array of remote peer ids
|
|
76
|
+
/>
|
|
77
|
+
{/* Text chat will be cleared when following button is clicked. */}
|
|
78
|
+
<button onClick={clearChat}>Clear Chat</button>
|
|
65
79
|
</div>
|
|
80
|
+
);
|
|
66
81
|
}
|
|
67
82
|
```
|
|
83
|
+
|
|
68
84
|
#### Partial Customization
|
|
69
|
-
|
|
85
|
+
|
|
86
|
+
Use the props provided by the `<Chat>` component for customization.
|
|
87
|
+
|
|
70
88
|
```jsx
|
|
71
|
-
import React from
|
|
72
|
-
import Chat from
|
|
73
|
-
import
|
|
89
|
+
import React from "react";
|
|
90
|
+
import Chat from "react-peer-chat";
|
|
91
|
+
import "react-peer-chat/styles.css";
|
|
74
92
|
|
|
75
93
|
export default function App() {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
94
|
+
return (
|
|
95
|
+
<Chat
|
|
96
|
+
name="John Doe"
|
|
97
|
+
peerId="my-unique-id"
|
|
98
|
+
remotePeerId="remote-unique-id"
|
|
99
|
+
dialogOptions={{
|
|
100
|
+
position: "left",
|
|
101
|
+
style: { padding: "4px" },
|
|
102
|
+
}}
|
|
103
|
+
props={{ title: "React Peer Chat Component" }}
|
|
104
|
+
onError={() => console.error("Browser not supported!")}
|
|
105
|
+
onMicError={() => console.error("Microphone not accessible!")}
|
|
87
106
|
/>
|
|
107
|
+
);
|
|
88
108
|
}
|
|
89
109
|
```
|
|
90
|
-
|
|
91
|
-
|
|
110
|
+
|
|
111
|
+
#### Full Customization
|
|
112
|
+
|
|
113
|
+
Use Function as Children (FaC) to fully customize the `<Chat>` component.
|
|
114
|
+
|
|
92
115
|
```jsx
|
|
93
116
|
import React from 'react'
|
|
94
117
|
import Chat from 'react-peer-chat'
|
|
95
|
-
// import 'react-peer-chat/
|
|
118
|
+
// import 'react-peer-chat/styles.css' (No need to import CSS when using custom component)
|
|
96
119
|
|
|
97
120
|
export default function App() {
|
|
98
121
|
return <Chat
|
|
@@ -102,7 +125,7 @@ export default function App() {
|
|
|
102
125
|
onError={() => console.error('Browser not supported!')}
|
|
103
126
|
onMicError={() => console.error('Microphone not accessible!')}
|
|
104
127
|
>
|
|
105
|
-
{({ remotePeers, messages,
|
|
128
|
+
{({ remotePeers, messages, sendMessage, audio, setAudio }) => (
|
|
106
129
|
<YourCustomComponent>
|
|
107
130
|
{...}
|
|
108
131
|
</YourCustomComponent>
|
|
@@ -110,36 +133,160 @@ export default function App() {
|
|
|
110
133
|
</Chat>
|
|
111
134
|
}
|
|
112
135
|
```
|
|
136
|
+
|
|
113
137
|
#### Custom ICE Servers
|
|
114
|
-
|
|
138
|
+
|
|
139
|
+
You can also provide custom ICE servers to avoid connectivity issues if the free TURN server provided by `react-peer-chat` expires.
|
|
140
|
+
|
|
115
141
|
```jsx
|
|
116
|
-
import React from
|
|
117
|
-
import Chat from
|
|
118
|
-
import
|
|
142
|
+
import React from "react";
|
|
143
|
+
import Chat from "react-peer-chat";
|
|
144
|
+
import "react-peer-chat/styles.css";
|
|
119
145
|
|
|
120
146
|
export default function App() {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
147
|
+
return (
|
|
148
|
+
<Chat
|
|
149
|
+
name="John Doe"
|
|
150
|
+
peerId="my-unique-id"
|
|
151
|
+
remotePeerId="remote-unique-id"
|
|
152
|
+
peerOptions={{
|
|
153
|
+
config: {
|
|
154
|
+
iceServers: [
|
|
155
|
+
{ urls: "stun:stun-server.example.com:19302" },
|
|
156
|
+
{
|
|
157
|
+
urls: "turn:turn-server.example.com:19403",
|
|
158
|
+
username: "optional-username",
|
|
159
|
+
credential: "auth-token",
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
// other peerjs options (optional)
|
|
164
|
+
}}
|
|
138
165
|
/>
|
|
166
|
+
);
|
|
139
167
|
}
|
|
140
168
|
```
|
|
141
|
-
|
|
142
|
-
|
|
169
|
+
|
|
170
|
+
### useChat Hook
|
|
171
|
+
|
|
172
|
+
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.
|
|
173
|
+
|
|
174
|
+
```jsx
|
|
175
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
176
|
+
import { clearChat, useChat } from "react-peer-chat";
|
|
177
|
+
import { BiSolidMessageDetail, BiSolidMessageX, BsFillMicFill, BsFillMicMuteFill, GrSend } from "react-peer-chat/icons";
|
|
178
|
+
import "react-peer-chat/styles.css"; // (No need to import CSS when using custom styles)
|
|
179
|
+
|
|
180
|
+
function Chat({ text = true, audio = true, onMessageReceived, dialogOptions, props = {}, children, ...hookProps }) {
|
|
181
|
+
const { peerId, audioStreamRef, ...childrenOptions } = useChat({
|
|
182
|
+
text,
|
|
183
|
+
audio,
|
|
184
|
+
onMessageReceived: modifiedOnMessageReceived,
|
|
185
|
+
...hookProps,
|
|
186
|
+
});
|
|
187
|
+
const { remotePeers, messages, sendMessage, audio: audioEnabled, setAudio } = childrenOptions;
|
|
188
|
+
const containerRef = useRef(null);
|
|
189
|
+
const [dialog, setDialog] = useState(false);
|
|
190
|
+
const dialogRef = useRef(null);
|
|
191
|
+
const inputRef = useRef(null);
|
|
192
|
+
const [notification, setNotification] = useState(false);
|
|
193
|
+
|
|
194
|
+
function modifiedOnMessageReceived(message) {
|
|
195
|
+
if (!dialogRef.current?.open) setNotification(true);
|
|
196
|
+
onMessageReceived?.(message);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
useEffect(() => {
|
|
200
|
+
if (dialog) dialogRef.current?.show();
|
|
201
|
+
else dialogRef.current?.close();
|
|
202
|
+
}, [dialog]);
|
|
203
|
+
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
const container = containerRef.current;
|
|
206
|
+
if (container) container.scrollTop = container.scrollHeight;
|
|
207
|
+
}, [dialog, remotePeers, messages]);
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
<div className="rpc-main rpc-font" {...props}>
|
|
211
|
+
{typeof children === "function" ? (
|
|
212
|
+
children(childrenOptions)
|
|
213
|
+
) : (
|
|
214
|
+
<>
|
|
215
|
+
{text && (
|
|
216
|
+
<div className="rpc-dialog-container">
|
|
217
|
+
{dialog ? (
|
|
218
|
+
<BiSolidMessageX title="Close chat" onClick={() => setDialog(false)} />
|
|
219
|
+
) : (
|
|
220
|
+
<div className="rpc-notification">
|
|
221
|
+
<BiSolidMessageDetail
|
|
222
|
+
title="Open chat"
|
|
223
|
+
onClick={() => {
|
|
224
|
+
setNotification(false);
|
|
225
|
+
setDialog(true);
|
|
226
|
+
}}
|
|
227
|
+
/>
|
|
228
|
+
{notification && <span className="rpc-badge" />}
|
|
229
|
+
</div>
|
|
230
|
+
)}
|
|
231
|
+
<dialog ref={dialogRef} className={`${dialog ? "rpc-dialog" : ""} rpc-position-${dialogOptions?.position || "center"}`} style={dialogOptions?.style}>
|
|
232
|
+
<div className="rpc-heading">Chat</div>
|
|
233
|
+
<hr className="rpc-hr" />
|
|
234
|
+
<div>
|
|
235
|
+
<div ref={containerRef} className="rpc-message-container">
|
|
236
|
+
{messages.map(({ id, text }, i) => (
|
|
237
|
+
<div key={i}>
|
|
238
|
+
<strong>{id === peerId ? "You" : remotePeers[id]}: </strong>
|
|
239
|
+
<span>{text}</span>
|
|
240
|
+
</div>
|
|
241
|
+
))}
|
|
242
|
+
</div>
|
|
243
|
+
<hr className="rpc-hr" />
|
|
244
|
+
<form
|
|
245
|
+
className="rpc-input-container"
|
|
246
|
+
onSubmit={(e) => {
|
|
247
|
+
e.preventDefault();
|
|
248
|
+
const text = inputRef.current?.value;
|
|
249
|
+
if (text) {
|
|
250
|
+
inputRef.current.value = "";
|
|
251
|
+
sendMessage({ id: peerId, text });
|
|
252
|
+
}
|
|
253
|
+
}}
|
|
254
|
+
>
|
|
255
|
+
<input ref={inputRef} className="rpc-input rpc-font" placeholder="Enter a message" />
|
|
256
|
+
<button type="submit" className="rpc-button">
|
|
257
|
+
<GrSend title="Send message" />
|
|
258
|
+
</button>
|
|
259
|
+
</form>
|
|
260
|
+
</div>
|
|
261
|
+
</dialog>
|
|
262
|
+
</div>
|
|
263
|
+
)}
|
|
264
|
+
{audio && <button>{audioEnabled ? <BsFillMicFill title="Turn mic off" onClick={() => setAudio(false)} /> : <BsFillMicMuteFill title="Turn mic on" onClick={() => setAudio(true)} />}</button>}
|
|
265
|
+
</>
|
|
266
|
+
)}
|
|
267
|
+
{audio && audioEnabled && <audio ref={audioStreamRef} autoPlay style={{ display: "none" }} />}
|
|
268
|
+
</div>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export default function App() {
|
|
273
|
+
return (
|
|
274
|
+
<div>
|
|
275
|
+
<Chat name="John Doe" peerId="my-unique-id" remotePeerId="remote-unique-id" />
|
|
276
|
+
{/* Text chat will be cleared when following button is clicked. */}
|
|
277
|
+
<button onClick={clearChat}>Clear Chat</button>
|
|
278
|
+
</div>
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
#### Basic Usage
|
|
284
|
+
|
|
285
|
+
## API Reference
|
|
286
|
+
|
|
287
|
+
### useChat Hook
|
|
288
|
+
|
|
289
|
+
Here is the full API for the `useChat` hook, these options can be passed as paramerters to the hook:
|
|
143
290
|
| Parameter | Type | Required | Default | Description |
|
|
144
291
|
| - | - | - | - | - |
|
|
145
292
|
| `name` | `string` | No | Anonymous User | Name of the peer which will be shown to the remote peer. |
|
|
@@ -147,43 +294,61 @@ Here is the full API for the `<Chat>` component, these properties can be set on
|
|
|
147
294
|
| `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). |
|
|
148
295
|
| `text` | `boolean` | No | `true` | Text chat will be enabled if this property is set to true. |
|
|
149
296
|
| `recoverChat` | `boolean` | No | `false` | Old chats will be recovered upon reconnecting with the same peer(s). |
|
|
150
|
-
| `
|
|
297
|
+
| `audio` | `boolean` | No | `true` | Voice chat will be enabled if this property is set to true. |
|
|
151
298
|
| `peerOptions` | [`PeerOptions`](#PeerOptions) | No | - | Options to customize peerjs Peer instance. |
|
|
152
|
-
| `dialogOptions` | [`DialogOptions`](#DialogOptions) | No | { position: 'center' } | Options to customize text dialog box styling. |
|
|
153
299
|
| `onError` | `Function` | No | `() => alert('Browser not supported! Try some other browser.')` | Function to be executed if browser doesn't support `WebRTC` |
|
|
154
300
|
| `onMicError` | `Function` | No | `() => alert('Microphone not accessible!')` | Function to be executed when microphone is not accessible. |
|
|
301
|
+
| `onMessageSent` | `Function` | No | - | Function to be executed when a text message is sent to other peers. |
|
|
302
|
+
| `onMessageReceived` | `Function` | No | - | Function to be executed when a text message is received from other peers. |
|
|
303
|
+
|
|
304
|
+
### Chat Component
|
|
305
|
+
|
|
306
|
+
Here is the full API for the `<Chat>` component, these properties can be set on an instance of `<Chat>`. It contains all the parameters
|
|
307
|
+
that are listed in [useChat Hook API Reference](#usechat-hook-1) along with the following parameters:
|
|
308
|
+
| Parameter | Type | Required | Default | Description |
|
|
309
|
+
| - | - | - | - | - |
|
|
310
|
+
| `dialogOptions` | [`DialogOptions`](#DialogOptions) | No | { position: 'center' } | Options to customize text dialog box styling. |
|
|
155
311
|
| `props` | `React.DetailedHTMLProps` | No | - | Props to customize the `<Chat>` component. |
|
|
156
312
|
| `children` | [`Children`](#Children) | No | - | See [usage with FaC](#Full-Customization) |
|
|
313
|
+
|
|
157
314
|
### Types
|
|
315
|
+
|
|
158
316
|
#### PeerOptions
|
|
317
|
+
|
|
159
318
|
```typescript
|
|
160
|
-
import { PeerOptions } from
|
|
319
|
+
import { PeerOptions } from "peerjs";
|
|
161
320
|
```
|
|
321
|
+
|
|
162
322
|
#### DialogOptions
|
|
323
|
+
|
|
163
324
|
```typescript
|
|
164
|
-
import { CSSProperties } from
|
|
165
|
-
type DialogPosition =
|
|
325
|
+
import { CSSProperties } from "react";
|
|
326
|
+
type DialogPosition = "left" | "center" | "right";
|
|
166
327
|
type DialogOptions = {
|
|
167
|
-
|
|
168
|
-
|
|
328
|
+
position: DialogPosition;
|
|
329
|
+
style: CSSProperties;
|
|
169
330
|
};
|
|
170
331
|
```
|
|
332
|
+
|
|
171
333
|
#### Children
|
|
334
|
+
|
|
172
335
|
```typescript
|
|
173
|
-
import { ReactNode } from
|
|
174
|
-
type RemotePeers = { [id: string]: string }
|
|
336
|
+
import { ReactNode } from "react";
|
|
337
|
+
type RemotePeers = { [id: string]: string };
|
|
175
338
|
type Message = {
|
|
176
|
-
|
|
177
|
-
|
|
339
|
+
id: string;
|
|
340
|
+
text: string;
|
|
178
341
|
};
|
|
179
342
|
type ChildrenOptions = {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
343
|
+
remotePeers?: RemotePeers;
|
|
344
|
+
messages?: Message[];
|
|
345
|
+
sendMessage?: (message: Message) => void;
|
|
346
|
+
audio?: boolean;
|
|
347
|
+
setAudio?: (audio: boolean) => void;
|
|
185
348
|
};
|
|
186
349
|
type Children = (childrenOptions: ChildrenOptions) => ReactNode;
|
|
187
350
|
```
|
|
351
|
+
|
|
188
352
|
## Author
|
|
189
|
-
|
|
353
|
+
|
|
354
|
+
[Sahil Aggarwal](https://www.github.com/SahilAggarwal2004)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const turnAccounts = [
|
|
2
|
+
{ username: "70061a377b51f3a3d01c11e3", credential: "lHV4NYJ5Rfl5JNa9" },
|
|
3
|
+
{ username: "13b19eb65bbf6e9f96d64b72", credential: "7R9P/+7y7Q516Etv" },
|
|
4
|
+
{ username: "3469603f5cdc7ca4a1e891ae", credential: "/jMyLSDbbcgqpVQv" },
|
|
5
|
+
{ username: "a7926f4dcc4a688d41f89752", credential: "ZYM8jFYeb8bQkL+N" },
|
|
6
|
+
{ username: "0be25ab7f61d9d733ba94809", credential: "hiiSwWVch+ftt3SX" },
|
|
7
|
+
{ username: "3c25ba948daeab04f9b66187", credential: "FQB3GQwd27Y0dPeK" },
|
|
8
|
+
];
|
|
9
|
+
export const defaultConfig = {
|
|
10
|
+
iceServers: [
|
|
11
|
+
{
|
|
12
|
+
urls: ["stun:stun.l.google.com:19302", "stun:stun.relay.metered.ca:80"],
|
|
13
|
+
},
|
|
14
|
+
].concat(turnAccounts.map((account) => (Object.assign({ urls: ["turn:standard.relay.metered.ca:80", "turn:standard.relay.metered.ca:80?transport=tcp", "turn:standard.relay.metered.ca:443", "turns:standard.relay.metered.ca:443?transport=tcp"] }, account)))),
|
|
15
|
+
};
|
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Message, RemotePeers, useChatProps } from "./types.js";
|
|
2
|
+
export declare function useChat({ name, peerId, remotePeerId, peerOptions, text, recoverChat, audio: allowed, onError, onMicError, onMessageSent, onMessageReceived }: useChatProps): {
|
|
3
|
+
peerId: string;
|
|
4
|
+
audioStreamRef: import("react").RefObject<HTMLMediaElement>;
|
|
5
|
+
remotePeers: RemotePeers;
|
|
6
|
+
messages: Message[];
|
|
7
|
+
sendMessage: (message: Message) => void;
|
|
8
|
+
audio: boolean;
|
|
9
|
+
setAudio: (value: boolean | ((old: boolean) => boolean)) => void;
|
|
10
|
+
};
|
|
11
|
+
export declare function useMessages(): readonly [Message[], (value: Message[] | ((old: Message[]) => Message[])) => void, (message: Message) => void];
|
|
12
|
+
export declare function useStorage<Value>(key: string, initialValue: Value, local?: boolean): [Value, (value: Value | ((old: Value) => Value)) => void];
|
|
13
|
+
export declare function useAudio(allowed: boolean): readonly [boolean, (value: boolean | ((old: boolean) => boolean)) => void];
|
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { useEffect, useRef, useState } from "react";
|
|
11
|
+
import { closeConnection } from "./connection.js";
|
|
12
|
+
import { defaultConfig } from "./constants.js";
|
|
13
|
+
import { getStorage, setStorage } from "./storage.js";
|
|
14
|
+
import { addPrefix } from "./utils.js";
|
|
15
|
+
export function useChat({ name, peerId, remotePeerId = [], peerOptions, text = true, recoverChat = false, audio: allowed = true, onError = () => alert("Browser not supported! Try some other browser."), onMicError = () => alert("Microphone not accessible!"), onMessageSent, onMessageReceived }) {
|
|
16
|
+
const [audio, setAudio] = useAudio(allowed);
|
|
17
|
+
const audioStreamRef = useRef(null);
|
|
18
|
+
const connRef = useRef({});
|
|
19
|
+
const localStream = useRef();
|
|
20
|
+
const [messages, setMessages, addMessage] = useMessages();
|
|
21
|
+
const [peer, setPeer] = useState();
|
|
22
|
+
const [remotePeers, setRemotePeers] = useStorage("rpc-remote-peer", {});
|
|
23
|
+
peerId = addPrefix(peerId);
|
|
24
|
+
if (typeof remotePeerId === "string")
|
|
25
|
+
remotePeerId = [remotePeerId];
|
|
26
|
+
const remotePeerIds = remotePeerId.map(addPrefix);
|
|
27
|
+
function handleConnection(conn) {
|
|
28
|
+
connRef.current[conn.peer] = conn;
|
|
29
|
+
conn.on("open", () => {
|
|
30
|
+
conn.on("data", ({ message, messages, remotePeerName, type }) => {
|
|
31
|
+
if (type === "message")
|
|
32
|
+
receiveMessage(message);
|
|
33
|
+
else if (type === "init") {
|
|
34
|
+
setRemotePeers((prev) => {
|
|
35
|
+
prev[conn.peer] = remotePeerName || "Anonymous User";
|
|
36
|
+
return prev;
|
|
37
|
+
});
|
|
38
|
+
if (recoverChat)
|
|
39
|
+
setMessages((old) => (messages.length > old.length ? messages : old));
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
conn.send({ type: "init", remotePeerName: name, messages });
|
|
43
|
+
});
|
|
44
|
+
conn.on("close", conn.removeAllListeners);
|
|
45
|
+
}
|
|
46
|
+
function handleError() {
|
|
47
|
+
setAudio(false);
|
|
48
|
+
onMicError();
|
|
49
|
+
}
|
|
50
|
+
function handleRemoteStream(remoteStream) {
|
|
51
|
+
if (audioStreamRef.current)
|
|
52
|
+
audioStreamRef.current.srcObject = remoteStream;
|
|
53
|
+
}
|
|
54
|
+
function receiveMessage(message) {
|
|
55
|
+
addMessage(message);
|
|
56
|
+
onMessageReceived === null || onMessageReceived === void 0 ? void 0 : onMessageReceived(message);
|
|
57
|
+
}
|
|
58
|
+
function sendMessage(message) {
|
|
59
|
+
addMessage(message);
|
|
60
|
+
Object.values(connRef.current).forEach((conn) => conn.send({ type: "message", message }));
|
|
61
|
+
onMessageSent === null || onMessageSent === void 0 ? void 0 : onMessageSent(message);
|
|
62
|
+
}
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (!text && !audio) {
|
|
65
|
+
setPeer(undefined);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
(function () {
|
|
69
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
70
|
+
const { Peer, util: { supports: { audioVideo, data }, }, } = yield import("peerjs");
|
|
71
|
+
if (!data || !audioVideo)
|
|
72
|
+
return onError();
|
|
73
|
+
const peer = new Peer(peerId, Object.assign({ config: defaultConfig }, peerOptions));
|
|
74
|
+
setPeer(peer);
|
|
75
|
+
});
|
|
76
|
+
})();
|
|
77
|
+
}, [audio]);
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!peer)
|
|
80
|
+
return;
|
|
81
|
+
let calls = {};
|
|
82
|
+
peer.on("open", () => {
|
|
83
|
+
remotePeerIds.forEach((id) => {
|
|
84
|
+
if (text)
|
|
85
|
+
handleConnection(peer.connect(id));
|
|
86
|
+
});
|
|
87
|
+
if (audio) {
|
|
88
|
+
const getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
|
|
89
|
+
try {
|
|
90
|
+
getUserMedia({
|
|
91
|
+
video: false,
|
|
92
|
+
audio: {
|
|
93
|
+
autoGainControl: false,
|
|
94
|
+
noiseSuppression: true,
|
|
95
|
+
echoCancellation: true,
|
|
96
|
+
},
|
|
97
|
+
}, (stream) => {
|
|
98
|
+
localStream.current = stream;
|
|
99
|
+
remotePeerIds.forEach((id) => {
|
|
100
|
+
const call = peer.call(id, stream);
|
|
101
|
+
call.on("stream", handleRemoteStream);
|
|
102
|
+
call.on("close", call.removeAllListeners);
|
|
103
|
+
calls[id] = call;
|
|
104
|
+
});
|
|
105
|
+
peer.on("call", (call) => {
|
|
106
|
+
call.answer(stream);
|
|
107
|
+
call.on("stream", handleRemoteStream);
|
|
108
|
+
call.on("close", call.removeAllListeners);
|
|
109
|
+
calls[call.peer] = call;
|
|
110
|
+
});
|
|
111
|
+
}, handleError);
|
|
112
|
+
}
|
|
113
|
+
catch (_a) {
|
|
114
|
+
handleError();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
peer.on("connection", handleConnection);
|
|
119
|
+
return () => {
|
|
120
|
+
var _a;
|
|
121
|
+
(_a = localStream.current) === null || _a === void 0 ? void 0 : _a.getTracks().forEach((track) => track.stop());
|
|
122
|
+
Object.values(connRef.current).forEach(closeConnection);
|
|
123
|
+
connRef.current = {};
|
|
124
|
+
Object.values(calls).forEach(closeConnection);
|
|
125
|
+
peer.removeAllListeners();
|
|
126
|
+
peer.destroy();
|
|
127
|
+
};
|
|
128
|
+
}, [peer]);
|
|
129
|
+
return { peerId, audioStreamRef, remotePeers, messages, sendMessage, audio, setAudio };
|
|
130
|
+
}
|
|
131
|
+
export function useMessages() {
|
|
132
|
+
const [messages, setMessages] = useStorage("rpc-messages", []);
|
|
133
|
+
const addMessage = (message) => setMessages((prev) => prev.concat(message));
|
|
134
|
+
return [messages, setMessages, addMessage];
|
|
135
|
+
}
|
|
136
|
+
export function useStorage(key, initialValue, local = false) {
|
|
137
|
+
const [storedValue, setStoredValue] = useState(() => {
|
|
138
|
+
if (typeof window === "undefined")
|
|
139
|
+
return initialValue;
|
|
140
|
+
return getStorage(key, initialValue, local);
|
|
141
|
+
});
|
|
142
|
+
const setValue = (value) => {
|
|
143
|
+
setStoredValue((old) => {
|
|
144
|
+
const updatedValue = typeof value === "function" ? value(old) : value;
|
|
145
|
+
setStorage(key, updatedValue, local);
|
|
146
|
+
return updatedValue;
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
return [storedValue, setValue];
|
|
150
|
+
}
|
|
151
|
+
export function useAudio(allowed) {
|
|
152
|
+
const [audio, setAudio] = useStorage("rpc-audio", false, true);
|
|
153
|
+
const enabled = audio || allowed;
|
|
154
|
+
return [enabled, setAudio];
|
|
155
|
+
}
|
package/dist/icons.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
|
|
2
|
+
import { IconProps } from "./types.js";
|
|
3
3
|
export declare function BiSolidMessageDetail(props: IconProps): React.JSX.Element;
|
|
4
4
|
export declare function BiSolidMessageX(props: IconProps): React.JSX.Element;
|
|
5
5
|
export declare function GrSend(props: IconProps): React.JSX.Element;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,42 +1,6 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export
|
|
6
|
-
export
|
|
7
|
-
position?: DialogPosition;
|
|
8
|
-
style?: CSSProperties;
|
|
9
|
-
};
|
|
10
|
-
export type RemotePeers = {
|
|
11
|
-
[id: string]: string;
|
|
12
|
-
};
|
|
13
|
-
export type Message = {
|
|
14
|
-
id: string;
|
|
15
|
-
text: string;
|
|
16
|
-
};
|
|
17
|
-
export type ChildrenOptions = {
|
|
18
|
-
remotePeers?: RemotePeers;
|
|
19
|
-
messages?: Message[];
|
|
20
|
-
addMessage?: (message: Message, sendToRemotePeer?: boolean) => void;
|
|
21
|
-
audio?: boolean;
|
|
22
|
-
setAudio?: (audio: boolean) => void;
|
|
23
|
-
};
|
|
24
|
-
export type Children = (childrenOptions: ChildrenOptions) => ReactNode;
|
|
25
|
-
export type Props = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
|
|
26
|
-
export type ChatProps = {
|
|
27
|
-
name?: string;
|
|
28
|
-
peerId: string;
|
|
29
|
-
remotePeerId?: RemotePeerId;
|
|
30
|
-
text?: boolean;
|
|
31
|
-
recoverChat?: boolean;
|
|
32
|
-
voice?: boolean;
|
|
33
|
-
peerOptions?: PeerOptions;
|
|
34
|
-
dialogOptions?: DialogOptions;
|
|
35
|
-
onError?: Function;
|
|
36
|
-
onMicError?: Function;
|
|
37
|
-
children?: Children;
|
|
38
|
-
props?: Props;
|
|
39
|
-
};
|
|
40
|
-
export type { IconProps } from "./icons.js";
|
|
41
|
-
export default function Chat({ name, peerId, remotePeerId, peerOptions, text, recoverChat, voice, dialogOptions, onError, onMicError, children, props }: ChatProps): React.JSX.Element;
|
|
42
|
-
export declare const clearChat: () => void;
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { useChat } from "./hooks.js";
|
|
3
|
+
import { clearChat } from "./storage.js";
|
|
4
|
+
import { ChatProps } from "./types.js";
|
|
5
|
+
export default function Chat({ text, audio, onMessageReceived, dialogOptions, props, children, ...hookProps }: ChatProps): React.JSX.Element;
|
|
6
|
+
export { clearChat, useChat };
|
package/dist/index.js
CHANGED
|
@@ -1,149 +1,34 @@
|
|
|
1
|
-
var
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
9
11
|
};
|
|
10
12
|
import React, { useEffect, useRef, useState } from "react";
|
|
11
|
-
import
|
|
13
|
+
import { useChat } from "./hooks.js";
|
|
12
14
|
import { BiSolidMessageDetail, BiSolidMessageX, BsFillMicFill, BsFillMicMuteFill, GrSend } from "./icons.js";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
{
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
{
|
|
19
|
-
|
|
20
|
-
];
|
|
21
|
-
const defaultConfig = {
|
|
22
|
-
iceServers: [
|
|
23
|
-
{
|
|
24
|
-
urls: ["stun:stun.l.google.com:19302", "stun:stun.relay.metered.ca:80"],
|
|
25
|
-
},
|
|
26
|
-
].concat(turnAccounts.map((account) => (Object.assign({ urls: ["turn:standard.relay.metered.ca:80", "turn:standard.relay.metered.ca:80?transport=tcp", "turn:standard.relay.metered.ca:443", "turns:standard.relay.metered.ca:443?transport=tcp"] }, account)))),
|
|
27
|
-
};
|
|
28
|
-
function closeConnection(conn) {
|
|
29
|
-
conn.removeAllListeners();
|
|
30
|
-
conn.close();
|
|
31
|
-
}
|
|
32
|
-
export default function Chat({ name, peerId, remotePeerId = [], peerOptions, text = true, recoverChat = false, voice = true, dialogOptions, onError = () => alert("Browser not supported! Try some other browser."), onMicError = () => alert("Microphone not accessible!"), children, props = {} }) {
|
|
33
|
-
const [peer, setPeer] = useState();
|
|
34
|
-
const [notification, setNotification] = useState(false);
|
|
35
|
-
const [remotePeers, setRemotePeers] = useStorage("rpc-remote-peer", {});
|
|
36
|
-
const [messages, setMessages] = useStorage("rpc-messages", []);
|
|
37
|
-
const connRef = useRef({});
|
|
15
|
+
import { clearChat } from "./storage.js";
|
|
16
|
+
export default function Chat(_a) {
|
|
17
|
+
var { text = true, audio = true, onMessageReceived, dialogOptions, props = {}, children } = _a, hookProps = __rest(_a, ["text", "audio", "onMessageReceived", "dialogOptions", "props", "children"]);
|
|
18
|
+
const _b = useChat(Object.assign({ text,
|
|
19
|
+
audio, onMessageReceived: modifiedOnMessageReceived }, hookProps)), { peerId, audioStreamRef } = _b, childrenOptions = __rest(_b, ["peerId", "audioStreamRef"]);
|
|
20
|
+
const { remotePeers, messages, sendMessage, audio: audioEnabled, setAudio } = childrenOptions;
|
|
21
|
+
const containerRef = useRef(null);
|
|
38
22
|
const [dialog, setDialog] = useState(false);
|
|
39
23
|
const dialogRef = useRef(null);
|
|
40
|
-
const containerRef = useRef(null);
|
|
41
24
|
const inputRef = useRef(null);
|
|
42
|
-
const [
|
|
43
|
-
|
|
44
|
-
const localStream = useRef();
|
|
45
|
-
peerId = `rpc-${peerId}`;
|
|
46
|
-
if (typeof remotePeerId === "string")
|
|
47
|
-
remotePeerId = [remotePeerId];
|
|
48
|
-
const remotePeerIds = remotePeerId.map((id) => `rpc-${id}`);
|
|
49
|
-
const handleRemoteStream = (remoteStream) => (streamRef.current.srcObject = remoteStream);
|
|
50
|
-
function addMessage(message, sendToRemotePeer = false) {
|
|
25
|
+
const [notification, setNotification] = useState(false);
|
|
26
|
+
function modifiedOnMessageReceived(message) {
|
|
51
27
|
var _a;
|
|
52
|
-
|
|
53
|
-
if (sendToRemotePeer)
|
|
54
|
-
Object.values(connRef.current).forEach((conn) => conn.send({ type: "message", message }));
|
|
55
|
-
else if (!((_a = dialogRef.current) === null || _a === void 0 ? void 0 : _a.open))
|
|
28
|
+
if (!((_a = dialogRef.current) === null || _a === void 0 ? void 0 : _a.open))
|
|
56
29
|
setNotification(true);
|
|
30
|
+
onMessageReceived === null || onMessageReceived === void 0 ? void 0 : onMessageReceived(message);
|
|
57
31
|
}
|
|
58
|
-
function handleConnection(conn) {
|
|
59
|
-
connRef.current[conn.peer] = conn;
|
|
60
|
-
conn.on("open", () => {
|
|
61
|
-
conn.on("data", ({ type, message, remotePeerName, messages }) => {
|
|
62
|
-
if (type === "message")
|
|
63
|
-
addMessage(message);
|
|
64
|
-
else if (type === "init") {
|
|
65
|
-
setRemotePeers((prev) => {
|
|
66
|
-
prev[conn.peer] = remotePeerName || "Anonymous User";
|
|
67
|
-
return prev;
|
|
68
|
-
});
|
|
69
|
-
if (recoverChat)
|
|
70
|
-
setMessages((old) => (messages.length > old.length ? messages : old));
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
conn.send({ type: "init", remotePeerName: name, messages });
|
|
74
|
-
});
|
|
75
|
-
conn.on("close", conn.removeAllListeners);
|
|
76
|
-
}
|
|
77
|
-
function handleError() {
|
|
78
|
-
setAudio(false);
|
|
79
|
-
onMicError();
|
|
80
|
-
}
|
|
81
|
-
useEffect(() => {
|
|
82
|
-
if (!text && !audio) {
|
|
83
|
-
setPeer(undefined);
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
(function () {
|
|
87
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
88
|
-
const { Peer, util: { supports: { audioVideo, data }, }, } = yield import("peerjs");
|
|
89
|
-
if (!data || !audioVideo)
|
|
90
|
-
return onError();
|
|
91
|
-
const peer = new Peer(peerId, Object.assign({ config: defaultConfig }, peerOptions));
|
|
92
|
-
setPeer(peer);
|
|
93
|
-
});
|
|
94
|
-
})();
|
|
95
|
-
}, [audio]);
|
|
96
|
-
useEffect(() => {
|
|
97
|
-
if (!peer)
|
|
98
|
-
return;
|
|
99
|
-
let calls = {};
|
|
100
|
-
peer.on("open", () => {
|
|
101
|
-
remotePeerIds.forEach((id) => {
|
|
102
|
-
if (text)
|
|
103
|
-
handleConnection(peer.connect(id));
|
|
104
|
-
});
|
|
105
|
-
if (audio) {
|
|
106
|
-
const getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
|
|
107
|
-
try {
|
|
108
|
-
getUserMedia({
|
|
109
|
-
video: false,
|
|
110
|
-
audio: {
|
|
111
|
-
autoGainControl: false,
|
|
112
|
-
noiseSuppression: true,
|
|
113
|
-
echoCancellation: true,
|
|
114
|
-
},
|
|
115
|
-
}, (stream) => {
|
|
116
|
-
localStream.current = stream;
|
|
117
|
-
remotePeerIds.forEach((id) => {
|
|
118
|
-
const call = peer.call(id, stream);
|
|
119
|
-
call.on("stream", handleRemoteStream);
|
|
120
|
-
call.on("close", call.removeAllListeners);
|
|
121
|
-
calls[id] = call;
|
|
122
|
-
});
|
|
123
|
-
peer.on("call", (call) => {
|
|
124
|
-
call.answer(stream);
|
|
125
|
-
call.on("stream", handleRemoteStream);
|
|
126
|
-
call.on("close", call.removeAllListeners);
|
|
127
|
-
calls[call.peer] = call;
|
|
128
|
-
});
|
|
129
|
-
}, handleError);
|
|
130
|
-
}
|
|
131
|
-
catch (_a) {
|
|
132
|
-
handleError();
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
peer.on("connection", handleConnection);
|
|
137
|
-
return () => {
|
|
138
|
-
var _a;
|
|
139
|
-
(_a = localStream.current) === null || _a === void 0 ? void 0 : _a.getTracks().forEach((track) => track.stop());
|
|
140
|
-
Object.values(connRef.current).forEach(closeConnection);
|
|
141
|
-
connRef.current = {};
|
|
142
|
-
Object.values(calls).forEach(closeConnection);
|
|
143
|
-
peer.removeAllListeners();
|
|
144
|
-
peer.destroy();
|
|
145
|
-
};
|
|
146
|
-
}, [peer]);
|
|
147
32
|
useEffect(() => {
|
|
148
33
|
var _a, _b;
|
|
149
34
|
if (dialog)
|
|
@@ -157,7 +42,7 @@ export default function Chat({ name, peerId, remotePeerId = [], peerOptions, tex
|
|
|
157
42
|
container.scrollTop = container.scrollHeight;
|
|
158
43
|
}, [dialog, remotePeers, messages]);
|
|
159
44
|
return (React.createElement("div", Object.assign({ className: "rpc-main rpc-font" }, props),
|
|
160
|
-
typeof children === "function" ? (children(
|
|
45
|
+
typeof children === "function" ? (children(childrenOptions)) : (React.createElement(React.Fragment, null,
|
|
161
46
|
text && (React.createElement("div", { className: "rpc-dialog-container" },
|
|
162
47
|
dialog ? (React.createElement(BiSolidMessageX, { title: "Close chat", onClick: () => setDialog(false) })) : (React.createElement("div", { className: "rpc-notification" },
|
|
163
48
|
React.createElement(BiSolidMessageDetail, { title: "Open chat", onClick: () => {
|
|
@@ -181,16 +66,13 @@ export default function Chat({ name, peerId, remotePeerId = [], peerOptions, tex
|
|
|
181
66
|
const text = (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.value;
|
|
182
67
|
if (text) {
|
|
183
68
|
inputRef.current.value = "";
|
|
184
|
-
|
|
69
|
+
sendMessage({ id: peerId, text });
|
|
185
70
|
}
|
|
186
71
|
} },
|
|
187
72
|
React.createElement("input", { ref: inputRef, className: "rpc-input rpc-font", placeholder: "Enter a message" }),
|
|
188
73
|
React.createElement("button", { type: "submit", className: "rpc-button" },
|
|
189
74
|
React.createElement(GrSend, { title: "Send message" }))))))),
|
|
190
|
-
|
|
191
|
-
|
|
75
|
+
audio && React.createElement("button", null, audioEnabled ? React.createElement(BsFillMicFill, { title: "Turn mic off", onClick: () => setAudio(false) }) : React.createElement(BsFillMicMuteFill, { title: "Turn mic on", onClick: () => setAudio(true) })))),
|
|
76
|
+
audio && audioEnabled && React.createElement("audio", { ref: audioStreamRef, autoPlay: true, style: { display: "none" } })));
|
|
192
77
|
}
|
|
193
|
-
export
|
|
194
|
-
removeStorage("rpc-remote-peer");
|
|
195
|
-
removeStorage("rpc-messages");
|
|
196
|
-
};
|
|
78
|
+
export { clearChat, useChat };
|
package/dist/storage.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export declare const removeStorage: (key: string, local?: boolean) => void;
|
|
2
|
-
export
|
|
2
|
+
export declare const setStorage: (key: string, value: any, local?: boolean) => void;
|
|
3
|
+
export declare const getStorage: (key: string, fallbackValue?: any, local?: boolean) => any;
|
|
4
|
+
export declare const clearChat: () => void;
|
package/dist/storage.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
const setStorage = (key, value, local = false) => (local ? localStorage : sessionStorage).setItem(key, JSON.stringify(value));
|
|
3
1
|
export const removeStorage = (key, local = false) => (local ? localStorage : sessionStorage).removeItem(key);
|
|
4
|
-
const
|
|
2
|
+
export const setStorage = (key, value, local = false) => (local ? localStorage : sessionStorage).setItem(key, JSON.stringify(value));
|
|
3
|
+
export const getStorage = (key, fallbackValue, local = false) => {
|
|
5
4
|
let value = (local ? localStorage : sessionStorage).getItem(key);
|
|
6
5
|
try {
|
|
7
6
|
if (!value)
|
|
@@ -20,18 +19,7 @@ const getStorage = (key, fallbackValue, local = false) => {
|
|
|
20
19
|
}
|
|
21
20
|
return value;
|
|
22
21
|
};
|
|
23
|
-
export
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return getStorage(key, initialValue, local);
|
|
28
|
-
});
|
|
29
|
-
const setValue = (value) => {
|
|
30
|
-
setStoredValue((old) => {
|
|
31
|
-
const updatedValue = typeof value === "function" ? value(old) : value;
|
|
32
|
-
setStorage(key, updatedValue, local);
|
|
33
|
-
return updatedValue;
|
|
34
|
-
});
|
|
35
|
-
};
|
|
36
|
-
return [storedValue, setValue];
|
|
37
|
-
}
|
|
22
|
+
export const clearChat = () => {
|
|
23
|
+
removeStorage("rpc-remote-peer");
|
|
24
|
+
removeStorage("rpc-messages");
|
|
25
|
+
};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { PeerOptions } from "peerjs";
|
|
2
|
+
import { CSSProperties, DetailedHTMLProps, HTMLAttributes, ReactNode } from "react";
|
|
3
|
+
export type Message = {
|
|
4
|
+
id: string;
|
|
5
|
+
text: string;
|
|
6
|
+
};
|
|
7
|
+
export type MessageEvent = (message: Message) => void;
|
|
8
|
+
export type { PeerOptions };
|
|
9
|
+
export type RemotePeerId = string | string[];
|
|
10
|
+
export type useChatProps = {
|
|
11
|
+
name?: string;
|
|
12
|
+
peerId: string;
|
|
13
|
+
remotePeerId?: RemotePeerId;
|
|
14
|
+
text?: boolean;
|
|
15
|
+
recoverChat?: boolean;
|
|
16
|
+
audio?: boolean;
|
|
17
|
+
peerOptions?: PeerOptions;
|
|
18
|
+
onError?: Function;
|
|
19
|
+
onMicError?: Function;
|
|
20
|
+
onMessageSent?: MessageEvent;
|
|
21
|
+
onMessageReceived?: MessageEvent;
|
|
22
|
+
};
|
|
23
|
+
export type IconProps = React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>;
|
|
24
|
+
export type RemotePeers = {
|
|
25
|
+
[id: string]: string;
|
|
26
|
+
};
|
|
27
|
+
export type Children = (childrenOptions: ChildrenOptions) => ReactNode;
|
|
28
|
+
export type ChildrenOptions = {
|
|
29
|
+
remotePeers?: RemotePeers;
|
|
30
|
+
messages?: Message[];
|
|
31
|
+
sendMessage?: (message: Message) => void;
|
|
32
|
+
audio?: boolean;
|
|
33
|
+
setAudio?: (audio: boolean) => void;
|
|
34
|
+
};
|
|
35
|
+
export type ChatProps = useChatProps & {
|
|
36
|
+
dialogOptions?: DialogOptions;
|
|
37
|
+
props?: DivProps;
|
|
38
|
+
children?: Children;
|
|
39
|
+
};
|
|
40
|
+
export type DialogOptions = {
|
|
41
|
+
position?: DialogPosition;
|
|
42
|
+
style?: CSSProperties;
|
|
43
|
+
};
|
|
44
|
+
export type DialogPosition = "left" | "center" | "right";
|
|
45
|
+
export type DivProps = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const addPrefix: (str: string) => string;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const addPrefix = (str) => `rpc-${str}`;
|
package/package.json
CHANGED
|
@@ -1,14 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-peer-chat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "An easy to use react component for impleting peer-to-peer chatting.",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Sahil Aggarwal <aggarwalsahil2004@gmail.com>",
|
|
7
|
+
"contributors": [],
|
|
8
|
+
"homepage": "https://github.com/SahilAggarwal2004/react-peer-chat#readme",
|
|
8
9
|
"repository": {
|
|
9
10
|
"type": "git",
|
|
10
11
|
"url": "git+https://github.com/SahilAggarwal2004/react-peer-chat.git"
|
|
11
12
|
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/SahilAggarwal2004/react-peer-chat/issues"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": "./dist/index.js",
|
|
19
|
+
"./icons": "./dist/icons.js",
|
|
20
|
+
"./styles.css": "./dist/styles.css",
|
|
21
|
+
"./types": "./dist/types.js"
|
|
22
|
+
},
|
|
23
|
+
"main": "dist/index.js",
|
|
24
|
+
"types": "dist/index.d.ts",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"peerjs": "^1.5.4"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/react": "^18.3.12"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"react": ">=17.0.0",
|
|
33
|
+
"react-dom": ">=17.0.0"
|
|
34
|
+
},
|
|
12
35
|
"keywords": [
|
|
13
36
|
"react",
|
|
14
37
|
"chat",
|
|
@@ -20,27 +43,11 @@
|
|
|
20
43
|
"webrtc",
|
|
21
44
|
"react-peer-chat",
|
|
22
45
|
"typescript",
|
|
23
|
-
"voice-chat",
|
|
24
46
|
"p2p-chat",
|
|
25
47
|
"text-chat",
|
|
26
|
-
"
|
|
48
|
+
"voice-chat",
|
|
49
|
+
"audio-chat"
|
|
27
50
|
],
|
|
28
|
-
"author": "Sahil Aggarwal",
|
|
29
|
-
"license": "MIT",
|
|
30
|
-
"bugs": {
|
|
31
|
-
"url": "https://github.com/SahilAggarwal2004/react-peer-chat/issues"
|
|
32
|
-
},
|
|
33
|
-
"homepage": "https://github.com/SahilAggarwal2004/react-peer-chat#readme",
|
|
34
|
-
"peerDependencies": {
|
|
35
|
-
"react": ">=17.0.0",
|
|
36
|
-
"react-dom": ">=17.0.0"
|
|
37
|
-
},
|
|
38
|
-
"devDependencies": {
|
|
39
|
-
"@types/react": "^18.3.12"
|
|
40
|
-
},
|
|
41
|
-
"dependencies": {
|
|
42
|
-
"peerjs": "^1.5.4"
|
|
43
|
-
},
|
|
44
51
|
"scripts": {
|
|
45
52
|
"copy": "copy .\\src\\*.css dist",
|
|
46
53
|
"build": "pnpm i && tsc && pnpm copy"
|