New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

@torolocos/react-rtc

Package Overview
Dependencies
Maintainers
2
Versions
52
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@torolocos/react-rtc - npm Package Compare versions

Comparing version
0.1.0-rc.0
to
0.1.0-rc.1
+11
dist/esm/hooks/__tests__/useErrorHandler.test.js
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 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;
+3
-8

@@ -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;

{
"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.