Use WebRtc with SimplePeer
authorgparant <g.parant@thecodingmachine.com>
Sat, 25 Apr 2020 14:05:33 +0000 (16:05 +0200)
committergparant <g.parant@thecodingmachine.com>
Sat, 25 Apr 2020 14:05:33 +0000 (16:05 +0200)
back/src/Controller/IoSocketController.ts
front/package.json
front/src/Connexion.ts
front/src/Phaser/Game/GameManager.ts
front/src/WebRtc/MediaManager.ts
front/src/WebRtc/PeerConnexionManager.ts [deleted file]
front/src/WebRtc/SimplePeer.ts [new file with mode: 0644]
front/src/WebRtc/WebRtcEventManager.ts [deleted file]
front/yarn.lock

index ff844358112af1d91d969bb751bca0f71fbabb4e..57adf92d3bbc50058cf6fd3c5eaa1195228b1e25 100644 (file)
@@ -7,6 +7,7 @@ import Jwt, {JsonWebTokenError} from "jsonwebtoken";
 import {SECRET_KEY} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..."
 import {ExtRooms, RefreshUserPositionFunction} from "../Model/Websocket/ExtRoom";
 import {ExtRoomsInterface} from "_Model/Websocket/ExtRoomsInterface";
+import {ExtWebSocket} from "../../../../publicis/sources/api/src/Entities/WebSocket/ExtWebSocket";
 
 export class IoSocketController{
     Io: socketIO.Server;
@@ -84,28 +85,25 @@ export class IoSocketController{
                 (socket as ExSocketInterface).roomId = data.roomId;
 
                 //if two persone in room share
+                console.log("nb user => " + data.roomId, this.Io.sockets.adapter.rooms[data.roomId].length);
                 if(this.Io.sockets.adapter.rooms[data.roomId].length < 2) {
                     return;
                 }
                 let clients : Array<any> = Object.values(this.Io.sockets.sockets);
 
                 //send start at one client to initialise offer webrtc
-                clients[0].emit('webrtc-start');
+                clients.forEach((client: ExtWebSocket, index : number) => {
+                    client.emit('webrtc-start', JSON.stringify({
+                        userId: client.userId,
+                        initiator : index === 0
+                    }));
+                });
             });
 
-            socket.on('video-offer', (message : string) => {
+            socket.on('webrtc-signal', (message : string) => {
                 let data : any = JSON.parse(message);
-                socket.to(data.roomId).emit('video-offer',  message);
-            });
-
-            socket.on('video-answer', (message : string) => {
-                let data : any = JSON.parse(message);
-                socket.to(data.roomId).emit('video-answer',  message);
-            });
-
-            socket.on('ice-candidate', (message : string) => {
-                let data : any = JSON.parse(message);
-                socket.to(data.roomId).emit('ice-candidate',  message);
+                console.info('webrtc-signal', message);
+                socket.to(data.roomId).emit('webrtc-signal',  message);
             });
         });
     }
