react-peer-chat 0.8.4 → 0.8.5

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
@@ -10,8 +10,8 @@ A simple-to-use React component for implementing peer-to-peer chatting, powered
10
10
  - Recovers old chats upon reconnection
11
11
  - Option to clear chat on command
12
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)
13
+ - Multiple peer connections. See [multi-peer usage](#multi-peer-usage)
14
+ - Fully customizable. See [usage with FaC](#full-customization)
15
15
 
16
16
  ## Installation
17
17
 
@@ -79,6 +79,8 @@ export default function App() {
79
79
  }
80
80
  ```
81
81
 
82
+ > **Note:** The `remotePeerId` prop is read at mount and whenever `peerId` changes. Changes to `remotePeerId` alone (without `peerId` changing) won't establish new connections. In peer-to-peer chat scenarios, new peers should connect to existing peers by providing their IDs at mount time, rather than existing peers updating this prop dynamically.
83
+
82
84
  #### Partial Customization
83
85
 
84
86
  Use the props provided by the `<Chat>` component for customization.
@@ -289,36 +291,51 @@ export default function App() {
289
291
  ### useChat Hook
290
292
 
291
293
  Here is the full API for the `useChat` hook, these options can be passed as paramerters to the hook:
292
- | Parameter | Type | Required | Default | Description |
293
- | - | - | - | - | - |
294
- | `name` | `string` | No | Anonymous User | Name of the peer which will be shown to the remote peer. |
295
- | `peerId` | `string` | Yes | - | It is the unique id that is alloted to a peer. It uniquely identifies a peer from other peers. |
296
- | `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). |
297
- | `text` | `boolean` | No | `true` | Text chat will be enabled if this property is set to true. |
298
- | `recoverChat` | `boolean` | No | `false` | Old chats will be recovered upon reconnecting with the same peer(s). |
299
- | `audio` | `boolean` | No | `true` | Voice chat will be enabled if this property is set to true. |
300
- | `peerOptions` | [`PeerOptions`](#PeerOptions) | No | - | Options to customize peerjs Peer instance. |
301
- | `onError` | `Function` | No | `() => alert('Browser not supported! Try some other browser.')` | Function to be executed if browser doesn't support `WebRTC` |
302
- | `onMicError` | `Function` | No | `() => alert('Microphone not accessible!')` | Function to be executed when microphone is not accessible. |
303
- | `onMessageSent` | `Function` | No | - | Function to be executed when a text message is sent to other peers. |
304
- | `onMessageReceived` | `Function` | No | - | Function to be executed when a text message is received from other peers. |
294
+
295
+ | Parameter | Type | Required | Default | Description |
296
+ | ------------------- | --------------------------------------------- | -------- | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
297
+ | `name` | `string` | No | Anonymous User | Name of the peer which will be shown to the remote peer. |
298
+ | `peerId` | `string` | Yes | - | It is the unique id that is alloted to a peer. It uniquely identifies a peer from other peers. |
299
+ | `remotePeerId` | `string \| string[]` | No | - | Unique id(s) of remote peer(s) to connect to. Read at mount and when `peerId` changes; changes to this prop alone won't create new connections. |
300
+ | `text` | `boolean` | No | `true` | Text chat will be enabled if this property is set to true. |
301
+ | `recoverChat` | `boolean` | No | `false` | Old chats will be recovered upon reconnecting with the same peer(s). |
302
+ | `audio` | `boolean` | No | `true` | Voice chat will be enabled if this property is set to true. |
303
+ | `peerOptions` | [`PeerOptions`](#peeroptions) | No | - | Options to customize peerjs Peer instance. |
304
+ | `onError` | [`ErrorHandler`](#errorhandler) | No | `() => alert('Browser not supported! Try some other browser.')` | Function to be executed if browser doesn't support `WebRTC` |
305
+ | `onMicError` | [`ErrorHandler`](#errorhandler) | No | `() => alert('Microphone not accessible!')` | Function to be executed when microphone is not accessible. |
306
+ | `onMessageSent` | [`MessageEventHandler`](#messageeventhandler) | No | - | Function to be executed when a text message is sent to other peers. |
307
+ | `onMessageReceived` | [`MessageEventHandler`](#messageeventhandler) | No | - | Function to be executed when a text message is received from other peers. |
305
308
 
306
309
  ### Chat Component
307
310
 
308
311
  Here is the full API for the `<Chat>` component, these properties can be set on an instance of `<Chat>`. It contains all the parameters
309
312
  that are listed in [useChat Hook API Reference](#usechat-hook-1) along with the following parameters:
310
- | Parameter | Type | Required | Default | Description |
311
- | - | - | - | - | - |
312
- | `dialogOptions` | [`DialogOptions`](#DialogOptions) | No | { position: 'center' } | Options to customize text dialog box styling. |
313
- | `props` | `React.DetailedHTMLProps` | No | - | Props to customize the `<Chat>` component. |
314
- | `children` | [`Children`](#Children) | No | - | See [usage with FaC](#Full-Customization) |
313
+
314
+ | Parameter | Type | Required | Default | Description |
315
+ | --------------- | --------------------------------- | -------- | ---------------------- | --------------------------------------------- |
316
+ | `dialogOptions` | [`DialogOptions`](#dialogoptions) | No | { position: 'center' } | Options to customize text dialog box styling. |
317
+ | `props` | [`DivProps`](#divprops) | No | - | Props to customize the `<Chat>` component. |
318
+ | `children` | [`Children`](#children) | No | - | See [usage with FaC](#full-customization) |
315
319
 
316
320
  ### Types
317
321
 
318
- #### PeerOptions
322
+ #### Children
319
323
 
320
324
  ```typescript
321
- import { PeerOptions } from "peerjs";
325
+ import { ReactNode } from "react";
326
+ type RemotePeers = { [id: string]: string };
327
+ type Message = {
328
+ id: string;
329
+ text: string;
330
+ };
331
+ type ChildrenOptions = {
332
+ remotePeers?: RemotePeers;
333
+ messages?: Message[];
334
+ sendMessage?: (message: Message) => void;
335
+ audio?: boolean;
336
+ setAudio?: (audio: boolean) => void;
337
+ };
338
+ type Children = (childrenOptions: ChildrenOptions) => ReactNode;
322
339
  ```
323
340
 
324
341
  #### DialogOptions
@@ -332,25 +349,35 @@ type DialogOptions = {
332
349
  };
333
350
  ```
334
351
 
335
- #### Children
352
+ #### DivProps
353
+
354
+ ```typescript
355
+ import { DetailedHTMLProps, HTMLAttributes } from "react";
356
+ type DivProps = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
357
+ ```
358
+
359
+ #### ErrorHandler
360
+
361
+ ```typescript
362
+ type ErrorHandler = () => void;
363
+ ```
364
+
365
+ #### MessageEventHandler
336
366
 
337
367
  ```typescript
338
- import { ReactNode } from "react";
339
- type RemotePeers = { [id: string]: string };
340
368
  type Message = {
341
369
  id: string;
342
370
  text: string;
343
371
  };
344
- type ChildrenOptions = {
345
- remotePeers?: RemotePeers;
346
- messages?: Message[];
347
- sendMessage?: (message: Message) => void;
348
- audio?: boolean;
349
- setAudio?: (audio: boolean) => void;
350
- };
351
- type Children = (childrenOptions: ChildrenOptions) => ReactNode;
372
+ type MessageEventHandler = (message: Message) => void;
373
+ ```
374
+
375
+ #### PeerOptions
376
+
377
+ ```typescript
378
+ import { PeerOptions } from "peerjs";
352
379
  ```
353
380
 
354
- ## Author
381
+ ## License
355
382
 
356
- [Sahil Aggarwal](https://github.com/SahilAggarwal2004)
383
+ This project is licensed under the [MIT License](LICENSE).
@@ -1,6 +1,8 @@
1
1
  import React from 'react';
2
2
 
3
3
  var __defProp = Object.defineProperty;
4
+ var __defProps = Object.defineProperties;
5
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
6
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
@@ -16,6 +18,7 @@ var __spreadValues = (a, b) => {
16
18
  }
17
19
  return a;
18
20
  };
21
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
19
22
  var __objRest = (source, exclude) => {
20
23
  var target = {};
21
24
  for (var prop in source)
@@ -28,26 +31,6 @@ var __objRest = (source, exclude) => {
28
31
  }
29
32
  return target;
30
33
  };
31
- var __async = (__this, __arguments, generator) => {
32
- return new Promise((resolve, reject) => {
33
- var fulfilled = (value) => {
34
- try {
35
- step(generator.next(value));
36
- } catch (e) {
37
- reject(e);
38
- }
39
- };
40
- var rejected = (value) => {
41
- try {
42
- step(generator.throw(value));
43
- } catch (e) {
44
- reject(e);
45
- }
46
- };
47
- var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
48
- step((generator = generator.apply(__this, __arguments)).next());
49
- });
50
- };
51
34
  function BiSolidMessageDetail(props) {
52
35
  return /* @__PURE__ */ React.createElement("span", __spreadValues({ className: "rpc-icon-container" }, props), /* @__PURE__ */ React.createElement("svg", { viewBox: "0 0 24 24", width: "1.25rem", height: "1.25rem" }, /* @__PURE__ */ 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" })));
53
36
  }
@@ -72,4 +55,4 @@ function BsFillMicMuteFill(props) {
72
55
  return /* @__PURE__ */ React.createElement("span", __spreadValues({ className: "rpc-icon-container" }, props), /* @__PURE__ */ React.createElement("svg", { viewBox: "0 0 16 16", fill: "currentColor", width: "1.25rem", height: "1.25rem" }, /* @__PURE__ */ 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" }), /* @__PURE__ */ 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" })));
73
56
  }
74
57
 
75
- export { BiSolidMessageDetail, BiSolidMessageX, BsFillMicFill, BsFillMicMuteFill, GrSend, __async, __objRest, __spreadValues };
58
+ export { BiSolidMessageDetail, BiSolidMessageX, BsFillMicFill, BsFillMicMuteFill, GrSend, __objRest, __spreadProps, __spreadValues };
package/dist/icons.d.ts CHANGED
@@ -1,11 +1,11 @@
1
- import React__default from 'react';
1
+ import React from 'react';
2
2
  import { IconProps } from './types.js';
3
3
  import 'peerjs';
4
4
 
5
- declare function BiSolidMessageDetail(props: IconProps): React__default.JSX.Element;
6
- declare function BiSolidMessageX(props: IconProps): React__default.JSX.Element;
7
- declare function GrSend(props: IconProps): React__default.JSX.Element;
8
- declare function BsFillMicFill(props: IconProps): React__default.JSX.Element;
9
- declare function BsFillMicMuteFill(props: IconProps): React__default.JSX.Element;
5
+ declare function BiSolidMessageDetail(props: IconProps): React.JSX.Element;
6
+ declare function BiSolidMessageX(props: IconProps): React.JSX.Element;
7
+ declare function GrSend(props: IconProps): React.JSX.Element;
8
+ declare function BsFillMicFill(props: IconProps): React.JSX.Element;
9
+ declare function BsFillMicMuteFill(props: IconProps): React.JSX.Element;
10
10
 
11
11
  export { BiSolidMessageDetail, BiSolidMessageX, BsFillMicFill, BsFillMicMuteFill, GrSend };
package/dist/icons.js CHANGED
@@ -1 +1 @@
1
- export { BiSolidMessageDetail, BiSolidMessageX, BsFillMicFill, BsFillMicMuteFill, GrSend } from './chunks/chunk-4CBQRGQ3.js';
1
+ export { BiSolidMessageDetail, BiSolidMessageX, BsFillMicFill, BsFillMicMuteFill, GrSend } from './chunks/chunk-NXKKI6WY.js';
package/dist/index.d.ts CHANGED
@@ -1,20 +1,11 @@
1
- import * as React from 'react';
2
- import React__default from 'react';
3
- import { useChatProps, RemotePeers, Message, ChatProps } from './types.js';
1
+ import React from 'react';
2
+ import { UseChatProps, UseChatReturn, ChatProps } from './types.js';
4
3
  import 'peerjs';
5
4
 
6
- declare function useChat({ name, peerId, remotePeerId, peerOptions, text, recoverChat, audio: allowed, onError, onMicError, onMessageSent, onMessageReceived, }: useChatProps): {
7
- peerId: string;
8
- audioStreamRef: React.RefObject<HTMLMediaElement | null>;
9
- remotePeers: RemotePeers;
10
- messages: Message[];
11
- sendMessage: (message: Message) => void;
12
- audio: boolean;
13
- setAudio: (value: boolean | ((old: boolean) => boolean)) => void;
14
- };
5
+ declare function useChat({ peerId, name, remotePeerId, peerOptions, text, recoverChat, audio: allowed, onError, onMicError, onMessageSent, onMessageReceived, }: UseChatProps): UseChatReturn;
15
6
 
16
7
  declare const clearChat: () => void;
17
8
 
18
- declare function Chat({ text, audio, onMessageReceived, dialogOptions, props, children, ...hookProps }: ChatProps): React__default.JSX.Element;
9
+ declare function Chat({ text, audio, onMessageReceived, dialogOptions, props, children, ...hookProps }: ChatProps): React.JSX.Element;
19
10
 
20
11
  export { clearChat, Chat as default, useChat };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { __spreadValues, __async, __objRest, BiSolidMessageX, BiSolidMessageDetail, GrSend, BsFillMicFill, BsFillMicMuteFill } from './chunks/chunk-4CBQRGQ3.js';
2
- import React, { useRef, useState, useEffect } from 'react';
1
+ import { __spreadValues, __objRest, BiSolidMessageX, BiSolidMessageDetail, GrSend, BsFillMicFill, BsFillMicMuteFill, __spreadProps } from './chunks/chunk-NXKKI6WY.js';
2
+ import React, { useState, useRef, useMemo, useEffect } from 'react';
3
3
 
4
4
  // src/constants.ts
5
5
  var turnAccounts = [
@@ -10,21 +10,25 @@ var turnAccounts = [
10
10
  { username: "0be25ab7f61d9d733ba94809", credential: "hiiSwWVch+ftt3SX" },
11
11
  { username: "3c25ba948daeab04f9b66187", credential: "FQB3GQwd27Y0dPeK" }
12
12
  ];
13
- var defaultConfig = {
14
- iceServers: [
15
- {
16
- urls: ["stun:stun.l.google.com:19302", "stun:stun.relay.metered.ca:80"]
17
- }
18
- ].concat(
19
- turnAccounts.map((account) => __spreadValues({
20
- urls: [
21
- "turn:standard.relay.metered.ca:80",
22
- "turn:standard.relay.metered.ca:80?transport=tcp",
23
- "turn:standard.relay.metered.ca:443",
24
- "turns:standard.relay.metered.ca:443?transport=tcp"
25
- ]
26
- }, account))
27
- )
13
+ var defaults = {
14
+ config: {
15
+ iceServers: [
16
+ {
17
+ urls: ["stun:stun.l.google.com:19302", "stun:stun.relay.metered.ca:80"]
18
+ }
19
+ ].concat(
20
+ turnAccounts.map((account) => __spreadValues({
21
+ urls: [
22
+ "turn:standard.relay.metered.ca:80",
23
+ "turn:standard.relay.metered.ca:80?transport=tcp",
24
+ "turn:standard.relay.metered.ca:443",
25
+ "turns:standard.relay.metered.ca:443?transport=tcp"
26
+ ]
27
+ }, account))
28
+ )
29
+ },
30
+ peerOptions: {},
31
+ remotePeerId: []
28
32
  };
29
33
 
30
34
  // src/lib/connection.ts
@@ -34,38 +38,41 @@ function closeConnection(conn) {
34
38
  }
35
39
 
36
40
  // src/lib/storage.ts
37
- var removeStorage = (key, local = false) => (local ? localStorage : sessionStorage).removeItem(key);
38
- var setStorage = (key, value, local = false) => (local ? localStorage : sessionStorage).setItem(key, JSON.stringify(value));
39
- var getStorage = (key, fallbackValue, local = false) => {
40
- let value = (local ? localStorage : sessionStorage).getItem(key);
41
- try {
42
- if (!value) throw new Error("Value doesn't exist");
43
- value = JSON.parse(value);
44
- } catch (e) {
45
- if (fallbackValue !== void 0) {
46
- value = fallbackValue;
47
- setStorage(key, value, local);
48
- } else {
49
- value = null;
50
- removeStorage(key, local);
51
- }
52
- }
53
- return value;
54
- };
41
+ var getStorageInstance = (local = true) => local ? localStorage : sessionStorage;
42
+ var removeStorage = (key, local = true) => getStorageInstance(local).removeItem(key);
55
43
  var clearChat = () => {
56
44
  removeStorage("rpc-remote-peer");
57
45
  removeStorage("rpc-messages");
58
46
  };
47
+ var setStorage = (key, value, local = true) => getStorageInstance(local).setItem(key, JSON.stringify(value));
48
+ function getStorage(key, fallbackValue, local = true) {
49
+ const value = getStorageInstance(local).getItem(key);
50
+ if (value) {
51
+ try {
52
+ return JSON.parse(value);
53
+ } catch (e) {
54
+ removeStorage(key, local);
55
+ }
56
+ }
57
+ if (fallbackValue !== void 0) setStorage(key, fallbackValue, local);
58
+ return fallbackValue;
59
+ }
59
60
 
60
61
  // src/lib/utils.ts
61
62
  var addPrefix = (str) => `rpc-${str}`;
62
63
 
64
+ // src/lib/react.ts
65
+ function isSetStateFunction(v) {
66
+ return typeof v === "function";
67
+ }
68
+
63
69
  // src/hooks.ts
70
+ var { config: defaultConfig, peerOptions: defaultPeerOptions, remotePeerId: defaultRemotePeerId } = defaults;
64
71
  function useChat({
65
- name,
66
72
  peerId,
67
- remotePeerId = [],
68
- peerOptions,
73
+ name = "Anonymous User",
74
+ remotePeerId = defaultRemotePeerId,
75
+ peerOptions = defaultPeerOptions,
69
76
  text = true,
70
77
  recoverChat = false,
71
78
  audio: allowed = true,
@@ -74,26 +81,25 @@ function useChat({
74
81
  onMessageSent,
75
82
  onMessageReceived
76
83
  }) {
84
+ const [peer, setPeer] = useState();
77
85
  const [audio, setAudio] = useAudio(allowed);
78
- const audioStreamRef = useRef(null);
79
86
  const connRef = useRef({});
80
- const localStream = useRef(null);
87
+ const localStreamRef = useRef(null);
88
+ const audioStreamRef = useRef(null);
89
+ const callsRef = useRef({});
81
90
  const [messages, setMessages, addMessage] = useMessages();
82
- const [peer, setPeer] = useState();
83
91
  const [remotePeers, setRemotePeers] = useStorage("rpc-remote-peer", {});
84
- peerId = addPrefix(peerId);
85
- if (typeof remotePeerId === "string") remotePeerId = [remotePeerId];
86
- const remotePeerIds = remotePeerId.map(addPrefix);
92
+ const { completePeerId, completeRemotePeerIds } = useMemo(() => {
93
+ const remotePeerIds = Array.isArray(remotePeerId) ? remotePeerId : [remotePeerId];
94
+ return { completePeerId: addPrefix(peerId), completeRemotePeerIds: remotePeerIds.map(addPrefix) };
95
+ }, [peerId]);
87
96
  function handleConnection(conn) {
88
97
  connRef.current[conn.peer] = conn;
89
98
  conn.on("open", () => {
90
99
  conn.on("data", ({ message, messages: messages2, remotePeerName, type }) => {
91
100
  if (type === "message") receiveMessage(message);
92
101
  else if (type === "init") {
93
- setRemotePeers((prev) => {
94
- prev[conn.peer] = remotePeerName || "Anonymous User";
95
- return prev;
96
- });
102
+ setRemotePeers((prev) => __spreadProps(__spreadValues({}, prev), { [conn.peer]: remotePeerName }));
97
103
  if (recoverChat) setMessages((old) => messages2.length > old.length ? messages2 : old);
98
104
  }
99
105
  });
@@ -118,80 +124,76 @@ function useChat({
118
124
  onMessageSent == null ? void 0 : onMessageSent(message);
119
125
  }
120
126
  useEffect(() => {
121
- if (!text && !audio) {
122
- setPeer(void 0);
123
- return;
124
- }
125
- (function() {
126
- return __async(this, null, function* () {
127
- const {
128
- Peer,
129
- util: {
130
- supports: { audioVideo, data }
131
- }
132
- } = yield import('peerjs');
127
+ if (!text && !audio) return;
128
+ import('peerjs').then(
129
+ ({
130
+ Peer,
131
+ util: {
132
+ supports: { audioVideo, data }
133
+ }
134
+ }) => {
133
135
  if (!data || !audioVideo) return onError();
134
- const peer2 = new Peer(peerId, __spreadValues({ config: defaultConfig }, peerOptions));
136
+ const peer2 = new Peer(completePeerId, __spreadValues({ config: defaultConfig }, peerOptions));
137
+ peer2.on("connection", handleConnection);
135
138
  setPeer(peer2);
139
+ }
140
+ );
141
+ return () => {
142
+ setPeer((prev) => {
143
+ prev == null ? void 0 : prev.removeAllListeners();
144
+ prev == null ? void 0 : prev.destroy();
145
+ return void 0;
136
146
  });
137
- })();
138
- }, [audio]);
147
+ };
148
+ }, [completePeerId]);
139
149
  useEffect(() => {
140
- if (!peer) return;
141
- let calls = {};
142
- peer.on("open", () => {
143
- remotePeerIds.forEach((id) => {
144
- if (text) handleConnection(peer.connect(id));
145
- });
146
- if (audio) {
147
- const getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
148
- try {
149
- getUserMedia(
150
- {
151
- video: false,
152
- audio: {
153
- autoGainControl: false,
154
- // Disable automatic gain control
155
- noiseSuppression: true,
156
- // Enable noise suppression
157
- echoCancellation: true
158
- // Enable echo cancellation
159
- }
160
- },
161
- (stream) => {
162
- localStream.current = stream;
163
- remotePeerIds.forEach((id) => {
164
- const call = peer.call(id, stream);
165
- call.on("stream", handleRemoteStream);
166
- call.on("close", call.removeAllListeners);
167
- calls[id] = call;
168
- });
169
- peer.on("call", (call) => {
170
- call.answer(stream);
171
- call.on("stream", handleRemoteStream);
172
- call.on("close", call.removeAllListeners);
173
- calls[call.peer] = call;
174
- });
175
- },
176
- handleError
177
- );
178
- } catch (e) {
179
- handleError();
180
- }
181
- }
182
- });
183
- peer.on("connection", handleConnection);
150
+ if (!text || !peer) return;
151
+ const handleOpen = () => completeRemotePeerIds.forEach((id) => handleConnection(peer.connect(id)));
152
+ if (peer.open) handleOpen();
153
+ else peer.once("open", handleOpen);
184
154
  return () => {
185
- var _a;
186
- (_a = localStream.current) == null ? void 0 : _a.getTracks().forEach((track) => track.stop());
187
155
  Object.values(connRef.current).forEach(closeConnection);
188
156
  connRef.current = {};
189
- Object.values(calls).forEach(closeConnection);
190
- peer.removeAllListeners();
191
- peer.destroy();
192
157
  };
193
- }, [peer]);
194
- return { peerId, audioStreamRef, remotePeers, messages, sendMessage, audio, setAudio };
158
+ }, [text, peer]);
159
+ useEffect(() => {
160
+ if (!audio || !peer) return;
161
+ const setupAudio = () => navigator.mediaDevices.getUserMedia({
162
+ video: false,
163
+ audio: {
164
+ autoGainControl: false,
165
+ // Disable automatic gain control
166
+ noiseSuppression: true,
167
+ // Enable noise suppression
168
+ echoCancellation: true
169
+ // Enable echo cancellation
170
+ }
171
+ }).then((stream) => {
172
+ localStreamRef.current = stream;
173
+ completeRemotePeerIds.forEach((id) => {
174
+ const call = peer.call(id, stream);
175
+ call.on("stream", handleRemoteStream);
176
+ call.on("close", call.removeAllListeners);
177
+ callsRef.current[id] = call;
178
+ });
179
+ peer.on("call", (call) => {
180
+ call.answer(stream);
181
+ call.on("stream", handleRemoteStream);
182
+ call.on("close", call.removeAllListeners);
183
+ callsRef.current[call.peer] = call;
184
+ });
185
+ }).catch(handleError);
186
+ if (peer.open) setupAudio();
187
+ else peer.once("open", setupAudio);
188
+ return () => {
189
+ var _a;
190
+ (_a = localStreamRef.current) == null ? void 0 : _a.getTracks().forEach((track) => track.stop());
191
+ localStreamRef.current = null;
192
+ Object.values(callsRef.current).forEach(closeConnection);
193
+ callsRef.current = {};
194
+ };
195
+ }, [audio, peer]);
196
+ return { peerId: completePeerId, audioStreamRef, remotePeers, messages, sendMessage, audio, setAudio };
195
197
  }
196
198
  function useMessages() {
197
199
  const [messages, setMessages] = useStorage("rpc-messages", []);
@@ -204,10 +206,10 @@ function useStorage(key, initialValue, local = false) {
204
206
  return getStorage(key, initialValue, local);
205
207
  });
206
208
  const setValue = (value) => {
207
- setStoredValue((old) => {
208
- const updatedValue = typeof value === "function" ? value(old) : value;
209
- setStorage(key, updatedValue, local);
210
- return updatedValue;
209
+ setStoredValue((prev) => {
210
+ const next = isSetStateFunction(value) ? value(prev) : value;
211
+ setStorage(key, next, local);
212
+ return next;
211
213
  });
212
214
  };
213
215
  return [storedValue, setValue];
package/dist/types.d.ts CHANGED
@@ -1,30 +1,43 @@
1
- import { PeerOptions } from 'peerjs';
1
+ import { PeerOptions, DataConnection, MediaConnection } from 'peerjs';
2
2
  export { PeerOptions } from 'peerjs';
3
- import { CSSProperties, DetailedHTMLProps, HTMLAttributes, ReactNode } from 'react';
3
+ import { DetailedHTMLProps, HTMLAttributes, RefObject, SetStateAction, CSSProperties, ReactNode } from 'react';
4
4
 
5
+ type Connection = DataConnection | MediaConnection;
6
+ type ErrorHandler = () => void;
5
7
  type Message = {
6
8
  id: string;
7
9
  text: string;
8
10
  };
9
- type MessageEvent = (message: Message) => void;
11
+ type MessageEventHandler = (message: Message) => void;
10
12
 
11
13
  type RemotePeerId = string | string[];
12
- type useChatProps = {
13
- name?: string;
14
+ type UseChatProps = {
14
15
  peerId: string;
16
+ name?: string;
15
17
  remotePeerId?: RemotePeerId;
16
18
  text?: boolean;
17
19
  recoverChat?: boolean;
18
20
  audio?: boolean;
19
21
  peerOptions?: PeerOptions;
20
- onError?: Function;
21
- onMicError?: Function;
22
- onMessageSent?: MessageEvent;
23
- onMessageReceived?: MessageEvent;
22
+ onError?: ErrorHandler;
23
+ onMicError?: ErrorHandler;
24
+ onMessageSent?: MessageEventHandler;
25
+ onMessageReceived?: MessageEventHandler;
24
26
  };
25
- type IconProps = React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>;
26
- type RemotePeers = {
27
- [id: string]: string;
27
+ type UseChatReturn = {
28
+ peerId: string;
29
+ audioStreamRef: RefObject<HTMLMediaElement | null>;
30
+ remotePeers: RemotePeers;
31
+ messages: Message[];
32
+ sendMessage: (message: Message) => void;
33
+ audio: boolean;
34
+ setAudio: (value: SetStateAction<boolean>) => void;
35
+ };
36
+ type IconProps = DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>;
37
+ type ChatProps = UseChatProps & {
38
+ dialogOptions?: DialogOptions;
39
+ props?: DivProps;
40
+ children?: Children;
28
41
  };
29
42
  type Children = (childrenOptions: ChildrenOptions) => ReactNode;
30
43
  type ChildrenOptions = {
@@ -34,16 +47,14 @@ type ChildrenOptions = {
34
47
  audio?: boolean;
35
48
  setAudio?: (audio: boolean) => void;
36
49
  };
37
- type ChatProps = useChatProps & {
38
- dialogOptions?: DialogOptions;
39
- props?: DivProps;
40
- children?: Children;
41
- };
42
50
  type DialogOptions = {
43
51
  position?: DialogPosition;
44
52
  style?: CSSProperties;
45
53
  };
46
54
  type DialogPosition = "left" | "center" | "right";
47
55
  type DivProps = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
56
+ type RemotePeers = {
57
+ [id: string]: string;
58
+ };
48
59
 
49
- export type { ChatProps, Children, ChildrenOptions, DialogOptions, DialogPosition, DivProps, IconProps, Message, MessageEvent, RemotePeerId, RemotePeers, useChatProps };
60
+ export type { ChatProps, Children, ChildrenOptions, Connection, DialogOptions, DialogPosition, DivProps, ErrorHandler, IconProps, Message, MessageEventHandler, RemotePeerId, RemotePeers, UseChatProps, UseChatReturn };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-peer-chat",
3
- "version": "0.8.4",
3
+ "version": "0.8.5",
4
4
  "description": "An easy to use react component for impleting peer-to-peer chatting.",
5
5
  "license": "MIT",
6
6
  "author": "Sahil Aggarwal <aggarwalsahil2004@gmail.com>",