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 +68 -38
- package/dist/chunks/chunk-GT3RG6VA.js +203 -0
- package/dist/chunks/{chunk-4CBQRGQ3.js → chunk-JJPIWKLG.js} +2 -49
- package/dist/chunks/chunk-LNEKYYG7.js +33 -0
- package/dist/chunks/chunk-YDZ27MG7.js +81 -0
- package/dist/chunks/chunk-ZYFPSCFE.js +22 -0
- package/dist/components.d.ts +7 -0
- package/dist/components.js +5 -0
- package/dist/hooks.d.ts +11 -0
- package/dist/hooks.js +3 -0
- package/dist/icons.d.ts +6 -6
- package/dist/icons.js +2 -1
- package/dist/index.d.ts +5 -19
- package/dist/index.js +5 -296
- package/dist/lib/storage.d.ts +6 -0
- package/dist/lib/storage.js +2 -0
- package/dist/types.d.ts +29 -18
- package/package.json +19 -16
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](#
|
|
14
|
-
- Fully customizable. See [usage with FaC](#
|
|
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
|
-
|
|
293
|
-
|
|
|
294
|
-
|
|
|
295
|
-
| `
|
|
296
|
-
| `
|
|
297
|
-
| `
|
|
298
|
-
| `
|
|
299
|
-
| `
|
|
300
|
-
| `
|
|
301
|
-
| `
|
|
302
|
-
| `
|
|
303
|
-
| `
|
|
304
|
-
| `
|
|
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
|
-
|
|
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
|
-
|
|
322
|
+
### Children
|
|
319
323
|
|
|
320
324
|
```typescript
|
|
321
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
##
|
|
384
|
+
## License
|
|
355
385
|
|
|
356
|
-
[
|
|
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
|
|
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 };
|
package/dist/hooks.d.ts
ADDED
|
@@ -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
package/dist/icons.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React from 'react';
|
|
2
2
|
import { IconProps } from './types.js';
|
|
3
3
|
import 'peerjs';
|
|
4
4
|
|
|
5
|
-
declare function BiSolidMessageDetail(props: IconProps):
|
|
6
|
-
declare function BiSolidMessageX(props: IconProps):
|
|
7
|
-
declare function GrSend(props: IconProps):
|
|
8
|
-
declare function BsFillMicFill(props: IconProps):
|
|
9
|
-
declare function BsFillMicMuteFill(props: IconProps):
|
|
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-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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 };
|
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
|
|
11
|
+
type MessageEventHandler = (message: Message) => void;
|
|
10
12
|
|
|
11
13
|
type RemotePeerId = string | string[];
|
|
12
|
-
type
|
|
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?:
|
|
21
|
-
onMicError?:
|
|
22
|
-
onMessageSent?:
|
|
23
|
-
onMessageReceived?:
|
|
22
|
+
onError?: ErrorHandler;
|
|
23
|
+
onMicError?: ErrorHandler;
|
|
24
|
+
onMessageSent?: MessageEventHandler;
|
|
25
|
+
onMessageReceived?: MessageEventHandler;
|
|
24
26
|
};
|
|
25
|
-
type
|
|
26
|
-
|
|
27
|
-
|
|
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,
|
|
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.
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
}
|