From c97e57be3cdd653a9e88a61aae40ad176acb2962 Mon Sep 17 00:00:00 2001 From: abhaydixit07 Date: Sat, 2 Nov 2024 23:14:08 +0530 Subject: [PATCH 1/4] Responsive --- src/app/editor/CodeEditor.tsx | 10 ++++++++-- src/app/editor/page.tsx | 31 +++++++++++++++---------------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/app/editor/CodeEditor.tsx b/src/app/editor/CodeEditor.tsx index 779fa7e..f331cf8 100644 --- a/src/app/editor/CodeEditor.tsx +++ b/src/app/editor/CodeEditor.tsx @@ -66,7 +66,7 @@ const CodeEditor: React.FC = ({ return (
= ({ .remote-cursor { border-left: 2px solid red; } + .code-editor-container { + flex: 1; + display: flex; + flex-direction: column; + width: 100%; + } `}
); }; -export default CodeEditor; +export default CodeEditor; \ No newline at end of file diff --git a/src/app/editor/page.tsx b/src/app/editor/page.tsx index 546d27a..4081147 100644 --- a/src/app/editor/page.tsx +++ b/src/app/editor/page.tsx @@ -138,51 +138,50 @@ const Landing: React.FC = (props) => { }; return ( -
-
+
+
setRoomId(e.target.value)} - className="border text-black rounded-md p-2" + className="border text-black rounded-md p-2 w-full md:w-auto" />
-
-
+
+
-
+
-
-
+
+
setFontSize(Number(e.target.value))} - className="border border-black mr-1 rounded px-2 custom-input" + className="border border-black mr-1 rounded px-2 custom-input w-full md:w-auto" min="10" max="40" style={{ color: "#000", fontSize: "0.8rem", lineHeight: "1.75rem", - width: "100%", background: "#fff", }} /> @@ -190,7 +189,7 @@ const Landing: React.FC = (props) => {
-
+
= (props) => { remoteCursorPosition={remoteCursorPosition} />
-
+
-
-
-
- -
-
- -
-
-
- - setFontSize(Number(e.target.value))} - className="border border-black mr-1 rounded px-2 custom-input w-full md:w-auto" - min="10" - max="40" - style={{ - color: "#000", - fontSize: "0.8rem", - lineHeight: "1.75rem", - background: "#fff", - }} - /> -
-
+
+ +
-
-
- -
-
-
- -
- -
- -
-
+
+ +
+
); }; -export default Landing; \ No newline at end of file +export default Landing; From a10732b4595bea98249375822eb3e4a6a3f07a6d Mon Sep 17 00:00:00 2001 From: abhaydixit07 Date: Tue, 5 Nov 2024 23:58:54 +0530 Subject: [PATCH 3/4] VIdeoCall Component --- src/app/editor/VideoCall.tsx | 201 ++++++++++++++++++++++++++++++++ src/app/editor/page.tsx | 214 +++++++++++++++-------------------- src/app/editor/video.css | 12 ++ 3 files changed, 303 insertions(+), 124 deletions(-) create mode 100644 src/app/editor/VideoCall.tsx create mode 100644 src/app/editor/video.css diff --git a/src/app/editor/VideoCall.tsx b/src/app/editor/VideoCall.tsx new file mode 100644 index 0000000..65aeb62 --- /dev/null +++ b/src/app/editor/VideoCall.tsx @@ -0,0 +1,201 @@ +"use client"; +import React, { useEffect, useRef, useState } from "react"; +import { io } from "socket.io-client"; + +const socket = io("http://localhost:5000"); + +const VideoCall = ({ roomId }) => { + const localVideoRef = useRef(null); + const remoteVideoRef = useRef(null); + const peerRef = useRef(null); + const [isInitiator, setIsInitiator] = useState(false); + const [connectionStatus, setConnectionStatus] = useState("Initializing..."); + + useEffect(() => { + const constraints = { video: true, audio: true }; + let localStream; + + const createPeerConnection = () => { + console.log("Creating peer connection..."); + const configuration = { + iceServers: [ + { urls: "stun:stun.l.google.com:19302" }, + { urls: "stun:stun1.l.google.com:19302" }, + ], + }; + + const peer = new RTCPeerConnection(configuration); + + peer.onconnectionstatechange = () => { + console.log("Connection state:", peer.connectionState); + setConnectionStatus(`Connection: ${peer.connectionState}`); + }; + + peer.oniceconnectionstatechange = () => { + console.log("ICE connection state:", peer.iceConnectionState); + }; + + localStream.getTracks().forEach((track) => { + peer.addTrack(track, localStream); + console.log("Added local track:", track.kind); + }); + + peer.onicecandidate = (event) => { + if (event.candidate) { + console.log("Sending ICE candidate"); + socket.emit("ice_candidate", { + roomId, + candidate: event.candidate, + }); + } + }; + + peer.ontrack = (event) => { + console.log("Received remote track:", event.track.kind); + if (remoteVideoRef.current && !remoteVideoRef.current.srcObject) { + remoteVideoRef.current.srcObject = event.streams[0]; + setConnectionStatus("Connected to remote peer"); + } + }; + + return peer; + }; + + const initializeLocalStream = async () => { + try { + console.log("Requesting media permissions..."); + localStream = await navigator.mediaDevices.getUserMedia(constraints); + console.log("Got local stream"); + + if (localVideoRef.current) { + localVideoRef.current.srcObject = localStream; + console.log("Set local video source"); + } + + console.log("Joining room:", roomId); + socket.emit("join_room", roomId); + + socket.on("room_status", ({ isFirst }) => { + console.log("Room status - isFirst:", isFirst); + setIsInitiator(isFirst); + setConnectionStatus(isFirst ? "Waiting for peer..." : "Connecting to peer..."); + peerRef.current = createPeerConnection(); + + if (isFirst) { + createAndSendOffer(); + } + }); + + socket.on("offer", async ({ offer }) => { + console.log("Received offer"); + if (!peerRef.current) { + peerRef.current = createPeerConnection(); + } + try { + await peerRef.current.setRemoteDescription(new RTCSessionDescription(offer)); + console.log("Set remote description (offer)"); + + const answer = await peerRef.current.createAnswer(); + await peerRef.current.setLocalDescription(answer); + console.log("Created and set local description (answer)"); + + socket.emit("answer", { roomId, answer }); + } catch (error) { + console.error("Error handling offer:", error); + setConnectionStatus("Error handling offer"); + } + }); + + socket.on("answer", async ({ answer }) => { + console.log("Received answer"); + try { + await peerRef.current.setRemoteDescription(new RTCSessionDescription(answer)); + console.log("Set remote description (answer)"); + } catch (error) { + console.error("Error handling answer:", error); + setConnectionStatus("Error handling answer"); + } + }); + + socket.on("ice_candidate", async ({ candidate }) => { + console.log("Received ICE candidate"); + if (peerRef.current) { + try { + await peerRef.current.addIceCandidate(new RTCIceCandidate(candidate)); + console.log("Added ICE candidate"); + } catch (error) { + console.error("Error adding ICE candidate:", error); + } + } + }); + + } catch (error) { + console.error("Error accessing media devices:", error); + setConnectionStatus(`Error: ${error.message}`); + } + }; + + const createAndSendOffer = async () => { + try { + console.log("Creating offer"); + const offer = await peerRef.current.createOffer(); + await peerRef.current.setLocalDescription(offer); + console.log("Created and set local description (offer)"); + socket.emit("offer", { roomId, offer }); + } catch (error) { + console.error("Error creating offer:", error); + setConnectionStatus("Error creating offer"); + } + }; + + initializeLocalStream(); + + return () => { + if (localStream) { + localStream.getTracks().forEach((track) => track.stop()); + } + if (peerRef.current) { + peerRef.current.close(); + } + socket.off("room_status"); + socket.off("offer"); + socket.off("answer"); + socket.off("ice_candidate"); + }; + }, [roomId]); + + return ( +
+
+ Status: {connectionStatus} +
+
+
+
+
+
+
+
+ ); +}; + +export default VideoCall; diff --git a/src/app/editor/page.tsx b/src/app/editor/page.tsx index 4d1e891..471d63f 100644 --- a/src/app/editor/page.tsx +++ b/src/app/editor/page.tsx @@ -1,5 +1,5 @@ "use client"; -import React, { useEffect, useState, useRef } from "react"; +import React, { useEffect, useState } from "react"; import CodeEditor from "./CodeEditor"; import { languageOptions } from "@/constants/languageOptions"; import { defineTheme } from "@/lib/defineTheme"; @@ -7,9 +7,9 @@ import LanguageDropdown from "./LanguageDropdown"; import ThemeDropdown from "./ThemeDropdown"; import OutputWindow from "./OutputWindow"; import CustomInput from "./CustomInput"; +import VideoCall from "./VideoCall"; // Import the VideoCall component import axios from "axios"; import io from "socket.io-client"; -import SimplePeer from "simple-peer"; import { v4 as uuidv4 } from "uuid"; const socket = io("http://localhost:5000"); // Replace with your server URL @@ -44,120 +44,23 @@ const Landing: React.FC = (props) => { const [fontSize, setFontSize] = useState(18); const [isLoading, setIsLoading] = useState(false); const [remoteCursorPosition, setRemoteCursorPosition] = useState<{ lineNumber: number; column: number } | null>(null); - const [peers, setPeers] = useState([]); - const videoRef = useRef(null); - const userVideoRef = useRef(null); - const peersRef = useRef([]); useEffect(() => { defineTheme("oceanic-next").then(() => { setTheme({ value: "oceanic-next", label: "Oceanic Next" }); }); + // Listen for incoming code and cursor changes socket.on("receive_code_change", ({ code, cursorPosition }) => { setCode(code); - setRemoteCursorPosition(cursorPosition); + setRemoteCursorPosition(cursorPosition); // Update remote cursor position }); - socket.on("initial_data", ({ code, users }) => { - setCode(code); - setRemoteCursorPosition(null); - }); - - // Handle video signaling - socket.on("video_signal", handleSignal); - return () => { socket.off("receive_code_change"); - socket.off("initial_data"); - socket.off("video_signal"); }; }, []); - useEffect(() => { - navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then((stream) => { - if (userVideoRef.current) { - userVideoRef.current.srcObject = stream; - } - - socket.on("update_users", (users) => { - const newPeers = []; - users.forEach((userId: string) => { - if (userId !== socket.id) { - const peer = createPeer(userId, socket.id, stream); - peersRef.current.push({ peerID: userId, peer }); - newPeers.push(peer); - } - }); - setPeers(newPeers); - }); - }); - }, [roomId]); - - const createPeer = (userToSignal: string, callerID: string, stream: MediaStream) => { - const peer = new SimplePeer({ - initiator: true, - trickle: false, - stream, - }); - - peer.on("signal", (signal) => { - socket.emit("video_signal", { signal, userId: userToSignal, from: callerID }); - }); - - peer.on("stream", (remoteStream) => { - addVideoStream(remoteStream); - }); - - return peer; - }; - - const addPeer = (incomingSignal: any, callerID: string, stream: MediaStream) => { - const peer = new SimplePeer({ - initiator: false, - trickle: false, - stream, - }); - - peer.on("signal", (signal) => { - socket.emit("video_signal", { signal, userId: callerID, from: socket.id }); - }); - - peer.on("stream", (remoteStream) => { - addVideoStream(remoteStream); - }); - - peer.signal(incomingSignal); - - return peer; - }; - - const addVideoStream = (stream: MediaStream) => { - const videoElement = document.createElement("video"); - videoElement.srcObject = stream; - videoElement.autoplay = true; - videoElement.playsInline = true; - videoElement.classList.add("remote-video"); - - videoElement.onloadedmetadata = () => { - videoElement.play(); - }; - - videoRef.current?.appendChild(videoElement); - }; - - const handleSignal = ({ signal, userId, from }: any) => { - const peerObj = peersRef.current.find((p) => p.peerID === from); - - if (peerObj) { - peerObj.peer.signal(signal); - } else { - const peer = addPeer(signal, from, userVideoRef.current?.srcObject as MediaStream); - peersRef.current.push({ peerID: from, peer }); - setPeers((prevPeers) => [...prevPeers, peer]); - } - }; - const handleThemeChange = (th: any) => { if (["light", "vs-dark"].includes(th.value)) { setTheme(th); @@ -177,7 +80,14 @@ const Landing: React.FC = (props) => { socket.emit("code_change", { roomId, code: newCode, - cursorPosition: remoteCursorPosition, + cursorPosition: remoteCursorPosition, // Send cursor position along with code changes + }); + }; + + const onCursorPositionChange = (position: any) => { + socket.emit("cursor_position_change", { + roomId, + cursorPosition: position, }); }; @@ -185,16 +95,20 @@ const Landing: React.FC = (props) => { const newRoomId = uuidv4(); setRoomId(newRoomId); socket.emit("join_room", newRoomId); + console.log(`Created and joined room: ${newRoomId}`); }; const joinRoom = () => { if (roomId) { socket.emit("join_room", roomId); + console.log(`Joined room: ${roomId}`); } }; const executeCode = async () => { setIsLoading(true); + const startTime = Date.now(); + try { const response = await axios.post("https://emkc.org/api/v2/piston/execute", { language: language.value, @@ -203,12 +117,19 @@ const Landing: React.FC = (props) => { stdin: customInput, }); + const endTime = Date.now(); + const compilationTime = ((endTime - startTime) / 1000).toFixed(2); + const runData = response.data.run; - setOutputDetails({ + const outputData = { stdout: runData.stdout, stderr: runData.stderr, status: runData.stderr ? "Error" : "Compilation Successful", - }); + time: compilationTime, + memory: "N/A", + }; + + setOutputDetails(outputData); } catch (error) { console.error("Error executing code:", error); setOutputDetails({ stdout: "", stderr: "Execution failed", status: "Failed" }); @@ -219,11 +140,6 @@ const Landing: React.FC = (props) => { return (
-
-
-
= (props) => { onChange={(e) => setRoomId(e.target.value)} className="border text-black rounded-md p-2 w-full md:w-auto" /> - -
+
+
+ +
+
+ +
+
+
+ + setFontSize(Number(e.target.value))} + className="border border-black mr-1 rounded px-2 custom-input w-full md:w-auto" + min="10" + max="40" + style={{ + color: "#000", + fontSize: "0.8rem", + lineHeight: "1.75rem", + background: "#fff", + }} + /> +
+
+
+
- - +
+ +
+
+
+ +
+ +
+ +
+
-
- - + {/* Video Call Section */} +
+
-
); }; diff --git a/src/app/editor/video.css b/src/app/editor/video.css new file mode 100644 index 0000000..011c24c --- /dev/null +++ b/src/app/editor/video.css @@ -0,0 +1,12 @@ +.video-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 10px; + } + + .controls { + display: flex; + justify-content: center; + margin-top: 10px; + } + \ No newline at end of file From 4abfe6c2567223fc4da806a4e947de49ca96a41c Mon Sep 17 00:00:00 2001 From: abhaydixit07 Date: Tue, 5 Nov 2024 23:59:58 +0530 Subject: [PATCH 4/4] VIdeoCall Component --- package-lock.json | 165 ++++++++++++++++++++++++++++++++++++++++++++-- package.json | 2 + 2 files changed, 162 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index e4aecfa..fce6e31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "react": "19.0.0-rc-69d4b800-20241021", "react-dom": "19.0.0-rc-69d4b800-20241021", "react-select": "^5.8.2", + "simple-peer": "^9.11.1", "socket.io-client": "^4.8.1", "uuid": "^11.0.2" }, @@ -22,6 +23,7 @@ "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "@types/simple-peer": "^9.11.8", "eslint": "^8", "eslint-config-next": "15.0.1", "postcss": "^8", @@ -1091,6 +1093,15 @@ "@types/react": "*" } }, + "node_modules/@types/simple-peer": { + "version": "9.11.8", + "resolved": "https://registry.npmjs.org/@types/simple-peer/-/simple-peer-9.11.8.tgz", + "integrity": "sha512-rvqefdp2rvIA6wiomMgKWd2UZNPe6LM2EV5AuY3CPQJF+8TbdrL5TjYdMf0VAjGczzlkH4l1NjDkihwbj3Xodw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.12.0.tgz", @@ -1656,6 +1667,25 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1690,6 +1720,29 @@ "node": ">=8" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -2147,6 +2200,11 @@ "node": ">=10.13.0" } }, + "node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==" + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -3019,6 +3077,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-browser-rtc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz", + "integrity": "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ==" + }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -3243,6 +3306,25 @@ "react-is": "^16.7.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3290,8 +3372,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/internal-slot": { "version": "1.0.7", @@ -4632,7 +4713,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -4648,6 +4728,14 @@ } ] }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/react": { "version": "19.0.0-rc-69d4b800-20241021", "resolved": "https://registry.npmjs.org/react/-/react-19.0.0-rc-69d4b800-20241021.tgz", @@ -4760,6 +4848,19 @@ "pify": "^2.3.0" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -4916,6 +5017,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/safe-regex-test": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", @@ -5072,6 +5192,34 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-peer": { + "version": "9.11.1", + "resolved": "https://registry.npmjs.org/simple-peer/-/simple-peer-9.11.1.tgz", + "integrity": "sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "buffer": "^6.0.3", + "debug": "^4.3.2", + "err-code": "^3.0.1", + "get-browser-rtc": "^1.1.0", + "queue-microtask": "^1.2.3", + "randombytes": "^2.1.0", + "readable-stream": "^3.6.0" + } + }, "node_modules/simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", @@ -5136,6 +5284,14 @@ "node": ">=10.0.0" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -5725,8 +5881,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/uuid": { "version": "11.0.2", diff --git a/package.json b/package.json index 329e73e..71e67ad 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "react": "19.0.0-rc-69d4b800-20241021", "react-dom": "19.0.0-rc-69d4b800-20241021", "react-select": "^5.8.2", + "simple-peer": "^9.11.1", "socket.io-client": "^4.8.1", "uuid": "^11.0.2" }, @@ -23,6 +24,7 @@ "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "@types/simple-peer": "^9.11.8", "eslint": "^8", "eslint-config-next": "15.0.1", "postcss": "^8",