Building a Live Code Sharing Platform With Dyte and React

Featured on Hashnode

TL;DR

At the conclusion of this tutorial, we will have created a “Live Code Sharing Platform” that allows users to share code and engage in video and audio calls. 🎉💻🥳

Introduction

Code sharing is an essential aspect of programming. With the rise of remote work and virtual collaboration, developers need reliable tools for code sharing that offer real-time communication, video and audio conferencing, and a friendly user interface.

Codeshare.io is one such example. But today, we're going to roll up our sleeves and build our very own code sharing playground using Dyte.io.

Buckle up! 🎢

Dyte is a developer-friendly platform that offers powerful SDKs to build live experiences within our product.

In this blog, we will walk you through the process of building a code sharing platform with Dyte.io and ReactJs. Let’s start! 🏃

Step 0: Setting up Dyte account

Before anything, we would need to setup a Dyte account. For this, first visit Dyte.io and then hit Start Building. On the next page, Sign in with Google or GitHub account to get your free Dyte account 🎉. You will find your API keys under the API Keys tab on the left sidebar. Keep your API keys secure and don’t share them with anyone.

Step 1: Setting up the environment

Hitting one more checkpoint before we dive into coding.

We will be using Node.js, a popular JavaScript runtime environment, and create-react-app, a tool that generates a React project with a pre-configured setup.

To get started, we will create three folders client, server, and plugin.

Note: 🧑‍💻 If you are on Mac, you should turn off “AirPlay Receiver” in System Settings as it occupied Port 5000 by default.

Disable AirPlay Receiver

Just for reference, this is how our final folder structure would look like at the end of this blog.

Project Structure in Visual Studio Code

We will go ahead and install Dyte CLI using the command below.

$ npm install -g @dytesdk/cli

Going ahead with the authorization part and selecting the organization with the following commands.

$ dyte auth login $ dyte auth org

For more information, visit Dyte CLI Docs.

Dyte Developer Portal

Step 2: Setting up a new Custom Plugin

To start building a custom Dyte Plugin we will clone Dyte Plugin Template using the following command. The plugin template allows us to get started quicker.

$ git clone https://github.com/dyte-in/react-plugin-template.git

This template uses @dytesdk/plugin-sdk and allows us to create our own real-time plugins that work seamlessly with Dyte meetings. It has many interesting features to help us solve complex problems in minutes. Now, we will install the dependencies using the “npm install” command.

$ npm install

Next, we will add a couple of dependencies by running the following command.

$ npm i @uiw/react-codemirror @codemirror/lang-javascript uuid

Here, we are adding react-codemirror, which provides a pre-built Code Editor with language support. We are also installing UUID that will help us in generating UUIDs with just a function call. This will come in handy soon. Now that we have everything set up, we can use this command to start and test our Custom Plugin Setup.

$ npm start

Step 3: Trying out our new Custom Plugin

To try using our new custom plugin, we will have to visit http://staging.dyte.io

Here, we will be prompted to create a new meeting. It is super simple, just add your name and a meeting name and hit Create. On the next page, it will ask you to join the meeting. Click on join and you’re in.

Find the Plugins button in the bottom-right corner, and click on it to reveal all existing plugins. We are interested in a plugin named Localhost Dev, click on launch and it will reveal your plugin inside the meeting itself 🤯.

We have everything ready with us. Now, we can get started with writing some actual code!

Let’s begin with our Code Editor component.

Step 4: Creating our Code Editor

Let’s get started with creating our own code editor 🧑‍💻.

For this, we are going to first create a component and then use the CodeMirror package that we installed earlier. First, create a new React Functional Component in file named CodeEditor.js inside src/containers and paste the following code.

<CodeMirror
  style={{ fontSize: "32px", textAlign: "left" }}
  value={code}
  onChange={handleCodeChange}
  height="100vh"
  width="100vw"
  theme={"dark"}
  extensions={[javascript({ jsx: true })]}
/>;

CodeMirror component provides a pre-built Code Editor. It comes with various syntax highlighting features.

Testing Plugin from Localhost Dev

Step 5: Handling Code Changes

To work on handling the live code changes, let's first create a new state named code

import { useEffect, useState, useRef } from "react";
const [code, setCode] = useState("function add(a, b) { return a + b;}");

