Search code examples
node.jsreactjsamazon-web-servicescanvassocket.io

Realtime collaborative drawing app with Node js, React js, and Socket.io not broadcasting on aws


I have made a realtime collaborative drawing web app using node js, react js and socket.io. It works fine locally. When I open multiple tabs of localhost url and draw in one of the canvas, it shows in the other canvas a few seconds later. However, i've deployed it on AWS and it is not doing this behavior. I try to draw on the canvas but I wait for a long time and it doesn't show up in the other canvas.

My Server :

const express = require('express')
const path = require('path')
const app = express()
app.use(express.static(path.join(__dirname + '/public')))

const PORT = process.env.PORT || 3000

let server = app.listen(PORT);

const io = require('socket.io')(server);
var savedData;
io.on('connection', (socket)=>{
    console.log('a user connected');

    socket.on('canvas-data', (data) => {
        savedData=data
        socket.broadcast.emit('canvas-data', data);

        console.log("canvas-data saved and emitted");
    });

    socket.on('saved-canvas-data', (data)=>{
        io.to(socket.id).emit("saved-canvas-data", savedData);
    });
})

My Client :

import React from 'react';
import io from 'socket.io-client';

import './style.css';

class Board extends React.Component {

    timeout;
    socket = io.connect("http://192.168.1.72:3000",{'force new connection': true});
    isDrawing = false;
    strokeStyle = "black";
    
    constructor(props){
        super(props);
        this.socket.emit('saved-canvas-data', null);
        this.socket.on('saved-canvas-data', function(data){
            var canvas = document.querySelector('#board');
            var ctx = canvas.getContext('2d');
            var image = new Image();
            image.onload = function(){
                ctx.drawImage(image, 0, 0);
            };
            image.src = data;
        });
        this.socket.on("canvas-data", function(data){
            var root = this;
            var interval = setInterval(function(){
                if(root.isDrawing) return;
                root.isDrawing = true;
                clearInterval(interval);
                var canvas = document.querySelector('#board');
                var ctx = canvas.getContext('2d');
                var image = new Image();
                image.onload = function(){
                    ctx.drawImage(image, 0, 0);
                    root.isDrawing = false;
                };
                image.src = data;
            });
        })
    }
    
    componentDidMount(){
        this.drawOnCanvas();
    }
    
    drawOnCanvas(){
        document.getElementById("black").style.border = "10px solid #ffbf00";
        var canvas = document.querySelector('#board');
        var ctx = canvas.getContext('2d');
    
        var sketch = document.querySelector('#sketch');
        var sketch_style = getComputedStyle(sketch);
        canvas.width = parseInt(sketch_style.getPropertyValue('width'));
        canvas.height = parseInt(sketch_style.getPropertyValue('height'));
    
        var mouse = {x: 0, y: 0};
        var last_mouse = {x: 0, y: 0};
    
        /* Mouse Capturing Work */
        canvas.addEventListener('mousemove', function(e) {
            last_mouse.x = mouse.x;
            last_mouse.y = mouse.y;
    
            mouse.x = e.pageX;
            mouse.y = e.pageY;
        }, false);
    
        /* Drawing on Paint App */
        ctx.lineWidth = 2;
        ctx.lineJoin = 'round';
        ctx.lineCap = 'round';
        ctx.strokeStyle = this.strokeStyle;
    
        canvas.addEventListener("mouseleave", function(event){
            var mouseEvent = new MouseEvent("mouseup", {});
                event.preventDefault();
                canvas.dispatchEvent(mouseEvent);
        });
    
        canvas.addEventListener('mousedown', function(e) {
            canvas.addEventListener('mousemove', onPaint, false);
        }, false);
    
        canvas.addEventListener('mouseup', function() {
            canvas.removeEventListener('mousemove', onPaint, false);
        }, false);
    
        var lastTouchY;
    
        canvas.addEventListener("touchstart", function (e) {
            mouse = {x: e.touches[0].pageX ,y: e.touches[0].pageY };
            var touch = e.touches[0];
            if (e.touches.length < 2){
                e.preventDefault();
            }
            var mouseEvent = new MouseEvent("mousedown", {
                clientX: touch.pageX,
                clientY: touch.pageY
            });
            canvas.dispatchEvent(mouseEvent);
        }, false);
        canvas.addEventListener("touchend", function (e) {
            var mouseEvent = new MouseEvent("mouseup", {});
            if (e.touches.length < 2){
                e.preventDefault();
            }
            canvas.dispatchEvent(mouseEvent);
        }, false);
        canvas.addEventListener("touchmove", function (e) {
            var touch = e.touches[0];
            if (e.touches.length < 2){
                e.preventDefault();
            }
            var mouseEvent = new MouseEvent("mousemove", {
                clientX: touch.pageX,
                clientY: touch.pageY
            });
            canvas.dispatchEvent(mouseEvent);
        }, false);
    
        var root = this;
        var onPaint = function() {
            ctx.beginPath();
            var rect = canvas.getBoundingClientRect();
            // var scrollY = document.body.scrollTop || document.documentElement.scrollTop;
            // var scrollX = document.body.scrollLeft || document.documentElement.scrollLeft;
            var scrollX = window.scrollX;
            var scrollY = window.scrollY;
            ctx.moveTo(last_mouse.x - rect.left - scrollX, last_mouse.y - rect.top - scrollY);
            ctx.lineTo(mouse.x - rect.left - scrollX, mouse.y - rect.top - scrollY);
            ctx.closePath();
            ctx.stroke();
    
            if(root.timeout != undefined) {
                clearTimeout(root.timeout);
            }
            root.timeout = setTimeout(function(){
                var base64ImageData = canvas.toDataURL("image/png");
                root.socket.emit("canvas-data", base64ImageData);
            }, 1000)
        };
    }
    
    changeColor(string){
        var canvas = document.querySelector('#board');
        var ctx = canvas.getContext('2d');
        ctx.strokeStyle = string;
        if (string == "white") {
            document.getElementById("white").style.border = "10px solid #ffbf00";
            document.getElementById("black").style.border = "2px solid black";
        }
         else {
            document.getElementById("black").style.border = "10px solid #ffbf00";
            document.getElementById("white").style.border = "2px solid black";
         }
    }
    
    render(){
        return(
            <div className="sketch" id="sketch">
                <div className="heading" id="heading">
                    <h1>The Board</h1>
                    <p><i>Anyone can draw</i></p>
                </div>
                <div className="colors">
                    <div onClick={() => this.changeColor("black")} className="black" id="black"></div>
                    <div onClick={() => this.changeColor("white")} className="white" id="white"></div>
                </div>
                <canvas className="board" id="board"></canvas>
            </div>
        )
    }

}

export default Board

I compile my react app and put the build inside my server folder with index.js and etc. The build command is set to npm install and start command is node index.js.

I have tried deploying on multiple sites like render.com, cloudflare, google cloud functions and etc. Render.com was the only one I could deploy and the others I couldn't figure out how to do. I was still facing the same issue though.


Solution

  • In client side, you just need to change :

    socket = io.connect("http://192.168.1.72:3000",{'force new connection': true});

    to :

    socket = io.connect();