@torolocos/react-rtc
Advanced tools
| import { renderHook } from '@testing-library/react-hooks'; | ||
| import { useErrorHandler } from '../useErrorHandler'; | ||
| describe('useErrorHandler', () => { | ||
| const dispatchEvent = jest.fn(); | ||
| const error = 'error'; | ||
| it('should handler error', () => { | ||
| const { result } = renderHook(() => useErrorHandler(dispatchEvent)); | ||
| result.current(error); | ||
| expect(dispatchEvent).toBeCalledWith('error', error); | ||
| }); | ||
| }); |
| import { act, renderHook } from '@testing-library/react-hooks'; | ||
| import { useMessaging } from '../useMessaging'; | ||
| describe('useMessaging', () => { | ||
| const peerConnections = new Map(); | ||
| const dispatchEvent = jest.fn(); | ||
| beforeAll(() => { | ||
| jest.mock('crypto', () => ({ randomUUID: jest.fn() })); | ||
| }); | ||
| it('should send a message', () => { | ||
| const { result } = renderHook(() => useMessaging('', peerConnections, dispatchEvent)); | ||
| act(() => { | ||
| result.current.send('test'); | ||
| }); | ||
| expect(dispatchEvent).toBeCalledWith('send'); | ||
| }); | ||
| }); |
| import { renderHook } from '@testing-library/react-hooks'; | ||
| import { usePeers } from '../usePeers'; | ||
| const close = jest.fn(); | ||
| const send = jest.fn(); | ||
| const dispatchEvent = jest.fn(); | ||
| Object.defineProperty(global, 'RTCPeerConnection', { | ||
| value: class { | ||
| constructor() { | ||
| this.createDataChannel = jest.fn(() => ({ send })); | ||
| this.close = close; | ||
| } | ||
| }, | ||
| }); | ||
| describe('usePeers', () => { | ||
| const id = ['a', 'b']; | ||
| const peerConnection = new RTCPeerConnection(); | ||
| const dataChannel = peerConnection.createDataChannel('test'); | ||
| it('should add and get a peer', () => { | ||
| const { result } = renderHook(() => usePeers(dispatchEvent)); | ||
| result.current.add(id[0], peerConnection, dataChannel); | ||
| expect(result.current.get(id[0])).toEqual({ | ||
| uuid: id[0], | ||
| pc: peerConnection, | ||
| dataChannel, | ||
| }); | ||
| }); | ||
| it('should remove a peer', () => { | ||
| const { result } = renderHook(() => usePeers(dispatchEvent)); | ||
| result.current.add(id[0], peerConnection, dataChannel); | ||
| result.current.remove(id[0]); | ||
| expect(result.current.get(id[0])).toBeUndefined(); | ||
| }); | ||
| it('should disconnect from all peers', () => { | ||
| const { result } = renderHook(() => usePeers(dispatchEvent)); | ||
| result.current.add(id[0], peerConnection, dataChannel); | ||
| result.current.add(id[1], peerConnection, dataChannel); | ||
| result.current.disconnect(); | ||
| expect(close).toBeCalledTimes(2); | ||
| }); | ||
| it('should send data to peer', () => { | ||
| const data = 'data'; | ||
| const { result } = renderHook(() => usePeers(dispatchEvent)); | ||
| result.current.add(id[0], peerConnection, dataChannel); | ||
| result.current.sendTo(id[0], data); | ||
| expect(send).toBeCalledWith(data); | ||
| expect(dispatchEvent).toBeCalledWith('send', [id[0], data]); | ||
| }); | ||
| it('should handle send error', () => { | ||
| send.mockImplementationOnce(() => { | ||
| throw new Error(); | ||
| }); | ||
| const data = 'data'; | ||
| const { result } = renderHook(() => usePeers(dispatchEvent)); | ||
| result.current.add(id[0], peerConnection, dataChannel); | ||
| result.current.sendTo(id[0], data); | ||
| expect(send).toBeCalledWith(data); | ||
| expect(dispatchEvent).toBeCalledWith('error', expect.anything()); | ||
| }); | ||
| it('should send data to all peers', () => { | ||
| const data = 'data'; | ||
| const { result } = renderHook(() => usePeers(dispatchEvent)); | ||
| result.current.add(id[0], peerConnection, dataChannel); | ||
| result.current.add(id[1], peerConnection, dataChannel); | ||
| result.current.sendToAll(data); | ||
| expect(send).toHaveBeenNthCalledWith(2, data); | ||
| expect(dispatchEvent).toHaveBeenCalledWith('send', [id[0], data]); | ||
| expect(dispatchEvent).toHaveBeenCalledWith('send', [id[1], data]); | ||
| }); | ||
| }); |
| import { renderHook } from '@testing-library/react-hooks'; | ||
| import { usePubSub } from '../usePubSub'; | ||
| Object.defineProperty(global, 'CustomEvent', { | ||
| value: Event, | ||
| }); | ||
| describe('usePubSub', () => { | ||
| it('should dispatch event', () => { | ||
| const { result } = renderHook(() => usePubSub()); | ||
| const handleEnter = jest.fn(); | ||
| result.current.on('enter', handleEnter); | ||
| result.current.dispatchEvent('enter'); | ||
| expect(handleEnter).toBeCalled(); | ||
| }); | ||
| }); |
| import { act, renderHook } from '@testing-library/react-hooks'; | ||
| import { useSignaling } from '../useSignaling'; | ||
| const send = jest.fn(); | ||
| const close = jest.fn(); | ||
| Object.defineProperty(global, 'WebSocket', { | ||
| value: class { | ||
| constructor() { | ||
| this.addEventListener = jest.fn((_type, handler) => handler()); | ||
| this.send = send; | ||
| this.close = close; | ||
| } | ||
| }, | ||
| }); | ||
| describe('useSignaling', () => { | ||
| const uuid = 'test'; | ||
| const signalingServer = 'ws://localhost:8001/'; | ||
| const dispatchEvent = jest.fn(); | ||
| it('should connect to signaling server', () => { | ||
| const { result } = renderHook(() => useSignaling(uuid, signalingServer, dispatchEvent)); | ||
| act(() => { | ||
| result.current.connect(); | ||
| }); | ||
| expect(dispatchEvent).toBeCalledWith('enter'); | ||
| expect(send).toBeCalledWith(expect.stringContaining(uuid)); | ||
| }); | ||
| it('should disconnect from signaling server', () => { | ||
| const { result } = renderHook(() => useSignaling(uuid, signalingServer, dispatchEvent)); | ||
| act(() => result.current.connect()); | ||
| result.current.disconnect(); | ||
| expect(close).toBeCalled(); | ||
| }); | ||
| }); |
| import { useState } from 'react'; | ||
| export const useId = () => { | ||
| const [id, setId] = useState(); | ||
| return {}; | ||
| }; |
| export const usePeer = () => { | ||
| // const getConnectionState = () => this?.peerConnection.iceConnectionState; | ||
| const create = (id, peerConnection, dataChannel) => ({ | ||
| id, | ||
| peerConnection, | ||
| dataChannel, | ||
| getConnectionState: () => peerConnection.iceConnectionState, | ||
| }); | ||
| return { create }; | ||
| }; |
| import { useRef } from 'react'; | ||
| import Peer from '../models/Peer'; | ||
| export const usePeers = () => { | ||
| const peers = useRef(new Map()); | ||
| const get = (id) => { | ||
| return peers.current.get(id); | ||
| }; | ||
| const add = ({ id, peerConnection, dataChannel, }) => { | ||
| const peer = new Peer({ id, peerConnection, dataChannel }); | ||
| peers.current.set(id, peer); | ||
| }; | ||
| const remove = (id) => peers.current.delete(id); | ||
| const forEach = (callback) => peers.current.forEach(callback); | ||
| return { | ||
| peers: peers.current, | ||
| get, | ||
| add, | ||
| remove, | ||
| forEach, | ||
| }; | ||
| }; |
| import { useRef } from 'react'; | ||
| import Peer from '../models/Peer'; | ||
| import { useErrorHandler } from './useErrorHandler'; | ||
| export const usePeers = (dispatchEvent) => { | ||
| const peers = useRef(new Map()); | ||
| const handleError = useErrorHandler(dispatchEvent); | ||
| const add = (id, peerConnection, dataChannel) => { | ||
| const peer = new Peer({ uuid: id, peerConnection, dataChannel }); | ||
| peers.current.set(id, peer); | ||
| }; | ||
| const get = (id) => peers.current.get(id); | ||
| const remove = (id) => peers.current.delete(id); | ||
| const forEach = (callback) => peers.current.forEach(callback); | ||
| const disconnect = () => { | ||
| forEach((peer) => peer.pc.close()); | ||
| peers.current.clear(); | ||
| }; | ||
| const send = (peer, data) => { | ||
| try { | ||
| peer.dataChannel.send(data); | ||
| dispatchEvent('send', [peer.uuid, data]); | ||
| } | ||
| catch (error) { | ||
| handleError(error); | ||
| } | ||
| }; | ||
| const sendTo = (id, data) => { | ||
| const peer = peers.current.get(id); | ||
| if (peer) | ||
| send(peer, data); | ||
| }; | ||
| const sendToAll = (data) => { | ||
| forEach((peer) => send(peer, data)); | ||
| }; | ||
| return { | ||
| add, | ||
| get, | ||
| remove, | ||
| sendTo, | ||
| sendToAll, | ||
| disconnect, | ||
| }; | ||
| }; |
| import Peer from './Peer'; | ||
| export default class PeerConnections { | ||
| constructor() { | ||
| this.peers = new Map(); | ||
| } | ||
| get lenght() { | ||
| return this.peers.size; | ||
| } | ||
| add({ id, peerConnection, dataChannel, }) { | ||
| const peer = new Peer({ id, peerConnection, dataChannel }); | ||
| this.peers.set(id, peer); | ||
| } | ||
| get(uuid) { | ||
| return this.peers.get(uuid); | ||
| } | ||
| delete(uuid) { | ||
| this.peers.delete(uuid); | ||
| } | ||
| forEach(callback) { | ||
| this.peers.forEach(callback); | ||
| } | ||
| } |
| import Peer from '../models/Peer'; | ||
| export default class PeerConnections { | ||
| constructor() { | ||
| this.peers = new Map(); | ||
| } | ||
| get lenght() { | ||
| return this.peers.size; | ||
| } | ||
| get users() { | ||
| console.log(this.peers); | ||
| return Array.from(this.peers).map(([, peer]) => peer.metadata); | ||
| } | ||
| add({ id, peerConnection, dataChannel, }) { | ||
| const peer = new Peer({ id, peerConnection, dataChannel }); | ||
| console.log('add', this.peers); | ||
| this.peers.set(id, peer); | ||
| } | ||
| get(uuid) { | ||
| return this.peers.get(uuid); | ||
| } | ||
| delete(uuid) { | ||
| this.peers.delete(uuid); | ||
| } | ||
| forEach(callback) { | ||
| this.peers.forEach(callback); | ||
| } | ||
| sendToAll(data) { | ||
| console.log(this); | ||
| this.forEach((peer) => peer.send(data)); | ||
| } | ||
| } |
| export const getUuid = () => crypto.randomUUID(); |
| export {}; |
| export {}; |
| export declare const useId: () => {}; |
| export declare const usePeer: () => { | ||
| create: (id: string, peerConnection: RTCPeerConnection, dataChannel: RTCDataChannel) => { | ||
| id: string; | ||
| peerConnection: RTCPeerConnection; | ||
| dataChannel: RTCDataChannel; | ||
| getConnectionState: () => RTCIceConnectionState; | ||
| }; | ||
| }; |
| import Peer from '../models/Peer'; | ||
| export declare const usePeers: () => { | ||
| peers: Map<string, Peer>; | ||
| get: (id: string) => Peer | undefined; | ||
| add: ({ id, peerConnection, dataChannel, }: { | ||
| id: string; | ||
| peerConnection: RTCPeerConnection; | ||
| dataChannel: RTCDataChannel; | ||
| }) => void; | ||
| remove: (id: string) => boolean; | ||
| forEach: (callback: (peer: Peer) => void) => void; | ||
| }; |
| import Peer from '../models/Peer'; | ||
| import type { DispatchEvent } from '../types'; | ||
| export declare const usePeers: (dispatchEvent: DispatchEvent) => { | ||
| add: (id: string, peerConnection: RTCPeerConnection, dataChannel: RTCDataChannel) => void; | ||
| get: (id: string) => Peer | undefined; | ||
| remove: (id: string) => boolean; | ||
| sendTo: (id: string, data: string) => void; | ||
| sendToAll: (data: string) => void; | ||
| disconnect: () => void; | ||
| }; |
| import Peer from './Peer'; | ||
| export default class PeerConnections { | ||
| private peers; | ||
| get lenght(): number; | ||
| add({ id, peerConnection, dataChannel, }: { | ||
| id: string; | ||
| peerConnection: RTCPeerConnection; | ||
| dataChannel: RTCDataChannel; | ||
| }): void; | ||
| get(uuid: string): Peer | undefined; | ||
| delete(uuid: string): void; | ||
| forEach(callback: (peer: Peer) => void): void; | ||
| } |
| import Peer from '../models/Peer'; | ||
| export default class PeerConnections { | ||
| private peers; | ||
| get lenght(): number; | ||
| get users(): (Record<string, string> | undefined)[]; | ||
| add({ id, peerConnection, dataChannel, }: { | ||
| id: string; | ||
| peerConnection: RTCPeerConnection; | ||
| dataChannel: RTCDataChannel; | ||
| }): void; | ||
| get(uuid: string): Peer | undefined; | ||
| delete(uuid: string): void; | ||
| forEach(callback: (peer: Peer) => void): void; | ||
| sendToAll(data: string): void; | ||
| } |
| export declare const getUuid: () => string; |
@@ -6,16 +6,11 @@ import { jsx as _jsx } from "react/jsx-runtime"; | ||
| import { usePeerConnection } from '../hooks/usePeerConnection'; | ||
| import { useMessaging } from '../hooks/useMessaging'; | ||
| export const RtcProvider = ({ children, signalingServer, iceServers, }) => { | ||
| const localUuid = useRef(crypto.randomUUID()); | ||
| const { dispatchEvent, on, off } = usePubSub(); | ||
| const { peerConnections, disconnect, connect: enter, } = usePeerConnection(localUuid.current, dispatchEvent, signalingServer, iceServers); | ||
| const { send } = useMessaging(localUuid.current, peerConnections, dispatchEvent); | ||
| const leave = () => { | ||
| disconnect(); | ||
| dispatchEvent('leave'); | ||
| }; | ||
| const { disconnect: leave, connect: enter, sendToPeer, sendToAllPeers, } = usePeerConnection(localUuid.current, dispatchEvent, signalingServer, iceServers); | ||
| return (_jsx(RtcContext.Provider, Object.assign({ value: { | ||
| enter, | ||
| leave, | ||
| send, | ||
| sendToPeer, | ||
| sendToAllPeers, | ||
| on, | ||
@@ -22,0 +17,0 @@ off, |
| import Message from '../models/Message'; | ||
| import { useErrorHandler } from './useErrorHandler'; | ||
| export const useMessaging = (id, peerConnections, dispatchEvent) => { | ||
| export const useMessaging = (id, sendToAllPeers, dispatchEvent) => { | ||
| const errorHandler = useErrorHandler(dispatchEvent); | ||
@@ -13,4 +13,3 @@ const send = (data, metadata) => { | ||
| }); | ||
| const payload = JSON.stringify(message); | ||
| peerConnections.forEach((peer) => peer.dataChannel.send(payload)); | ||
| sendToAllPeers(JSON.stringify(message)); | ||
| dispatchEvent('send', message); | ||
@@ -17,0 +16,0 @@ } |
@@ -10,11 +10,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
| }; | ||
| import { useEffect, useRef } from 'react'; | ||
| import Message from '../models/Message'; | ||
| import Peer from '../models/Peer'; | ||
| import { useEffect } from 'react'; | ||
| import { ConnectionState } from '../types'; | ||
| import { useErrorHandler } from './useErrorHandler'; | ||
| import { usePeers } from './usePeers'; | ||
| import { useSignaling } from './useSignaling'; | ||
| export const usePeerConnection = (localUuid, dispatchEvent, signalingServer, iceServers) => { | ||
| const peerConnections = useRef(new Map()); | ||
| const { sendSignalingMessage, signaling, connect: connectToSginaling, disconnect: disconnectFromSignaling, } = useSignaling(localUuid, signalingServer); | ||
| const peerConnections = usePeers(dispatchEvent); | ||
| const { sendSignalingMessage, signaling, connect: connectToSginaling, disconnect: disconnectFromSignaling, } = useSignaling(localUuid, signalingServer, dispatchEvent); | ||
| const handleError = useErrorHandler(dispatchEvent); | ||
@@ -24,3 +23,4 @@ const connect = connectToSginaling; | ||
| disconnectFromSignaling(); | ||
| peerConnections.current.forEach((connection) => connection.pc.close()); | ||
| peerConnections.disconnect(); | ||
| dispatchEvent('leave'); | ||
| }; | ||
@@ -36,19 +36,15 @@ const onIceCandidate = (event, peerUuid) => { | ||
| peerConnection.addEventListener('iceconnectionstatechange', () => checkPeerDisconnect(peerUuid)); | ||
| peerConnection.addEventListener('datachannel', (event) => Object.defineProperty(peerConnections.current.get(peerUuid), 'dataChannel', { | ||
| value: event.channel, | ||
| })); | ||
| peerConnection.addEventListener('datachannel', (event) => { | ||
| Object.defineProperty(peerConnections.get(peerUuid), 'dataChannel', { | ||
| value: event.channel, | ||
| }); | ||
| dispatchEvent('dataChannel', peerUuid); | ||
| }); | ||
| peerConnection.addEventListener('connectionstatechange', () => { | ||
| const peer = peerConnections.current.get(peerUuid); | ||
| if (peer && peer.pc.connectionState === 'connected' && !initCall) | ||
| const peer = peerConnections.get(peerUuid); | ||
| const isConnected = (peer === null || peer === void 0 ? void 0 : peer.pc.connectionState) === 'connected'; | ||
| if (isConnected && !initCall) | ||
| dispatchEvent('peerConnected', peer); | ||
| }); | ||
| dataChannel.addEventListener('message', (event) => { | ||
| try { | ||
| const message = new Message(JSON.parse(event.data)); | ||
| dispatchEvent('receive', message); | ||
| } | ||
| catch (error) { | ||
| dispatchEvent('error', error); | ||
| } | ||
| }); | ||
| dataChannel.addEventListener('message', (event) => dispatchEvent('receive', [peerUuid, event.data])); | ||
| if (initCall) { | ||
@@ -60,3 +56,3 @@ peerConnection | ||
| } | ||
| peerConnections.current.set(peerUuid, new Peer({ uuid: peerUuid, peerConnection, dataChannel })); | ||
| peerConnections.add(peerUuid, peerConnection, dataChannel); | ||
| }; | ||
@@ -72,6 +68,5 @@ const sendSignalingMessageToNewcomers = (uuid) => { | ||
| try { | ||
| yield ((_a = peerConnections.current | ||
| .get(peerUuid)) === null || _a === void 0 ? void 0 : _a.pc.setLocalDescription(description)); | ||
| yield ((_a = peerConnections.get(peerUuid)) === null || _a === void 0 ? void 0 : _a.pc.setLocalDescription(description)); | ||
| sendSignalingMessage(peerUuid, { | ||
| sdp: (_b = peerConnections.current.get(peerUuid)) === null || _b === void 0 ? void 0 : _b.pc.localDescription, | ||
| sdp: (_b = peerConnections.get(peerUuid)) === null || _b === void 0 ? void 0 : _b.pc.localDescription, | ||
| }); | ||
@@ -90,3 +85,3 @@ } | ||
| if (((_c = signal.sdp) === null || _c === void 0 ? void 0 : _c.type) == 'offer') { | ||
| const description = yield ((_d = peerConnections.current | ||
| const description = yield ((_d = peerConnections | ||
| .get(signal.uuid)) === null || _d === void 0 ? void 0 : _d.pc.createAnswer()); | ||
@@ -121,3 +116,3 @@ if (description) | ||
| return; | ||
| const currentPeerConnection = (_a = peerConnections.current.get(peerUuid)) === null || _a === void 0 ? void 0 : _a.pc; | ||
| const currentPeerConnection = (_a = peerConnections.get(peerUuid)) === null || _a === void 0 ? void 0 : _a.pc; | ||
| if (currentPeerConnection) { | ||
@@ -143,4 +138,4 @@ if (isSessionDescription) { | ||
| var _a; | ||
| const state = (_a = peerConnections.current.get(peerUuid)) === null || _a === void 0 ? void 0 : _a.pc.iceConnectionState; | ||
| const peer = peerConnections.current.get(peerUuid); | ||
| const state = (_a = peerConnections.get(peerUuid)) === null || _a === void 0 ? void 0 : _a.pc.iceConnectionState; | ||
| const peer = peerConnections.get(peerUuid); | ||
| if (peer && | ||
@@ -150,4 +145,4 @@ (state === ConnectionState.FAILED || | ||
| state === ConnectionState.DISCONNECT)) { | ||
| peerConnections.remove(peerUuid); | ||
| dispatchEvent('peerDisconnected', peer); | ||
| peerConnections.current.delete(peerUuid); | ||
| } | ||
@@ -162,6 +157,7 @@ }; | ||
| return { | ||
| peerConnections: peerConnections.current, | ||
| connect, | ||
| disconnect, | ||
| sendToPeer: peerConnections.sendTo, | ||
| sendToAllPeers: peerConnections.sendToAll, | ||
| }; | ||
| }; |
| import { useState, useEffect } from 'react'; | ||
| export const useSignaling = (uuid, signalingServer) => { | ||
| export const useSignaling = (uuid, signalingServer, dispatchEvent) => { | ||
| const [signaling, setSignaling] = useState(); | ||
@@ -10,3 +10,6 @@ const connect = () => setSignaling(new WebSocket(signalingServer)); | ||
| }; | ||
| const handleSignalingOpen = () => sendSignalingMessage('all', { newPeer: true }); | ||
| const handleSignalingOpen = () => { | ||
| sendSignalingMessage('all', { newPeer: true }); | ||
| dispatchEvent('enter'); | ||
| }; | ||
| useEffect(() => { | ||
@@ -13,0 +16,0 @@ signaling === null || signaling === void 0 ? void 0 : signaling.addEventListener('open', handleSignalingOpen); |
| export * from './contexts/RtcProvider'; | ||
| export * from './types'; | ||
| export * from './hooks/useRtc'; | ||
| export { default as Message } from './models/Message'; |
@@ -1,5 +0,4 @@ | ||
| import Peer from '../models/Peer'; | ||
| import type { DispatchEvent, Send } from '../types'; | ||
| export declare const useMessaging: (id: string, peerConnections: Map<string, Peer>, dispatchEvent: DispatchEvent) => { | ||
| export declare const useMessaging: (id: string, sendToAllPeers: (data: string) => void, dispatchEvent: DispatchEvent) => { | ||
| send: Send; | ||
| }; |
@@ -1,2 +0,1 @@ | ||
| import Peer from '../models/Peer'; | ||
| import { type DispatchEvent } from '../types'; | ||
@@ -6,5 +5,6 @@ export declare const usePeerConnection: (localUuid: string, dispatchEvent: DispatchEvent, signalingServer: string, iceServers: { | ||
| }[]) => { | ||
| peerConnections: Map<string, Peer>; | ||
| connect: () => void; | ||
| disconnect: () => void; | ||
| sendToPeer: (id: string, data: string) => void; | ||
| sendToAllPeers: (data: string) => void; | ||
| }; |
@@ -1,2 +0,3 @@ | ||
| export declare const useSignaling: (uuid: string, signalingServer: string) => { | ||
| import type { DispatchEvent } from '../types'; | ||
| export declare const useSignaling: (uuid: string, signalingServer: string, dispatchEvent: DispatchEvent) => { | ||
| sendSignalingMessage: (destination: string, data: Record<string, unknown>) => void; | ||
@@ -3,0 +4,0 @@ signaling: WebSocket | undefined; |
| export * from './contexts/RtcProvider'; | ||
| export * from './types'; | ||
| export * from './hooks/useRtc'; | ||
| export { default as Message } from './models/Message'; |
@@ -1,2 +0,1 @@ | ||
| import type Message from './models/Message'; | ||
| import type Peer from './models/Peer'; | ||
@@ -8,4 +7,4 @@ export declare type DispatchEvent = <Type extends keyof EventsDetail>(type: Type, detail?: EventsDetail[Type]) => boolean; | ||
| interface EventsDetail { | ||
| receive: Message; | ||
| send: Message; | ||
| receive: [string, string]; | ||
| send: [string, string]; | ||
| error: unknown; | ||
@@ -15,11 +14,10 @@ peerConnected: Peer; | ||
| leave: unknown; | ||
| enter: unknown; | ||
| dataChannel: string; | ||
| } | ||
| export declare type Send = <Metadata = undefined>(data: string, metadata?: Metadata) => void; | ||
| export interface ContextType { | ||
| send?: Send; | ||
| enter?: () => void; | ||
| leave?: () => void; | ||
| state?: { | ||
| isEntered: boolean; | ||
| }; | ||
| sendToPeer?: (id: string, data: string) => void; | ||
| sendToAllPeers?: (data: string) => void; | ||
| on?: AddEventListener; | ||
@@ -26,0 +24,0 @@ off?: RemoveEventListener; |
+3
-2
| { | ||
| "name": "@torolocos/react-rtc", | ||
| "version": "0.1.0-rc.0", | ||
| "version": "0.1.0-rc.1", | ||
| "description": "React real-time communication library.", | ||
@@ -40,3 +40,4 @@ "keywords": [ | ||
| "tsc:check": "tsc --pretty --noEmit", | ||
| "lint": "eslint . --ext ts --ext tsx" | ||
| "lint": "eslint . --ext ts --ext tsx", | ||
| "test": "jest" | ||
| }, | ||
@@ -43,0 +44,0 @@ "peerDependencies": { |
+117
-3
@@ -1,4 +0,4 @@ | ||
| # react-rtc | ||
| # React-RTC | ||
| React real-time communication library. | ||
| This library provides you simple and versatile wrapper around [WebRTC](https://webrtc.org/) technology in [React](https://reactjs.org/) ecosystem. | ||
@@ -21,2 +21,116 @@ ## Installation | ||
| See [example](https://github.com/torolocos/react-rtc/tree/main/apps/example) app. | ||
| 1. Run [signaling](https://www.wowza.com/blog/webrtc-signaling-servers) server. | ||
| - To use [WebRTC](https://webrtc.org/) you have to run [signaling](https://www.wowza.com/blog/webrtc-signaling-servers) server. Check out our [implementation](https://github.com/torolocos/react-rtc/tree/main/apps/signaling). | ||
| 2. Wrap your top level component with `RtcProvider`. | ||
| ```tsx | ||
| import { RtcProvider } from '@torolocos/react-rtc'; | ||
| import Chat from './components/Chat'; | ||
| const App = () => { | ||
| return ( | ||
| <RtcProvider | ||
| signalingServer="ws://localhost:8001/" | ||
| iceServers={[{ urls: 'stun:stun.l.google.com:19302' }]} | ||
| > | ||
| <Chat /> | ||
| </RtcProvider> | ||
| ); | ||
| }; | ||
| export default App; | ||
| ``` | ||
| 3. Use `useRtc` hook in your components. | ||
| ```tsx | ||
| import { useEffect, useState, useRef } from 'react'; | ||
| import { type RtcEvent, Message, useRtc } from '@torolocos/react-rtc'; | ||
| import './styles.css'; | ||
| type MessageMetadata = { username?: string }; | ||
| const isMessage = (message: unknown): message is Message<MessageMetadata> => { | ||
| return ( | ||
| message instanceof Message && | ||
| 'username' in message.metadata && | ||
| typeof message.metadata.username === 'string' | ||
| ); | ||
| }; | ||
| const Chat = () => { | ||
| const { send, enter, leave, on, off } = useRtc(); | ||
| const [inputValue, setInputValue] = useState(''); | ||
| const [messageData, setMessageData] = useState<Message<MessageMetadata>[]>( | ||
| [] | ||
| ); | ||
| const [error, setError] = useState(''); | ||
| const username = useRef(Math.random().toPrecision(4).toString()); | ||
| const onMessageSend = () => { | ||
| if (send) send<MessageMetadata>(inputValue, { username: username.current }); | ||
| setInputValue(''); | ||
| }; | ||
| const handleMessageReceived = (event: RtcEvent<'receive'>) => { | ||
| if (isMessage(event.detail)) { | ||
| const message = event.detail; | ||
| setMessageData((messages) => [...messages, message]); | ||
| } | ||
| }; | ||
| const handleMessageSent = (event: RtcEvent<'send'>) => { | ||
| if (isMessage(event.detail)) { | ||
| const message = event.detail; | ||
| setMessageData((messages) => [...messages, message]); | ||
| } | ||
| }; | ||
| const handleError = () => setError('Err'); | ||
| useEffect(() => { | ||
| if (enter) enter(); | ||
| if (on) { | ||
| on('receive', handleMessageReceived); | ||
| on('send', handleMessageSent); | ||
| on('error', handleError); | ||
| } | ||
| return () => { | ||
| if (off) { | ||
| off('receive', handleMessageReceived); | ||
| off('send', handleMessageSent); | ||
| off('error', handleError); | ||
| } | ||
| if (leave) leave(); | ||
| }; | ||
| }, []); | ||
| return ( | ||
| <> | ||
| {error && <div className="errorText">Something went wrong</div>} | ||
| <div> | ||
| {messageData.map(({ id, message, metadata }) => ( | ||
| <div key={id}> | ||
| {metadata?.username}: {message} | ||
| </div> | ||
| ))} | ||
| </div> | ||
| <input | ||
| value={inputValue} | ||
| onChange={({ target: { value } }) => setInputValue(value)} | ||
| /> | ||
| <button onClick={onMessageSend}>send</button> | ||
| </> | ||
| ); | ||
| }; | ||
| export default Chat; | ||
| ``` | ||
| > See the [example](https://github.com/torolocos/react-rtc/tree/main/apps/example) app. |
31075
83.46%50
92.31%702
88.2%136
518.18%