├── .gitignore ├── .dockerignore ├── renovate.json ├── app.js ├── Dockerfile ├── package.json ├── README.md ├── public ├── index.html ├── styles.css └── sketch.js ├── LICENSE ├── server.js └── .eslintrc.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /node_modules -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | 3 | const app = express() 4 | 5 | app.use(express.static('public')) 6 | 7 | module.exports = app -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY package*.json ./ 6 | 7 | RUN npm install 8 | 9 | COPY . . 10 | 11 | EXPOSE 3000:3000 12 | 13 | CMD [ "node", "server.js" ] -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socketio-drawing", 3 | "version": "1.0.0", 4 | "description": "A realtime drawing app using socketIO", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Gabriel Tanner", 10 | "license": "MIT", 11 | "keywords": [ 12 | "socket io", 13 | "drawing", 14 | "javascript", 15 | "sockets" 16 | ], 17 | "dependencies": { 18 | "body-parser": "^1.19.1", 19 | "cors": "^2.8.5", 20 | "express": "^4.17.2", 21 | "p5": "^1.4.0", 22 | "socket.io": "^4.4.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SocketIO drawing app 2 | 3 | Basic example of how to use socketIO to create simple communication between multiple browser instances. 4 | 5 | ## Getting started 6 | 7 | Start using Node 8 | 9 | ```bash 10 | # Install dependencies for server 11 | npm install 12 | 13 | # Run the server 14 | node server 15 | ``` 16 | 17 | Start using Docker 18 | 19 | ```bash 20 | # Building the image 21 | docker build --tag socketiodrawing . 22 | 23 | # Run the image in a container 24 | docker run -d -p 3000:3000 socketiodrawing 25 | ``` 26 | 27 | ## Author 28 | 29 | Gabriel Tanner 30 | 31 | ## Support me 32 | 33 | Buy Me A Coffee 34 | 35 | ## License 36 | 37 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE) file for details 38 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Websockets drawing app 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

Choose color (# hex)

16 | 17 |
18 | 19 |
20 |

Choose stroke width

21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Tanner Gabriel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /public/styles.css: -------------------------------------------------------------------------------- 1 | input.call-picker { 2 | border: 1px solid #AAA; 3 | color: #666; 4 | text-transform: uppercase; 5 | float: left; 6 | outline: none; 7 | padding: 10px; 8 | text-transform: uppercase; 9 | width: 85px; 10 | } 11 | 12 | .color-picker { 13 | width: 130px; 14 | background: #F3F3F3; 15 | height: 81px; 16 | padding: 5px; 17 | border: 5px solid #fff; 18 | box-shadow: 0px 0px 3px 1px #DDD; 19 | position: absolute; 20 | top: 61px; 21 | left: 2px; 22 | } 23 | 24 | .color-holder { 25 | background: #fff; 26 | cursor: pointer; 27 | border: 1px solid #AAA; 28 | width: 40px; 29 | height: 36px; 30 | float: left; 31 | margin-left: 5px; 32 | } 33 | 34 | input.stroke_width_picker { 35 | border: 1px solid #AAA; 36 | color: #666; 37 | text-transform: uppercase; 38 | float: left; 39 | outline: none; 40 | padding: 10px; 41 | text-transform: uppercase; 42 | width: 85px; 43 | } 44 | 45 | p { 46 | margin-top: 2rem; 47 | margin-bottom: 2rem; 48 | color: #DDD; 49 | } 50 | 51 | button { 52 | margin-left: 1rem; 53 | } 54 | 55 | html, body { 56 | height: 100%; 57 | background-color: #373d55; 58 | } 59 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const http = require('http') 2 | const app = require('./app') 3 | 4 | const normalizePort = val => { 5 | const port = parseInt(val, 10) 6 | 7 | if (isNaN(port)) { 8 | return val 9 | } 10 | if (port >= 0) { 11 | return port 12 | } 13 | return false 14 | } 15 | const port = normalizePort(process.env.PORT || '3000') 16 | app.set('port', port) 17 | 18 | const errorHandler = error => { 19 | if (error.syscall !== 'listen') { 20 | throw error 21 | } 22 | const address = server.address() 23 | const bind = typeof address === 'string' ? 'pipe ' + address : 'port: ' + port 24 | switch (error.code) { 25 | case 'EACCES': 26 | console.error(bind + ' requires elevated privileges.') 27 | process.exit(1) 28 | break 29 | case 'EADDRINUSE': 30 | console.error(bind + ' is already in use.') 31 | process.exit(1) 32 | break 33 | default: 34 | throw error 35 | } 36 | } 37 | 38 | const server = http.createServer(app) 39 | 40 | server.on('error', errorHandler) 41 | server.on('listening', () => { 42 | const address = server.address() 43 | const bind = typeof address === 'string' ? 'pipe ' + address : 'port ' + port 44 | console.log('Listening on ' + bind) 45 | }) 46 | 47 | 48 | // Web sockets 49 | const io = require('socket.io')(server) 50 | 51 | io.sockets.on('connection', (socket) => { 52 | console.log('Client connected: ' + socket.id) 53 | 54 | socket.on('mouse', (data) => socket.broadcast.emit('mouse', data)) 55 | 56 | socket.on('disconnect', () => console.log('Client has disconnected')) 57 | }) 58 | 59 | server.listen(port) -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "env": { 4 | "node": true, 5 | "es6": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 2017 9 | }, 10 | "rules": { 11 | "brace-style": ["error", "stroustrup", { 12 | "allowSingleLine": true 13 | }], 14 | "comma-dangle": ["error", "always-multiline"], 15 | "comma-spacing": "error", 16 | "comma-style": "error", 17 | "curly": ["error", "multi-line", "consistent"], 18 | "dot-location": ["error", "property"], 19 | "handle-callback-err": "off", 20 | "indent": ["error", "tab"], 21 | "max-nested-callbacks": ["error", { 22 | "max": 4 23 | }], 24 | "max-statements-per-line": ["error", { 25 | "max": 2 26 | }], 27 | "no-console": "off", 28 | "no-empty-function": "error", 29 | "no-floating-decimal": "error", 30 | "no-inline-comments": "error", 31 | "no-lonely-if": "error", 32 | "no-multi-spaces": "error", 33 | "no-multiple-empty-lines": ["error", { 34 | "max": 2, 35 | "maxEOF": 1, 36 | "maxBOF": 0 37 | }], 38 | "no-shadow": ["error", { 39 | "allow": ["err", "resolve", "reject"] 40 | }], 41 | "no-trailing-spaces": ["error"], 42 | "no-var": "error", 43 | "object-curly-spacing": ["error", "always"], 44 | "prefer-const": "error", 45 | "quotes": ["error", "single"], 46 | "semi": ["error", "never"], 47 | "space-before-blocks": "error", 48 | "space-before-function-paren": ["error", { 49 | "anonymous": "never", 50 | "named": "never", 51 | "asyncArrow": "always" 52 | }], 53 | "space-in-parens": "error", 54 | "space-infix-ops": "error", 55 | "space-unary-ops": "error", 56 | "spaced-comment": "error", 57 | "yoda": "error" 58 | } 59 | } -------------------------------------------------------------------------------- /public/sketch.js: -------------------------------------------------------------------------------- 1 | let socket 2 | let color = '#000' 3 | let strokeWidth = 4 4 | let cv 5 | 6 | function setup() { 7 | // Creating canvas 8 | cv = createCanvas(windowWidth / 2, windowHeight / 2) 9 | centerCanvas() 10 | cv.background(255, 255, 255) 11 | 12 | // Start the socket connection 13 | socket = io.connect('http://localhost:3000') 14 | 15 | // Callback function 16 | socket.on('mouse', data => { 17 | stroke(data.color) 18 | strokeWeight(data.strokeWidth) 19 | line(data.x, data.y, data.px, data.py) 20 | }) 21 | 22 | // Getting our buttons and the holder through the p5.js dom 23 | const color_picker = select('#pickcolor') 24 | const color_btn = select('#color-btn') 25 | const color_holder = select('#color-holder') 26 | 27 | const stroke_width_picker = select('#stroke-width-picker') 28 | const stroke_btn = select('#stroke-btn') 29 | 30 | // Adding a mousePressed listener to the button 31 | color_btn.mousePressed(() => { 32 | // Checking if the input is a valid hex color 33 | if (/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(color_picker.value())) { 34 | color = color_picker.value() 35 | color_holder.style('background-color', color) 36 | } else { 37 | console.log('Enter a valid hex value') 38 | } 39 | }) 40 | 41 | // Adding a mousePressed listener to the button 42 | stroke_btn.mousePressed(() => { 43 | const width = parseInt(stroke_width_picker.value()) 44 | if (width > 0) strokeWidth = width 45 | }) 46 | } 47 | 48 | function windowResized() { 49 | centerCanvas() 50 | cv.resizeCanvas(windowWidth / 2, windowHeight / 2, false) 51 | } 52 | 53 | 54 | function centerCanvas() { 55 | const x = (windowWidth - width) / 2 56 | const y = (windowHeight - height) / 2 57 | cv.position(x, y) 58 | } 59 | 60 | 61 | function mouseDragged() { 62 | // Draw 63 | stroke(color) 64 | strokeWeight(strokeWidth) 65 | line(mouseX, mouseY, pmouseX, pmouseY) 66 | 67 | // Send the mouse coordinates 68 | sendmouse(mouseX, mouseY, pmouseX, pmouseY) 69 | } 70 | 71 | // Sending data to the socket 72 | function sendmouse(x, y, pX, pY) { 73 | const data = { 74 | x: x, 75 | y: y, 76 | px: pX, 77 | py: pY, 78 | color: color, 79 | strokeWidth: strokeWidth, 80 | } 81 | 82 | socket.emit('mouse', data) 83 | } --------------------------------------------------------------------------------