react-peer-chat 0.8.4 → 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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,42 +291,59 @@ 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) |
315
313
 
316
- ### Types
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) |
319
+
320
+ ## Types
317
321
 
318
- #### PeerOptions
322
+ ### Children
319
323
 
320
324
  ```typescript
321
- import { PeerOptions } from "peerjs";
325
+ import type { ReactNode } from "react";
326
+
327
+ type RemotePeers = { [id: string]: string };
328
+ type Message = {
329
+ id: string;
330
+ text: string;
331
+ };
332
+ type ChildrenOptions = {
333
+ remotePeers?: RemotePeers;
334
+ messages?: Message[];
335
+ sendMessage?: (message: Message) => void;
336
+ audio?: boolean;
337
+ setAudio?: (audio: boolean) => void;
338
+ };
339
+ type Children = (childrenOptions: ChildrenOptions) => ReactNode;
322
340
  ```
323
341
 
324
- #### DialogOptions
342
+ ### DialogOptions
325
343
 
326
344
  ```typescript
327
- import { CSSProperties } from "react";
345
+ import type { CSSProperties } from "react";
346
+
328
347
  type DialogPosition = "left" | "center" | "right";
329
348
  type DialogOptions = {
330
349
  position: DialogPosition;
@@ -332,25 +351,36 @@ type DialogOptions = {
332
351
  };
333
352
  ```
334
353
 
335
- #### Children
354
+ ### DivProps
355
+
356
+ ```typescript
357
+ import type { DetailedHTMLProps, HTMLAttributes } from "react";
358
+
359
+ type DivProps = DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
360
+ ```
361
+
362
+ ### ErrorHandler
363
+
364
+ ```typescript
365
+ type ErrorHandler = () => void;
366
+ ```
367
+
368
+ ### MessageEventHandler
336
369
 
337
370
  ```typescript
338
- import { ReactNode } from "react";
339
- type RemotePeers = { [id: string]: string };
340
371
  type Message = {
341
372
  id: string;
342
373
  text: string;
343
374
  };
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;
375
+ type MessageEventHandler = (message: Message) => void;
376
+ ```
377
+
378
+ ### PeerOptions
379
+
380
+ ```typescript
381
+ import type { PeerOptions } from "peerjs";
352
382
  ```
353
383
 
354
- ## Author
384
+ ## License
355
385
 
