diff --git a/.prettierrc.js b/.prettierrc.js
new file mode 100644
index 00000000..4c2c6c78
--- /dev/null
+++ b/.prettierrc.js
@@ -0,0 +1,7 @@
+module.exports = {
+ semi: true,
+ trailingComma: "all",
+ singleQuote: true,
+ printWidth: 120,
+ tabWidth: 4
+ };
\ No newline at end of file
diff --git a/README.md b/README.md
index bf6cae78..e7808daa 100644
--- a/README.md
+++ b/README.md
@@ -145,6 +145,7 @@ curl -X POST "http://localhost:3000/api/v1/meeting" -H "authorization: mirotalk_
curl -X POST "https://mirotalk.up.railway.app/api/v1/meeting" -H "authorization: mirotalk_default_secret" -H "Content-Type: application/json"
curl -X POST "https://mirotalk.herokuapp.com/api/v1/meeting" -H "authorization: mirotalk_default_secret" -H "Content-Type: application/json"
```
+
## API Documentation
The API documentation uses [swagger](https://swagger.io/) at http://localhost:3000/api/v1/docs. Or check it out on [railway](https://mirotalk.up.railway.app/api/v1/docs) & [heroku](https://mirotalk.herokuapp.com/api/v1/docs).
diff --git a/api/README.md b/api/README.md
index d20ed4f6..23ad8968 100644
--- a/api/README.md
+++ b/api/README.md
@@ -1,4 +1,5 @@
[](https://mirotalk.up.railway.app/api/v1/docs)
+
## Create a meeting
Create a meeting with a `HTTP request` containing the `API_KEY` sent to MiroTalk’s server. The response contains a `meeting` URL that can be `embedded` in your client within an `iframe`.
@@ -20,9 +21,9 @@ Embedding a meeting into a `service` or `app` requires using an `iframe` with th
```html
```
@@ -32,8 +33,8 @@ Develop your `website` or `application`, and bring `video meetings` in with a si
```html
```
diff --git a/api/meeting.js b/api/meeting.js
index 213e3966..925adba2 100644
--- a/api/meeting.js
+++ b/api/meeting.js
@@ -1,22 +1,22 @@
-const fetch = require("node-fetch");
+const fetch = require('node-fetch');
-const API_KEY = "mirotalk_default_secret";
+const API_KEY = 'mirotalk_default_secret';
// const MIROTALK_URL = "http://localhost:3000/api/v1/meeting";
// const MIROTALK_URL = "https://mirotalk.herokuapp.com/api/v1/meeting";
-const MIROTALK_URL = "https://mirotalk.up.railway.app/api/v1/meeting";
+const MIROTALK_URL = 'https://mirotalk.up.railway.app/api/v1/meeting';
function getResponse() {
- return fetch(MIROTALK_URL, {
- method: "POST",
- headers: {
- authorization: API_KEY,
- "Content-Type": "application/json",
- },
- });
+ return fetch(MIROTALK_URL, {
+ method: 'POST',
+ headers: {
+ authorization: API_KEY,
+ 'Content-Type': 'application/json',
+ },
+ });
}
getResponse().then(async (res) => {
- console.log("Status code:", res.status);
- const data = await res.json();
- console.log("meeting:", data.meeting);
+ console.log('Status code:', res.status);
+ const data = await res.json();
+ console.log('meeting:', data.meeting);
});
diff --git a/api/swagger.yaml b/api/swagger.yaml
index 6f680cef..f59d4084 100644
--- a/api/swagger.yaml
+++ b/api/swagger.yaml
@@ -1,47 +1,47 @@
-swagger: "2.0"
+swagger: '2.0'
info:
- title: MiroTalk API
- description: API description for external applications that integrates with MiroTalk.
- version: 1.0.0
+ title: MiroTalk API
+ description: API description for external applications that integrates with MiroTalk.
+ version: 1.0.0
basePath: /api/v1
schemes:
- - https
- - http
+ - https
+ - http
paths:
- /meeting:
- post:
- tags:
- - "meeting"
- summary: "Create meeting"
- description: "Create meeting"
- consumes:
- - "application/json"
- produces:
- - "application/json"
- security:
- - secretApiKey: []
- responses:
- "200":
- description: "Meeting created"
- schema:
- $ref: "#/definitions/MeetingResponse"
- "403":
- description: "Unauthorized!"
+ /meeting:
+ post:
+ tags:
+ - 'meeting'
+ summary: 'Create meeting'
+ description: 'Create meeting'
+ consumes:
+ - 'application/json'
+ produces:
+ - 'application/json'
+ security:
+ - secretApiKey: []
+ responses:
+ '200':
+ description: 'Meeting created'
+ schema:
+ $ref: '#/definitions/MeetingResponse'
+ '403':
+ description: 'Unauthorized!'
securityDefinitions:
- secretApiKey:
- type: "apiKey"
- name: "authorization"
- in: "header"
- description: "Format like this: authorization: {API_KEY_SECRET}"
+ secretApiKey:
+ type: 'apiKey'
+ name: 'authorization'
+ in: 'header'
+ description: 'Format like this: authorization: {API_KEY_SECRET}'
definitions:
- MeetingResponse:
- type: "object"
- properties:
- meeting:
- type: "string"
+ MeetingResponse:
+ type: 'object'
+ properties:
+ meeting:
+ type: 'string'
diff --git a/docker-compose.yml b/docker-compose.yml
index dbfb6096..3c5541f8 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,18 +1,18 @@
version: '3'
services:
- mirotalk:
- image: mirotalk:latest
- build:
- context: .
- dockerfile: Dockerfile
- container_name: mirotalk
- hostname: mirotalk
- volumes:
- - .env:/usr/src/app/.env:ro
- restart: unless-stopped
- ports:
- - "3000:3000"
+ mirotalk:
+ image: mirotalk:latest
+ build:
+ context: .
+ dockerfile: Dockerfile
+ container_name: mirotalk
+ hostname: mirotalk
+ volumes:
+ - .env:/usr/src/app/.env:ro
+ restart: unless-stopped
+ ports:
+ - '3000:3000'
# Uncomment below, remove "ports:" section above and configure labels as
# needed for LetsEncrypt TLS certificates with Traefik.
# See https://doc.traefik.io/traefik/user-guides/docker-compose/basic-example/
@@ -23,4 +23,4 @@ services:
# - "traefik.http.routers.mirotalk.rule=Host(`mirotalk.example.com`)"
# - "traefik.http.routers.mirotalk.entrypoints=websecure"
# - "traefik.http.routers.mirotalk.tls.certresolver=myresolver"
-# - "traefik.http.services.mirotalk.loadbalancer.server.port=3000"
\ No newline at end of file
+# - "traefik.http.services.mirotalk.loadbalancer.server.port=3000"
diff --git a/package.json b/package.json
index 8949411e..84078919 100644
--- a/package.json
+++ b/package.json
@@ -1,29 +1,30 @@
{
- "name": "mirotalk",
- "version": "1.0.0",
- "description": "A free WebRTC browser-based video call",
- "main": "server.js",
- "scripts": {
- "start": "node server.js",
- "test": "echo \"Error: no test specified\" && exit 1"
- },
- "repository": {
- "type": "git",
- "url": "git+https://github.com/miroslavpejic85/mirotalk"
- },
- "author": "Miroslav Pejic",
- "license": "AGPL-3.0",
- "homepage": "https://github.com/miroslavpejic85/mirotalk",
- "dependencies": {
- "compression": "^1.7.4",
- "dotenv": "^10.0.0",
- "express": "^4.17.1",
- "ngrok": "^4.0.1",
- "socket.io": "^4.1.2",
- "swagger-ui-express": "^4.1.6",
- "yamljs": "^0.3.0"
- },
- "devDependencies": {
- "node-fetch": "^2.6.1"
- }
+ "name": "mirotalk",
+ "version": "1.0.0",
+ "description": "A free WebRTC browser-based video call",
+ "main": "server.js",
+ "scripts": {
+ "start": "node server.js",
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/miroslavpejic85/mirotalk"
+ },
+ "author": "Miroslav Pejic",
+ "license": "AGPL-3.0",
+ "homepage": "https://github.com/miroslavpejic85/mirotalk",
+ "dependencies": {
+ "compression": "^1.7.4",
+ "dotenv": "^10.0.0",
+ "express": "^4.17.1",
+ "ngrok": "^4.0.1",
+ "socket.io": "^4.1.2",
+ "swagger-ui-express": "^4.1.6",
+ "yamljs": "^0.3.0"
+ },
+ "devDependencies": {
+ "node-fetch": "^2.6.1",
+ "prettier": "2.3.2"
+ }
}
diff --git a/server.js b/server.js
index e7c03542..916458af 100755
--- a/server.js
+++ b/server.js
@@ -8,13 +8,13 @@ http://patorjk.com/software/taag/#p=display&f=ANSI%20Regular&t=Server
███████ ███████ ██ ██ ████ ███████ ██ ██
dependencies: {
- compression : https://www.npmjs.com/package/compression
- dotenv : https://www.npmjs.com/package/dotenv
- express : https://www.npmjs.com/package/express
- ngrok : https://www.npmjs.com/package/ngrok
- socket.io : https://www.npmjs.com/package/socket.io
- swagger : https://www.npmjs.com/package/swagger-ui-express
- yamljs : https://www.npmjs.com/package/yamljs
+ compression : https://www.npmjs.com/package/compression
+ dotenv : https://www.npmjs.com/package/dotenv
+ express : https://www.npmjs.com/package/express
+ ngrok : https://www.npmjs.com/package/ngrok
+ socket.io : https://www.npmjs.com/package/socket.io
+ swagger : https://www.npmjs.com/package/swagger-ui-express
+ yamljs : https://www.npmjs.com/package/yamljs
}
MiroTalk Signaling Server
@@ -35,29 +35,29 @@ along with this program. If not, see .
*/
-"use strict"; // https://www.w3schools.com/js/js_strict.asp
+'use strict'; // https://www.w3schools.com/js/js_strict.asp
-require("dotenv").config();
+require('dotenv').config();
-const compression = require("compression");
-const express = require("express");
-const path = require("path");
+const compression = require('compression');
+const express = require('express');
+const path = require('path');
const app = express();
app.use(compression()); // Compress all HTTP responses GZip
-const http = require("http");
+const http = require('http');
const server = http.createServer(app);
-const { Server } = require("socket.io");
+const { Server } = require('socket.io');
const io = new Server().listen(server);
-const ngrok = require("ngrok");
-const yamlJS = require("yamljs");
-const swaggerUi = require("swagger-ui-express");
-const swaggerDocument = yamlJS.load(__dirname + "/api/swagger.yaml");
-const apiBasePath = "/api/v1";
+const ngrok = require('ngrok');
+const yamlJS = require('yamljs');
+const swaggerUi = require('swagger-ui-express');
+const swaggerDocument = yamlJS.load(__dirname + '/api/swagger.yaml');
+const apiBasePath = '/api/v1';
-let API_KEY_SECRET = process.env.API_KEY_SECRET || "mirotalk_default_secret";
+let API_KEY_SECRET = process.env.API_KEY_SECRET || 'mirotalk_default_secret';
let PORT = process.env.PORT || 3000; // signalingServerPort
-let localHost = "http://localhost:" + PORT; // http
-let api_docs = localHost + apiBasePath + "/docs"; // api docs
+let localHost = 'http://localhost:' + PORT; // http
+let api_docs = localHost + apiBasePath + '/docs'; // api docs
let channels = {}; // collect channels
let sockets = {}; // collect sockets
let peers = {}; // collect peers info grp by channels
@@ -70,105 +70,101 @@ let turnUsername = process.env.TURN_USERNAME;
let turnCredential = process.env.TURN_PASSWORD;
// Use all static files from the www folder
-app.use(express.static(path.join(__dirname, "www")));
+app.use(express.static(path.join(__dirname, 'www')));
// Api parse body data as json
app.use(express.json());
// Remove trailing slashes in url handle bad requests
app.use((err, req, res, next) => {
- if (err instanceof SyntaxError && err.status === 400 && "body" in err) {
- logme("Request Error", {
- header: req.headers,
- body: req.body,
- error: err.message,
- });
- return res.status(400).send({ status: 404, message: err.message }); // Bad request
- }
- if (req.path.substr(-1) === "/" && req.path.length > 1) {
- let query = req.url.slice(req.path.length);
- res.redirect(301, req.path.slice(0, -1) + query);
- } else {
- next();
- }
+ if (err instanceof SyntaxError && err.status === 400 && 'body' in err) {
+ logme('Request Error', {
+ header: req.headers,
+ body: req.body,
+ error: err.message,
+ });
+ return res.status(400).send({ status: 404, message: err.message }); // Bad request
+ }
+ if (req.path.substr(-1) === '/' && req.path.length > 1) {
+ let query = req.url.slice(req.path.length);
+ res.redirect(301, req.path.slice(0, -1) + query);
+ } else {
+ next();
+ }
});
/*
app.get(["/"], (req, res) => {
- res.sendFile(path.join(__dirname, "www/client.html"))
+ res.sendFile(path.join(__dirname, "www/client.html"))
}); */
// all start from here
-app.get(["/"], (req, res) => {
- res.sendFile(path.join(__dirname, "www/landing.html"));
+app.get(['/'], (req, res) => {
+ res.sendFile(path.join(__dirname, 'www/landing.html'));
});
// set new room name and join
-app.get(["/newcall"], (req, res) => {
- res.sendFile(path.join(__dirname, "www/newcall.html"));
+app.get(['/newcall'], (req, res) => {
+ res.sendFile(path.join(__dirname, 'www/newcall.html'));
});
// if not allow video/audio
-app.get(["/permission"], (req, res) => {
- res.sendFile(path.join(__dirname, "www/permission.html"));
+app.get(['/permission'], (req, res) => {
+ res.sendFile(path.join(__dirname, 'www/permission.html'));
});
// privacy policy
-app.get(["/privacy"], (req, res) => {
- res.sendFile(path.join(__dirname, "www/privacy.html"));
+app.get(['/privacy'], (req, res) => {
+ res.sendFile(path.join(__dirname, 'www/privacy.html'));
});
// no room name specified to join
-app.get("/join/", (req, res) => {
- res.redirect("/");
+app.get('/join/', (req, res) => {
+ res.redirect('/');
});
// join to room
-app.get("/join/*", (req, res) => {
- if (Object.keys(req.query).length > 0) {
- logme("redirect:" + req.url + " to " + url.parse(req.url).pathname);
- res.redirect(url.parse(req.url).pathname);
- } else {
- res.sendFile(path.join(__dirname, "www/client.html"));
- }
+app.get('/join/*', (req, res) => {
+ if (Object.keys(req.query).length > 0) {
+ logme('redirect:' + req.url + ' to ' + url.parse(req.url).pathname);
+ res.redirect(url.parse(req.url).pathname);
+ } else {
+ res.sendFile(path.join(__dirname, 'www/client.html'));
+ }
});
/**
- MiroTalk API v1
- The response will give you a entrypoint / Room URL for your meeting.
- For api docs we use: https://swagger.io/
+ MiroTalk API v1
+ The response will give you a entrypoint / Room URL for your meeting.
+ For api docs we use: https://swagger.io/
*/
// api docs
-app.use(
- apiBasePath + "/docs",
- swaggerUi.serve,
- swaggerUi.setup(swaggerDocument)
-);
+app.use(apiBasePath + '/docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));
// request meeting room endpoint
-app.post([apiBasePath + "/meeting"], (req, res) => {
- // check if user was authorized for the api call
- let authorization = req.headers.authorization;
- if (authorization != API_KEY_SECRET) {
- logme("MiroTalk get meeting - Unauthorized", {
- header: req.headers,
- body: req.body,
- });
- return res.status(403).json({ error: "Unauthorized!" });
- }
- // setup meeting URL
- let host = req.headers.host;
- let meetingURL = getMeetingURL(host) + "/join/" + makeId(15);
- res.setHeader("Content-Type", "application/json");
- res.end(JSON.stringify({ meeting: meetingURL }));
+app.post([apiBasePath + '/meeting'], (req, res) => {
+ // check if user was authorized for the api call
+ let authorization = req.headers.authorization;
+ if (authorization != API_KEY_SECRET) {
+ logme('MiroTalk get meeting - Unauthorized', {
+ header: req.headers,
+ body: req.body,
+ });
+ return res.status(403).json({ error: 'Unauthorized!' });
+ }
+ // setup meeting URL
+ let host = req.headers.host;
+ let meetingURL = getMeetingURL(host) + '/join/' + makeId(15);
+ res.setHeader('Content-Type', 'application/json');
+ res.end(JSON.stringify({ meeting: meetingURL }));
- // logme the output if all done
- logme("MiroTalk get meeting - Authorized", {
- header: req.headers,
- body: req.body,
- meeting: meetingURL,
- });
+ // logme the output if all done
+ logme('MiroTalk get meeting - Authorized', {
+ header: req.headers,
+ body: req.body,
+ meeting: meetingURL,
+ });
});
/**
@@ -177,7 +173,7 @@ app.post([apiBasePath + "/meeting"], (req, res) => {
* @returns meeting Room URL
*/
function getMeetingURL(host) {
- return "http" + (host.includes("localhost") ? "" : "s") + "://" + host;
+ return 'http' + (host.includes('localhost') ? '' : 's') + '://' + host;
}
/**
@@ -186,14 +182,13 @@ function getMeetingURL(host) {
* @returns random id
*/
function makeId(length) {
- let result = "";
- let characters =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
- let charactersLength = characters.length;
- for (let i = 0; i < length; i++) {
- result += characters.charAt(Math.floor(Math.random() * charactersLength));
- }
- return result;
+ let result = '';
+ let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+ let charactersLength = characters.length;
+ for (let i = 0; i < length; i++) {
+ result += characters.charAt(Math.floor(Math.random() * charactersLength));
+ }
+ return result;
}
// end of MiroTalk API v1
@@ -208,14 +203,14 @@ function makeId(length) {
* Check the functionality of STUN/TURN servers:
* https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/
*/
-let iceServers = [{ urls: "stun:stun.l.google.com:19302" }];
+let iceServers = [{ urls: 'stun:stun.l.google.com:19302' }];
-if (turnEnabled == "true") {
- iceServers.push({
- urls: turnUrls,
- username: turnUsername,
- credential: turnCredential,
- });
+if (turnEnabled == 'true') {
+ iceServers.push({
+ urls: turnUrls,
+ username: turnUsername,
+ credential: turnCredential,
+ });
}
/**
@@ -223,37 +218,37 @@ if (turnEnabled == "true") {
* https://ngrok.com
*/
async function ngrokStart() {
- try {
- await ngrok.authtoken(ngrokAuthToken);
- await ngrok.connect(PORT);
- let api = ngrok.getApi();
- let data = await api.listTunnels();
- let pu0 = data.tunnels[0].public_url;
- let pu1 = data.tunnels[1].public_url;
- let tunnelHttps = pu0.startsWith("https") ? pu0 : pu1;
- // server settings
- logme("settings", {
- http: localHost,
- https: tunnelHttps,
- api_docs: api_docs,
- api_key_secret: API_KEY_SECRET,
- iceServers: iceServers,
- ngrok: {
- ngrok_enabled: ngrokEnabled,
- ngrok_token: ngrokAuthToken,
- },
- });
- } catch (err) {
- console.error("[Error] ngrokStart", err);
- }
+ try {
+ await ngrok.authtoken(ngrokAuthToken);
+ await ngrok.connect(PORT);
+ let api = ngrok.getApi();
+ let data = await api.listTunnels();
+ let pu0 = data.tunnels[0].public_url;
+ let pu1 = data.tunnels[1].public_url;
+ let tunnelHttps = pu0.startsWith('https') ? pu0 : pu1;
+ // server settings
+ logme('settings', {
+ http: localHost,
+ https: tunnelHttps,
+ api_docs: api_docs,
+ api_key_secret: API_KEY_SECRET,
+ iceServers: iceServers,
+ ngrok: {
+ ngrok_enabled: ngrokEnabled,
+ ngrok_token: ngrokAuthToken,
+ },
+ });
+ } catch (err) {
+ console.error('[Error] ngrokStart', err);
+ }
}
/**
* Start Local Server with ngrok https tunnel (optional)
*/
server.listen(PORT, null, () => {
- logme(
- `%c
+ logme(
+ `%c
███████╗██╗ ██████╗ ███╗ ██╗ ███████╗███████╗██████╗ ██╗ ██╗███████╗██████╗
██╔════╝██║██╔════╝ ████╗ ██║ ██╔════╝██╔════╝██╔══██╗██║ ██║██╔════╝██╔══██╗
@@ -263,21 +258,21 @@ server.listen(PORT, null, () => {
╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚══════╝╚══════╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝ started...
`,
- "font-family:monospace"
- );
+ 'font-family:monospace',
+ );
- // https tunnel
- if (ngrokEnabled == "true") {
- ngrokStart();
- } else {
- // server settings
- logme("settings", {
- http: localHost,
- api_docs: api_docs,
- api_key_secret: API_KEY_SECRET,
- iceServers: iceServers,
- });
- }
+ // https tunnel
+ if (ngrokEnabled == 'true') {
+ ngrokStart();
+ } else {
+ // server settings
+ logme('settings', {
+ http: localHost,
+ api_docs: api_docs,
+ api_key_secret: API_KEY_SECRET,
+ iceServers: iceServers,
+ });
+ }
});
/**
@@ -291,403 +286,365 @@ server.listen(PORT, null, () => {
* the peer connection and will be in streaming audio/video between eachother.
* On peer connected
*/
-io.sockets.on("connect", (socket) => {
- logme("[" + socket.id + "] --> connection accepted");
+io.sockets.on('connect', (socket) => {
+ logme('[' + socket.id + '] --> connection accepted');
- socket.channels = {};
- sockets[socket.id] = socket;
+ socket.channels = {};
+ sockets[socket.id] = socket;
- /**
- * On peer diconnected
- */
- socket.on("disconnect", () => {
- for (let channel in socket.channels) {
- removePeerFrom(channel);
- }
- logme("[" + socket.id + "] <--> disconnected");
- delete sockets[socket.id];
- });
-
- /**
- * On peer join
- */
- socket.on("join", (config) => {
- logme("[" + socket.id + "] --> join ", config);
-
- let channel = config.channel;
- let peer_name = config.peerName;
- let peer_video = config.peerVideo;
- let peer_audio = config.peerAudio;
- let peer_hand = config.peerHand;
-
- if (channel in socket.channels) {
- logme("[" + socket.id + "] [Warning] already joined", channel);
- return;
- }
- // no channel aka room in channels init
- if (!(channel in channels)) {
- channels[channel] = {};
- }
-
- // no channel aka room in peers init
- if (!(channel in peers)) {
- peers[channel] = {};
- }
-
- // room locked by the participants can't join
- if (peers[channel]["Locked"] === true) {
- logme("[" + socket.id + "] [Warning] Room Is Locked", channel);
- socket.emit("roomIsLocked");
- return;
- }
-
- // collect peers info grp by channels
- peers[channel][socket.id] = {
- peer_name: peer_name,
- peer_video: peer_video,
- peer_audio: peer_audio,
- peer_hand: peer_hand,
- };
- logme("connected peers grp by roomId", peers);
-
- for (let id in channels[channel]) {
- // offer false
- channels[channel][id].emit("addPeer", {
- peer_id: socket.id,
- peers: peers[channel],
- should_create_offer: false,
- iceServers: iceServers,
- });
- // offer true
- socket.emit("addPeer", {
- peer_id: id,
- peers: peers[channel],
- should_create_offer: true,
- iceServers: iceServers,
- });
- logme("[" + socket.id + "] emit addPeer [" + id + "]");
- }
-
- channels[channel][socket.id] = socket;
- socket.channels[channel] = channel;
- });
-
- /**
- * Remove peers from channel aka room
- * @param {*} channel
- */
- async function removePeerFrom(channel) {
- if (!(channel in socket.channels)) {
- logme("[" + socket.id + "] [Warning] not in ", channel);
- return;
- }
-
- delete socket.channels[channel];
- delete channels[channel][socket.id];
- delete peers[channel][socket.id];
-
- switch (Object.keys(peers[channel]).length) {
- case 0:
- // last peer disconnected from the room without room status set, delete room data
- delete peers[channel];
- break;
- case 1:
- // last peer disconnected from the room having room status set, delete room data
- if ("Locked" in peers[channel]) delete peers[channel];
- break;
- }
-
- for (let id in channels[channel]) {
- await channels[channel][id].emit("removePeer", { peer_id: socket.id });
- await socket.emit("removePeer", { peer_id: id });
- logme("[" + socket.id + "] emit removePeer [" + id + "]");
- }
- }
-
- /**
- * Relay ICE to peers
- */
- socket.on("relayICE", (config) => {
- let peer_id = config.peer_id;
- let ice_candidate = config.ice_candidate;
- /*
- logme(
- "[" + socket.id + "] relay ICE-candidate to [" + peer_id + "] ",
- { address: config.ice_candidate.address }
- );
- */
- if (peer_id in sockets) {
- sockets[peer_id].emit("iceCandidate", {
- peer_id: socket.id,
- ice_candidate: ice_candidate,
- });
- }
- });
-
- /**
- * Relay SDP to peers
- */
- socket.on("relaySDP", (config) => {
- let peer_id = config.peer_id;
- let session_description = config.session_description;
-
- logme(
- "[" + socket.id + "] relay SessionDescription to [" + peer_id + "] ",
- { type: session_description.type }
- );
-
- if (peer_id in sockets) {
- sockets[peer_id].emit("sessionDescription", {
- peer_id: socket.id,
- session_description: session_description,
- });
- }
- });
-
- /**
- * Refresh Room Status (Locked/Unlocked)
- */
- socket.on("roomStatus", (config) => {
- let peerConnections = config.peerConnections;
- let room_id = config.room_id;
- let room_locked = config.room_locked;
- let peer_name = config.peer_name;
-
- peers[room_id]["Locked"] = room_locked;
-
- if (Object.keys(peerConnections).length != 0) {
- logme(
- "[" +
- socket.id +
- "] emit roomStatus" +
- " to [room_id: " +
- room_id +
- " locked: " +
- room_locked +
- "]"
- );
- for (let peer_id in peerConnections) {
- if (sockets[peer_id]) {
- sockets[peer_id].emit("roomStatus", {
- peer_name: peer_name,
- room_locked: room_locked,
- });
+ /**
+ * On peer diconnected
+ */
+ socket.on('disconnect', () => {
+ for (let channel in socket.channels) {
+ removePeerFrom(channel);
+ }
+ logme('[' + socket.id + '] <--> disconnected');
+ delete sockets[socket.id];
+ });
+
+ /**
+ * On peer join
+ */
+ socket.on('join', (config) => {
+ logme('[' + socket.id + '] --> join ', config);
+
+ let channel = config.channel;
+ let peer_name = config.peerName;
+ let peer_video = config.peerVideo;
+ let peer_audio = config.peerAudio;
+ let peer_hand = config.peerHand;
+
+ if (channel in socket.channels) {
+ logme('[' + socket.id + '] [Warning] already joined', channel);
+ return;
+ }
+ // no channel aka room in channels init
+ if (!(channel in channels)) {
+ channels[channel] = {};
+ }
+
+ // no channel aka room in peers init
+ if (!(channel in peers)) {
+ peers[channel] = {};
+ }
+
+ // room locked by the participants can't join
+ if (peers[channel]['Locked'] === true) {
+ logme('[' + socket.id + '] [Warning] Room Is Locked', channel);
+ socket.emit('roomIsLocked');
+ return;
+ }
+
+ // collect peers info grp by channels
+ peers[channel][socket.id] = {
+ peer_name: peer_name,
+ peer_video: peer_video,
+ peer_audio: peer_audio,
+ peer_hand: peer_hand,
+ };
+ logme('connected peers grp by roomId', peers);
+
+ for (let id in channels[channel]) {
+ // offer false
+ channels[channel][id].emit('addPeer', {
+ peer_id: socket.id,
+ peers: peers[channel],
+ should_create_offer: false,
+ iceServers: iceServers,
+ });
+ // offer true
+ socket.emit('addPeer', {
+ peer_id: id,
+ peers: peers[channel],
+ should_create_offer: true,
+ iceServers: iceServers,
+ });
+ logme('[' + socket.id + '] emit addPeer [' + id + ']');
+ }
+
+ channels[channel][socket.id] = socket;
+ socket.channels[channel] = channel;
+ });
+
+ /**
+ * Remove peers from channel aka room
+ * @param {*} channel
+ */
+ async function removePeerFrom(channel) {
+ if (!(channel in socket.channels)) {
+ logme('[' + socket.id + '] [Warning] not in ', channel);
+ return;
+ }
+
+ delete socket.channels[channel];
+ delete channels[channel][socket.id];
+ delete peers[channel][socket.id];
+
+ switch (Object.keys(peers[channel]).length) {
+ case 0:
+ // last peer disconnected from the room without room status set, delete room data
+ delete peers[channel];
+ break;
+ case 1:
+ // last peer disconnected from the room having room status set, delete room data
+ if ('Locked' in peers[channel]) delete peers[channel];
+ break;
+ }
+
+ for (let id in channels[channel]) {
+ await channels[channel][id].emit('removePeer', { peer_id: socket.id });
+ await socket.emit('removePeer', { peer_id: id });
+ logme('[' + socket.id + '] emit removePeer [' + id + ']');
}
- }
}
- });
- /**
- * Relay NAME to peers
- */
- socket.on("peerName", (config) => {
- let peerConnections = config.peerConnections;
- let room_id = config.room_id;
- let peer_name_old = config.peer_name_old;
- let peer_name_new = config.peer_name_new;
- let peer_id_to_update = null;
+ /**
+ * Relay ICE to peers
+ */
+ socket.on('relayICE', (config) => {
+ let peer_id = config.peer_id;
+ let ice_candidate = config.ice_candidate;
- // update peers new name in the specified room
- for (let peer_id in peers[room_id]) {
- if (peers[room_id][peer_id]["peer_name"] == peer_name_old) {
- peers[room_id][peer_id]["peer_name"] = peer_name_new;
- peer_id_to_update = peer_id;
- /*
- logme("[" + socket.id + "] change peer name", {
- room_id: room_id,
- peer_id: peer_id,
- peer_name_old: peer_name_old,
- peer_name_new: peer_name_new,
+ // logme('[' + socket.id + '] relay ICE-candidate to [' + peer_id + '] ', {
+ // address: config.ice_candidate.address,
+ // });
+
+ if (peer_id in sockets) {
+ sockets[peer_id].emit('iceCandidate', {
+ peer_id: socket.id,
+ ice_candidate: ice_candidate,
+ });
+ }
+ });
+
+ /**
+ * Relay SDP to peers
+ */
+ socket.on('relaySDP', (config) => {
+ let peer_id = config.peer_id;
+ let session_description = config.session_description;
+
+ logme('[' + socket.id + '] relay SessionDescription to [' + peer_id + '] ', { type: session_description.type });
+
+ if (peer_id in sockets) {
+ sockets[peer_id].emit('sessionDescription', {
+ peer_id: socket.id,
+ session_description: session_description,
+ });
+ }
+ });
+
+ /**
+ * Refresh Room Status (Locked/Unlocked)
+ */
+ socket.on('roomStatus', (config) => {
+ let peerConnections = config.peerConnections;
+ let room_id = config.room_id;
+ let room_locked = config.room_locked;
+ let peer_name = config.peer_name;
+
+ peers[room_id]['Locked'] = room_locked;
+
+ if (Object.keys(peerConnections).length != 0) {
+ logme('[' + socket.id + '] emit roomStatus' + ' to [room_id: ' + room_id + ' locked: ' + room_locked + ']');
+ for (let peer_id in peerConnections) {
+ if (sockets[peer_id]) {
+ sockets[peer_id].emit('roomStatus', {
+ peer_name: peer_name,
+ room_locked: room_locked,
+ });
+ }
+ }
+ }
+ });
+
+ /**
+ * Relay NAME to peers
+ */
+ socket.on('peerName', (config) => {
+ let peerConnections = config.peerConnections;
+ let room_id = config.room_id;
+ let peer_name_old = config.peer_name_old;
+ let peer_name_new = config.peer_name_new;
+ let peer_id_to_update = null;
+
+ // update peers new name in the specified room
+ for (let peer_id in peers[room_id]) {
+ if (peers[room_id][peer_id]['peer_name'] == peer_name_old) {
+ peers[room_id][peer_id]['peer_name'] = peer_name_new;
+ peer_id_to_update = peer_id;
+
+ // logme('[' + socket.id + '] change peer name', {
+ // room_id: room_id,
+ // peer_id: peer_id,
+ // peer_name_old: peer_name_old,
+ // peer_name_new: peer_name_new,
+ // });
+ }
+ }
+
+ // refresh if found
+ if (peer_id_to_update && Object.keys(peerConnections).length != 0) {
+ logme('[' + socket.id + '] emit peerName to [room_id: ' + room_id + ']', {
+ peer_id: peer_id_to_update,
+ peer_name: peer_name_new,
+ });
+ for (let peer_id in peerConnections) {
+ if (sockets[peer_id]) {
+ sockets[peer_id].emit('peerName', {
+ peer_id: peer_id_to_update,
+ peer_name: peer_name_new,
+ });
+ }
+ }
+ }
+ });
+
+ /**
+ * Relay Audio Video Hand ... Status to peers
+ */
+ socket.on('peerStatus', (config) => {
+ let peerConnections = config.peerConnections;
+ let room_id = config.room_id;
+ let peer_name = config.peer_name;
+ let element = config.element;
+ let status = config.status;
+
+ // update peers video-audio status in the specified room
+ for (let peer_id in peers[room_id]) {
+ if (peers[room_id][peer_id]['peer_name'] == peer_name) {
+ switch (element) {
+ case 'video':
+ peers[room_id][peer_id]['peer_video'] = status;
+ break;
+ case 'audio':
+ peers[room_id][peer_id]['peer_audio'] = status;
+ break;
+ case 'hand':
+ peers[room_id][peer_id]['peer_hand'] = status;
+ break;
+ }
+
+ // logme('[' + socket.id + '] change ' + element + ' status', {
+ // room_id: room_id,
+ // peer_name: peer_name,
+ // element: element,
+ // status: status,
+ // });
+ }
+ }
+
+ // socket.id aka peer that send this status
+ if (Object.keys(peerConnections).length != 0) {
+ logme('[' + socket.id + '] emit peerStatus to [room_id: ' + room_id + ']', {
+ peer_id: socket.id,
+ element: element,
+ status: status,
+ });
+ for (let peer_id in peerConnections) {
+ if (sockets[peer_id]) {
+ sockets[peer_id].emit('peerStatus', {
+ peer_id: socket.id,
+ peer_name: peer_name,
+ element: element,
+ status: status,
+ });
+ }
+ }
+ }
+ });
+
+ /**
+ * Relay actions to peers in the same room
+ */
+ socket.on('peerAction', (config) => {
+ let peerConnections = config.peerConnections;
+ let room_id = config.room_id;
+ let peer_name = config.peer_name;
+ let peer_action = config.peer_action;
+
+ // socket.id aka peer that send this status
+ if (Object.keys(peerConnections).length != 0) {
+ logme('[' + socket.id + '] emit peerAction to [room_id: ' + room_id + ']', {
+ peer_id: socket.id,
+ peer_name: peer_name,
+ peer_action: peer_action,
+ });
+ for (let peer_id in peerConnections) {
+ if (sockets[peer_id]) {
+ sockets[peer_id].emit('peerAction', {
+ peer_name: peer_name,
+ peer_action: peer_action,
+ });
+ }
+ }
+ }
+ });
+
+ /**
+ * Relay Kick out peer from room
+ */
+ socket.on('kickOut', (config) => {
+ let room_id = config.room_id;
+ let peer_id = config.peer_id;
+ let peer_name = config.peer_name;
+
+ logme('[' + socket.id + '] kick out peer [' + peer_id + '] from room_id [' + room_id + ']');
+
+ if (peer_id in sockets) {
+ sockets[peer_id].emit('kickOut', {
+ peer_name: peer_name,
+ });
+ }
+ });
+
+ /**
+ * Relay File info
+ */
+ socket.on('fileInfo', (config) => {
+ let peerConnections = config.peerConnections;
+ let room_id = config.room_id;
+ let peer_name = config.peer_name;
+ let file = config.file;
+
+ logme('[' + socket.id + '] Peer [' + peer_name + '] send file to room_id [' + room_id + ']', {
+ fileName: file.fileName,
+ fileSize: bytesToSize(file.fileSize),
+ fileType: file.fileType,
});
- */
- }
- }
- // refresh if found
- if (peer_id_to_update && Object.keys(peerConnections).length != 0) {
- logme("[" + socket.id + "] emit peerName to [room_id: " + room_id + "]", {
- peer_id: peer_id_to_update,
- peer_name: peer_name_new,
- });
- for (let peer_id in peerConnections) {
- if (sockets[peer_id]) {
- sockets[peer_id].emit("peerName", {
- peer_id: peer_id_to_update,
- peer_name: peer_name_new,
- });
+ function bytesToSize(bytes) {
+ let sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
+ if (bytes == 0) return '0 Byte';
+ let i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
+ return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
}
- }
- }
- });
- /**
- * Relay Audio Video Hand ... Status to peers
- */
- socket.on("peerStatus", (config) => {
- let peerConnections = config.peerConnections;
- let room_id = config.room_id;
- let peer_name = config.peer_name;
- let element = config.element;
- let status = config.status;
-
- // update peers video-audio status in the specified room
- for (let peer_id in peers[room_id]) {
- if (peers[room_id][peer_id]["peer_name"] == peer_name) {
- switch (element) {
- case "video":
- peers[room_id][peer_id]["peer_video"] = status;
- break;
- case "audio":
- peers[room_id][peer_id]["peer_audio"] = status;
- break;
- case "hand":
- peers[room_id][peer_id]["peer_hand"] = status;
- break;
+ if (Object.keys(peerConnections).length != 0) {
+ for (let peer_id in peerConnections) {
+ if (sockets[peer_id]) {
+ sockets[peer_id].emit('fileInfo', file);
+ }
+ }
}
- /*
- logme("[" + socket.id + "] change " + element + " status", {
- room_id: room_id,
- peer_name: peer_name,
- element: element,
- status: status,
- });
- */
- }
- }
+ });
- // socket.id aka peer that send this status
- if (Object.keys(peerConnections).length != 0) {
- logme(
- "[" + socket.id + "] emit peerStatus to [room_id: " + room_id + "]",
- {
- peer_id: socket.id,
- element: element,
- status: status,
+ /**
+ * Whiteboard actions for all user in the same room
+ */
+ socket.on('wb', (config) => {
+ let peerConnections = config.peerConnections;
+ delete config.peerConnections;
+ if (Object.keys(peerConnections).length != 0) {
+ // logme("[" + socket.id + "] whiteboard config", config);
+ for (let peer_id in peerConnections) {
+ if (sockets[peer_id]) {
+ sockets[peer_id].emit('wb', config);
+ }
+ }
}
- );
- for (let peer_id in peerConnections) {
- if (sockets[peer_id]) {
- sockets[peer_id].emit("peerStatus", {
- peer_id: socket.id,
- peer_name: peer_name,
- element: element,
- status: status,
- });
- }
- }
- }
- });
-
- /**
- * Relay actions to peers in the same room
- */
- socket.on("peerAction", (config) => {
- let peerConnections = config.peerConnections;
- let room_id = config.room_id;
- let peer_name = config.peer_name;
- let peer_action = config.peer_action;
-
- // socket.id aka peer that send this status
- if (Object.keys(peerConnections).length != 0) {
- logme(
- "[" + socket.id + "] emit peerAction to [room_id: " + room_id + "]",
- {
- peer_id: socket.id,
- peer_name: peer_name,
- peer_action: peer_action,
- }
- );
- for (let peer_id in peerConnections) {
- if (sockets[peer_id]) {
- sockets[peer_id].emit("peerAction", {
- peer_name: peer_name,
- peer_action: peer_action,
- });
- }
- }
- }
- });
-
- /**
- * Relay Kick out peer from room
- */
- socket.on("kickOut", (config) => {
- let room_id = config.room_id;
- let peer_id = config.peer_id;
- let peer_name = config.peer_name;
-
- logme(
- "[" +
- socket.id +
- "] kick out peer [" +
- peer_id +
- "] from room_id [" +
- room_id +
- "]"
- );
-
- if (peer_id in sockets) {
- sockets[peer_id].emit("kickOut", {
- peer_name: peer_name,
- });
- }
- });
-
- /**
- * Relay File info
- */
- socket.on("fileInfo", (config) => {
- let peerConnections = config.peerConnections;
- let room_id = config.room_id;
- let peer_name = config.peer_name;
- let file = config.file;
-
- logme(
- "[" +
- socket.id +
- "] Peer [" +
- peer_name +
- "] send file to room_id [" +
- room_id +
- "]",
- {
- fileName: file.fileName,
- fileSize: bytesToSize(file.fileSize),
- fileType: file.fileType,
- }
- );
-
- function bytesToSize(bytes) {
- let sizes = ["Bytes", "KB", "MB", "GB", "TB"];
- if (bytes == 0) return "0 Byte";
- let i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
- return Math.round(bytes / Math.pow(1024, i), 2) + " " + sizes[i];
- }
-
- if (Object.keys(peerConnections).length != 0) {
- for (let peer_id in peerConnections) {
- if (sockets[peer_id]) {
- sockets[peer_id].emit("fileInfo", file);
- }
- }
- }
- });
-
- /**
- * Whiteboard actions for all user in the same room
- */
- socket.on("wb", (config) => {
- let peerConnections = config.peerConnections;
- delete config.peerConnections;
- if (Object.keys(peerConnections).length != 0) {
- // logme("[" + socket.id + "] whiteboard config", config);
- for (let peer_id in peerConnections) {
- if (sockets[peer_id]) {
- sockets[peer_id].emit("wb", config);
- }
- }
- }
- });
+ });
}); // end [sockets.on-connect]
/**
@@ -695,7 +652,7 @@ io.sockets.on("connect", (socket) => {
* @param {*} msg message any
* @param {*} op optional params
*/
-function logme(msg, op = "") {
- let dataTime = new Date().toISOString().replace(/T/, " ").replace(/Z/, "");
- console.log("[" + dataTime + "] " + msg, op);
+function logme(msg, op = '') {
+ let dataTime = new Date().toISOString().replace(/T/, ' ').replace(/Z/, '');
+ console.log('[' + dataTime + '] ' + msg, op);
}
diff --git a/www/client.html b/www/client.html
index 63953a89..c91f88ac 100755
--- a/www/client.html
+++ b/www/client.html
@@ -1,418 +1,376 @@
-
- MiroTalk WebRTC Video call, Chat Room & Screen Sharing.
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ gtag('config', 'G-3XM60XK9RQ');
+
+ MiroTalk WebRTC Video call, Chat Room & Screen Sharing.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
- WebRTC
+
+
+ WebRTC
-
-
-
Loading...
-
+
+
+
Loading...
+
Please allow camera & microphone
access to use this app.
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
Friends Name
-
00:00:00
-
+
+
+
Friends Name
+
00:00:00
+
-
Public message example
-
-
+
Public message example
+
+
-
-
+
+
-
-
+
+
-
Public message example
-
-
+
Public message example
+
+
-
-
-
+
+
+
-
-
-
Friends Name
-
00:00:00
-
+
+
+
Friends Name
+
00:00:00
+
-
Private message example
-
-
+
Private message example
+
+
-
-
+
+
-
-
+
+
-
Private message example
-
-
-
+
Private message example
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
+
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ -->
+