Now, we will create a handleCodeChange function that will emit events whenever there is a change in our code in CodeMirror using plugin.emit() function.

Here, we are emitting an object, that has two properties. The first one is a randomly generated user id and the second one is our whole code.

import { useEffect, useState, useRef } from "react";
import CodeMirror from "@uiw/react-codemirror";
import { javascript } from "@codemirror/lang-javascript";

const CodeEditor = ({ plugin }) => {
  const [code, setCode] = useState("function add(a, b) {return a + b;}");
  const [userId, setUserId] = useState();

  const handleCodeChange = async (code) => {
    plugin.emit(CODE_CHANGE, { code, user });
  };

  return (
    <>
      <CodeMirror
        style={{ fontSize: "32px", textAlign: "left" }}
        value={code}
        onChange={handleCodeChange}
        height="100vh"
        width="100vw"
        theme={"dark"}
        extensions={[javascript({ jsx: true })]}
      />
    </>
  );
};

export default CodeEditor;

Step 6: Listening to Code Change Events

We need to listen to the event when other people change the code. For this, we will use the plugin.on() function as shown below. The function accepts event name as a parameter and receives the code changes.

One more thing to note here is that we have to update our current code only if it is sent by other users. For this we need to put a simple conditional statement if(data.user != userId){}

import { useEffect, useState, useRef } from "react";
import CodeMirror from "@uiw/react-codemirror";
import { javascript } from "@codemirror/lang-javascript";
import { v4 } from "uuid";

const user = v4();

const CodeEditor = ({ plugin }) => {
  const [code, setCode] = useState("function add(a, b) {\n return a + b;\n}");
  const [userId, setUserId] = useState();

  useEffect(() => {
    if (plugin) {
      const startListening = async () => {
        plugin.on(CODE_CHANGE, (data) => {
          if (data.user != user) {
            setCode(data.code);
          }
        });
      };
      startListening();
    }
  }, [plugin]);

  const handleCodeChange = async (code) => {
    plugin.emit(CODE_CHANGE, { code, user });
  };

  return (
    <>
      {" "}
      <CodeMirror
        style={{ fontSize: "32px", textAlign: "left" }}
        value={code}
        onChange={handleCodeChange}
        height="100vh"
        width="100vw"
        theme={"dark"}
        extensions={[javascript({ jsx: true })]}
      />{" "}
    </>
  );
};

export default CodeEditor;

In this component, we are creating a Code Editor using CodeMirror. Any changes to the editor emit an event CODE_CHANGE to all users in the meeting, using plugin.emit() function call. emit function takes eventName and data as arguments.

In the next step, we need to import the CodeEditor component to Main.tsx file. Your file should look something like this. 👇

import { useDytePlugin } from "../context";
import CodeEditor from "./CodeEditor";

const Main = () => {
  const plugin = useDytePlugin();

  return <div style={{ height: "100%" }} />;
};

export default Main;

Code for our “Collaborative Code Editor Plugin” 😉 is now ready. How did someone write the first code editor without a code editor 😂? Jokes aside, we are ready with our Plugin 🎉.

To checkout, open up staging.dyte.io and follow along. Enter your name and meeting title to get in. Hit join meeting. Open up the Localhost Dev plugin and you are good to go.

Plugin in Action

Step 7: Publishing our Component

🧑‍💻 Now, it’s time to publish our content, this is a simple process with Dyte CLI. For this we have to first build our plugin and then run dyte plugins publish command.

$ dyte plugins create 
$ npm run build 
$ cp dyte-config.json ./build/dyte-config.json 
$ cd build $ dyte plugins publish

Publishing Plugin

Step 8: Getting started with our Code Sharing Platform

Now that we have built the plugin which will help us collaborate on code, we can get started with building the platform to use this plugin on.

Let's start with the client side. Inside the client folder, we will set up a new ReactJS project using create-react-app and create our react app using the following command.

$ npx create-react-app .

Next, let us install the dependencies of Dyte and code-editor by running the following command:

$ npm i @dytesdk/react-ui-kit @dytesdk/react-web-core react-simple-code-editor

🎬 Now, let’s start our development server with npm start:

$ npm start

Step 9: Building the Layout

Let us open app.js file inside the src folder. We will remove the contents of this file and add the following code snippet 👇.

