react-peer-chat 0.11.11 → 0.12.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/README.md CHANGED
@@ -47,6 +47,7 @@ The default export of `react-peer-chat` is the `<Chat>` component, which offers
47
47
  ```jsx
48
48
  import React from "react";
49
49
  import Chat, { clearChat } from "react-peer-chat";
50
+ import "react-peer-chat/style.css";
50
51
 
51
52
  export default function App() {
52
53
  return (
@@ -64,6 +65,7 @@ export default function App() {
64
65
  ```jsx
65
66
  import React from "react";
66
67
  import Chat, { clearChat } from "react-peer-chat";
68
+ import "react-peer-chat/style.css";
67
69
 
68
70
  export default function App() {
69
71
  return (
@@ -89,6 +91,7 @@ Use the props provided by the `<Chat>` component for customization.
89
91
  ```jsx
90
92
  import React from "react";
91
93
  import Chat from "react-peer-chat";
94
+ import "react-peer-chat/style.css";
92
95
 
93
96
  export default function App() {
94
97
  return (
@@ -141,6 +144,7 @@ You can also provide custom ICE servers to avoid connectivity issues if the free
141
144
  ```jsx
142
145
  import React from "react";
143
146
  import Chat from "react-peer-chat";
147
+ import "react-peer-chat/style.css";
144
148
 
145
149
  export default function App() {
146
150
  return (
@@ -176,6 +180,7 @@ The `useChat` hook is ideal when you want to completely redesign the Chat UI.
176
180
  import React, { useEffect, useRef, useState } from "react";
177
181
  import { clearChat, useChat } from "react-peer-chat";
178
182
  import { BiSolidMessageDetail, BiSolidMessageX, BsFillMicFill, BsFillMicMuteFill, GrSend } from "react-peer-chat/icons";
183
+ import "react-peer-chat/style.css";
179
184
 
180
185
  function Chat({ text = true, audio = true, onMessageReceived, dialogOptions, props = {}, children, ...hookProps }) {
181
186
  const {
@@ -1,11 +1,11 @@
1
- import React from 'react';
2
- import { IconProps } from './types.js';
3
- import 'peerjs';
1
+ import { l as IconProps } from "./types-CwAnkJpd.mjs";
2
+ import React from "react";
4
3
 
4
+ //#region src/icons.d.ts
5
5
  declare function BiSolidMessageDetail(props: IconProps): React.JSX.Element;
6
6
  declare function BiSolidMessageX(props: IconProps): React.JSX.Element;
7
7
  declare function GrSend(props: IconProps): React.JSX.Element;
8
8
  declare function BsFillMicFill(props: IconProps): React.JSX.Element;
9
9
  declare function BsFillMicMuteFill(props: IconProps): React.JSX.Element;
10
-
11
- export { BiSolidMessageDetail, BiSolidMessageX, BsFillMicFill, BsFillMicMuteFill, GrSend };
10
+ //#endregion
11
+ export { BiSolidMessageDetail, BiSolidMessageX, BsFillMicFill, BsFillMicMuteFill, GrSend };
package/dist/icons.mjs ADDED
@@ -0,0 +1,62 @@
1
+ import React from "react";
2
+ //#region src/icons.tsx
3
+ function BiSolidMessageDetail(props) {
4
+ return /* @__PURE__ */ React.createElement("span", {
5
+ className: "rpc-icon-container",
6
+ ...props
7
+ }, /* @__PURE__ */ React.createElement("svg", {
8
+ viewBox: "0 0 24 24",
9
+ width: "1.25rem",
10
+ height: "1.25rem"
11
+ }, /* @__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" })));
12
+ }
13
+ function BiSolidMessageX(props) {
14
+ return /* @__PURE__ */ React.createElement("span", {
15
+ className: "rpc-icon-container",
16
+ ...props
17
+ }, /* @__PURE__ */ React.createElement("svg", {
18
+ viewBox: "0 0 24 24",
19
+ width: "1.25rem",
20
+ height: "1.25rem"
21
+ }, /* @__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-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" })));
22
+ }
23
+ function GrSend(props) {
24
+ return /* @__PURE__ */ React.createElement("span", {
25
+ className: "rpc-icon-container",
26
+ ...props
27
+ }, /* @__PURE__ */ React.createElement("svg", {
28
+ viewBox: "0 0 24 24",
29
+ width: "1.25rem",
30
+ height: "1.25rem",
31
+ className: "rpc-invert"
32
+ }, /* @__PURE__ */ React.createElement("path", {
33
+ fill: "none",
34
+ stroke: "#000",
35
+ strokeWidth: 2,
36
+ 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"
37
+ })));
38
+ }
39
+ function BsFillMicFill(props) {
40
+ return /* @__PURE__ */ React.createElement("span", {
41
+ className: "rpc-icon-container",
42
+ ...props
43
+ }, /* @__PURE__ */ React.createElement("svg", {
44
+ viewBox: "0 0 16 16",
45
+ fill: "currentColor",
46
+ width: "1.25rem",
47
+ height: "1.25rem"
48
+ }, /* @__PURE__ */ React.createElement("path", { d: "M5 3a3 3 0 0 1 6 0v5a3 3 0 0 1-6 0V3z" }), /* @__PURE__ */ 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" })));
49
+ }
50
+ function BsFillMicMuteFill(props) {
51
+ return /* @__PURE__ */ React.createElement("span", {
52
+ className: "rpc-icon-container",
53
+ ...props
54
+ }, /* @__PURE__ */ React.createElement("svg", {
55
+ viewBox: "0 0 16 16",
56
+ fill: "currentColor",
57
+ width: "1.25rem",
58
+ height: "1.25rem"
59
+ }, /* @__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" })));
60
+ }
61
+ //#endregion
62
+ export { BiSolidMessageDetail, BiSolidMessageX, BsFillMicFill, BsFillMicMuteFill, GrSend };
@@ -0,0 +1,34 @@
1
+ import { b as UseChatReturn, t as ChatProps, y as UseChatProps } from "./types-CwAnkJpd.mjs";
2
+ import React, { SetStateAction } from "react";
3
+
4
+ //#region src/components.d.ts
5
+ declare function Chat({
6
+ text,
7
+ audio,
8
+ onMessageReceived,
9
+ dialogOptions,
10
+ props,
11
+ children,
12
+ ...hookProps
13
+ }: ChatProps): React.JSX.Element;
14
+ //#endregion
15
+ //#region src/hooks.d.ts
16
+ declare function useChat({
17
+ peerId,
18
+ name,
19
+ remotePeerId,
20
+ peerOptions,
21
+ text,
22
+ recoverChat,
23
+ audio: allowed,
24
+ onError,
25
+ onPeerError,
26
+ onNetworkError,
27
+ onMessageSent,
28
+ onMessageReceived
29
+ }: UseChatProps): UseChatReturn;
30
+ //#endregion
31
+ //#region src/lib/storage.d.ts
32
+ declare function clearChat(): void;
33
+ //#endregion
34
+ export { clearChat, Chat as default, useChat };
package/dist/index.mjs ADDED
@@ -0,0 +1,440 @@
1
+ import { BiSolidMessageDetail, BiSolidMessageX, BsFillMicFill, BsFillMicMuteFill, GrSend } from "./icons.mjs";
2
+ import React, { useEffect, useMemo, useRef, useState } from "react";
3
+ const defaults = {
4
+ config: { iceServers: [{ urls: ["stun:stun.l.google.com:19302", "stun:stun.relay.metered.ca:80"] }].concat([
5
+ {
6
+ username: "70061a377b51f3a3d01c11e3",
7
+ credential: "lHV4NYJ5Rfl5JNa9"
8
+ },
9
+ {
10
+ username: "13b19eb65bbf6e9f96d64b72",
11
+ credential: "7R9P/+7y7Q516Etv"
12
+ },
13
+ {
14
+ username: "3469603f5cdc7ca4a1e891ae",
15
+ credential: "/jMyLSDbbcgqpVQv"
16
+ },
17
+ {
18
+ username: "a7926f4dcc4a688d41f89752",
19
+ credential: "ZYM8jFYeb8bQkL+N"
20
+ },
21
+ {
22
+ username: "0be25ab7f61d9d733ba94809",
23
+ credential: "hiiSwWVch+ftt3SX"
24
+ },
25
+ {
26
+ username: "3c25ba948daeab04f9b66187",
27
+ credential: "FQB3GQwd27Y0dPeK"
28
+ }
29
+ ].map((account) => ({
30
+ urls: [
31
+ "turn:standard.relay.metered.ca:80",
32
+ "turn:standard.relay.metered.ca:80?transport=tcp",
33
+ "turn:standard.relay.metered.ca:443",
34
+ "turns:standard.relay.metered.ca:443?transport=tcp"
35
+ ],
36
+ ...account
37
+ }))) },
38
+ peerOptions: {},
39
+ remotePeerId: []
40
+ };
41
+ const iosRegex = /iPhone|iPad|iPod/i;
42
+ const mobileRegex = /Android|webOS|BlackBerry|IEMobile|Opera Mini/i;
43
+ //#endregion
44
+ //#region src/lib/connection.ts
45
+ function closeConnection(conn) {
46
+ conn.removeAllListeners();
47
+ conn.close();
48
+ }
49
+ //#endregion
50
+ //#region src/lib/react.ts
51
+ function isSetStateFunction(v) {
52
+ return typeof v === "function";
53
+ }
54
+ //#endregion
55
+ //#region src/lib/storage.ts
56
+ const listeners = /* @__PURE__ */ new Map();
57
+ function clearChat() {
58
+ removeStorage("rpc-remote-peer", false);
59
+ removeStorage("rpc-messages", false);
60
+ }
61
+ const getStorageInstance = (local) => local ? localStorage : sessionStorage;
62
+ const getNamespacedKey = (key, local) => `${local ? "local" : "session"}:${key}`;
63
+ function getStorage(key, local, fallbackValue) {
64
+ if (typeof window === "undefined") return fallbackValue;
65
+ const value = getStorageInstance(local).getItem(key);
66
+ if (value) try {
67
+ return JSON.parse(value);
68
+ } catch {
69
+ removeStorage(key, local);
70
+ }
71
+ if (fallbackValue !== void 0) setStorage(key, fallbackValue, local);
72
+ return fallbackValue;
73
+ }
74
+ function publish(key, local, value) {
75
+ const callbacks = listeners.get(getNamespacedKey(key, local));
76
+ if (callbacks) callbacks.forEach((callback) => callback(value));
77
+ }
78
+ function removeStorage(key, local) {
79
+ getStorageInstance(local).removeItem(key);
80
+ publish(key, local);
81
+ }
82
+ function setStorage(key, value, local) {
83
+ const next = isSetStateFunction(value) ? value(getStorage(key, local)) : value;
84
+ getStorageInstance(local).setItem(key, JSON.stringify(next));
85
+ publish(key, local, next);
86
+ }
87
+ function subscribeToStorage(key, local, callback) {
88
+ key = getNamespacedKey(key, local);
89
+ if (!listeners.has(key)) listeners.set(key, /* @__PURE__ */ new Set());
90
+ const set = listeners.get(key);
91
+ set.add(callback);
92
+ return () => {
93
+ set.delete(callback);
94
+ if (set.size === 0) listeners.delete(key);
95
+ };
96
+ }
97
+ //#endregion
98
+ //#region src/lib/utils.ts
99
+ const addPrefix = (str) => `rpc-${str}`;
100
+ function isMobile(iOS = true) {
101
+ let result = navigator.userAgentData?.mobile;
102
+ result ??= mobileRegex.test(navigator.userAgent) || iOS && iosRegex.test(navigator.userAgent);
103
+ return result;
104
+ }
105
+ //#endregion
106
+ //#region src/hooks.ts
107
+ const { config: defaultConfig, peerOptions: defaultPeerOptions, remotePeerId: defaultRemotePeerId } = defaults;
108
+ function useChat({ peerId, name = "Anonymous User", remotePeerId = defaultRemotePeerId, peerOptions = defaultPeerOptions, text = true, recoverChat = false, audio: allowed = true, onError = console.error, onPeerError = console.error, onNetworkError, onMessageSent, onMessageReceived }) {
109
+ const [peerEpoch, setPeerEpoch] = useState(0);
110
+ const [peerGeneration, setPeerGeneration] = useState(0);
111
+ const [audio, setAudio] = useAudio(allowed);
112
+ const peerRef = useRef(null);
113
+ const scheduleReconnectRef = useRef(null);
114
+ const connRef = useRef({});
115
+ const callsRef = useRef({});
116
+ const localStreamRef = useRef(null);
117
+ const audioContextRef = useRef(null);
118
+ const mixerRef = useRef(null);
119
+ const sourceNodesRef = useRef({});
120
+ const [messages, setMessages, addMessage] = useMessages();
121
+ const [remotePeers, setRemotePeers] = useStorage("rpc-remote-peer", {});
122
+ const { completePeerId, completeRemotePeerIds } = useMemo(() => {
123
+ const remotePeerIds = Array.isArray(remotePeerId) ? remotePeerId : [remotePeerId];
124
+ return {
125
+ completePeerId: addPrefix(peerId),
126
+ completeRemotePeerIds: remotePeerIds.map(addPrefix)
127
+ };
128
+ }, [peerId]);
129
+ function resetConnections(type = "all") {
130
+ switch (type) {
131
+ case "all":
132
+ resetConnections("data");
133
+ resetConnections("call");
134
+ break;
135
+ case "data":
136
+ Object.values(connRef.current).forEach(closeConnection);
137
+ connRef.current = {};
138
+ break;
139
+ case "call":
140
+ Object.values(callsRef.current).forEach(closeConnection);
141
+ Object.keys(sourceNodesRef.current).forEach(removePeerAudio);
142
+ callsRef.current = {};
143
+ break;
144
+ }
145
+ }
146
+ function handleConnection(conn) {
147
+ const peerId = conn.peer;
148
+ conn.on("open", () => {
149
+ connRef.current[peerId]?.close();
150
+ connRef.current[peerId] = conn;
151
+ conn.on("data", ({ type, message, messages, remotePeerName }) => {
152
+ switch (type) {
153
+ case "init":
154
+ setRemotePeers((prev) => ({
155
+ ...prev,
156
+ [peerId]: remotePeerName
157
+ }));
158
+ if (recoverChat) setMessages((old) => messages.length > old.length ? messages : old);
159
+ break;
160
+ case "message":
161
+ receiveMessage(message);
162
+ break;
163
+ }
164
+ });
165
+ conn.send({
166
+ type: "init",
167
+ remotePeerName: name,
168
+ messages
169
+ });
170
+ });
171
+ conn.on("close", () => {
172
+ conn.removeAllListeners();
173
+ if (connRef.current[peerId] === conn) delete connRef.current[peerId];
174
+ });
175
+ }
176
+ function handleCall(call) {
177
+ const peerId = call.peer;
178
+ if (!call.localStream) {
179
+ if (!localStreamRef.current) return call.close();
180
+ call.answer(localStreamRef.current);
181
+ }
182
+ call.on("stream", () => {
183
+ callsRef.current[peerId]?.close();
184
+ callsRef.current[peerId] = call;
185
+ if (!audioContextRef.current) audioContextRef.current = new AudioContext();
186
+ if (audioContextRef.current.state === "suspended") audioContextRef.current.resume();
187
+ if (!mixerRef.current) {
188
+ mixerRef.current = audioContextRef.current.createGain();
189
+ mixerRef.current.connect(audioContextRef.current.destination);
190
+ }
191
+ removePeerAudio(peerId);
192
+ const audio = new Audio();
193
+ audio.srcObject = call.remoteStream;
194
+ audio.autoplay = true;
195
+ audio.muted = false;
196
+ const source = audioContextRef.current.createMediaElementSource(audio);
197
+ source.connect(mixerRef.current);
198
+ sourceNodesRef.current[peerId] = source;
199
+ });
200
+ call.on("close", () => {
201
+ call.removeAllListeners();
202
+ if (callsRef.current[peerId] === call) {
203
+ removePeerAudio(peerId);
204
+ delete callsRef.current[peerId];
205
+ }
206
+ });
207
+ }
208
+ function receiveMessage(message) {
209
+ addMessage(message);
210
+ onMessageReceived?.(message);
211
+ }
212
+ function removePeerAudio(peerId) {
213
+ const source = sourceNodesRef.current[peerId];
214
+ if (!source) return;
215
+ source.disconnect();
216
+ delete sourceNodesRef.current[peerId];
217
+ }
218
+ function sendMessage(message) {
219
+ const event = {
220
+ type: "message",
221
+ message: {
222
+ ...message,
223
+ name
224
+ }
225
+ };
226
+ addMessage(event.message);
227
+ Object.values(connRef.current).forEach((conn) => conn.send(event));
228
+ onMessageSent?.(event.message);
229
+ }
230
+ useEffect(() => {
231
+ const onOnline = () => peerRef.current?.disconnected && scheduleReconnectRef.current?.();
232
+ window.addEventListener("online", onOnline);
233
+ return () => window.removeEventListener("online", onOnline);
234
+ }, []);
235
+ useEffect(() => {
236
+ if (!text && !audio) return;
237
+ let destroyed = false;
238
+ let reconnecting = false;
239
+ let reconnectTimer;
240
+ scheduleReconnectRef.current = () => {
241
+ if (destroyed || reconnecting) return;
242
+ reconnecting = true;
243
+ reconnectTimer = setTimeout(() => {
244
+ const peer = peerRef.current;
245
+ if (peer) if (isMobile()) setPeerGeneration((prev) => prev + 1);
246
+ else peer.reconnect();
247
+ reconnecting = false;
248
+ }, 1e3);
249
+ };
250
+ const scheduleReconnect = scheduleReconnectRef.current;
251
+ import("peerjs").then(({ Peer, util: { supports: { audioVideo, data } } }) => {
252
+ if (destroyed) return;
253
+ if (!data || !audioVideo) return onError(/* @__PURE__ */ new Error("Browser not supported! Try some other browser."));
254
+ peerRef.current = new Peer(completePeerId, {
255
+ config: defaultConfig,
256
+ ...peerOptions
257
+ });
258
+ setPeerEpoch((prev) => prev + 1);
259
+ const peer = peerRef.current;
260
+ peer.on("connection", handleConnection);
261
+ peer.on("call", handleCall);
262
+ peer.on("disconnected", () => {
263
+ resetConnections();
264
+ scheduleReconnect();
265
+ });
266
+ peer.on("error", (error) => {
267
+ if (error.type === "network" || error.type === "server-error") {
268
+ resetConnections();
269
+ scheduleReconnect();
270
+ onNetworkError?.(error);
271
+ }
272
+ onPeerError(error);
273
+ });
274
+ });
275
+ return () => {
276
+ destroyed = true;
277
+ reconnecting = false;
278
+ clearTimeout(reconnectTimer);
279
+ peerRef.current?.removeAllListeners();
280
+ peerRef.current?.destroy();
281
+ peerRef.current = null;
282
+ };
283
+ }, [completePeerId, peerGeneration]);
284
+ useEffect(() => {
285
+ if (!text) return;
286
+ const peer = peerRef.current;
287
+ if (!peer) return;
288
+ const connectData = () => completeRemotePeerIds.forEach((id) => handleConnection(peer.connect(id)));
289
+ if (peer.open) connectData();
290
+ peer.on("open", connectData);
291
+ return () => {
292
+ peer.off("open", connectData);
293
+ resetConnections("data");
294
+ };
295
+ }, [text, peerEpoch]);
296
+ useEffect(() => {
297
+ if (!audio) return;
298
+ const peer = peerRef.current;
299
+ if (!peer) return;
300
+ const setupAudio = async () => {
301
+ try {
302
+ if (!localStreamRef.current) localStreamRef.current = await navigator.mediaDevices.getUserMedia({
303
+ video: false,
304
+ audio: {
305
+ autoGainControl: true,
306
+ noiseSuppression: true,
307
+ echoCancellation: true
308
+ }
309
+ });
310
+ completeRemotePeerIds.forEach((id) => localStreamRef.current && handleCall(peer.call(id, localStreamRef.current)));
311
+ } catch {
312
+ setAudio(false);
313
+ onError(/* @__PURE__ */ new Error("Microphone not accessible"));
314
+ }
315
+ };
316
+ if (peer.open) setupAudio();
317
+ peer.on("open", setupAudio);
318
+ return () => {
319
+ peer.off("open", setupAudio);
320
+ localStreamRef.current?.getTracks().forEach((track) => track.stop());
321
+ resetConnections("call");
322
+ audioContextRef.current?.close();
323
+ localStreamRef.current = null;
324
+ audioContextRef.current = null;
325
+ mixerRef.current = null;
326
+ };
327
+ }, [audio, peerEpoch]);
328
+ return {
329
+ peerId: completePeerId,
330
+ remotePeers,
331
+ messages,
332
+ sendMessage,
333
+ audio,
334
+ setAudio
335
+ };
336
+ }
337
+ function useMessages() {
338
+ const [messages, setMessages] = useStorage("rpc-messages", []);
339
+ const addMessage = (message) => setMessages((prev) => prev.concat(message));
340
+ return [
341
+ messages,
342
+ setMessages,
343
+ addMessage
344
+ ];
345
+ }
346
+ function useStorage(key, initialValue, local = false) {
347
+ const [storedValue, setStoredValue] = useState(() => typeof window === "undefined" ? initialValue : getStorage(key, local, initialValue));
348
+ const setValue = (value) => {
349
+ setStoredValue((prev) => {
350
+ const next = isSetStateFunction(value) ? value(prev) : value;
351
+ setStorage(key, next, local);
352
+ return next;
353
+ });
354
+ };
355
+ useEffect(() => {
356
+ return subscribeToStorage(key, local, (value) => setStoredValue(value ?? initialValue));
357
+ }, [key, local]);
358
+ return [storedValue, setValue];
359
+ }
360
+ function useAudio(allowed) {
361
+ const [audio, setAudio] = useStorage("rpc-audio", false, true);
362
+ return [audio && allowed, setAudio];
363
+ }
364
+ //#endregion
365
+ //#region src/components.tsx
366
+ function Chat({ text = true, audio = true, onMessageReceived, dialogOptions, props = {}, children, ...hookProps }) {
367
+ const { peerId, ...childrenOptions } = useChat({
368
+ text,
369
+ audio,
370
+ onMessageReceived: receiveMessageHandler,
371
+ ...hookProps
372
+ });
373
+ const { remotePeers, messages, sendMessage, audio: audioEnabled, setAudio } = childrenOptions;
374
+ const containerRef = useRef(null);
375
+ const [dialog, setDialog] = useState(false);
376
+ const dialogRef = useRef(null);
377
+ const inputRef = useRef(null);
378
+ const [notification, setNotification] = useState(false);
379
+ function receiveMessageHandler(message) {
380
+ if (!dialogRef.current?.open) setNotification(true);
381
+ onMessageReceived?.(message);
382
+ }
383
+ useEffect(() => {
384
+ if (dialog) dialogRef.current?.show();
385
+ else dialogRef.current?.close();
386
+ }, [dialog]);
387
+ useEffect(() => {
388
+ const container = containerRef.current;
389
+ if (container) container.scrollTop = container.scrollHeight;
390
+ }, [
391
+ dialog,
392
+ remotePeers,
393
+ messages
394
+ ]);
395
+ return /* @__PURE__ */ React.createElement("div", {
396
+ className: "rpc-main rpc-font",
397
+ ...props
398
+ }, typeof children === "function" ? children(childrenOptions) : /* @__PURE__ */ React.createElement(React.Fragment, null, text && /* @__PURE__ */ React.createElement("div", { className: "rpc-dialog-container" }, dialog ? /* @__PURE__ */ React.createElement(BiSolidMessageX, {
399
+ title: "Close chat",
400
+ onClick: () => setDialog(false)
401
+ }) : /* @__PURE__ */ React.createElement("div", { className: "rpc-notification" }, /* @__PURE__ */ React.createElement(BiSolidMessageDetail, {
402
+ title: "Open chat",
403
+ onClick: () => {
404
+ setNotification(false);
405
+ setDialog(true);
406
+ }
407
+ }), notification && /* @__PURE__ */ React.createElement("span", { className: "rpc-badge" })), /* @__PURE__ */ React.createElement("dialog", {
408
+ ref: dialogRef,
409
+ className: `${dialog ? "rpc-dialog" : ""} rpc-position-${dialogOptions?.position || "center"}`,
410
+ style: dialogOptions?.style
411
+ }, /* @__PURE__ */ React.createElement("div", { className: "rpc-heading" }, "Chat"), /* @__PURE__ */ React.createElement("hr", { className: "rpc-hr" }), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", {
412
+ ref: containerRef,
413
+ className: "rpc-message-container"
414
+ }, messages.map(({ id, name, text }, i) => /* @__PURE__ */ React.createElement("div", { key: i }, /* @__PURE__ */ React.createElement("strong", null, id === peerId ? "You" : name, ": "), /* @__PURE__ */ React.createElement("span", null, text)))), /* @__PURE__ */ React.createElement("hr", { className: "rpc-hr" }), /* @__PURE__ */ React.createElement("form", {
415
+ className: "rpc-input-container",
416
+ onSubmit: (e) => {
417
+ e.preventDefault();
418
+ const text = inputRef.current?.value;
419
+ if (text) {
420
+ inputRef.current.value = "";
421
+ sendMessage({
422
+ id: peerId,
423
+ text
424
+ });
425
+ }
426
+ }
427
+ }, /* @__PURE__ */ React.createElement("input", {
428
+ ref: inputRef,
429
+ className: "rpc-input rpc-font",
430
+ placeholder: "Enter a message"
431
+ }), /* @__PURE__ */ React.createElement("button", {
432
+ type: "submit",
433
+ className: "rpc-button"
434
+ }, /* @__PURE__ */ React.createElement(GrSend, { title: "Send message" })))))), audio && /* @__PURE__ */ React.createElement("button", {
435
+ className: "rpc-button",
436
+ onClick: () => setAudio(!audioEnabled)
437
+ }, audioEnabled ? /* @__PURE__ */ React.createElement(BsFillMicFill, { title: "Turn mic off" }) : /* @__PURE__ */ React.createElement(BsFillMicMuteFill, { title: "Turn mic on" }))));
438
+ }
439
+ //#endregion
440
+ export { clearChat, Chat as default, useChat };