356
- [Sahil Aggarwal](https://github.com/SahilAggarwal2004)
386
+ This project is licensed under the [MIT License](LICENSE).
@@ -0,0 +1,203 @@
1
+ import { getStorage, setStorage } from './chunk-ZYFPSCFE.js';
2
+ import { __spreadValues, __spreadProps } from './chunk-LNEKYYG7.js';
3
+ import { useState, useRef, useMemo, useEffect } from 'react';
4
+
5
+ // src/constants.ts
6
+ var turnAccounts = [
7
+ { username: "70061a377b51f3a3d01c11e3", credential: "lHV4NYJ5Rfl5JNa9" },
8
+ { username: "13b19eb65bbf6e9f96d64b72", credential: "7R9P/+7y7Q516Etv" },
9
+ { username: "3469603f5cdc7ca4a1e891ae", credential: "/jMyLSDbbcgqpVQv" },
10
+ { username: "a7926f4dcc4a688d41f89752", credential: "ZYM8jFYeb8bQkL+N" },
11
+ { username: "0be25ab7f61d9d733ba94809", credential: "hiiSwWVch+ftt3SX" },
12
+ { username: "3c25ba948daeab04f9b66187", credential: "FQB3GQwd27Y0dPeK" }
13
+ ];
14
+ var defaults = {
15
+ config: {
16
+ iceServers: [
17
+ {
18
+ urls: ["stun:stun.l.google.com:19302", "stun:stun.relay.metered.ca:80"]
19
+ }
20
+ ].concat(
21
+ turnAccounts.map((account) => __spreadValues({
22
+ urls: [
23
+ "turn:standard.relay.metered.ca:80",
24
+ "turn:standard.relay.metered.ca:80?transport=tcp",
25
+ "turn:standard.relay.metered.ca:443",
26
+ "turns:standard.relay.metered.ca:443?transport=tcp"
27
+ ]
28
+ }, account))
29
+ )
30
+ },
31
+ peerOptions: {},
32
+ remotePeerId: []
33
+ };
34
+
35
+ // src/lib/connection.ts
36
+ function closeConnection(conn) {
37
+ conn.removeAllListeners();
38
+ conn.close();
39
+ }
40
+
41
+ // src/lib/utils.ts
42
+ var addPrefix = (str) => `rpc-${str}`;
43
+
44
+ // src/lib/react.ts
45
+ function isSetStateFunction(v) {
46
+ return typeof v === "function";
47
+ }
48
+
49
+ // src/hooks.ts
50
+ var { config: defaultConfig, peerOptions: defaultPeerOptions, remotePeerId: defaultRemotePeerId } = defaults;
51
+ function useChat({
52
+ peerId,
53
+ name = "Anonymous User",
54
+ remotePeerId = defaultRemotePeerId,
55
+ peerOptions = defaultPeerOptions,
56
+ text = true,
57
+ recoverChat = false,
58
+ audio: allowed = true,
59
+ onError = () => alert("Browser not supported! Try some other browser."),
60
+ onMicError = () => alert("Microphone not accessible!"),
61
+ onMessageSent,
62
+ onMessageReceived
63
+ }) {
64
+ const [peer, setPeer] = useState();
65
+ const [audio, setAudio] = useAudio(allowed);
66
+ const connRef = useRef({});
67
+ const localStreamRef = useRef(null);
68
+ const audioStreamRef = useRef(null);
69
+ const callsRef = useRef({});
70
+ const [messages, setMessages, addMessage] = useMessages();
71
+ const [remotePeers, setRemotePeers] = useStorage("rpc-remote-peer", {});
72
+ const { completePeerId, completeRemotePeerIds } = useMemo(() => {
73
+ const remotePeerIds = Array.isArray(remotePeerId) ? remotePeerId : [remotePeerId];
74
+ return { completePeerId: addPrefix(peerId), completeRemotePeerIds: remotePeerIds.map(addPrefix) };
75
+ }, [peerId]);
76
+ function handleConnection(conn) {
77
+ connRef.current[conn.peer] = conn;
78
+ conn.on("open", () => {
79
+ conn.on("data", ({ message, messages: messages2, remotePeerName, type }) => {
80
+ if (type === "message") receiveMessage(message);
81
+ else if (type === "init") {
82
+ setRemotePeers((prev) => __spreadProps(__spreadValues({}, prev), { [conn.peer]: remotePeerName }));
83
+ if (recoverChat) setMessages((old) => messages2.length > old.length ? messages2 : old);
84
+ }
85
+ });
86
+ conn.send({ type: "init", remotePeerName: name, messages });
87
+ });
88
+ conn.on("close", conn.removeAllListeners);
89
+ }
90
+ function handleError() {
91
+ setAudio(false);
92
+ onMicError();
93
+ }
94
+ function handleRemoteStream(remoteStream) {
95
+ if (audioStreamRef.current) audioStreamRef.current.srcObject = remoteStream;
96
+ }
97
+ function receiveMessage(message) {
98
+ addMessage(message);
99
+ onMessageReceived == null ? void 0 : onMessageReceived(message);
100
+ }
101
+ function sendMessage(message) {
102
+ addMessage(message);
103
+ Object.values(connRef.current).forEach((conn) => conn.send({ type: "message", message }));
104
+ onMessageSent == null ? void 0 : onMessageSent(message);
105
+ }
106
+ useEffect(() => {
107
+ if (!text && !audio) return;
108
+ import('peerjs').then(
109
+ ({
110
+ Peer,
111
+ util: {
112
+ supports: { audioVideo, data }
113
+ }
114
+ }) => {
115
+ if (!data || !audioVideo) return onError();
116
+ const peer2 = new Peer(completePeerId, __spreadValues({ config: defaultConfig }, peerOptions));
117
+ peer2.on("connection", handleConnection);
118
+ setPeer(peer2);
119
+ }
120
+ );
121
+ return () => {
122
+ setPeer((prev) => {
123
+ prev == null ? void 0 : prev.removeAllListeners();
124
+ prev == null ? void 0 : prev.destroy();
125
+ return void 0;
126
+ });
127
+ };
128
+ }, [completePeerId]);
129
+ useEffect(() => {
130
+ if (!text || !peer) return;
131
+ const handleOpen = () => completeRemotePeerIds.forEach((id) => handleConnection(peer.connect(id)));
132
+ if (peer.open) handleOpen();
133
+ else peer.once("open", handleOpen);
134
+ return () => {
135
+ Object.values(connRef.current).forEach(closeConnection);
136
+ connRef.current = {};
137
+ };
138
+ }, [text, peer]);
139
+ useEffect(() => {
140
+ if (!audio || !peer) return;
141
+ const setupAudio = () => navigator.mediaDevices.getUserMedia({
142
+ video: false,
143
+ audio: {
144
+ autoGainControl: false,
145
+ // Disable automatic gain control
146
+ noiseSuppression: true,
147
+ // Enable noise suppression
148
+ echoCancellation: true
149
+ // Enable echo cancellation
150
+ }
151
+ }).then((stream) => {
152
+ localStreamRef.current = stream;
153
+ completeRemotePeerIds.forEach((id) => {
154
+ const call = peer.call(id, stream);
155
+ call.on("stream", handleRemoteStream);
156
+ call.on("close", call.removeAllListeners);
157
+ callsRef.current[id] = call;
158
+ });
159
+ peer.on("call", (call) => {
160
+ call.answer(stream);
161
+ call.on("stream", handleRemoteStream);
162
+ call.on("close", call.removeAllListeners);
163
+ callsRef.current[call.peer] = call;
164
+ });
165
+ }).catch(handleError);
166
+ if (peer.open) setupAudio();
167
+ else peer.once("open", setupAudio);
168
+ return () => {
169
+ var _a;
170
+ (_a = localStreamRef.current) == null ? void 0 : _a.getTracks().forEach((track) => track.stop());
171
+ localStreamRef.current = null;
172
+ Object.values(callsRef.current).forEach(closeConnection);
173
+ callsRef.current = {};
174
+ };
175
+ }, [audio, peer]);
176
+ return { peerId: completePeerId, audioStreamRef, remotePeers, messages, sendMessage, audio, setAudio };
177
+ }
178
+ function useMessages() {
179
+ const [messages, setMessages] = useStorage("rpc-messages", []);
180
+ const addMessage = (message) => setMessages((prev) => prev.concat(message));
181
+ return [messages, setMessages, addMessage];
182
+ }
183
+ function useStorage(key, initialValue, local = false) {
184
+ const [storedValue, setStoredValue] = useState(() => {
185
+ if (typeof window === "undefined") return initialValue;
186
+ return getStorage(key, initialValue, local);
187
+ });
188
+ const setValue = (value) => {
189
+ setStoredValue((prev) => {
190
+ const next = isSetStateFunction(value) ? value(prev) : value;
191
+ setStorage(key, next, local);
192
+ return next;
193
+ });
194
+ };
195
+ return [storedValue, setValue];
196
+ }
197
+ function useAudio(allowed) {
198
+ const [audio, setAudio] = useStorage("rpc-audio", false, true);
199
+ const enabled = audio && allowed;
200
+ return [enabled, setAudio];
201
+ }
202
+
203
+ export { useAudio, useChat, useMessages, useStorage };
@@ -1,53 +1,6 @@
1
+ import { __spreadValues } from './chunk-LNEKYYG7.js';
1
2
  import React from 'react';
2
3
 
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
- var __spreadValues = (a, b) => {
9
- for (var prop in b || (b = {}))
10
- if (__hasOwnProp.call(b, prop))
11
- __defNormalProp(a, prop, b[prop]);
12
- if (__getOwnPropSymbols)
13
- for (var prop of __getOwnPropSymbols(b)) {
14
- if (__propIsEnum.call(b, prop))
15
- __defNormalProp(a, prop, b[prop]);
16
- }
17
- return a;
18
- };
19
- var __objRest = (source, exclude) => {
20
- var target = {};
21
- for (var prop in source)
22
- if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
23
- target[prop] = source[prop];
24
- if (source != null && __getOwnPropSymbols)
25
- for (var prop of __getOwnPropSymbols(source)) {
26
- if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
27
- target[prop] = source[prop];
28
- }
29
- return target;
30
- };
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
4
  function BiSolidMessageDetail(props) {
52
5
  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
6
  }
@@ -72,4 +25,4 @@ function BsFillMicMuteFill(props) {
72
25
  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
26
  }
74
27
 
75
- export { BiSolidMessageDetail, BiSolidMessageX, BsFillMicFill, BsFillMicMuteFill, GrSend, __async, __objRest, __spreadValues };
28
+ export { BiSolidMessageDetail, BiSolidMessageX, BsFillMicFill, BsFillMicMuteFill, GrSend };
@@ -0,0 +1,33 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+ var __objRest = (source, exclude) => {
21
+ var target = {};
22
+ for (var prop in source)
23
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
24
+ target[prop] = source[prop];
25
+ if (source != null && __getOwnPropSymbols)
26
+ for (var prop of __getOwnPropSymbols(source)) {
27
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
28
+ target[prop] = source[prop];
29
+ }
30
+ return target;
31
+ };
32
+
33
+ export { __objRest, __spreadProps, __spreadValues };
@@ -0,0 +1,81 @@
1
+ import { useChat } from './chunk-GT3RG6VA.js';
2
+ import { BiSolidMessageX, BiSolidMessageDetail, GrSend, BsFillMicFill, BsFillMicMuteFill } from './chunk-JJPIWKLG.js';
3
+ import { __objRest, __spreadValues } from './chunk-LNEKYYG7.js';
4
+ import React, { useRef, useState, useEffect } from 'react';
5
+
6
+ // src/styles.css
7
+ function injectStyle(css) {
8
+ if (typeof document === "undefined") return;
9
+ const head = document.head || document.getElementsByTagName("head")[0];
10
+ const style = document.createElement("style");
11
+ style.type = "text/css";
12
+ if (head.firstChild) {
13
+ head.insertBefore(style, head.firstChild);
14
+ } else {
15
+ head.appendChild(style);
16
+ }
17
+ if (style.styleSheet) {
18
+ style.styleSheet.cssText = css;
19
+ } else {
20
+ style.appendChild(document.createTextNode(css));
21
+ }
22
+ }
23
+ injectStyle('.rpc-font {\n font-family:\n "Trebuchet MS",\n "Lucida Sans Unicode",\n "Lucida Grande",\n "Lucida Sans",\n Arial,\n sans-serif;\n}\n.rpc-main {\n display: flex;\n align-items: center;\n column-gap: 0.5rem;\n}\n.rpc-dialog-container {\n position: relative;\n}\n.rpc-notification {\n position: relative;\n}\n.rpc-notification .rpc-badge {\n background-color: red;\n position: absolute;\n top: 0;\n right: 0;\n width: 6px;\n aspect-ratio: 1;\n border-radius: 100%;\n}\n.rpc-dialog {\n position: absolute;\n background-color: black;\n color: white;\n padding: 0.4rem 0 0.25rem 0;\n border-radius: 0.4rem;\n font-size: small;\n}\n.rpc-position-left {\n left: 0.6rem;\n translate: -100%;\n}\n.rpc-position-center {\n left: 0.5rem;\n translate: -50%;\n}\n.rpc-position-right {\n left: 0.3rem;\n}\n.rpc-hr {\n margin: 0.25rem 0;\n border-color: rgba(255, 255, 255, 0.7);\n}\n.rpc-message-container {\n height: 7rem;\n overflow-y: scroll;\n padding-inline: 0.5rem;\n margin-bottom: 0.25rem;\n}\n.rpc-message-container::-webkit-scrollbar {\n width: 2.5px;\n}\n.rpc-message-container::-webkit-scrollbar-track {\n background: gray;\n}\n.rpc-message-container::-webkit-scrollbar-thumb {\n background-color: white;\n}\n.rpc-heading {\n text-align: center;\n font-size: medium;\n font-weight: bold;\n padding-inline: 0.5rem;\n}\n.rpc-input-container {\n display: flex;\n width: 15rem;\n max-width: 90vw;\n column-gap: 0.25rem;\n padding: 0.25rem 0.5rem;\n}\n.rpc-input {\n color: white;\n width: 100%;\n background-color: black;\n border: none;\n outline: 1px solid rgba(255, 255, 255, 0.8);\n border-radius: 0.25rem;\n padding: 0.3rem 0.25rem;\n}\n.rpc-input::placeholder {\n color: rgba(255, 255, 255, 0.7);\n}\n.rpc-input:focus {\n outline: 2px solid white;\n}\n.rpc-button {\n all: unset;\n display: flex;\n}\n.rpc-icon-container {\n display: flex;\n align-items: center;\n}\n.rpc-invert {\n filter: invert(100%);\n}\n');
24
+
25
+ // src/components.tsx
26
+ function Chat(_a) {
27
+ var _b = _a, { text = true, audio = true, onMessageReceived, dialogOptions, props = {}, children } = _b, hookProps = __objRest(_b, ["text", "audio", "onMessageReceived", "dialogOptions", "props", "children"]);
28
+ const _a2 = useChat(__spreadValues({
29
+ text,
30
+ audio,
31
+ onMessageReceived: modifiedOnMessageReceived
32
+ }, hookProps)), { peerId, audioStreamRef } = _a2, childrenOptions = __objRest(_a2, ["peerId", "audioStreamRef"]);
33
+ const { remotePeers, messages, sendMessage, audio: audioEnabled, setAudio } = childrenOptions;
34
+ const containerRef = useRef(null);
35
+ const [dialog, setDialog] = useState(false);
36
+ const dialogRef = useRef(null);
37
+ const inputRef = useRef(null);
38
+ const [notification, setNotification] = useState(false);
39
+ function modifiedOnMessageReceived(message) {
40
+ var _a3;
41
+ if (!((_a3 = dialogRef.current) == null ? void 0 : _a3.open)) setNotification(true);
42
+ onMessageReceived == null ? void 0 : onMessageReceived(message);
43
+ }
44
+ useEffect(() => {
45
+ var _a3, _b2;
46
+ if (dialog) (_a3 = dialogRef.current) == null ? void 0 : _a3.show();
47
+ else (_b2 = dialogRef.current) == null ? void 0 : _b2.close();
48
+ }, [dialog]);
49
+ useEffect(() => {
50
+ const container = containerRef.current;
51
+ if (container) container.scrollTop = container.scrollHeight;
52
+ }, [dialog, remotePeers, messages]);
53
+ return /* @__PURE__ */ React.createElement("div", __spreadValues({ className: "rpc-main rpc-font" }, props), 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, { title: "Close chat", onClick: () => setDialog(false) }) : /* @__PURE__ */ React.createElement("div", { className: "rpc-notification" }, /* @__PURE__ */ React.createElement(
54
+ BiSolidMessageDetail,
55
+ {
56
+ title: "Open chat",
57
+ onClick: () => {
58
+ setNotification(false);
59
+ setDialog(true);
60
+ }
61
+ }
62
+ ), notification && /* @__PURE__ */ React.createElement("span", { className: "rpc-badge" })), /* @__PURE__ */ React.createElement("dialog", { ref: dialogRef, className: `${dialog ? "rpc-dialog" : ""} rpc-position-${(dialogOptions == null ? void 0 : dialogOptions.position) || "center"}`, style: dialogOptions == null ? void 0 : dialogOptions.style }, /* @__PURE__ */ React.createElement("div", { className: "rpc-heading" }, "Chat"), /* @__PURE__ */ React.createElement("hr", { className: "rpc-hr" }), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { ref: containerRef, className: "rpc-message-container" }, messages.map(({ id, text: text2 }, i) => /* @__PURE__ */ React.createElement("div", { key: i }, /* @__PURE__ */ React.createElement("strong", null, id === peerId ? "You" : remotePeers[id], ": "), /* @__PURE__ */ React.createElement("span", null, text2)))), /* @__PURE__ */ React.createElement("hr", { className: "rpc-hr" }), /* @__PURE__ */ React.createElement(
63
+ "form",
64
+ {
65
+ className: "rpc-input-container",
66
+ onSubmit: (e) => {
67
+ var _a3;
68
+ e.preventDefault();
69
+ const text2 = (_a3 = inputRef.current) == null ? void 0 : _a3.value;
70
+ if (text2) {
71
+ inputRef.current.value = "";
72
+ sendMessage({ id: peerId, text: text2 });
73
+ }
74
+ }
75
+ },
76
+ /* @__PURE__ */ React.createElement("input", { ref: inputRef, className: "rpc-input rpc-font", placeholder: "Enter a message" }),
77
+ /* @__PURE__ */ React.createElement("button", { type: "submit", className: "rpc-button" }, /* @__PURE__ */ React.createElement(GrSend, { title: "Send message" }))
78
+ )))), audio && /* @__PURE__ */ React.createElement("button", { className: "rpc-button", onClick: () => setAudio(!audioEnabled) }, audioEnabled ? /* @__PURE__ */ React.createElement(BsFillMicFill, { title: "Turn mic off" }) : /* @__PURE__ */ React.createElement(BsFillMicMuteFill, { title: "Turn mic on" }))), audio && audioEnabled && /* @__PURE__ */ React.createElement("audio", { ref: audioStreamRef, autoPlay: true, style: { display: "none" } }));
79
+ }
80
+
81
+ export { Chat };
@@ -0,0 +1,22 @@
1
+ // src/lib/storage.ts
2
+ var getStorageInstance = (local = true) => local ? localStorage : sessionStorage;
3
+ var removeStorage = (key, local = true) => getStorageInstance(local).removeItem(key);
4
+ var clearChat = () => {
5
+ removeStorage("rpc-remote-peer");
6
+ removeStorage("rpc-messages");
7
+ };
8
+ var setStorage = (key, value, local = true) => getStorageInstance(local).setItem(key, JSON.stringify(value));
9
+ function getStorage(key, fallbackValue, local = true) {
10
+ const value = getStorageInstance(local).getItem(key);
11
+ if (value) {
12
+ try {
13
+ return JSON.parse(value);
14
+ } catch (e) {
15
+ removeStorage(key, local);
16
+ }
17
+ }
18
+ if (fallbackValue !== void 0) setStorage(key, fallbackValue, local);
19
+ return fallbackValue;
20
+ }
21
+
22
+ export { clearChat, getStorage, removeStorage, setStorage };
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import { ChatProps } from './types.js';
3
+ import 'peerjs';
4
+
5
+ declare function Chat({ text, audio, onMessageReceived, dialogOptions, props, children, ...hookProps }: ChatProps): React.JSX.Element;
6
+
7
+ export { Chat as default };
@@ -0,0 +1,5 @@
1
+ export { Chat as default } from './chunks/chunk-YDZ27MG7.js';
2
+ import './chunks/chunk-GT3RG6VA.js';
3
+ import './chunks/chunk-JJPIWKLG.js';
4
+ import './chunks/chunk-ZYFPSCFE.js';
5
+ import './chunks/chunk-LNEKYYG7.js';
@@ -0,0 +1,11 @@
1
+ import { SetStateAction } from 'react';
2
+ import { UseChatProps, UseChatReturn, Message } from './types.js';
3
+ import 'peerjs';
4
+
5
+ declare function useChat({ peerId, name, remotePeerId, peerOptions, text, recoverChat, audio: allowed, onError, onMicError, onMessageSent, onMessageReceived, }: UseChatProps): UseChatReturn;
6
+ declare function useMessages(): readonly [Message[], (value: SetStateAction<Message[]>) => void, (message: Message) => void];
7
+ declare function useStorage<T>(key: string, initialValue: T, local?: boolean): readonly [T, (value: SetStateAction<T>) => void];
8
+ declare function useStorage<T>(key: string, initialValue?: T, local?: boolean): readonly [T | undefined, (value: SetStateAction<T | undefined>) => void];
9
+ declare function useAudio(allowed: boolean): readonly [boolean, (value: SetStateAction<boolean>) => void];
10
+
11
+ export { useAudio, useChat, useMessages, useStorage };
package/dist/hooks.js ADDED
@@ -0,0 +1,3 @@
1
+ export { useAudio, useChat, useMessages, useStorage } from './chunks/chunk-GT3RG6VA.js';
2
+ import './chunks/chunk-ZYFPSCFE.js';
3
+ import './chunks/chunk-LNEKYYG7.js';
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,2 @@
1
- export { BiSolidMessageDetail, BiSolidMessageX, BsFillMicFill, BsFillMicMuteFill, GrSend } from './chunks/chunk-4CBQRGQ3.js';
1
+ export { BiSolidMessageDetail, BiSolidMessageX, BsFillMicFill, BsFillMicMuteFill, GrSend } from './chunks/chunk-JJPIWKLG.js';
2
+ import './chunks/chunk-LNEKYYG7.js';
package/dist/index.d.ts CHANGED
@@ -1,20 +1,6 @@
1
- import * as React from 'react';
2
- import React__default from 'react';
3
- import { useChatProps, RemotePeers, Message, ChatProps } from './types.js';
1
+ export { default } from './components.js';
2
+ export { useChat } from './hooks.js';
3
+ export { clearChat } from './lib/storage.js';
4
+ import 'react';
5
+ import './types.js';
4
6
  import 'peerjs';
