@cloudflare/realtimekit-react 0.0.0 → 2.5.0-staging.88
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 +86 -0
- package/build-types.sh +12 -0
- package/dist/index.cjs.js +23 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.es.js +30980 -0
- package/example/App.tsx +37 -0
- package/example/components/Chat.tsx +48 -0
- package/example/components/Meeting.tsx +83 -0
- package/example/components/Participants.tsx +112 -0
- package/example/index.css +24 -0
- package/example/main.tsx +11 -0
- package/example/vite-env.d.ts +1 -0
- package/package.json +30 -4
- package/tsconfig.node.json +8 -0
- package/types/context.d.ts +36 -0
- package/types/index.d.ts +14 -0
package/example/App.tsx
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
import { DyteProvider, useDyteClient } from '../src';
|
|
4
|
+
import Meeting from './components/Meeting';
|
|
5
|
+
import Participants from './components/Participants';
|
|
6
|
+
import Chat from './components/Chat';
|
|
7
|
+
|
|
8
|
+
const roomName = 'avbigx-hemnqy';
|
|
9
|
+
|
|
10
|
+
function App() {
|
|
11
|
+
const [client, loadClient] = useDyteClient();
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
fetch('https://api.cluster.dyte.in/auth/anonymous')
|
|
15
|
+
.then((res) => res.json())
|
|
16
|
+
.then((data) => {
|
|
17
|
+
const { authToken } = data;
|
|
18
|
+
loadClient({
|
|
19
|
+
roomName,
|
|
20
|
+
authToken,
|
|
21
|
+
defaults: { video: false, audio: false },
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}, []);
|
|
25
|
+
|
|
26
|
+
Object.assign(window, { client });
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<DyteProvider value={client}>
|
|
30
|
+
<Meeting />
|
|
31
|
+
<Participants />
|
|
32
|
+
<Chat />
|
|
33
|
+
</DyteProvider>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default App;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useDyteSelector } from '../../src';
|
|
3
|
+
|
|
4
|
+
function Chat() {
|
|
5
|
+
const roomJoined = useDyteSelector((meeting) => meeting.self.roomJoined);
|
|
6
|
+
const chat = useDyteSelector((meeting) => meeting.chat);
|
|
7
|
+
if (!roomJoined) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<fieldset className="section">
|
|
13
|
+
<legend>Chat</legend>
|
|
14
|
+
<div>
|
|
15
|
+
<ul>
|
|
16
|
+
{chat?.messages.map((message) => (
|
|
17
|
+
<li key={message.id}>
|
|
18
|
+
{message.type}
|
|
19
|
+
{' '}
|
|
20
|
+
-
|
|
21
|
+
{message.type === 'text' && message.message}
|
|
22
|
+
</li>
|
|
23
|
+
))}
|
|
24
|
+
</ul>
|
|
25
|
+
<form
|
|
26
|
+
onSubmit={(e) => {
|
|
27
|
+
e.preventDefault();
|
|
28
|
+
const messageInput = (e.target as any).elements
|
|
29
|
+
.message as HTMLInputElement;
|
|
30
|
+
const message = messageInput.value.trim();
|
|
31
|
+
|
|
32
|
+
if (message !== '') {
|
|
33
|
+
if (chat) {
|
|
34
|
+
chat.sendTextMessage(message);
|
|
35
|
+
}
|
|
36
|
+
messageInput.value = '';
|
|
37
|
+
}
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
40
|
+
<input type="text" name="message" />
|
|
41
|
+
<button type="submit">Send</button>
|
|
42
|
+
</form>
|
|
43
|
+
</div>
|
|
44
|
+
</fieldset>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default Chat;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useDyteMeeting, useDyteSelector } from '../../src';
|
|
3
|
+
|
|
4
|
+
function Meeting() {
|
|
5
|
+
const { meeting } = useDyteMeeting();
|
|
6
|
+
const roomJoined = useDyteSelector((m) => m.self.roomJoined);
|
|
7
|
+
const participantCount = useDyteSelector(
|
|
8
|
+
(m) => m.participants.count,
|
|
9
|
+
);
|
|
10
|
+
const self = useDyteSelector((m) => m.self);
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<fieldset>
|
|
14
|
+
<legend>Meeting details</legend>
|
|
15
|
+
<div>
|
|
16
|
+
{!roomJoined && (
|
|
17
|
+
<div>
|
|
18
|
+
<div>
|
|
19
|
+
Status:
|
|
20
|
+
{' '}
|
|
21
|
+
{roomJoined === undefined ? 'Connecting...' : 'Connected'}
|
|
22
|
+
</div>
|
|
23
|
+
{meeting && (
|
|
24
|
+
<div>
|
|
25
|
+
<button type="button" onClick={() => meeting.joinRoom()}>Join</button>
|
|
26
|
+
</div>
|
|
27
|
+
)}
|
|
28
|
+
</div>
|
|
29
|
+
)}
|
|
30
|
+
{roomJoined && (
|
|
31
|
+
<div>
|
|
32
|
+
<div>
|
|
33
|
+
Name:
|
|
34
|
+
{self?.name}
|
|
35
|
+
</div>
|
|
36
|
+
<div>
|
|
37
|
+
audio:
|
|
38
|
+
{' '}
|
|
39
|
+
<span className="token">
|
|
40
|
+
{JSON.stringify(self?.audioEnabled)}
|
|
41
|
+
</span>
|
|
42
|
+
, video:
|
|
43
|
+
{' '}
|
|
44
|
+
<span className="token">
|
|
45
|
+
{JSON.stringify(self?.videoEnabled)}
|
|
46
|
+
</span>
|
|
47
|
+
</div>
|
|
48
|
+
<div>
|
|
49
|
+
Participant Count:
|
|
50
|
+
{participantCount}
|
|
51
|
+
</div>
|
|
52
|
+
<button
|
|
53
|
+
type="button"
|
|
54
|
+
onClick={() => {
|
|
55
|
+
if (self?.audioEnabled) {
|
|
56
|
+
self?.disableAudio();
|
|
57
|
+
} else {
|
|
58
|
+
self?.enableAudio();
|
|
59
|
+
}
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
{self?.audioEnabled ? 'Disable Audio' : 'Enable Audio'}
|
|
63
|
+
</button>
|
|
64
|
+
<button
|
|
65
|
+
type="button"
|
|
66
|
+
onClick={() => {
|
|
67
|
+
if (self?.videoEnabled) {
|
|
68
|
+
self?.disableVideo();
|
|
69
|
+
} else {
|
|
70
|
+
self?.enableVideo();
|
|
71
|
+
}
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
74
|
+
{self?.videoEnabled ? 'Disable Video' : 'Enable Video'}
|
|
75
|
+
</button>
|
|
76
|
+
</div>
|
|
77
|
+
)}
|
|
78
|
+
</div>
|
|
79
|
+
</fieldset>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export default Meeting;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/* eslint-disable jsx-a11y/media-has-caption */
|
|
2
|
+
import React, { FC, useEffect, useRef } from 'react';
|
|
3
|
+
import { useDyteSelector } from '../../src';
|
|
4
|
+
|
|
5
|
+
const PeerView: FC<{
|
|
6
|
+
peerId: string,
|
|
7
|
+
}> = ({
|
|
8
|
+
peerId,
|
|
9
|
+
}) => {
|
|
10
|
+
const {
|
|
11
|
+
audioEnabled, audioTrack, videoEnabled, videoTrack,
|
|
12
|
+
} = useDyteSelector(
|
|
13
|
+
(meeting) => meeting.participants.active.get(peerId),
|
|
14
|
+
)!;
|
|
15
|
+
const videoRef = useRef<HTMLVideoElement>(null);
|
|
16
|
+
const audioRef = useRef<HTMLAudioElement>(null);
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (!audioRef.current) return;
|
|
20
|
+
|
|
21
|
+
if (audioEnabled && audioTrack) {
|
|
22
|
+
const stream = new MediaStream();
|
|
23
|
+
stream.addTrack(audioTrack);
|
|
24
|
+
audioRef.current.srcObject = stream;
|
|
25
|
+
} else {
|
|
26
|
+
audioRef.current.srcObject = null;
|
|
27
|
+
}
|
|
28
|
+
}, [audioEnabled, audioTrack]);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!videoRef.current) return;
|
|
32
|
+
|
|
33
|
+
if (videoEnabled && videoTrack) {
|
|
34
|
+
const stream = new MediaStream();
|
|
35
|
+
stream.addTrack(videoTrack);
|
|
36
|
+
videoRef.current.srcObject = stream;
|
|
37
|
+
} else {
|
|
38
|
+
videoRef.current.srcObject = null;
|
|
39
|
+
}
|
|
40
|
+
}, [videoEnabled, videoTrack]);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div>
|
|
44
|
+
<video
|
|
45
|
+
width={100}
|
|
46
|
+
height={100}
|
|
47
|
+
ref={videoRef}
|
|
48
|
+
autoPlay
|
|
49
|
+
playsInline
|
|
50
|
+
style={{ display: videoEnabled ? 'block' : 'none', background: 'wheat' }}
|
|
51
|
+
/>
|
|
52
|
+
<audio ref={audioRef} autoPlay />
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function ActiveParticipants() {
|
|
58
|
+
const activeParticipants = useDyteSelector(
|
|
59
|
+
(meeting) => meeting.participants.active,
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div>
|
|
64
|
+
{activeParticipants?.toArray().map((peer) => (
|
|
65
|
+
<PeerView
|
|
66
|
+
key={peer.id}
|
|
67
|
+
peerId={peer.id}
|
|
68
|
+
/>
|
|
69
|
+
))}
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function Participants() {
|
|
75
|
+
const roomJoined = useDyteSelector((meeting) => meeting.self.roomJoined);
|
|
76
|
+
const joinedParticipants = useDyteSelector(
|
|
77
|
+
(meeting) => meeting.participants.joined,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
if (!joinedParticipants || !roomJoined) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<fieldset className="section">
|
|
86
|
+
<legend>Participants</legend>
|
|
87
|
+
<div>
|
|
88
|
+
<h2>Active Participants</h2>
|
|
89
|
+
<ActiveParticipants />
|
|
90
|
+
</div>
|
|
91
|
+
<div>
|
|
92
|
+
<h2>Joined Participants</h2>
|
|
93
|
+
<ul style={{ paddingLeft: '1.25rem' }}>
|
|
94
|
+
{joinedParticipants?.toArray().map((peer) => (
|
|
95
|
+
<li key={peer.id}>
|
|
96
|
+
{peer.name}
|
|
97
|
+
{' '}
|
|
98
|
+
- audio:
|
|
99
|
+
{' '}
|
|
100
|
+
<span className="token">{JSON.stringify(peer.audioEnabled)}</span>
|
|
101
|
+
, video:
|
|
102
|
+
{' '}
|
|
103
|
+
<span className="token">{JSON.stringify(peer.videoEnabled)}</span>
|
|
104
|
+
</li>
|
|
105
|
+
))}
|
|
106
|
+
</ul>
|
|
107
|
+
</div>
|
|
108
|
+
</fieldset>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export default Participants;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
body {
|
|
2
|
+
font-family: sans-serif;
|
|
3
|
+
margin: 1rem;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
span.token {
|
|
7
|
+
color: #2160fd;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.section {
|
|
11
|
+
margin-top: 1rem;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
fieldset {
|
|
15
|
+
border: 2px solid #c7c7c7;
|
|
16
|
+
border-radius: 6px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
#root {
|
|
20
|
+
display: block;
|
|
21
|
+
width: 100%;
|
|
22
|
+
max-width: 768px;
|
|
23
|
+
margin: 0 auto;
|
|
24
|
+
}
|
package/example/main.tsx
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
package/package.json
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
"name": "@cloudflare/realtimekit-react",
|
|
3
|
+
"version": "2.5.0-staging.88",
|
|
4
|
+
"description": "A real-time video and audio SDK for building custom, collaborative communication experiences.",
|
|
5
|
+
"main": "./dist/index.cjs.js",
|
|
6
|
+
"module": "./dist/index.es.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.es.js",
|
|
10
|
+
"require": "./dist/index.cjs.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://realtime.cloudflare.com/issues"
|
|
17
|
+
},
|
|
18
|
+
"private": false,
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@cloudflare/realtimekit": "2.5.0-staging.88"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"react": ">=16.8.6"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"tag": "staging"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"postpublish": "mv package.json.bak package.json"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
|
+
import DyteClient from '@dytesdk/web-core';
|
|
3
|
+
type Listener = () => void;
|
|
4
|
+
declare class DyteUpdates {
|
|
5
|
+
private meeting;
|
|
6
|
+
private l;
|
|
7
|
+
constructor(meeting: DyteClient);
|
|
8
|
+
clean(): void;
|
|
9
|
+
addListener(listener: Listener): void;
|
|
10
|
+
removeListener(listener: Listener): void;
|
|
11
|
+
private onUpdate;
|
|
12
|
+
}
|
|
13
|
+
export declare const DyteContext: React.Context<{
|
|
14
|
+
meeting: DyteClient | undefined;
|
|
15
|
+
updates: DyteUpdates | undefined;
|
|
16
|
+
}>;
|
|
17
|
+
/**
|
|
18
|
+
* Provider component which makes the DyteClient object
|
|
19
|
+
* available to nested components
|
|
20
|
+
* @component
|
|
21
|
+
* @param value The DyteClient instance from `useDyteClient()`
|
|
22
|
+
* @param fallback Any fallback UI you want to render until the instance initialises.
|
|
23
|
+
*/
|
|
24
|
+
export declare function DyteProvider({ value, children, fallback, }: {
|
|
25
|
+
value: DyteClient | undefined;
|
|
26
|
+
children: ReactNode;
|
|
27
|
+
fallback?: ReactNode;
|
|
28
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
29
|
+
/**
|
|
30
|
+
* Hook which returns the reference to the DyteClient object
|
|
31
|
+
* @returns meeting instance from `useDyteClient()`
|
|
32
|
+
*/
|
|
33
|
+
export declare const useDyteMeeting: () => {
|
|
34
|
+
meeting: DyteClient;
|
|
35
|
+
};
|
|
36
|
+
export {};
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import DyteClient, { DyteConfigOptions } from '@dytesdk/web-core';
|
|
2
|
+
interface DyteClientParams {
|
|
3
|
+
resetOnLeave?: boolean;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Hook which manages a DyteClient instance
|
|
7
|
+
*/
|
|
8
|
+
export declare const useDyteClient: (dyteClientParams?: DyteClientParams) => readonly [DyteClient | undefined, (options: DyteConfigOptions) => Promise<DyteClient | undefined>];
|
|
9
|
+
type StateSelector<T extends object, U> = (state: T) => U;
|
|
10
|
+
/**
|
|
11
|
+
* Hook which extracts data from the DyteClient object
|
|
12
|
+
*/
|
|
13
|
+
export declare const useDyteSelector: <StateSlice>(selector: StateSelector<DyteClient, StateSlice>) => StateSlice;
|
|
14
|
+
export * from './context';
|