import Layout from "./components/Layout";

function App() {}

export default App;

Next, we will write the Layout component, we will be creating a layout with our logo, title and meeting UI.

We will use several libraries, including DyteMeeting and PrismJS, for building a collaborative code editor and meeting UI.

import Meet from "./Meeting"


const Layout = () => {
  return (
        <>
      <div style={{ padding: "30px", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
        <img src="https://dyte.io/blog/content/images/2021/09/Dyte-Logo.svg" height={"70px"}/>
        <span style={{ fontSize: "30px", color: "#3e75fd" }}>Collaborative Code Editor</span>
        <img style={{ opacity: "0"}} src="https://dyte.io/blog/content/images/2021/09/Dyte-Logo.svg" height={"80px"}/>
      </div>
      <div style={{ height: "88vh" }} ><Meet /></div>
    </>
  )
}

export default Layout

Step 10: The Meeting Component

🧑‍💻 First, we need to create a few utility functions in a file client/src/utils/api.js

const createMeeting = async () => {
    const resp = await fetch("http://localhost:3000/meetings", {
        method: "POST",
        body: JSON.stringify({ title: "New Code pair" }),
        headers: { "Content-Type": "application/json" }
    })
    const data = await resp.json()
    console.log(data)
    return data.data.id;
}

const joinMeeting = async (id) => {
    const resp = await fetch(`http://localhost:3000/meetings/${id}/participants`, {
        method: "POST",
        body: JSON.stringify({ name: "new user", preset_name: "group_call_host" }),
        headers: { "Content-Type": "application/json" }
    })
    const data = await resp.json()
    console.log(data)
    return data.data.token;
}

export { createMeeting, joinMeeting }

These functions talk to our backend to create meetings and add participants. For meeting creation, we pass title as an optional parameter.

And for adding participants, we pass name parameter (optional), picture parameter (optional), and preset_name parameter (required) along with meetingId.

Time for our Meeting component. For this, we will use Dyte UI kit ✨ which makes it super easy to integrate live Audio/Video sharing in your application. Yes, these 10-15 lines of code do all the heavy lifting 🏋🏼‍♂️.

import { useState, useEffect, useRef } from "react";
import { DyteMeeting, provideDyteDesignSystem } from "@dytesdk/react-ui-kit";
import { useDyteClient } from "@dytesdk/react-web-core";
import { createMeeting, joinMeeting } from "../utils/api";

const Meet = () => {
    const meetingEl = useRef();
    const [meeting, initMeeting] = useDyteClient();
    const [userToken, setUserToken] = useState();
    const [meetingId, setMeetingId] = useState();

    const createMeetingId = async () => {
        const newMeetingId = await createMeeting();
        setMeetingId(newMeetingId);
    };

    useEffect(() => {
        const id = window.location.pathname.split("/")[2];
        if (!id) {
            createMeetingId();
        } else {
            setMeetingId(id);
        }
    }, []);

    const joinMeetingId = async () => {
        if (meetingId) {
            const authToken = await joinMeeting(meetingId);
            await initMeeting({
                authToken,
                modules: {
                    plugin: true,
                    devTools: {
                        logs: true,
                        plugins: [
                            {
                                name: "Collaborative-code-editor",
                                port: "5000",
                                id: "<your-plugin-id>",
                            },
                        ],
                    },
                },
            });
            setUserToken(authToken);
        }
    };

    useEffect(() => {
        if (meetingId && !userToken) joinMeetingId();
    }, [meetingId]);

    useEffect(() => {
        if (userToken) {
            provideDyteDesignSystem(meetingEl.current, {
                theme: "dark",
            });
        }
    }, [userToken]);

    return (
        <>
            {userToken && meetingId ? (
                <DyteMeeting mode="fill" meeting={meeting} ref={meetingEl} />
            ) : (
                <div>Loading...</div>
            )}
        </>
    );
};

export default Meet;

We are ready with our Code Sharing Platform's UI now 🎉

Step 11: Getting the Backend Ready

🧑‍💻 Dyte provides a variety of powerful APIs that enhance the developer experience and meet a wide range of developer requirements.

We can manage Dyte’s organizations, sessions, meetings, recordings, webhooks, live streaming, analytics, and much more.

To simplify the process, we will use Express with Node to create a backend which will help with authentication, meeting creation, and adding participants. ✨

To get started in the project folder, follow the following steps:

$ mkdir server && cd server

We’ll start with installing a couple of dependencies, cd into the 'server' directory, and use the following command.

$ npm init -y 
$ npm install express cors axios dotenv 
$ npm install -g nodemon

First, let's create a .env file to store our API key in server/src . You can find these keys on Dyte Dashboard.

DYTE_ORG_ID= 
DYTE_API_KEY=

Let’s also add a few scripts ✍️ that will help us run our server application. Add the below lines in your package.json file inside scripts tag.

"start": "node dist/index.js", 
"dev": "nodemon src/index.js"

Let's create our files and folders now. All our code will live inside server/src folder. Inside src create another folder utils.

Initialize a file index.js inside src and dyte-api.js inside utils. Now let’s add our .env file in src, which will hold our API secrets.

Open up src/.env file and add the following lines to it. Replace the placeholder values with the API secrets from the Dyte Dashboard.

DYTE_ORG_ID=<YOUR-DYTE-ORG-ID>
DYTE_API_KEY=<YOUR-DYTE-API-KEY>

We can start writing code now. Let’s start with creating axios config for accessing Dyte APIs. Open up utils/dyte-api.js and put in the following code.

This code will help to communicate with Dyte APIs and authentication.

const axios = require('axios');

require('dotenv').config();

const DYTE_API_KEY = process.env.DYTE_API_KEY;
const DYTE_ORG_ID = process.env.DYTE_ORG_ID;

const API_HASH = Buffer.from(
  `${DYTE_ORG_ID}:${DYTE_API_KEY}`,
  'utf-8'
).toString('base64');

const DyteAPI = axios.create({
  baseURL: 'https://api.cluster.dyte.in/v2',
  headers: {
    Authorization: `Basic ${API_HASH}`,
  },
});

module.exports = DyteAPI;

Next, we will write the routes.

Our front end will communicate on these routes to create meetings and add participants to meetings.

Let's open up index.js and add the following code snippet.👇

const express = require('express');
const cors = require('cors');
const DyteAPI = require('./utils/dyte-api')


const PORT = process.env.PORT || 3000;
const app = express();

app.use(cors("http://localhost:3001"));
app.use(express.json());


app.post('/meetings', async (req, res) => {
  const { title } = req.body
  const response = await DyteAPI.post('/meetings', {
    title,
  });
  return res.status(response.status).json(response.data);
});

app.post('/meetings/:meetingId/participants', async (req, res) => {
  const meetingId = req.params.meetingId
  const { name, picture, preset_name } = req.body
  const client_specific_id = `react-samples::${name.replaceAll(' ', '-')}-${Math.random().toString(36).substring(2, 7)}`;

    const response = await DyteAPI.post(`/meetings/${meetingId}/participants`, {
      name,
      picture,
      preset_name,
      client_specific_id,
    });

    return res.status(response.status).json(response.data);
});


app.listen(PORT, () => {
  console.log(`Started listening on ${PORT}...`)
});

Ta-da! 🎩✨ We did it!

Now, we'll finally try out our code sharing platform to collaborate while coding with our friends and teammates.

With our shiny new Code editor and Dyte meeting all set up, we’ll finally try out our platform!

To run the whole application locally:

Inside client type PORT=3001 npm start

Inside plugin type npm start

Inside server type PORT=3000 npm run dev

And there you have it, in-app video conferencing and collaboration with our own “Code Collaboration Plugin”.

🧑‍💻 You can try out the code sharing platform here.

Live Plugin Demo

Conclusion

🎉 Woohoo! You've made it to the end, my friend! I hope you've learned a thing or two today and had a good time following along the way.

Together, we've built up a snazzy live pair programming and code sharing platform, complete with in-app meetings, all with just a pinch of React and a dollop of Dyte. Talk about a recipe for success!

We’ve got your back with our plugin templates and powerful SDKs, making it a breeze to jump right in and build your own collaborative masterpieces, just like the one we cooked up together today.

So what are you waiting for? Head on over to Dyte.io and let your creative juices flow! Start building your own collaborative applications and you might just create the next big thing! 🚀

Every developer's best companion — a coding cat.