5
-
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
- };
15
-
16
- declare const clearChat: () => void;
17
-
18
- declare function Chat({ text, audio, onMessageReceived, dialogOptions, props, children, ...hookProps }: ChatProps): React__default.JSX.Element;
19
-
20
- export { clearChat, Chat as default, useChat };
package/dist/index.js CHANGED
@@ -1,296 +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';
3
-
4
- // src/constants.ts
5
- var turnAccounts = [
6
- { username: "70061a377b51f3a3d01c11e3", credential: "lHV4NYJ5Rfl5JNa9" },
7
- { username: "13b19eb65bbf6e9f96d64b72", credential: "7R9P/+7y7Q516Etv" },
8
- { username: "3469603f5cdc7ca4a1e891ae", credential: "/jMyLSDbbcgqpVQv" },
9
- { username: "a7926f4dcc4a688d41f89752", credential: "ZYM8jFYeb8bQkL+N" },
10
- { username: "0be25ab7f61d9d733ba94809", credential: "hiiSwWVch+ftt3SX" },
11
- { username: "3c25ba948daeab04f9b66187", credential: "FQB3GQwd27Y0dPeK" }
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
- )
28
- };
29
-
30
- // src/lib/connection.ts
31
- function closeConnection(conn) {
32
- conn.removeAllListeners();
33
- conn.close();
34
- }
35
-
36
- // 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
- };
55
- var clearChat = () => {
56
- removeStorage("rpc-remote-peer");
57
- removeStorage("rpc-messages");
58
- };
59
-
60
- // src/lib/utils.ts
61
- var addPrefix = (str) => `rpc-${str}`;
62
-
63
- // src/hooks.ts
64
- function useChat({
65
- name,
66
- peerId,
67
- remotePeerId = [],
68
- peerOptions,
69
- text = true,
70
- recoverChat = false,
71
- audio: allowed = true,
72
- onError = () => alert("Browser not supported! Try some other browser."),
73
- onMicError = () => alert("Microphone not accessible!"),
74
- onMessageSent,
75
- onMessageReceived
76
- }) {
77
- const [audio, setAudio] = useAudio(allowed);
78
- const audioStreamRef = useRef(null);
79
- const connRef = useRef({});
80
- const localStream = useRef(null);
81
- const [messages, setMessages, addMessage] = useMessages();
82
- const [peer, setPeer] = useState();
83
- const [remotePeers, setRemotePeers] = useStorage("rpc-remote-peer", {});
84
- peerId = addPrefix(peerId);
85
- if (typeof remotePeerId === "string") remotePeerId = [remotePeerId];
86
- const remotePeerIds = remotePeerId.map(addPrefix);
87
- function handleConnection(conn) {
88
- connRef.current[conn.peer] = conn;
89
- conn.on("open", () => {
90
- conn.on("data", ({ message, messages: messages2, remotePeerName, type }) => {
91
- if (type === "message") receiveMessage(message);
92
- else if (type === "init") {
93
- setRemotePeers((prev) => {
94
- prev[conn.peer] = remotePeerName || "Anonymous User";
95
- return prev;
96
- });
97
- if (recoverChat) setMessages((old) => messages2.length > old.length ? messages2 : old);
98
- }
99
- });
100
- conn.send({ type: "init", remotePeerName: name, messages });
101
- });
102
- conn.on("close", conn.removeAllListeners);
103
- }
104
- function handleError() {
105
- setAudio(false);
106
- onMicError();
107
- }
108
- function handleRemoteStream(remoteStream) {
109
- if (audioStreamRef.current) audioStreamRef.current.srcObject = remoteStream;
110
- }
111
- function receiveMessage(message) {
112
- addMessage(message);
113
- onMessageReceived == null ? void 0 : onMessageReceived(message);
114
- }
115
- function sendMessage(message) {
116
- addMessage(message);
117
- Object.values(connRef.current).forEach((conn) => conn.send({ type: "message", message }));
118
- onMessageSent == null ? void 0 : onMessageSent(message);
119
- }
120
- 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');
133
- if (!data || !audioVideo) return onError();
134
- const peer2 = new Peer(peerId, __spreadValues({ config: defaultConfig }, peerOptions));
135
- setPeer(peer2);
136
- });
137
- })();
138
- }, [audio]);
139
- 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);
184
- return () => {
185
- var _a;
186
- (_a = localStream.current) == null ? void 0 : _a.getTracks().forEach((track) => track.stop());
187
- Object.values(connRef.current).forEach(closeConnection);
188
- connRef.current = {};
189
- Object.values(calls).forEach(closeConnection);
190
- peer.removeAllListeners();
191
- peer.destroy();
192
- };
193
- }, [peer]);
194
- return { peerId, audioStreamRef, remotePeers, messages, sendMessage, audio, setAudio };
195
- }
196
- function useMessages() {
197
- const [messages, setMessages] = useStorage("rpc-messages", []);
198
- const addMessage = (message) => setMessages((prev) => prev.concat(message));
199
- return [messages, setMessages, addMessage];
200
- }
201
- function useStorage(key, initialValue, local = false) {
202
- const [storedValue, setStoredValue] = useState(() => {
203
- if (typeof window === "undefined") return initialValue;
204
- return getStorage(key, initialValue, local);
205
- });
206
- const setValue = (value) => {
207
- setStoredValue((old) => {
208
- const updatedValue = typeof value === "function" ? value(old) : value;
209
- setStorage(key, updatedValue, local);
210
- return updatedValue;
211
- });
212
- };
213
- return [storedValue, setValue];
214
- }
215
- function useAudio(allowed) {
216
- const [audio, setAudio] = useStorage("rpc-audio", false, true);
217
- const enabled = audio && allowed;
218
- return [enabled, setAudio];
219
- }
220
-
221
- // src/styles.css
222
- function injectStyle(css) {
223
- if (typeof document === "undefined") return;
224
- const head = document.head || document.getElementsByTagName("head")[0];
225
- const style = document.createElement("style");
226
- style.type = "text/css";
227
- if (head.firstChild) {
228
- head.insertBefore(style, head.firstChild);
229
- } else {
230
- head.appendChild(style);
231
- }
232
- if (style.styleSheet) {
233
- style.styleSheet.cssText = css;
234
- } else {
235
- style.appendChild(document.createTextNode(css));
236
- }
237
- }
238
- injectStyle('.rpc-font {\n font-family:\n "Trebuchet MS",\n "Lucida Sans Unicode",\n "Lucida Grande",\n "Lucida Sans",\n Arial,\n sans-serif;\n}\n.rpc-main {\n display: flex;\n align-items: center;\n column-gap: 0.5rem;\n}\n.rpc-dialog-container {\n position: relative;\n}\n.rpc-notification {\n position: relative;\n}\n.rpc-notification .rpc-badge {\n background-color: red;\n position: absolute;\n top: 0;\n right: 0;\n width: 6px;\n aspect-ratio: 1;\n border-radius: 100%;\n}\n.rpc-dialog {\n position: absolute;\n background-color: black;\n color: white;\n padding: 0.4rem 0 0.25rem 0;\n border-radius: 0.4rem;\n font-size: small;\n}\n.rpc-position-left {\n left: 0.6rem;\n translate: -100%;\n}\n.rpc-position-center {\n left: 0.5rem;\n translate: -50%;\n}\n.rpc-position-right {\n left: 0.3rem;\n}\n.rpc-hr {\n margin: 0.25rem 0;\n border-color: rgba(255, 255, 255, 0.7);\n}\n.rpc-message-container {\n height: 7rem;\n overflow-y: scroll;\n padding-inline: 0.5rem;\n margin-bottom: 0.25rem;\n}\n.rpc-message-container::-webkit-scrollbar {\n width: 2.5px;\n}\n.rpc-message-container::-webkit-scrollbar-track {\n background: gray;\n}\n.rpc-message-container::-webkit-scrollbar-thumb {\n background-color: white;\n}\n.rpc-heading {\n text-align: center;\n font-size: medium;\n font-weight: bold;\n padding-inline: 0.5rem;\n}\n.rpc-input-container {\n display: flex;\n width: 15rem;\n max-width: 90vw;\n column-gap: 0.25rem;\n padding: 0.25rem 0.5rem;\n}\n.rpc-input {\n color: white;\n width: 100%;\n background-color: black;\n border: none;\n outline: 1px solid rgba(255, 255, 255, 0.8);\n border-radius: 0.25rem;\n padding: 0.3rem 0.25rem;\n}\n.rpc-input::placeholder {\n color: rgba(255, 255, 255, 0.7);\n}\n.rpc-input:focus {\n outline: 2px solid white;\n}\n.rpc-button {\n all: unset;\n display: flex;\n}\n.rpc-icon-container {\n display: flex;\n align-items: center;\n}\n.rpc-invert {\n filter: invert(100%);\n}\n');
239
-
240
- // src/index.tsx
241
- function Chat(_a) {
242
- var _b = _a, { text = true, audio = true, onMessageReceived, dialogOptions, props = {}, children } = _b, hookProps = __objRest(_b, ["text", "audio", "onMessageReceived", "dialogOptions", "props", "children"]);
243
- const _a2 = useChat(__spreadValues({
244
- text,
245
- audio,
246
- onMessageReceived: modifiedOnMessageReceived
247
- }, hookProps)), { peerId, audioStreamRef } = _a2, childrenOptions = __objRest(_a2, ["peerId", "audioStreamRef"]);
248
- const { remotePeers, messages, sendMessage, audio: audioEnabled, setAudio } = childrenOptions;
249
- const containerRef = useRef(null);
250
- const [dialog, setDialog] = useState(false);
251
- const dialogRef = useRef(null);
252
- const inputRef = useRef(null);
253
- const [notification, setNotification] = useState(false);
254
- function modifiedOnMessageReceived(message) {
255
- var _a3;
256
- if (!((_a3 = dialogRef.current) == null ? void 0 : _a3.open)) setNotification(true);
257
- onMessageReceived == null ? void 0 : onMessageReceived(message);
258
- }
259
- useEffect(() => {
260
- var _a3, _b2;
261
- if (dialog) (_a3 = dialogRef.current) == null ? void 0 : _a3.show();
262
- else (_b2 = dialogRef.current) == null ? void 0 : _b2.close();
263
- }, [dialog]);
264
- useEffect(() => {
265
- const container = containerRef.current;
266
- if (container) container.scrollTop = container.scrollHeight;
267
- }, [dialog, remotePeers, messages]);
268
- return /* @__PURE__ */ React.createElement("div", __spreadValues({ className: "rpc-main rpc-font" }, props), 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, { title: "Close chat", onClick: () => setDialog(false) }) : /* @__PURE__ */ React.createElement("div", { className: "rpc-notification" }, /* @__PURE__ */ React.createElement(
269
- BiSolidMessageDetail,
270
- {
271
- title: "Open chat",
272
- onClick: () => {
273
- setNotification(false);
274
- setDialog(true);
275
- }
276
- }
277
- ), notification && /* @__PURE__ */ React.createElement("span", { className: "rpc-badge" })), /* @__PURE__ */ React.createElement("dialog", { ref: dialogRef, className: `${dialog ? "rpc-dialog" : ""} rpc-position-${(dialogOptions == null ? void 0 : dialogOptions.position) || "center"}`, style: dialogOptions == null ? void 0 : dialogOptions.style }, /* @__PURE__ */ React.createElement("div", { className: "rpc-heading" }, "Chat"), /* @__PURE__ */ React.createElement("hr", { className: "rpc-hr" }), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { ref: containerRef, className: "rpc-message-container" }, messages.map(({ id, text: text2 }, i) => /* @__PURE__ */ React.createElement("div", { key: i }, /* @__PURE__ */ React.createElement("strong", null, id === peerId ? "You" : remotePeers[id], ": "), /* @__PURE__ */ React.createElement("span", null, text2)))), /* @__PURE__ */ React.createElement("hr", { className: "rpc-hr" }), /* @__PURE__ */ React.createElement(
278
- "form",
279
- {
280
- className: "rpc-input-container",
281
- onSubmit: (e) => {
282
- var _a3;
283
- e.preventDefault();
284
- const text2 = (_a3 = inputRef.current) == null ? void 0 : _a3.value;
285
- if (text2) {
286
- inputRef.current.value = "";
287
- sendMessage({ id: peerId, text: text2 });
288
- }
289
- }
290
- },
291
- /* @__PURE__ */ React.createElement("input", { ref: inputRef, className: "rpc-input rpc-font", placeholder: "Enter a message" }),
292
- /* @__PURE__ */ React.createElement("button", { type: "submit", className: "rpc-button" }, /* @__PURE__ */ React.createElement(GrSend, { title: "Send message" }))
293
- )))), audio && /* @__PURE__ */ React.createElement("button", { className: "rpc-button", onClick: () => setAudio(!audioEnabled) }, audioEnabled ? /* @__PURE__ */ React.createElement(BsFillMicFill, { title: "Turn mic off" }) : /* @__PURE__ */ React.createElement(BsFillMicMuteFill, { title: "Turn mic on" }))), audio && audioEnabled && /* @__PURE__ */ React.createElement("audio", { ref: audioStreamRef, autoPlay: true, style: { display: "none" } }));
294
- }
295
-
296
- export { clearChat, Chat as default, useChat };
1
+ export { Chat as default } from './chunks/chunk-YDZ27MG7.js';
2
+ export { useChat } from './chunks/chunk-GT3RG6VA.js';
3
+ import './chunks/chunk-JJPIWKLG.js';
4
+ export { clearChat } from './chunks/chunk-ZYFPSCFE.js';
5
+ import './chunks/chunk-LNEKYYG7.js';
@@ -0,0 +1,6 @@
1
+ declare const removeStorage: (key: string, local?: boolean) => void;
2
+ declare const clearChat: () => void;
3
+ declare const setStorage: (key: string, value: unknown, local?: boolean) => void;
4
+ declare function getStorage<T>(key: string, fallbackValue?: T, local?: boolean): T | undefined;
5
+
6
+ export { clearChat, getStorage, removeStorage, setStorage };
@@ -0,0 +1,2 @@
1
+ export { clearChat, getStorage, removeStorage, setStorage } from '../chunks/chunk-ZYFPSCFE.js';
2
+ import '../chunks/chunk-LNEKYYG7.js';
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 { CSSProperties, DetailedHTMLProps, HTMLAttributes, ReactNode, RefObject, SetStateAction } 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.6",
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>",
@@ -14,54 +14,57 @@
14
14
  "url": "https://github.com/SahilAggarwal2004/react-peer-chat/issues"