index 25d613e6b250e92e19aeac26edd3da077d90e6a8..17c08137f52ff1be204a318af921b8750e9c024c 100644 (file)
   },
   "dependencies": {
     "@types/axios": "^0.14.0",
+    "@types/simple-peer": "^9.6.0",
     "@types/socket.io-client": "^1.4.32",
     "phaser": "^3.22.0",
+    "simple-peer": "^9.6.2",
     "socket.io-client": "^2.3.0"
   },
   "scripts": {
index 0d9f998a1f74e225ab0d428da1cec46e99da2347..b6f2db728e4813379d93f20cd76db71de2315aab 100644 (file)
@@ -4,6 +4,15 @@ const SocketIo = require('socket.io-client');
 import Axios from "axios";
 import {API_URL} from "./Enum/EnvironmentVariable";
 
+enum EventMessage{
+    WEBRTC_SIGNAL = "webrtc-signal",
+    WEBRTC_START = "webrtc-start",
+    WEBRTC_ROOM = "webrtc-room",
+    JOIN_ROOM = "join-room",
+    USER_POSITION = "user-position",
+    MESSAGE_ERROR = "message-error"
+}
+
 class Message {
     userId: string;
     roomId: string;
@@ -56,6 +65,7 @@ export interface MessageUserPositionInterface {
     roomId: string;
     position: PointInterface;
 }
+
 class MessageUserPosition extends Message implements MessageUserPositionInterface{
     position: PointInterface;
 
@@ -76,14 +86,15 @@ class MessageUserPosition extends Message implements MessageUserPositionInterfac
 }
 
 export interface ListMessageUserPositionInterface {
-    roomId : string;
+    roomId: string;
     listUsersPosition: Array<MessageUserPosition>;
 }
-class ListMessageUserPosition{
-    roomId : string;
+
+class ListMessageUserPosition {
+    roomId: string;
     listUsersPosition: Array<MessageUserPosition>;
 
-    constructor(roomId : string, data : any) {
+    constructor(roomId: string, data: any) {
         this.roomId = roomId;
         this.listUsersPosition = new Array<MessageUserPosition>();
         data.forEach((userPosition: any) => {
@@ -99,32 +110,47 @@ class ListMessageUserPosition{
         });
     }
 }
+
 export interface ConnexionInterface {
-    socket : any;
-    token : string;
-    email : string;
+    socket: any;
+    token: string;
+    email: string;
     userId: string;
-    startedRoom : string;
-    createConnexion() : Promise<any>;
-    joinARoom(roomId : string) : void;
-    sharePosition(roomId : string, x : number, y : number, direction : string) : void;
-    positionOfAllUser() : void;
+    startedRoom: string;
+
+    createConnexion(): Promise<any>;
+
+    joinARoom(roomId: string): void;
+
+    sharePosition(roomId: string, x: number, y: number, direction: string): void;
+
+    positionOfAllUser(): void;
+
+    /*webrtc*/
+    sendWebrtcRomm(roomId: string): void;
+
+    sendWebrtcSignal(signal: any, roomId: string): void;
+
+    receiveWebrtcSignal(callBack: Function): void;
+
+    receiveWebrtcStart(callBack: Function): void;
 }
-export class Connexion implements ConnexionInterface{
-    socket : any;
-    token : string;
-    email : string;
+
+export class Connexion implements ConnexionInterface {
+    socket: any;
+    token: string;
+    email: string;
     userId: string;
-    startedRoom : string;
+    startedRoom: string;
 
     GameManager: GameManagerInterface;
 
-    constructor(email : string, GameManager: GameManagerInterface) {
+    constructor(email: string, GameManager: GameManagerInterface) {
         this.email = email;
         this.GameManager = GameManager;
     }
 
-    createConnexion() : Promise<ConnexionInterface>{
+    createConnexion(): Promise<ConnexionInterface> {
         return Axios.post(`${API_URL}/login`, {email: this.email})
             .then((res) => {
                 this.token = res.data.token;
@@ -159,9 +185,9 @@ export class Connexion implements ConnexionInterface{
      * Permit to join a room
      * @param roomId
      */
-    joinARoom(roomId : string) : void {
+    joinARoom(roomId: string): void {
         let messageUserPosition = new MessageUserPosition(this.userId, this.startedRoom, new Point(0, 0));
-        this.socket.emit('join-room', messageUserPosition.toString());
+        this.socket.emit(EventMessage.JOIN_ROOM, messageUserPosition.toString());
     }
 
     /**
@@ -171,12 +197,12 @@ export class Connexion implements ConnexionInterface{
      * @param y
      * @param direction
      */
-    sharePosition(roomId : string, x : number, y : number, direction : string = "none") : void{
-        if(!this.socket){
+    sharePosition(roomId: string, x: number, y: number, direction: string = "none"): void {
+        if (!this.socket) {
             return;
         }
         let messageUserPosition = new MessageUserPosition(this.userId, roomId, new Point(x, y, direction));
-        this.socket.emit('user-position', messageUserPosition.toString());
+        this.socket.emit(EventMessage.USER_POSITION, messageUserPosition.toString());
     }
 
     /**
@@ -194,8 +220,8 @@ export class Connexion implements ConnexionInterface{
      * ...
      * ]
      **/
-    positionOfAllUser() : void {
-        this.socket.on("user-position", (message: string) => {
+    positionOfAllUser(): void {
+        this.socket.on(EventMessage.USER_POSITION, (message: string) => {
             let dataList = JSON.parse(message);
             dataList.forEach((UserPositions: any) => {
                 let listMessageUserPosition = new ListMessageUserPosition(UserPositions[0], UserPositions[1]);
@@ -204,9 +230,29 @@ export class Connexion implements ConnexionInterface{
         });
     }
 
-    errorMessage() : void {
-        this.socket.on('message-error', (message : string) => {
-            console.error("message-error", message);
+    sendWebrtcSignal(signal: any, roomId: string) {
+        this.socket.emit(EventMessage.WEBRTC_SIGNAL, JSON.stringify({
+            userId: this.userId,
+            roomId: roomId,
+            signal: signal
+        }));
+    }
+
+    sendWebrtcRomm(roomId: string) {
+        this.socket.emit(EventMessage.WEBRTC_ROOM, JSON.stringify({roomId: roomId}));
+    }
+
+    receiveWebrtcStart(callback: Function) {
+        this.socket.on(EventMessage.WEBRTC_START, callback);
+    }
+
+    receiveWebrtcSignal(callback: Function) {
+        this.socket.on(EventMessage.WEBRTC_SIGNAL, callback);
+    }
+
+    errorMessage(): void {
+        this.socket.on(EventMessage.MESSAGE_ERROR, (message: string) => {
+            console.error(EventMessage.MESSAGE_ERROR, message);
         })
     }
 }
\ No newline at end of file
index fffca2e9cb04ce2c55e4fbc81244c5193d854868..fbc1d5bde18eb6e42d8eeb272e38a57c4b8583a0 100644 (file)
@@ -1,7 +1,7 @@
 import {GameSceneInterface, GameScene} from "./GameScene";
 import {ROOM} from "../../Enum/EnvironmentVariable"
 import {Connexion, ConnexionInterface, ListMessageUserPositionInterface} from "../../Connexion";
-import {WebRtcEventManager} from "../../WebRtc/WebRtcEventManager";
+import {SimplePeer} from "../../WebRtc/SimplePeer";
 
 export enum StatusGameManagerEnum {
     IN_PROGRESS = 1,
@@ -30,7 +30,7 @@ export class GameManager implements GameManagerInterface {
             this.configureGame();
             /** TODO add loader in the page **/
             //initialise cam
-            new WebRtcEventManager(ConnexionInstance);
+            new SimplePeer(ConnexionInstance);
         }).catch((err) => {
             console.error(err);
             throw err;
index 605be96581ad6ceaf92a2a01e21645b3d992ea3c..e8666066ecdc188d3613c9e2b87e2c203349415e 100644 (file)
@@ -47,7 +47,7 @@ export class MediaManager {
         let webRtc = document.getElementById('webRtc');
         webRtc.classList.add('active');
 
-        this.getCamera();
+        //this.getCamera();
     }
 
     enabledCamera() {
@@ -56,7 +56,7 @@ export class MediaManager {
         this.constraintsMedia.video = true;
         this.localStream = null;
         this.myCamVideo.srcObject = null;
-        this.getCamera();
+        //this.getCamera();
     }
 
     disabledCamera() {
@@ -74,14 +74,14 @@ export class MediaManager {
         }
         this.localStream = null;
         this.myCamVideo.srcObject = null;
-        this.getCamera();
+        //this.getCamera();
     }
 
     enabledMicrophone() {
         this.microphoneClose.style.display = "none";
         this.microphone.style.display = "block";
         this.constraintsMedia.audio = true;
-        this.getCamera();
+        //this.getCamera();
     }
 
     disabledMicrophone() {
@@ -95,17 +95,17 @@ export class MediaManager {
                 }
             });
         }
-        this.getCamera();
+        //this.getCamera();
     }
 
     //get camera
     getCamera() {
-        this.getCameraPromise = navigator.mediaDevices.getUserMedia(this.constraintsMedia)
+        return this.getCameraPromise = navigator.mediaDevices.getUserMedia(this.constraintsMedia)
             .then((stream: MediaStream) => {
-                console.log("constraintsMedia", stream);
                 this.localStream = stream;
                 this.myCamVideo.srcObject = this.localStream;
                 this.myCamVideo.play();
+                return stream;
             }).catch((err) => {
                 console.error(err);
                 this.localStream = null;
diff --git a/front/src/WebRtc/PeerConnexionManager.ts b/front/src/WebRtc/PeerConnexionManager.ts
deleted file mode 100644 (file)
index f2b4b28..0000000
+++ /dev/null
@@ -1,137 +0,0 @@
-import {WebRtcEventManager} from "./WebRtcEventManager";
-import {MediaManager} from "./MediaManager";
-const offerOptions = {
-    offerToReceiveAudio: 1,
-    offerToReceiveVideo: 1,
-    iceServers: [{url:'stun:stun.l.google.com:19302'}],
-};
-
-export class PeerConnexionManager {
-
-    WebRtcEventManager: WebRtcEventManager;
-    MediaManager : MediaManager;
-
-    peerConnection: RTCPeerConnection;
-
-    constructor(WebRtcEventManager : WebRtcEventManager) {
-        this.WebRtcEventManager = WebRtcEventManager;
-        this.MediaManager = new MediaManager();
-    }
-
-    createPeerConnection(data: any = null): Promise<any> {
-        return this.MediaManager.getCameraPromise.then(() => {
-            this.peerConnection = new RTCPeerConnection();
-
-            //init all events peer connection
-            this.createEventPeerConnection();
-
-            this.MediaManager.localStream.getTracks().forEach(
-                (track: MediaStreamTrack) => this.peerConnection.addTrack(track, this.MediaManager.localStream)
-            );
-
-            //if no data, create offer
-            if (!data || !data.message) {
-                return this.createOffer();
-            }
-
-            let description = new RTCSessionDescription(data.message);
-            return this.peerConnection.setRemoteDescription(description).catch((err) => {
-                console.error("createPeerConnection => setRemoteDescription", err);
-                throw err;
-            });
-        });
-    }
-
-    createOffer(): Promise<any> {
-        console.log('pc1 createOffer start');
-        // @ts-ignore
-        return this.peerConnection.createOffer(offerOptions).then((offer: RTCSessionDescriptionInit) => {
-            this.peerConnection.setLocalDescription(offer).then(() => {
-                let message = {message: this.peerConnection.localDescription};
-                this.WebRtcEventManager.emitVideoOffer(message);
-            }).catch((err) => {
-                console.error("createOffer => setLocalDescription", err);
-                throw err;
-            });
-        }).catch((err: Error) => {
-            console.error("createOffer => createOffer", err);
-            throw err;
-        });
-    }
-
-    createAnswer(): Promise<any> {
-        return this.peerConnection.createAnswer().then((answer : RTCSessionDescriptionInit) => {
-            this.peerConnection.setLocalDescription(answer).then(() => {
-                //push video-answer
-                let messageSend = {message: this.peerConnection.localDescription};
-                this.WebRtcEventManager.emitVideoAnswer(messageSend);
-                console.info("video-answer => send", messageSend);
-            }).catch((err) => {
-                console.error("eventVideoOffer => createAnswer => setLocalDescription", err);
-                throw err;
-            })
-        }).catch((err) => {
-            console.error("eventVideoOffer => createAnswer", err);
-            throw err;
-        })
-    }
-
-    setRemoteDescription(data: any): Promise<any> {
-        let description = new RTCSessionDescription(data.message);
-        return this.peerConnection.setRemoteDescription(description).catch((err) => {
-            console.error("PeerConnexionManager => setRemoteDescription", err);
-            throw err;
-        })
-    }
-
-    addIceCandidate(data: any): Promise<any> {
-        return this.peerConnection.addIceCandidate(data.message)
-            .catch((err) => {
-                console.error("PeerConnexionManager => addIceCandidate", err);
-                throw err;
-            })
-    }
-
-    hangup() {
-        console.log('Ending call');
-        if (this.peerConnection) {
-            this.peerConnection.close();
-        }
-        this.peerConnection = null;
-    }
-
-    createEventPeerConnection(){
-        //define creator of offer
-        this.peerConnection.addEventListener('icecandidate', ({candidate}) => {
-            let message = {message: candidate};
-            if (!candidate) {
-                return;
-            }
-            this.WebRtcEventManager.emitIceCandidate(message);
-        });
-
-        this.peerConnection.addEventListener('iceconnectionstatechange', (e : Event) => {
-            console.info('oniceconnectionstatechange => iceConnectionState', this.peerConnection.iceConnectionState);
-        });
-
-        this.peerConnection.addEventListener('negotiationneeded', (e : Event) => {
-            console.info("Event:negotiationneeded => call()", e);
-            this.createOffer()
-        });
-
-        this.peerConnection.addEventListener("track", (e:RTCTrackEvent) => {
-            console.info('Event:track', e);
-            if (this.MediaManager.remoteVideo.srcObject !== e.streams[0]) {
-                this.MediaManager.remoteVideo.srcObject = e.streams[0];
-                console.log('pc1 received remote stream');
-            }
-        });
-
-        this.peerConnection.onicegatheringstatechange = () => {
-            console.info('onicegatheringstatechange => iceConnectionState', this.peerConnection.iceConnectionState);
-        };
-        this.peerConnection.onsignalingstatechange = () => {
-            console.info('onsignalingstatechange => iceConnectionState', this.peerConnection.iceConnectionState);
-        };
-    }
-}
\ No newline at end of file
diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts
new file mode 100644 (file)
index 0000000..9d199cc
--- /dev/null
@@ -0,0 +1,106 @@
+import {ConnexionInterface} from "../Connexion";
+import {MediaManager} from "./MediaManager";
+let Peer = require('simple-peer');
+
+export class SimplePeer {
+    Connexion: ConnexionInterface;
+    MediaManager: MediaManager;
+    RoomId: string;
+
+    PeerConnexion: any;
+
+    constructor(Connexion: ConnexionInterface, roomId: string = "test-webrtc") {
+        this.Connexion = Connexion;
+        this.MediaManager = new MediaManager();
+        this.RoomId = roomId;
+        this.initialise();
+    }
+
+    /**
+     * server has two person connected, start the meet
+     */
+    initialise() {
+        return this.MediaManager.getCamera().then(() => {
+            //send message to join a room
+            this.Connexion.sendWebrtcRomm(this.RoomId);
+
+            //receive message start
+            this.Connexion.receiveWebrtcStart((message : string) => {
+                this.receiveWebrtcStart(message);
+            });
+
+            //receive signal by gemer
+            this.Connexion.receiveWebrtcSignal((message : string) => {
+                this.receiveWebrtcSignal(message);
+            });
+
+        }).catch((err) => {
+            console.error(err);
+        });
+    }
+
+    /**
+     *
+     */
+    receiveWebrtcStart(message: string) {
+        let data = JSON.parse(message);
+
+        //create pear connexion of user stared
+        this.createPeerConnexion(data.initiator);
+    }
+
+    /**
+     *
+     * @param userId
+     * @param initiator
+     */
+    createPeerConnexion(initiator : boolean = false){
+        this.PeerConnexion = new Peer({initiator: initiator});
+        this.addMedia();
+
+        this.PeerConnexion.on('signal', (data: any) => {
+            this.sendWebrtcSignal(data);
+        });
+
+        this.PeerConnexion.on('stream', (stream: MediaStream) => {
+            this.stream(stream)
+        });
+    }
+
+    /**
+     * permit to send signal
+     * @param data
+     */
+    sendWebrtcSignal(data: any) {
+        this.Connexion.sendWebrtcSignal(data, this.RoomId);
+    }
+
+    /**
+     *
+     * @param message
+     */
+    receiveWebrtcSignal(message: string) {
+        let data = JSON.parse(message);
+        if(!this.PeerConnexion){
+            return;
+        }
+        this.PeerConnexion.signal(data.signal);
+    }
+
+    /**
+     * permit stream video
+     * @param stream
+     */
+    stream(stream: MediaStream) {
+        this.MediaManager.remoteStream = stream;
+        this.MediaManager.remoteVideo.srcObject = this.MediaManager.remoteStream;
+    }
+
+    /**
+     * Permit to update stream
+     * @param stream
+     */
+     addMedia () {
+         this.PeerConnexion.addStream(this.MediaManager.localStream) // <- add streams to peer dynamically
+    }
+}
\ No newline at end of file
diff --git a/front/src/WebRtc/WebRtcEventManager.ts b/front/src/WebRtc/WebRtcEventManager.ts
deleted file mode 100644 (file)
index 28c6acf..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-import {ConnexionInterface} from "../Connexion";
-import {PeerConnexionManager} from "./PeerConnexionManager";
-
-export class WebRtcEventManager {
-    Connexion: ConnexionInterface;
-    PeerConnexionManager: PeerConnexionManager;
-    RoomId : string;
-
-    constructor(Connexion : ConnexionInterface, roomId : string = "test-webrtc") {
-        this.RoomId = roomId;
-        this.Connexion = Connexion;
-        this.PeerConnexionManager = new PeerConnexionManager(this);
-
-        this.start();
-        this.eventVideoOffer();
-        this.eventVideoAnswer();
-        this.eventIceCandidate();
-
-        //start to connect on event
-        //TODO test 
-        this.emitWebRtcRoom();
-    }
-
-    /**
-     * server has two person connected, start the meet
-     */
-    start(){
-        this.Connexion.socket.on('webrtc-start', () => {
-            return this.PeerConnexionManager.createPeerConnection();
-        });
-    }
-
-    /**
-     * Receive video offer
-     */
-    eventVideoOffer() {
-        this.Connexion.socket.on("video-offer", (message : any) => {
-            let data = JSON.parse(message);
-            console.info("video-offer", data);
-            this.PeerConnexionManager.createPeerConnection(data).then(() => {
-                return this.PeerConnexionManager.createAnswer();
-            });
-        });
-    }
-
-    /**
-     * Receive video answer
-     */
-    eventVideoAnswer() {
-        this.Connexion.socket.on("video-answer", (message : any) => {
-            let data = JSON.parse(message);
-            console.info("video-answer", data);
-            this.PeerConnexionManager.setRemoteDescription(data)
-                .catch((err) => {
-                    console.error("video-answer => setRemoteDescription", err)
-                })
-        });
-    }
-
-    /**
-     * Receive ice candidate
-     */
-    eventIceCandidate() {
-        this.Connexion.socket.on("ice-candidate", (message : any) => {
-            let data = JSON.parse(message);
-            console.info("ice-candidate", data);
-            this.PeerConnexionManager.addIceCandidate(data).then(() => {
-                console.log(`ICE candidate:\n${data.message ? data.message.candidate : '(null)'}`);
-            });
-        });
-    }
-
-    emitWebRtcRoom(){
-        //connect on the room to create a meet
-        this.Connexion.socket.emit('webrtc-room', JSON.stringify({roomId: this.RoomId}));
-    }
-
-    emitIceCandidate(message : any){
-        message.roomId = this.RoomId;
-        this.Connexion.socket.emit('ice-candidate', JSON.stringify(message));
-    }
-
-    emitVideoOffer(message : any){
-        message.roomId = this.RoomId;
-        this.Connexion.socket.emit('video-offer', JSON.stringify(message));
-    }
-
-    emitVideoAnswer(message : any){
-        message.roomId = this.RoomId;
-        this.Connexion.socket.emit("video-answer", JSON.stringify(message));
-    }
-}
\ No newline at end of file
index c9b34f3f857d94d3731b83fdf58032c4ec12ea68..5b3a1cc05cf3c238f8c9ac152837ab41b1747470 100644 (file)
   version "13.11.0"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.0.tgz#390ea202539c61c8fa6ba4428b57e05bc36dc47b"
 
+"@types/simple-peer@^9.6.0":
+  version "9.6.0"
+  resolved "https://registry.yarnpkg.com/@types/simple-peer/-/simple-peer-9.6.0.tgz#b5828d835b7f42dde27db584ba127e7a9f9072f4"
+  integrity sha512-X2y6s+vE/3j03hkI90oqld2JH2J/m1L7yFCYYPyFV/whrOK1h4neYvJL3GIE+UcACJacXZqzdmDKudwec18RbA==
+  dependencies:
+    "@types/node" "*"
+
 "@types/socket.io-client@^1.4.32":
   version "1.4.32"
   resolved "https://registry.yarnpkg.com/@types/socket.io-client/-/socket.io-client-1.4.32.tgz#988a65a0386c274b1c22a55377fab6a30789ac14"
@@ -1718,6 +1725,11 @@ functional-red-black-tree@^1.0.1:
   resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
   integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
 
+get-browser-rtc@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/get-browser-rtc/-/get-browser-rtc-1.0.2.tgz#bbcd40c8451a7ed4ef5c373b8169a409dd1d11d9"
+  integrity sha1-u81AyEUaftTvXDc7gWmkCd0dEdk=
+
 get-caller-file@^1.0.1:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
@@ -3097,7 +3109,12 @@ querystringify@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e"
 
-randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
+queue-microtask@^1.1.0:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.1.2.tgz#139bf8186db0c545017ec66c2664ac646d5c571e"
+  integrity sha512-F9wwNePtXrzZenAB3ax0Y8TSKGvuB7Qw16J30hspEUTbfUM+H827XyN3rlpwhVmtm5wuZtbKIHjOnwDn7MUxWQ==
+
+randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.3, randombytes@^2.0.5:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
   dependencies:
@@ -3135,7 +3152,7 @@ raw-body@2.4.0:
     string_decoder "~1.1.1"
     util-deprecate "~1.0.1"
 
-readable-stream@^3.0.6:
+readable-stream@^3.0.6, readable-stream@^3.4.0:
   version "3.6.0"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
   dependencies:
@@ -3418,6 +3435,17 @@ signal-exit@^3.0.0, signal-exit@^3.0.2:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
 
+simple-peer@^9.6.2:
+  version "9.6.2"
+  resolved "https://registry.yarnpkg.com/simple-peer/-/simple-peer-9.6.2.tgz#42418e77cf8f9184e4fa22ef1017b195c2bf84d7"
+  integrity sha512-EOKoImCaqtNvXIntxT1CBBK/3pVi7tMAoJ3shdyd9qk3zLm3QPiRLb/sPC1G2xvKJkJc5fkQjCXqRZ0AknwTig==
+  dependencies:
+    debug "^4.0.1"
+    get-browser-rtc "^1.0.0"
+    queue-microtask "^1.1.0"
+    randombytes "^2.0.3"
+    readable-stream "^3.4.0"
+
 slice-ansi@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636"