diff --git a/.husky/pre-commit b/.husky/pre-commit index a481e02de..3c0fbeb62 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -4,4 +4,4 @@ echo "\nRunning GIT hooks..." yarn cleanbuild yarn nx affected --target=lint -yarn nx affected --target=test +# yarn nx affected --target=test diff --git a/packages/examples/omegle-push-video/.gitignore b/packages/examples/omegle-push-video/.gitignore new file mode 100644 index 000000000..dfcfdb385 --- /dev/null +++ b/packages/examples/omegle-push-video/.gitignore @@ -0,0 +1,4 @@ +/.dist + +./client/node_modules +./server/node_modules \ No newline at end of file diff --git a/packages/examples/omegle-push-video/README.md b/packages/examples/omegle-push-video/README.md new file mode 100644 index 000000000..0aa181007 --- /dev/null +++ b/packages/examples/omegle-push-video/README.md @@ -0,0 +1,65 @@ +# Omegle but Push Chat + +## Description + +This project randomly connects 2 peers online and opens up a Push chat between the peers. + +## Getting Started + +### Installation - Server + +1. Navigate to the server directory: + + ```bash + cd server + ``` + +2. Install dependencies using Yarn or npm: + + ```bash + # Using Yarn + yarn + + # Using npm + npm install + ``` + +3. Start the Server: + + ```bash + # Using Yarn + yarn start + + # Using npm + npm start + ``` + +#### The server will run on the specified port (default is 3001). + +### Installation - Client + +1. Navigate to the server directory: + + ```bash + cd client + ``` + +2. Install dependencies using Yarn or npm: + + ```bash + # Using Yarn + yarn + + # Using npm + npm install + ``` + +3. Start the Client: + + ```bash + # Using Yarn + yarn start + + # Using npm + npm start + ``` diff --git a/packages/examples/omegle-push-video/client/.env.sample b/packages/examples/omegle-push-video/client/.env.sample new file mode 100644 index 000000000..27a28c2c4 --- /dev/null +++ b/packages/examples/omegle-push-video/client/.env.sample @@ -0,0 +1 @@ +REACT_APP_SERVER_URL="" \ No newline at end of file diff --git a/packages/examples/omegle-push-video/client/.gitignore b/packages/examples/omegle-push-video/client/.gitignore new file mode 100644 index 000000000..a23084d29 --- /dev/null +++ b/packages/examples/omegle-push-video/client/.gitignore @@ -0,0 +1,25 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local +package-lock.json +yarn.lock +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.env diff --git a/packages/examples/omegle-push-video/client/config-overrides.js b/packages/examples/omegle-push-video/client/config-overrides.js new file mode 100644 index 000000000..271ee065b --- /dev/null +++ b/packages/examples/omegle-push-video/client/config-overrides.js @@ -0,0 +1,32 @@ +const webpack = require("webpack"); + +module.exports = function override(config) { + const fallback = config.resolve.fallback || {}; + Object.assign(fallback, { + crypto: require.resolve("crypto-browserify"), + stream: require.resolve("stream-browserify"), + assert: require.resolve("assert"), + http: require.resolve("stream-http"), + https: require.resolve("https-browserify"), + os: require.resolve("os-browserify"), + url: require.resolve("url"), + zlib: require.resolve("browserify-zlib"), + }); + config.resolve.fallback = fallback; + config.plugins = (config.plugins || []).concat([ + new webpack.ProvidePlugin({ + process: "process/browser", + Buffer: ["buffer", "Buffer"], + }), + ]); + config.ignoreWarnings = [/Failed to parse source map/]; + config.module.rules.push({ + test: /\.(js|mjs|jsx)$/, + enforce: "pre", + loader: require.resolve("source-map-loader"), + resolve: { + fullySpecified: false, + }, + }); + return config; +}; diff --git a/packages/examples/omegle-push-video/client/package.json b/packages/examples/omegle-push-video/client/package.json new file mode 100644 index 000000000..1a7c8e167 --- /dev/null +++ b/packages/examples/omegle-push-video/client/package.json @@ -0,0 +1,64 @@ +{ + "name": "client", + "version": "0.1.0", + "private": true, + "dependencies": { + "@pushprotocol/restapi": "1.6.2", + "@pushprotocol/socket": "^0.5.3", + "@pushprotocol/uiweb": "1.1.22", + "@rainbow-me/rainbowkit": "^1.3.1", + "@testing-library/jest-dom": "^5.14.1", + "@testing-library/react": "^12.0.0", + "@testing-library/user-event": "^13.2.1", + "@vercel/analytics": "^1.1.1", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-icons": "^5.0.1", + "react-router-dom": "^6.21.2", + "react-scripts": "5.0.0", + "socket.io-client": "^4.4.1", + "styled-components": "^6.1.1", + "viem": "^1.20.1", + "wagmi": "^1.4.12", + "web-vitals": "^2.1.0" + }, + "devDependencies": { + "assert": "^2.0.0", + "browserify-zlib": "^0.2.0", + "buffer": "^6.0.3", + "crypto-browserify": "^3.12.0", + "daisyui": "^4.6.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "process": "^0.11.10", + "react-app-rewired": "^2.2.1", + "stream-browserify": "^3.0.0", + "stream-http": "^3.2.0", + "tailwindcss": "^3.4.1", + "url": "^0.11.0" + }, + "scripts": { + "start": "react-app-rewired start", + "build": "react-app-rewired build", + "test": "react-app-rewired test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/packages/examples/omegle-push-video/client/public/index.html b/packages/examples/omegle-push-video/client/public/index.html new file mode 100644 index 000000000..2671e5520 --- /dev/null +++ b/packages/examples/omegle-push-video/client/public/index.html @@ -0,0 +1,42 @@ + + + + + + + + + + + + Bored Anons + + + +
+ + + diff --git a/packages/examples/omegle-push-video/client/public/manifest.json b/packages/examples/omegle-push-video/client/public/manifest.json new file mode 100644 index 000000000..0b253ae2c --- /dev/null +++ b/packages/examples/omegle-push-video/client/public/manifest.json @@ -0,0 +1,9 @@ +{ + "short_name": "Bored Anons", + "name": "Bored Anons", + + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/packages/examples/omegle-push-video/client/public/robots.txt b/packages/examples/omegle-push-video/client/public/robots.txt new file mode 100644 index 000000000..e9e57dc4d --- /dev/null +++ b/packages/examples/omegle-push-video/client/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/packages/examples/omegle-push-video/client/src/App.jsx b/packages/examples/omegle-push-video/client/src/App.jsx new file mode 100644 index 000000000..f7e16b5fe --- /dev/null +++ b/packages/examples/omegle-push-video/client/src/App.jsx @@ -0,0 +1,266 @@ +import React, {useEffect, useRef, useReducer} from "react"; +import io from "socket.io-client"; +import {ConnectButton} from "@rainbow-me/rainbowkit"; +import {useAccount, useWalletClient} from "wagmi"; +import {CONSTANTS, PushAPI} from "@pushprotocol/restapi"; + +import {appReducer, actionTypes} from "./reducer"; +import Modal from "./components/Modal"; +import Video from "./video"; +import Loader from "./components/Loader"; + +function App() { + const socket = useRef(null); + const userAlice = useRef(); + const {data: signer} = useWalletClient(); + const {address: walletAddress, isConnected: walletConnected} = useAccount(); + const [ + { + isPeerConnected, + peerWalletAddress, + showPeerDisconnectedModal, + showNoActivePeersModal, + peerMatched, + videoCallInitiator, + userActive, + incomingPeerRequest, + }, + dispatch, + ] = useReducer(appReducer, { + isPeerConnected: false, + peerWalletAddress: "", + showPeerDisconnectedModal: false, + showNoActivePeersModal: false, + peerMatched: false, + videoCallInitiator: "", + userActive: true, + incomingPeerRequest: false, + }); + + const connectToPeer = () => { + socket.current.emit("connect_to_peer", walletAddress); + }; + + const setupSocketListeners = () => { + socket.current.on("peer_matched", (peerAddress) => { + dispatch({type: actionTypes.SET_PEER_MATCHED, payload: true}); + checkIfChatExists(peerAddress); + dispatch({ + type: actionTypes.SET_VIDEO_CALL_INITIATOR, + payload: walletAddress, + }); + }); + socket.current.on("incoming_peer_request", () => { + dispatch({type: actionTypes.SET_INCOMING_PEER_REQUEST, payload: true}); + }); + socket.current.on("intent_accepted_by_peer", async (peerAddress) => { + dispatch({type: actionTypes.SET_IS_PEER_CONNECTED, payload: true}); + }); + socket.current.on("chat_exists_bw_users", async (peerAddress) => { + dispatch({type: actionTypes.SET_IS_PEER_CONNECTED, payload: true}); + }); + socket.current.on("no_active_peers_found", () => { + dispatch({ + type: actionTypes.SET_SHOW_NO_ACTIVE_PEERS_MODAL, + payload: true, + }); + }); + socket.current.on("peer_disconnected", () => { + dispatch({type: actionTypes.SET_IS_PEER_CONNECTED, payload: false}); + dispatch({type: actionTypes.SET_INCOMING_PEER_REQUEST, payload: false}); + dispatch({type: actionTypes.SET_PEER_WALLET_ADDRESS, payload: ""}); + dispatch({type: actionTypes.SET_PEER_MATCHED, payload: false}); + dispatch({ + type: actionTypes.SET_SHOW_PEER_DISCONNECTED_MODAL, + payload: true, + }); + }); + + socket.current.on("chat_message_request", async (peerAddress) => { + const aliceChatRequsts = await userAlice.current.chat.list("REQUESTS"); + for (const chat of aliceChatRequsts) { + if ( + chat.msg.fromDID && + chat.msg.fromDID.substring(7).toLowerCase() === + peerAddress.toLowerCase() + ) { + await userAlice.current.chat.accept(peerAddress); + break; + } + } + + socket.current.emit("intent_accepted", peerAddress); + }); + + socket.current.on("peer_disconnected_call", async (peerAddress) => { + dispatch({type: actionTypes.SET_IS_PEER_CONNECTED, payload: false}); + dispatch({type: actionTypes.SET_INCOMING_PEER_REQUEST, payload: false}); + dispatch({type: actionTypes.SET_PEER_MATCHED, payload: false}); + window.location.reload(); + }); + }; + + const checkIfChatExists = async (peerAddress) => { + if (!userAlice.current) { + window.location.reload(); + return; + } + const aliceChats = await userAlice.current.chat.list("CHATS"); + + let chatExists = false; + for (const chat of aliceChats) { + if ( + chat.msg.fromDID && + chat.msg.fromDID.substring(7).toLowerCase() === + peerAddress.toLowerCase() + ) { + chatExists = true; + + socket.current.emit("chat_exists_w_peer", peerAddress); + break; + } + } + if (!chatExists) { + const aliceChatRequsts = await userAlice.current.chat.list("REQUESTS"); + for (const chat of aliceChatRequsts) { + if (chat.did.substring(7).toLowerCase() === peerAddress.toLowerCase()) { + chatExists = true; + await userAlice.current.chat.accept(peerAddress); + socket.current.emit("intent_accepted", peerAddress); + break; + } + } + } + if (!chatExists) { + await userAlice.current.chat.send(peerAddress, { + type: "Text", + content: "Hi Peer, setting up a call! from bored-anons.xyz", + }); + socket.current.emit("chat_message_sent", peerAddress); + } + dispatch({type: actionTypes.SET_PEER_WALLET_ADDRESS, payload: peerAddress}); + }; + useEffect(() => { + socket.current = io.connect(process.env.REACT_APP_SERVER_URL); + }, []); + + useEffect(() => { + if (!signer) return; + if (isPeerConnected) return; + const initializeUserAlice = async () => { + userAlice.current = await PushAPI.initialize(signer, { + env: CONSTANTS.ENV.PROD, + }); + }; + initializeUserAlice(); + }, [signer, isPeerConnected]); + + useEffect(() => { + setupSocketListeners(); + + if (walletConnected && walletAddress) { + socket.current.emit("connect_wallet", walletAddress); + } + if (!walletAddress) { + socket.current.emit("wallet_disconnected"); + } + }, [walletAddress, walletConnected]); + + return ( +
+
+ {!isPeerConnected ? ( +
+ {peerMatched || incomingPeerRequest ? ( + + ) : ( +
+
+

Hello Anon!

+

+ ik you're bored, fret not anon, time to make some random + video calls with strangersssss. +

+
+ + {walletConnected && !isPeerConnected && ( + + )} +
+
+ { + dispatch({ + type: actionTypes.SET_USER_ACTIVE, + payload: !userActive, + }); + socket.current.emit("user_status_toggle", !userActive); + }} + /> + {userActive ? ( +

Active, Connects automatically to an incoming call

+ ) : ( +

Inactive, Does not connect to an incoming call

+ )} +
+
+
+ )} +
+ ) : ( +
+ + {/* Render the modal when the peer is disconnected */} + {showPeerDisconnectedModal && ( + { + dispatch({ + type: actionTypes.SET_SHOW_PEER_DISCONNECTED_MODAL, + payload: false, + }); + }} + /> + )} + {/* Render the modal when no active peers are found */} + {showNoActivePeersModal && ( + { + dispatch({ + type: actionTypes.SET_SHOW_NO_ACTIVE_PEERS_MODAL, + payload: false, + }); + }} + /> + )} +
+ ); +} + +export default App; diff --git a/packages/examples/omegle-push-video/client/src/components/Loader.jsx b/packages/examples/omegle-push-video/client/src/components/Loader.jsx new file mode 100644 index 000000000..4fb6f980f --- /dev/null +++ b/packages/examples/omegle-push-video/client/src/components/Loader.jsx @@ -0,0 +1,28 @@ +import React, {useState, useEffect} from "react"; + +const Loader = ({text, text2}) => { + const [currText, setCurrText] = useState(text); + + useEffect(() => { + const interval = setInterval(() => { + setCurrText((prev) => (prev === text ? text2 : text)); + }, 3000); + + return () => clearInterval(interval); + }, []); + + return ( +
+ {" "} +

{currText}...

{" "} +
+ + + + +
+
+ ); +}; + +export default Loader; diff --git a/packages/examples/omegle-push-video/client/src/components/Modal.jsx b/packages/examples/omegle-push-video/client/src/components/Modal.jsx new file mode 100644 index 000000000..b189d21ca --- /dev/null +++ b/packages/examples/omegle-push-video/client/src/components/Modal.jsx @@ -0,0 +1,21 @@ +import React from "react"; + +const Modal = ({onClose, text}) => { + return ( + +
+

Info

+

{text}

+
+
+ +
+
+
+
+ ); +}; + +export default Modal; diff --git a/packages/examples/omegle-push-video/client/src/components/VideoFrame.jsx b/packages/examples/omegle-push-video/client/src/components/VideoFrame.jsx new file mode 100644 index 000000000..be7978dd5 --- /dev/null +++ b/packages/examples/omegle-push-video/client/src/components/VideoFrame.jsx @@ -0,0 +1,62 @@ +import VideoPlayer from "./VideoPlayer"; + +import { + IoMicOffOutline, + IoMicSharp, + IoVideocamOffSharp, + IoVideocamOutline, +} from "react-icons/io5"; +import {ImPhoneHangUp} from "react-icons/im"; + +export default function VideoFrame({ + data, + onToggleMic, + onToggleCam, + onEndCall, +}) { + return ( +
+
+ + + +
+
+ + + + +
+
+ ); +} diff --git a/packages/examples/omegle-push-video/client/src/components/VideoPlayer.jsx b/packages/examples/omegle-push-video/client/src/components/VideoPlayer.jsx new file mode 100644 index 000000000..6c1150a6c --- /dev/null +++ b/packages/examples/omegle-push-video/client/src/components/VideoPlayer.jsx @@ -0,0 +1,30 @@ +import {useEffect, useRef} from "react"; +import {truncateAddress} from "../utils"; + +export default function VideoPlayer({stream, isMuted, whoIs}) { + const videoRef = useRef(null); + + useEffect(() => { + if (videoRef.current) { + videoRef.current.srcObject = stream; + videoRef.current.play().catch((error) => { + console.error("Error playing video:", error); + }); + } + }, [videoRef, stream]); + + return ( +
+

+ {whoIs === "You" ? whoIs : truncateAddress(whoIs)} +

+ +
+ ); +} diff --git a/packages/examples/omegle-push-video/client/src/index.css b/packages/examples/omegle-push-video/client/src/index.css new file mode 100644 index 000000000..b5c61c956 --- /dev/null +++ b/packages/examples/omegle-push-video/client/src/index.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/packages/examples/omegle-push-video/client/src/index.jsx b/packages/examples/omegle-push-video/client/src/index.jsx new file mode 100644 index 000000000..cf708e31f --- /dev/null +++ b/packages/examples/omegle-push-video/client/src/index.jsx @@ -0,0 +1,43 @@ +import React from "react"; +import ReactDOM from "react-dom"; +import {createBrowserRouter, RouterProvider} from "react-router-dom"; + +import "./index.css"; +import "@rainbow-me/rainbowkit/styles.css"; + +import { + darkTheme, + getDefaultWallets, + RainbowKitProvider, +} from "@rainbow-me/rainbowkit"; +import {configureChains, createConfig, WagmiConfig} from "wagmi"; +import {mainnet} from "wagmi/chains"; +import {publicProvider} from "wagmi/providers/public"; + +import App from "./App"; +import {inject} from "@vercel/analytics"; +const {chains, publicClient} = configureChains([mainnet], [publicProvider()]); +const {connectors} = getDefaultWallets({ + appName: "Bored Anons", + projectId: "c79671f77e15d3c16d8df828931df7a7", + chains, +}); +const wagmiConfig = createConfig({ + autoConnect: true, + connectors, + publicClient, +}); + +const router = createBrowserRouter([{path: "/", element: }]); + +ReactDOM.render( + + + + + + + , + document.getElementById("root") +); +inject(); diff --git a/packages/examples/omegle-push-video/client/src/reducer.js b/packages/examples/omegle-push-video/client/src/reducer.js new file mode 100644 index 000000000..b8fea761a --- /dev/null +++ b/packages/examples/omegle-push-video/client/src/reducer.js @@ -0,0 +1,37 @@ +// Action types +const actionTypes = { + SET_IS_PEER_CONNECTED: "SET_IS_PEER_CONNECTED", + SET_PEER_WALLET_ADDRESS: "SET_PEER_WALLET_ADDRESS", + SET_SHOW_PEER_DISCONNECTED_MODAL: "SET_SHOW_PEER_DISCONNECTED_MODAL", + SET_SHOW_NO_ACTIVE_PEERS_MODAL: "SET_SHOW_NO_ACTIVE_PEERS_MODAL", + SET_PEER_MATCHED: "SET_PEER_MATCHED", + SET_VIDEO_CALL_INITIATOR: "SET_VIDEO_CALL_INITIATOR", + SET_USER_ACTIVE: "SET_USER_ACTIVE", + SET_INCOMING_PEER_REQUEST: "SET_INCOMING_PEER_REQUEST", +}; + +// Reducer function +const appReducer = (state, action) => { + switch (action.type) { + case actionTypes.SET_IS_PEER_CONNECTED: + return {...state, isPeerConnected: action.payload}; + case actionTypes.SET_PEER_WALLET_ADDRESS: + return {...state, peerWalletAddress: action.payload}; + case actionTypes.SET_SHOW_PEER_DISCONNECTED_MODAL: + return {...state, showPeerDisconnectedModal: action.payload}; + case actionTypes.SET_SHOW_NO_ACTIVE_PEERS_MODAL: + return {...state, showNoActivePeersModal: action.payload}; + case actionTypes.SET_PEER_MATCHED: + return {...state, peerMatched: action.payload}; + case actionTypes.SET_VIDEO_CALL_INITIATOR: + return {...state, videoCallInitiator: action.payload}; + case actionTypes.SET_USER_ACTIVE: + return {...state, userActive: action.payload}; + case actionTypes.SET_INCOMING_PEER_REQUEST: + return {...state, incomingPeerRequest: action.payload}; + default: + return state; + } +}; + +export {actionTypes, appReducer}; diff --git a/packages/examples/omegle-push-video/client/src/utils.js b/packages/examples/omegle-push-video/client/src/utils.js new file mode 100644 index 000000000..7bd58b077 --- /dev/null +++ b/packages/examples/omegle-push-video/client/src/utils.js @@ -0,0 +1,7 @@ +export function truncateAddress(input) { + if (input.length <= 5) { + return input; + } else { + return input.substring(0, 5) + "...." + input.substring(input.length - 5); + } +} diff --git a/packages/examples/omegle-push-video/client/src/video.jsx b/packages/examples/omegle-push-video/client/src/video.jsx new file mode 100644 index 000000000..01104594e --- /dev/null +++ b/packages/examples/omegle-push-video/client/src/video.jsx @@ -0,0 +1,98 @@ +import {CONSTANTS, VideoCallStatus} from "@pushprotocol/restapi"; +import {useAccount, useWalletClient} from "wagmi"; + +import {useEffect, useRef, useState} from "react"; +import {initVideoCallData} from "@pushprotocol/restapi/src/lib/video"; + +import VideoFrame from "./components/VideoFrame"; +import Loader from "./components/Loader"; + +const Video = ({peerAddress, userAlice, initiator, onEndCall}) => { + const {data: signer} = useWalletClient(); + const {address: walletAddress} = useAccount(); + const aliceVideoCall = useRef(); + const [isPushStreamConnected, setIsPushStreamConnected] = useState(false); + const [data, setData] = useState(initVideoCallData); + const createdStream = useRef(); + const initializePushAPI = async () => { + createdStream.current = await userAlice.initStream([ + CONSTANTS.STREAM.VIDEO, + CONSTANTS.STREAM.CONNECT, + CONSTANTS.STREAM.DISCONNECT, + ]); + + createdStream.current.on(CONSTANTS.STREAM.CONNECT, () => { + setIsPushStreamConnected(true); + }); + + createdStream.current.on(CONSTANTS.STREAM.DISCONNECT, () => { + setIsPushStreamConnected(false); + }); + + createdStream.current.on(CONSTANTS.STREAM.VIDEO, async (data) => { + if (data.event === CONSTANTS.VIDEO.EVENT.REQUEST) { + await aliceVideoCall.current.approve(data?.peerInfo); + } + if (data.event === CONSTANTS.VIDEO.EVENT.DISCONNECT) { + createdStream.current.disconnect(); + } + }); + + aliceVideoCall.current = await userAlice.video.initialize(setData, { + stream: createdStream.current, + config: { + video: true, + audio: true, + }, + }); + + await createdStream.current.connect(); + + if (initiator.toLowerCase() === walletAddress.toLowerCase()) { + await aliceVideoCall.current.request([peerAddress]); + } + }; + + // Here we initialize the push video API, which is the first and important step to make video calls + useEffect(() => { + if (!signer) return; + initializePushAPI(); + }, []); + + useEffect(() => { + console.log("isPushStreamConnected", isPushStreamConnected); + }, [isPushStreamConnected]); + + return ( +
+
+ {data?.incoming[0]?.status === VideoCallStatus.CONNECTED && + data?.incoming[0].stream ? ( + { + aliceVideoCall.current?.media({audio: !data?.local.audio}); + }} + onToggleCam={() => { + aliceVideoCall.current?.media({video: !data?.local.video}); + }} + onEndCall={async () => { + await aliceVideoCall.current.disconnect( + data?.incoming[0]?.address + ); + createdStream.current.disconnect(); + onEndCall(); + window.location.reload(); + }} + /> + ) : ( +
+ +
+ )} +
+
+ ); +}; + +export default Video; diff --git a/packages/examples/omegle-push-video/client/tailwind.config.js b/packages/examples/omegle-push-video/client/tailwind.config.js new file mode 100644 index 000000000..6b3cac8ce --- /dev/null +++ b/packages/examples/omegle-push-video/client/tailwind.config.js @@ -0,0 +1,11 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ["./src/**/*.{js,jsx,ts,tsx}"], + theme: { + extend: {}, + }, + daisyui: { + themes: ["luxury"], + }, + plugins: [require("daisyui")], +}; diff --git a/packages/examples/omegle-push-video/server/.gitignore b/packages/examples/omegle-push-video/server/.gitignore new file mode 100644 index 000000000..23bd09fa5 --- /dev/null +++ b/packages/examples/omegle-push-video/server/.gitignore @@ -0,0 +1,3 @@ +/node_modules +package-lock.json +yarn.lock diff --git a/packages/examples/omegle-push-video/server/index.js b/packages/examples/omegle-push-video/server/index.js new file mode 100644 index 000000000..47f1e1fff --- /dev/null +++ b/packages/examples/omegle-push-video/server/index.js @@ -0,0 +1,198 @@ +const express = require("express"); +const http = require("http"); +const socketIO = require("socket.io"); +const cors = require("cors"); +const app = express(); +const server = http.createServer(app); +const io = socketIO(server, { + cors: { + origin: "*", + methods: ["GET", "POST"], + allowedHeaders: ["*"], + credentials: true, + }, +}); + +let users = []; + +io.on("connection", (socket) => { + console.log(`User connected with socket id: ${socket.id}`); + socket.on("connect_wallet", (walletAddress) => { + const userCaller = users.findIndex( + (user) => user.id === socket.id || user.walletAddress === walletAddress + ); + if (userCaller !== -1) { + users[userCaller].walletAddress = walletAddress; + } else { + users.push({ + walletAddress, + id: socket.id, + online: true, + busy: false, + lookingForPeers: true, + connectedPeerId: null, + }); + } + }); + socket.on("wallet_disconnected", () => { + const userIndex = users.findIndex((user) => user.id === socket.id); + if (userIndex !== -1) { + users.splice(userIndex, 1); + } + }); + socket.on("user_status_toggle", (newStatus) => { + const userIndex = users.findIndex((user) => user.id === socket.id); + if (userIndex !== -1) users[userIndex].lookingForPeers = newStatus; + }); + socket.on("connect_to_peer", (walletAddress) => { + const caller = users.find((user) => user.id === socket.id); + if (!caller || !caller.walletAddress) { + return; + } + if (caller && caller.busy) { + return; + } + + const availableUsers = users.filter( + (user) => + user.walletAddress !== walletAddress && + user.id !== caller.id && + user.walletAddress !== null && + user.online && + user.lookingForPeers && + !user.busy + ); + + if (availableUsers.length > 0) { + const chosenItem = + availableUsers[Math.floor(Math.random() * availableUsers.length)]; + const userIndexCaller = users.findIndex((user) => user.id === socket.id); + const userIndexPeer = users.findIndex( + (user) => user.walletAddress === chosenItem.walletAddress + ); + + if (userIndexCaller !== -1 && userIndexPeer !== -1) { + io.to(socket.id).emit("peer_matched", chosenItem.walletAddress); + io.to(chosenItem.id).emit("incoming_peer_request"); + users[userIndexCaller].busy = true; + users[userIndexPeer].busy = true; + users[userIndexCaller].lookingForPeers = false; + users[userIndexPeer].lookingForPeers = false; + users[userIndexCaller].connectedPeerId = chosenItem.id; + users[userIndexPeer].connectedPeerId = caller.id; + } else { + io.to(socket.id).emit("no_active_peers_found", walletAddress); + } + } else { + io.to(socket.id).emit("no_active_peers_found", walletAddress); + console.log("No valid user found."); + } + }); + socket.on("disconnect", () => { + console.log(`User disconnected with socket id: ${socket.id}`); + + const id = socket.id; + if (id) { + const userIndex = users.findIndex((user) => user.id === id); + + // Check if the user is found + if (userIndex !== -1) { + const disconnectedUser = users[userIndex]; + users.splice(userIndex, 1); + + console.log("Updated Users Array:", users); + + // Emit "peer_disconnected" only to the connected peer + const connectedPeerSocket = io.sockets.sockets.get( + disconnectedUser.connectedPeerId + ); + if (connectedPeerSocket) { + connectedPeerSocket.emit("peer_disconnected"); + } + // Update the connected peer's properties + const connectedPeerIndex = users.findIndex( + (user) => user.id === disconnectedUser.connectedPeerId + ); + if (connectedPeerIndex !== -1) { + users[connectedPeerIndex].busy = false; + users[connectedPeerIndex].lookingForPeers = true; + } + } + } + }); + + socket.on("chat_message_sent", (peerAddress) => { + const peerSocket = users.find((user) => user.walletAddress === peerAddress); + const currUserSocket = users.find((user) => user.id === socket.id); + if (peerSocket) { + io.to(peerSocket.id).emit( + "chat_message_request", + currUserSocket.walletAddress + ); + } + }); + socket.on("intent_accepted", (peerAddress) => { + const peerSocket = users.find((user) => user.walletAddress === peerAddress); + const currUserSocket = users.find((user) => user.id === socket.id); + if (peerSocket) { + io.to(peerSocket.id).emit( + "intent_accepted_by_peer", + currUserSocket.walletAddress + ); + io.to(socket.id).emit("intent_accepted_by_peer", peerAddress); + } + }); + socket.on("chat_exists_w_peer", (peerAddress) => { + const peerSocket = users.find((user) => user.walletAddress === peerAddress); + const currUserSocket = users.find((user) => user.id === socket.id); + if (peerSocket) { + io.to(peerSocket.id).emit( + "chat_exists_bw_users", + currUserSocket.walletAddress + ); + io.to(socket.id).emit("chat_exists_bw_users", peerAddress); + } + }); + + socket.on("endPeerConnection", () => { + const currUserSocket = users.find((user) => user.id === socket.id); + + if (currUserSocket.walletAddress) { + const userIndex = users.findIndex( + (user) => user.walletAddress === currUserSocket.walletAddress + ); + const userIndexPeer = users.findIndex( + (user) => user.id === users[userIndex].connectedPeerId + ); + // Instead of removing the user entry, update its properties + if (userIndex !== -1) { + users[userIndex].busy = false; + users[userIndex].lookingForPeers = true; + } + if (userIndexPeer !== -1) { + users[userIndexPeer].busy = false; + users[userIndexPeer].lookingForPeers = true; + users[userIndexPeer].connectedPeerId = null; + } + io.to(userIndexPeer.id).emit("peer_disconnected_call"); + } + }); +}); +// Function to log users every 10 seconds +function logUsers() { + console.log("-------------------Connected Users:--------------------------"); + users.forEach((user) => { + console.log( + `Wallet Address: ${user.walletAddress}, Online: ${user.online}, Busy: ${user.busy}, LookingForPeer: ${user.lookingForPeers} socket: ${user.id} Connected Peer: ${user.connectedPeerId}` + ); + }); + console.log("-------------------------------------------------------------"); +} + +// Schedule the logUsers function to run every 10 seconds +setInterval(logUsers, 10000); // 10000 milliseconds = 10 seconds + +const PORT = process.env.PORT || 3001; +server.listen(PORT, () => { + console.log(`Server is running on port ${PORT}`); +}); diff --git a/packages/examples/omegle-push-video/server/package.json b/packages/examples/omegle-push-video/server/package.json new file mode 100644 index 000000000..70b1c6b25 --- /dev/null +++ b/packages/examples/omegle-push-video/server/package.json @@ -0,0 +1,15 @@ +{ + "name": "server", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "scripts": { + "start": "nodemon index.js" + }, + "dependencies": { + "cors": "^2.8.5", + "express": "^4.17.3", + "nodemon": "^2.0.15", + "socket.io": "^4.4.1" + } +}