15
15
  },
16
16
  "type": "module",
17
- "files": [
18
- "dist"
19
- ],
20
17
  "exports": {
21
18
  ".": "./dist/index.js",
22
19
  "./icons": "./dist/icons.js",
23
20
  "./types": "./dist/types.js"
24
21
  },
25
22
  "main": "dist/index.js",
26
- "types": "dist/index.d.ts",
23
+ "files": [
24
+ "dist"
25
+ ],
27
26
  "sideEffects": [
28
27
  "**/*.css"
29
28
  ],
29
+ "types": "dist/index.d.ts",
30
30
  "dependencies": {
31
31
  "peerjs": "^1.5.5"
32
32
  },
33
+ "peerDependencies": {
34
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
35
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
36
+ },
33
37
  "devDependencies": {
34
38
  "@release-it/conventional-changelog": "^10.0.4",
35
39
  "@types/react": "^19.2.7",
40
+ "prettier-package-json": "^2.8.0",
36
41
  "release-it": "^19.2.2",
37
42
  "tsup": "^8.5.1",
38
43
  "typescript": "^5.9.3"
39
44
  },
40
- "peerDependencies": {
41
- "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
42
- "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
43
- },
44
45
  "keywords": [
45
- "react",
46
+ "audio-chat",
46
47
  "chat",
47
48
  "component",
48
- "peer",
49
- "peerjs",
50
49
  "p2p",
50
+ "p2p-chat",
51
+ "peer",
51
52
  "peer-to-peer",
52
- "webrtc",
53
+ "peerjs",
54
+ "react",
53
55
  "react-peer-chat",
54
- "typescript",
55
- "p2p-chat",
56
56
  "text-chat",
57
+ "typescript",
57
58
  "voice-chat",
58
- "audio-chat"
59
+ "webrtc"
59
60
  ],
60
61
  "scripts": {
61
62
  "build": "pnpm i && pnpm run compile",
62
63
  "compile": "tsup",
63
64
  "dev": "tsup --watch",
64
65
  "dry-release": "release-it --ci --dry-run",
66
+ "prettier": "prettier-package-json --write package.json",
67
+ "pub": "pnpm login && pnpm publish",
65
68
  "release": "release-it --ci"
66
69
  }
67
70
  }