├── Procfile ├── .babelrc ├── .DS_Store ├── public ├── favicon.ico ├── assets │ ├── fun.png │ ├── smiley.png │ ├── weird.png │ ├── big_smile.png │ ├── daftSmile.png │ ├── eyesFloor.png │ ├── super_man.png │ ├── loveTongue.png │ ├── smLoveTongue.png │ ├── heartThoughts.png │ └── emojione │ │ └── small │ │ ├── 2705.png │ │ ├── 1f354.png │ │ ├── 1f382.png │ │ ├── 1f385.png │ │ ├── 1f389.png │ │ ├── 1f414.png │ │ ├── 1f436.png │ │ ├── 1f4a9.png │ │ ├── 1f4af.png │ │ ├── 1f4b8.png │ │ ├── 1f602.png │ │ ├── 1f60d.png │ │ ├── 1f64a.png │ │ ├── 1f64c.png │ │ ├── 1f951.png │ │ └── 1f984.png ├── css │ └── main.css ├── index.html └── index.js ├── .gitignore ├── .npmignore ├── src ├── components │ ├── insertFunctions.js │ ├── mirageStore.js │ ├── mediaGenerator.js │ ├── chunk.js │ ├── mergeRectangles.js │ ├── evalStagesCalcs.js │ ├── mirageWebRTC.js │ ├── mirageApp.js │ └── cssChunk.js ├── utils │ ├── domReady.js │ ├── utilStore.js │ ├── listenerFuncs.js │ ├── domFunctions.js │ └── funcStore.js └── index.js ├── karma.conf.js ├── tests └── client │ ├── sockets.test.js │ └── funcStore.test.js ├── .eslintrc.js ├── server_cert ├── server.csr ├── server.key ├── server.crt └── server.enc.key ├── webpack.config.js ├── server.js ├── package.json ├── mirageSockets.js └── README.md /Procfile: -------------------------------------------------------------------------------- 1 | web: node server.js 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015", "stage-1"] 3 | } 4 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/.DS_Store -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/assets/fun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/fun.png -------------------------------------------------------------------------------- /public/assets/smiley.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/smiley.png -------------------------------------------------------------------------------- /public/assets/weird.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/weird.png -------------------------------------------------------------------------------- /public/assets/big_smile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/big_smile.png -------------------------------------------------------------------------------- /public/assets/daftSmile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/daftSmile.png -------------------------------------------------------------------------------- /public/assets/eyesFloor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/eyesFloor.png -------------------------------------------------------------------------------- /public/assets/super_man.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/super_man.png -------------------------------------------------------------------------------- /public/assets/loveTongue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/loveTongue.png -------------------------------------------------------------------------------- /public/assets/smLoveTongue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/smLoveTongue.png -------------------------------------------------------------------------------- /public/assets/heartThoughts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/heartThoughts.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /node_modules 3 | /*.env 4 | npm-debug.log 5 | / 6 | /lib 7 | # IntelliJ 8 | *.iml 9 | /.idea 10 | -------------------------------------------------------------------------------- /public/assets/emojione/small/2705.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/emojione/small/2705.png -------------------------------------------------------------------------------- /public/assets/emojione/small/1f354.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/emojione/small/1f354.png -------------------------------------------------------------------------------- /public/assets/emojione/small/1f382.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/emojione/small/1f382.png -------------------------------------------------------------------------------- /public/assets/emojione/small/1f385.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/emojione/small/1f385.png -------------------------------------------------------------------------------- /public/assets/emojione/small/1f389.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/emojione/small/1f389.png -------------------------------------------------------------------------------- /public/assets/emojione/small/1f414.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/emojione/small/1f414.png -------------------------------------------------------------------------------- /public/assets/emojione/small/1f436.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/emojione/small/1f436.png -------------------------------------------------------------------------------- /public/assets/emojione/small/1f4a9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/emojione/small/1f4a9.png -------------------------------------------------------------------------------- /public/assets/emojione/small/1f4af.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/emojione/small/1f4af.png -------------------------------------------------------------------------------- /public/assets/emojione/small/1f4b8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/emojione/small/1f4b8.png -------------------------------------------------------------------------------- /public/assets/emojione/small/1f602.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/emojione/small/1f602.png -------------------------------------------------------------------------------- /public/assets/emojione/small/1f60d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/emojione/small/1f60d.png -------------------------------------------------------------------------------- /public/assets/emojione/small/1f64a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/emojione/small/1f64a.png -------------------------------------------------------------------------------- /public/assets/emojione/small/1f64c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/emojione/small/1f64c.png -------------------------------------------------------------------------------- /public/assets/emojione/small/1f951.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/emojione/small/1f951.png -------------------------------------------------------------------------------- /public/assets/emojione/small/1f984.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/team-genjutsu/miragejs/HEAD/public/assets/emojione/small/1f984.png -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /node_modules 3 | /server_cert 4 | /*.env 5 | npm-debug.log 6 | / 7 | /src 8 | # IntelliJ 9 | *.iml 10 | /.idea 11 | 12 | <<<<<<< HEAD 13 | /public/index.html 14 | ======= 15 | /public 16 | >>>>>>> 81730e407dd4956e4f4a55fd3982a0c99db59e51 17 | 18 | .babelrc 19 | .eslintrc.js 20 | webpack.config.js 21 | server.js 22 | /tests 23 | karma.conf.js 24 | -------------------------------------------------------------------------------- /src/components/insertFunctions.js: -------------------------------------------------------------------------------- 1 | import { 2 | cssChunk 3 | } from './cssChunk'; 4 | import { 5 | mirageChunk 6 | } from './chunk'; 7 | 8 | function insertCss(){ 9 | document.head.insertAdjacentHTML('beforeend', cssChunk); 10 | } 11 | 12 | function insertHtml(){ 13 | document.body.insertAdjacentHTML('afterbegin', mirageChunk); 14 | } 15 | 16 | export { 17 | insertCss, 18 | insertHtml 19 | }; 20 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | var webpackConfig = require('./webpack.config.js') 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | browsers: ['Chrome'], 6 | singleRun: true, 7 | frameworks: ['mocha'], 8 | files: ['tests/**/*.test.js'], 9 | preprocessors: { 10 | './**/*.test.js': ['webpack', 'sourcemap'] 11 | }, 12 | reporters: ['mocha'], 13 | client: { 14 | mocha: { 15 | timeout: '5000' 16 | } 17 | }, 18 | webpack: webpackConfig, 19 | webpackServer: { 20 | noInfo: true 21 | } 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /tests/client/sockets.test.js: -------------------------------------------------------------------------------- 1 | // import * as servers from '../../server.js'; 2 | 3 | // import io from 'socket.io-client'; 4 | // import chai, { expect } from 'chai'; 5 | // const should = chai.should(); 6 | 7 | // client side socket test 8 | // describe("Connects to the socket server", function () { 9 | // 10 | // const options = { 11 | // transports: ['websocket'], 12 | // 'force new connection': true 13 | // }; 14 | // 15 | // it("echos message", function (done) { 16 | // const client = io.connect("http://localhost:8000", options); 17 | // 18 | // client.once("joinRoom", function (message) { 19 | // message.should.equal(true); 20 | // done(); 21 | // }); 22 | // }); 23 | // }); 24 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "es6": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "parserOptions": { 9 | "sourceType": "module" 10 | }, 11 | "rules": { 12 | "indent": [ 13 | "error", 14 | 2 15 | ], 16 | "linebreak-style": [ 17 | "error", 18 | "unix" 19 | ], 20 | "quotes": [ 21 | "error", 22 | "single" 23 | ], 24 | "semi": [ 25 | "error", 26 | "always" 27 | ], 28 | "comma-dangle": 0, 29 | "no-unused-vars": 0, 30 | "no-console": 0 31 | }, 32 | "plugins": [ 33 | "react" 34 | ] 35 | }; 36 | -------------------------------------------------------------------------------- /server_cert/server.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIB7jCCAVcCAQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCmNhbGlmb3JuaWEx 3 | EDAOBgNVBAcTB2J1cmJhbmsxETAPBgNVBAoTCHBlcnNvbmFsMQ4wDAYDVQQDEwVC 4 | bGFrZTEjMCEGCSqGSIb3DQEJARYUaWFtc29ibGFrZUBnbWFpbC5jb20wgZ8wDQYJ 5 | KoZIhvcNAQEBBQADgY0AMIGJAoGBAMjnhpZ7f4RXcdQFGgzhSRopWhJ6bviRfAHQ 6 | g/TDcRxC5/yADu/P6Xm0Gga6SFB8yjFnebKkH+NkP5tRriZ2+vlAyNEZ7gu7f5Bl 7 | xi9YnquJfmVY0JH4NpSWlh8rPZNr+qAF4du9zDfpbhkHdSYCHI6+NKQTJI5yGQ18 8 | ViM06eeJAgMBAAGgMjAXBgkqhkiG9w0BCQIxChMIc3RyZWFtYXIwFwYJKoZIhvcN 9 | AQkHMQoTCHN0cmVhbWFyMA0GCSqGSIb3DQEBBQUAA4GBAMQtBL8Y4DK8mIuqvM6K 10 | QcacSm9nkWFAqQ3CLA9fSDP6kFl7XsW1/XQ0YJ5PAdw6WCtyP/+mEviVpc03xTtG 11 | Vzbsgu/CLfnidMmfa8FW8b+aKOuG/E/gdcq6/q/k2ybYdVcpWhW4Y5vLQh6IONLI 12 | tENYmVEDUVY7DSDfvl1UEUDe 13 | -----END CERTIFICATE REQUEST----- 14 | -------------------------------------------------------------------------------- /server_cert/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXQIBAAKBgQDI54aWe3+EV3HUBRoM4UkaKVoSem74kXwB0IP0w3EcQuf8gA7v 3 | z+l5tBoGukhQfMoxZ3mypB/jZD+bUa4mdvr5QMjRGe4Lu3+QZcYvWJ6riX5lWNCR 4 | +DaUlpYfKz2Ta/qgBeHbvcw36W4ZB3UmAhyOvjSkEySOchkNfFYjNOnniQIDAQAB 5 | AoGANGxdrTSqkJD0zdtK8eNzXNU/gKTcAhaMYjjTxfGduT8oI7YSr7bz+GLri6fe 6 | KMp3RC75DYlQHNjS1CebJsRTNnKYb0WKeP6arGDwiRV7RLp86FTQLaA5YOvbqtY1 7 | 9dpbhUtvrYn4OPfA9MX+BBR80K8ak2K1FY6wpVroLWnv1nUCQQDt78Ohic3zOdFK 8 | MsQRA7YeX0GKGhlS8dCeiy59uo319SfCj6YGSMxzDj3KFBq29pts7Oap/yQgQmtf 9 | d/gPf2k7AkEA2CgM+gUGXNark+uIDTPXy/PKL9wVgkyEibiJFodRFYjs8FvC0DPH 10 | 6rELkDXqMAY93mW/Rxj8gQU73DR3ElsGCwJBAL8LTavNpwSNPuNxoYX70eXPHCOT 11 | 0xOLP7OdE0Y2XTfIkRUiG+9d0nLrU2eG/vA7UA6EpKyIROx2CBqOjCl9ZvMCQQDB 12 | DIwCY6h6E9NrKYj52ZpacaHauqGPHI2mfmQY9cNqCz8pr5o+3nAm3/Ym1rAQMhLh 13 | ZFe/xMYG6Mi8czMA9sBfAkB4UJgRmxkNh675LUFhI+TxGy1XcnqNukHu2i54G7N7 14 | Kgq7ARMGKyanRO964EWQIDgBGYKJmEwtY+RHE5nvaPci 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /server_cert/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICbzCCAdgCCQD0tRGuOruwzDANBgkqhkiG9w0BAQUFADB8MQswCQYDVQQGEwJV 3 | UzETMBEGA1UECBMKY2FsaWZvcm5pYTEQMA4GA1UEBxMHYnVyYmFuazERMA8GA1UE 4 | ChMIcGVyc29uYWwxDjAMBgNVBAMTBUJsYWtlMSMwIQYJKoZIhvcNAQkBFhRpYW1z 5 | b2JsYWtlQGdtYWlsLmNvbTAeFw0xNjA4MTIwNDQ2MjNaFw0xNzA4MTIwNDQ2MjNa 6 | MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpjYWxpZm9ybmlhMRAwDgYDVQQHEwdi 7 | dXJiYW5rMREwDwYDVQQKEwhwZXJzb25hbDEOMAwGA1UEAxMFQmxha2UxIzAhBgkq 8 | hkiG9w0BCQEWFGlhbXNvYmxha2VAZ21haWwuY29tMIGfMA0GCSqGSIb3DQEBAQUA 9 | A4GNADCBiQKBgQDI54aWe3+EV3HUBRoM4UkaKVoSem74kXwB0IP0w3EcQuf8gA7v 10 | z+l5tBoGukhQfMoxZ3mypB/jZD+bUa4mdvr5QMjRGe4Lu3+QZcYvWJ6riX5lWNCR 11 | +DaUlpYfKz2Ta/qgBeHbvcw36W4ZB3UmAhyOvjSkEySOchkNfFYjNOnniQIDAQAB 12 | MA0GCSqGSIb3DQEBBQUAA4GBAHCWPoXRXha39Oe2mGuSssn8j2YDYpFFurDzkNfw 13 | Q6vAsG3enmbYQNYrvA1e3jLIOZ0TqLy7+wrgxjnVpRTSgqByAD+APZ+Dhm3S93Nv 14 | sZTBBVPCiEBzRzHwzuwLNxkBCrlYGEaoOt8kIgkHtsSbSGrmFa2tyDdIr67BtmVD 15 | eFRC 16 | -----END CERTIFICATE----- 17 | -------------------------------------------------------------------------------- /server_cert/server.enc.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: DES-EDE3-CBC,1F2FBB429BF9359F 4 | 5 | 0aMnb4JjbgZbLxdpjCqw9zY3vjt1wYWvn2cuFZt2aF9qpR0elyzr2ngdA349MQkM 6 | Juh6QO+gyJgWgbCBejOWK4vADq9zQc2HmpPorEk5g1yFGlYqwcg92WmXt5DDnMGH 7 | TXNw5dTgLHS51/ZFjcmqwjhHQvW7iV9fp9a55jzgpXJodeblwnLz9WIhvBOWLVkQ 8 | WfKozKd9txdU93oVotzopEqwWS1PoCSurcj6Jy2ycoEDmOTJP2nwulBI6G/wuVsF 9 | z+zT2kaKtbN4Ej9eR48bypVWHtibDiScEYlCzJgnvYeZYejf1YDNiAprfHtim9tQ 10 | //ExXVC0SaQlV3CTjB338iO9NS9NmRitR2eUtOzlWt9zYZ5bfocAp5u6YloeB10Y 11 | 3bZN72DM2LBW4dc7ruLVnc2ndPV5NIyIyv+sJZHaZAJ5J5Pph7DlI69lwOyKS4A/ 12 | cyNW6ZulUpfb4PYERoKr04RoWFs1DHT2Z6EWkeIAwRz4clijcY5z9170LwnP33b0 13 | Id398IBD2aWQFkDut/d7qQ9QkuqMyC9aPxUl6UIzkNt4kjSKtv8OH+AGNtaDXkjq 14 | tQ6em4K9/qZlXi1vlpo1+HHxu9a2B386Dj000d5m0p7FY6KwXfWI664IKrD/Dx3t 15 | iO0UGIbFLayp9GuVqMyTeQMcT8SmtWmoF7oz5iR4z+3W42xenUT2sARLCqBZUnLS 16 | NiZd+8MEbcJzccSLsqGpi4ZU5AGK2wRiUUzp37f5ydiijE6BOF1gWh1ISqsRqKuB 17 | nTkXZaBtqP6vVVRMb1qExsbye9pnB9VdNMyT2erKtRXsNSYlU/uaRg== 18 | -----END RSA PRIVATE KEY----- 19 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | 4 | // NODE_ENV set to production in heroku, so defaulting to 'development' here 5 | process.env.NODE_ENV = process.env.NODE_ENV || 'development'; 6 | 7 | module.exports = { 8 | entry: [ 9 | './public/index.js', 10 | ], 11 | output: { 12 | path: path.join(__dirname, './public'), 13 | filename: 'bundle.js', 14 | publicPath: '/' 15 | }, 16 | module: { 17 | loaders: [{ 18 | loader: 'babel', 19 | exclude: /node_modules/, 20 | query: { 21 | presets: ['react', 'es2015', 'stage-1'], 22 | }, 23 | }], 24 | }, 25 | devtool: process.env.NODE_ENV !== 'production' ? 'inline-sourcemap' : null, 26 | plugins: process.env.NODE_ENV !== 'production' ? [ 27 | new webpack.optimize.OccurrenceOrderPlugin(), 28 | new webpack.NoErrorsPlugin()] : [ 29 | new webpack.optimize.DedupePlugin(), 30 | new webpack.optimize.Occurence.Plugin(), 31 | new webpack.optimize.UglifyJsPlugin({ mangle: false, sourcemap: false }), 32 | ], 33 | resolve: { 34 | root: __dirname, 35 | // this allows up to use aliases when we require from the following folders 36 | modulesDirectories: [ 37 | 'node_modules', 38 | './src/components', 39 | './src/reducers', 40 | './src/actions', 41 | ], 42 | extensions: ['', '.js', '.jsx'], 43 | }, 44 | 45 | //excluding sourcemaps when on heroku 46 | devtool: process.env.NODE_ENV === 'production' ? undefined : 'cheap-module-eval-source-map' 47 | }; 48 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const http = require('http'); 5 | const https = require('https'); 6 | const express = require('express'); 7 | const app = express(); 8 | const favicon = require('serve-favicon'); 9 | const path = require('path'); 10 | const mirageSockets = require('./mirageSockets.js'); 11 | 12 | let env = process.env.NODE_ENV = process.env.NODE_ENV || 'development'; 13 | let httpPort = process.env.PORT || 8000; 14 | // let httpsPort = process.env.PORT || 1337; 15 | 16 | let server = http.createServer(app).listen(httpPort, function(){ 17 | console.log('Listening on ' + httpPort); 18 | }); 19 | 20 | const options = { 21 | key: fs.readFileSync(__dirname + '/server_cert/server.key'), 22 | cert: fs.readFileSync(__dirname + '/server_cert/server.crt') 23 | }; 24 | 25 | 26 | // const server = https.createServer(options, app).listen(port, function(){ 27 | // console.log('Listening on ' + port) 28 | // }); 29 | 30 | // redirects all incoming requests from http protocol to https equivalent 31 | // app.use((req, res, next) => { 32 | // if (req.headers['x-forwarded-proto'] === 'http') { 33 | // res.redirect('https://' + req.hostname + req.url); 34 | // } else { 35 | // next(); 36 | // } 37 | // }); 38 | 39 | app.use('/', express.static(path.join(__dirname, 'public'))); 40 | app.use(favicon(path.join(__dirname,'public','favicon.ico'))); 41 | 42 | 43 | // let httpsServer = https.createServer(options, app).listen(httpsPort, function(){ 44 | 45 | // console.log('Also listening on ' + httpsPort); 46 | // }); 47 | 48 | mirageSockets(server); 49 | -------------------------------------------------------------------------------- /src/utils/domReady.js: -------------------------------------------------------------------------------- 1 | let domReady = function(callback) { 2 | 3 | let ready = false; 4 | 5 | let detach = function() { 6 | if(document.addEventListener) { 7 | document.removeEventListener('DOMContentLoaded', completed); 8 | window.removeEventListener('load', completed); 9 | } else { 10 | document.detachEvent('onreadystatechange', completed); 11 | window.detachEvent('onload', completed); 12 | } 13 | }; 14 | let completed = function() { 15 | if(!ready && (document.addEventListener || event.type === 'load' || document.readyState === 'complete')) { 16 | ready = true; 17 | detach(); 18 | callback(); 19 | } 20 | }; 21 | 22 | if(document.readyState === 'complete') { 23 | callback(); 24 | } else if(document.addEventListener) { 25 | document.addEventListener('DOMContentLoaded', completed); 26 | window.addEventListener('load', completed); 27 | } else { 28 | document.attachEvent('onreadystatechange', completed); 29 | window.attachEvent('onload', completed); 30 | 31 | let top = false; 32 | 33 | try { 34 | top = window.frameElement == null && document.documentElement; 35 | } catch(e) {console.log(e);} 36 | 37 | if(top && top.doScroll) { 38 | (function scrollCheck() { 39 | if(ready) return; 40 | 41 | try { 42 | top.doScroll('left'); 43 | } catch(e) { 44 | return setTimeout(scrollCheck, 50); 45 | } 46 | 47 | ready = true; 48 | detach(); 49 | callback(); 50 | })(); 51 | } 52 | } 53 | }; 54 | 55 | export { domReady }; 56 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | insertCss, 3 | insertHtml 4 | } from './components/insertFunctions'; 5 | 6 | import { 7 | mirageApp 8 | } from './components/mirageApp'; 9 | 10 | 11 | export function Mirage() { 12 | 13 | this.domIds = null; 14 | 15 | this.state = {}; 16 | 17 | this.events = {}; 18 | 19 | Object.defineProperties(this.events, { 20 | 'initial': { 21 | value: null, 22 | writable: true 23 | }, 24 | 'preStream': { 25 | value: null, 26 | writable: true 27 | }, 28 | 'localStream': { 29 | value: null, 30 | writable: true 31 | }, 32 | 'readyConnect': { 33 | value: null, 34 | writable: true 35 | }, 36 | 'connectTriggered': { 37 | value: null, 38 | writable: true 39 | }, 40 | 'streams': { 41 | value: null, 42 | writable: true 43 | }, 44 | 'onMessage': { 45 | value: null, 46 | writable: true 47 | }, 48 | 'onData': { 49 | value: null, 50 | writable: true 51 | }, 52 | 'nonInitiatorData': { 53 | value: null, 54 | writable: true 55 | }, 56 | 'end': { 57 | value: null, 58 | writable: true 59 | } 60 | }); 61 | 62 | Object.preventExtensions(this.events); 63 | 64 | this.on = (event, func) => { 65 | if (this.events.hasOwnProperty(event)) { 66 | this.events[event] = func; 67 | } 68 | }; 69 | 70 | //methods for mirage component usage, very opinionated// 71 | this.insertCss = insertCss; 72 | this.insertChunk = insertHtml; 73 | //end opinions// 74 | 75 | this.startApp = () => { 76 | mirageApp(this.events, this.state, this.domIds); 77 | }; 78 | 79 | } 80 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mirage-js", 3 | 4 | "version": "1.0.17", 5 | "description": "Allows developers access to a customizable video chat component", 6 | "main": "./lib/index.js", 7 | "scripts": { 8 | "prepublish": "node_modules/babel-cli/bin/babel.js src --out-dir lib", 9 | "test": "karma start", 10 | "dev": "nodemon server.js", 11 | "start": "node server.js" 12 | }, 13 | "babel": { 14 | "presets": ["es2015"] 15 | }, 16 | "authors": [ 17 | "Kevin ", 18 | "Morgan ", 19 | "Blake " 20 | ], 21 | "license": "ISC", 22 | "homepage": "https://github.com/team-genjutsu/miragejs", 23 | "dependencies": { 24 | "socket.io": "^1.3.6", 25 | "socket.io-client": "^1.4.8", 26 | "webrtc-adapter": "^2.0.2" 27 | }, 28 | "devDependencies": { 29 | "babel-cli": "^6.14.0", 30 | "babel-core": "^6.2.1", 31 | "babel-loader": "^6.2.0", 32 | "babel-preset-es2015": "^6.1.18", 33 | "babel-preset-react": "^6.1.18", 34 | "babel-preset-react-hmre": "^1.1.1", 35 | "babel-preset-stage-1": "^6.13.0", 36 | "babel-tape-runner": "^2.0.1", 37 | "chai": "^3.5.0", 38 | "eslint": "^3.2.2", 39 | "eslint-config-airbnb": "^10.0.0", 40 | "eslint-plugin-import": "^1.13.0", 41 | "eslint-plugin-jsx-a11y": "^2.1.0", 42 | "eslint-plugin-react": "^6.0.0", 43 | "express": "^4.14.0", 44 | "karma": "^0.13.22", 45 | "karma-chrome-launcher": "^0.2.2", 46 | "karma-mocha": "^0.2.2", 47 | "karma-mocha-reporter": "^2.0.0", 48 | "karma-sourcemap-loader": "^0.3.7", 49 | "karma-webpack": "^1.7.0", 50 | "mocha": "^3.0.2", 51 | "nodemon": "^1.10.0", 52 | "serve-favicon": "^2.3.0", 53 | "sinon": "^2.0.0-pre.2", 54 | "tape": "^4.6.0", 55 | "webpack": "^1.12.9" 56 | }, 57 | "engines": { 58 | "node": "6.2.2" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/components/mirageStore.js: -------------------------------------------------------------------------------- 1 | 2 | function roomStore(url) { 3 | this.vendorUrl = url; 4 | this.chattersClient = []; 5 | this.chatterThisClient = null; 6 | this.roomID = null; 7 | this.startMedia = false; 8 | } 9 | 10 | function mediaStore(localBooth, remoteBooth) { 11 | this.peerBooth = document.getElementById(remoteBooth); 12 | // this.peerMedia = null; 13 | this.peerVideo = null; 14 | this.peerCanvas = null; 15 | // this.peerCanvasListeners = []; 16 | this.peerContext = null; 17 | this.myBooth = document.getElementById(localBooth); 18 | // this.myMedia = null; 19 | this.myCanvas = null; 20 | // this.myCanvasListeners = []; 21 | this.myVideo = null; 22 | this.myContext = null; 23 | } 24 | 25 | 26 | function rtcStore() { 27 | this.sdpConstraints = { 28 | 'mandatory': { 29 | 'OfferToReceiveAudio': true, 30 | 'OfferToReceiveVideo': true 31 | } 32 | }; 33 | this.peerConn = null; 34 | this.isChannelReady = false; 35 | this.isInitiator = false; 36 | this.isStarted = false; 37 | this.localStream = null; 38 | this.remoteStream = null; 39 | this.turnReady = null; 40 | this.dataChannel = null; 41 | //stun server to use 42 | this.pcConfig = { 43 | 'iceServers': [{ 44 | //public stun server to use, change to local if preferred 45 | 'url': 'stun:stun.l.google.com:19302' 46 | }, 47 | { 48 | //public turn server to use, change to local if preferred 49 | url: 'turn:numb.viagenie.ca', 50 | credential: 'muazkh', 51 | username: 'webrtc@live.com' 52 | }] 53 | }; 54 | } 55 | 56 | function elementStore(idArray){ 57 | this.joinId = idArray[0]; 58 | this.connectId = idArray[1]; 59 | this.disconnectId = idArray[2]; 60 | this.roomInputId = idArray[3]; 61 | this.localBoothId = idArray[4]; 62 | this.localVidId = idArray[5]; 63 | this.localCanvasId = idArray[6]; 64 | this.remoteBoothId = idArray[7]; 65 | this.remoteVidId = idArray[8]; 66 | this.remoteCanvasId = idArray[9]; 67 | this.joinButton = document.getElementById(this.joinId); 68 | this.connectElement = document.getElementById(this.connectId); 69 | this.disconnectElement = document.getElementById(this.disconnectId); 70 | } 71 | 72 | export { 73 | roomStore, 74 | mediaStore, 75 | rtcStore, 76 | elementStore 77 | }; 78 | -------------------------------------------------------------------------------- /src/utils/utilStore.js: -------------------------------------------------------------------------------- 1 | 2 | function animeStore(animeBtnId, animeDispId, emojiClass, functionArray) { 3 | this.anime = { 4 | paste: functionArray[0], //paste, 5 | bounce: functionArray[1], //bounce, 6 | orbit: functionArray[2] //orbit 7 | }; 8 | this.animeKeys = ['paste', 'bounce', 'orbit']; 9 | this.idx = 1; 10 | this.animeBtn = document.getElementById(animeBtnId); 11 | this.currAnime = document.getElementById(animeDispId); 12 | this.currentAnimation = null; 13 | this.temp = null; 14 | this.raf = null; 15 | this.rafObj = {}; 16 | this.emoImg = new Image(); 17 | this.currentImg = null; 18 | this.emojis = ['https://cdnjs.cloudflare.com/ajax/libs/emojione/2.2.6/assets/png/1f4a9.png', 19 | 'https://cdnjs.cloudflare.com/ajax/libs/emojione/2.2.6/assets/png/1f4af.png', 20 | 'https://cdnjs.cloudflare.com/ajax/libs/emojione/2.2.6/assets/png/1f354.png', 21 | 'https://cdnjs.cloudflare.com/ajax/libs/emojione/2.2.6/assets/png/1f436.png', 22 | 'https://cdnjs.cloudflare.com/ajax/libs/emojione/2.2.6/assets/png/1f414.png', 23 | 'https://cdnjs.cloudflare.com/ajax/libs/emojione/2.2.6/assets/png/1f389.png', 24 | 'https://cdnjs.cloudflare.com/ajax/libs/emojione/2.2.6/assets/png/1f60d.png', 25 | 'https://cdnjs.cloudflare.com/ajax/libs/emojione/2.2.6/assets/png/1f4b8.png', 26 | 'https://cdnjs.cloudflare.com/ajax/libs/emojione/2.2.6/assets/png/1f951.png' 27 | ]; 28 | this.emojiSpillOver = []; 29 | this.addEmoji = (emojiArr) => { 30 | if (emojiArr != undefined || emojiArr.length > 0) { 31 | emojiArr.forEach( (ele, idx) => { 32 | if(this.emojis.length >= 9){ 33 | this.emojiSpillOver.push(this.emojis.pop()); 34 | this.emojis.unshift(ele); 35 | } 36 | }); 37 | } 38 | }; 39 | this.emoBtns = document.getElementsByClassName(emojiClass); 40 | } 41 | 42 | function filterStore(filterDispId, filterBtnId) { 43 | this.currFilter = document.getElementById(filterDispId); 44 | this.filterBtn = document.getElementById(filterBtnId); 45 | this.filters = ['blur(5px)', 'brightness(0.4)', 'contrast(200%)', 'grayscale(100%)', 'hue-rotate(90deg)', 'invert(100%)', 'sepia(100%)', 'saturate(20)', 'none']; 46 | this.idx = 0; 47 | this.addFilters = (filterArr) => { 48 | if (filterArr != undefined || filterArr.length > 0) { 49 | filterArr.forEach((ele, idx) => { 50 | if(this.filters.indexOf(ele) < 0){ 51 | this.filters.push(ele); 52 | } 53 | }); 54 | } 55 | }; 56 | } 57 | 58 | export { 59 | filterStore, 60 | animeStore 61 | }; 62 | -------------------------------------------------------------------------------- /src/components/mediaGenerator.js: -------------------------------------------------------------------------------- 1 | function mediaGenerator(stream, localBool, state) { 2 | 3 | //please god, don't judge me for this monstrosity! 4 | let boothId, vidId, canId; 5 | if(localBool){ 6 | boothId = state.elementState.localBoothId; 7 | vidId = state.elementState.localVidId; 8 | canId = state.elementState.localCanvasId; 9 | }else{ 10 | boothId = state.elementState.remoteBoothId; 11 | vidId = state.elementState.remoteVidId; 12 | canId = state.elementState.remoteCanvasId; 13 | } 14 | 15 | let vidContainer = document.getElementById('MRGvidContainer'); 16 | let vidContainerStyle = window.getComputedStyle(vidContainer); 17 | 18 | //size dependent upon width 19 | let styleWidth = vidContainerStyle.getPropertyValue('width'); 20 | let videoWidth = Math.round(+styleWidth.substring(0, styleWidth.length - 2)); 21 | let videoHeight = Math.round((videoWidth / 4) * 3); 22 | vidContainer.style.height = videoHeight +'px'; 23 | let video = document.createElement('video'); 24 | video.setAttribute('id', vidId); 25 | video.setAttribute('width', '' + videoWidth); 26 | video.setAttribute('height', '' + videoHeight); 27 | document.getElementById(boothId).appendChild(video); 28 | video.src = state.roomState.vendorUrl.createObjectURL(stream); 29 | 30 | video.play(); 31 | 32 | //draw local overlay canvas// 33 | let canvas = document.createElement('canvas'); 34 | canvas.setAttribute('id', canId); 35 | document.getElementById(boothId).appendChild(canvas); 36 | let context = canvas.getContext('2d'); 37 | 38 | //width and height should eventually be translated to exact coordination 39 | //with incoming video stream 40 | canvas.width = videoWidth; 41 | canvas.height = videoHeight; 42 | 43 | //draws blank canvas on top of video 44 | context.strokeRect(0, 0, videoWidth, videoHeight); 45 | //end// 46 | 47 | //crucial styling// 48 | document.getElementById(boothId).style.position = 'absolute'; 49 | video.style.position = 'absolute'; 50 | canvas.style.position = 'absolute'; 51 | canvas.style.zIndex = '2147483000'; 52 | 53 | if(localBool){ 54 | state.mediaState.myVideo = video; 55 | state.mediaState.myCanvas = canvas; 56 | state.mediaState.myContext = context; 57 | }else{ 58 | state.mediaState.peerVideo = video; 59 | state.mediaState.peerCanvas = canvas; 60 | state.mediaState.peerContext = context; 61 | } 62 | 63 | // return { 64 | // video: video, 65 | // canvas: canvas, 66 | // context: context 67 | // }; 68 | } 69 | 70 | export { 71 | mediaGenerator 72 | }; 73 | -------------------------------------------------------------------------------- /src/components/chunk.js: -------------------------------------------------------------------------------- 1 | let mirageChunk = `
2 |
3 | 4 |
5 |
6 |
7 |

Choose a room!

8 |
9 |
10 | 11 | 12 |
13 |
14 | 15 | 16 |
17 |
18 | 19 | 20 | 21 | 22 |
23 |
24 | 25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | 33 |
34 |
35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 |
47 |
48 |
49 |
50 |
51 | 52 |
`; 53 | 54 | export { 55 | mirageChunk 56 | }; 57 | -------------------------------------------------------------------------------- /src/utils/listenerFuncs.js: -------------------------------------------------------------------------------- 1 | 2 | function filterListener(vid, whoisFilter, currFilter, whoisBool, channel, func) { 3 | document.getElementById(whoisFilter).addEventListener('click', () => { 4 | let filterDataObj; 5 | // sends boolean data about remote filter application and adds filter on your side 6 | filterDataObj = JSON.stringify({ 7 | filter: whoisBool, 8 | filterType: currFilter.innerHTML 9 | }); 10 | func(vid, currFilter.innerHTML); 11 | channel.send(filterDataObj); 12 | }, false); 13 | } 14 | 15 | function peerTrackingListener(vid, cv, ctx, img, channel, trackFace, trackingObj, videoStream) { 16 | document.getElementById('MRGpeerTracking').addEventListener('click', () => { 17 | let emoji = img; 18 | //console.log(trackingObj); 19 | trackFace(vid, cv, ctx, trackingObj, videoStream, emoji, channel); 20 | }, false); 21 | } 22 | 23 | 24 | function myTrackingListener(vid, cv, ctx, img, trackingObj, channel) { 25 | document.getElementById('MRGmyTracking').addEventListener('click', () => { 26 | let trackingDataObj; 27 | // console.log(channel.readyState); 28 | trackingDataObj = JSON.stringify({ 29 | myTrack: vid, 30 | image: img, 31 | tracking: trackingObj 32 | }); 33 | channel.send(trackingDataObj); 34 | }, false); 35 | } 36 | 37 | 38 | function animationListener(canvas, img, animeObj, animeEle, context, reqAnim, array, channel, local, func, rafObj) { 39 | 40 | let channelAnime = (event) => { 41 | 42 | let position = func(canvas, event); 43 | let emoImage = new Image(); 44 | emoImage.src = img.src; 45 | // let currImg = 46 | let animation = animeObj[animeEle.innerHTML]; 47 | //animation for local display and data transmission to peer 48 | animation(canvas, context, event, position, emoImage, reqAnim, array, rafObj); 49 | 50 | let canvasObj = JSON.stringify({ 51 | animation: animeEle.innerHTML, 52 | localEmoji: local, 53 | currentImg: emoImage.src, 54 | position: { 55 | x: position.x, 56 | y: position.y 57 | } 58 | }); 59 | 60 | channel.send(canvasObj); 61 | }; 62 | 63 | canvas.addEventListener('click', channelAnime, false); 64 | return channelAnime; 65 | } 66 | 67 | function clearListener(channel, func, clearButton, animeSt, mediaSt) { 68 | clearButton.addEventListener('click', (event) => { 69 | func(animeSt, mediaSt); 70 | channel.send(JSON.stringify({'type' : 'clear'})); 71 | }, false); 72 | } 73 | //send to other client to run clear function 74 | export { 75 | filterListener, 76 | animationListener, 77 | clearListener, 78 | peerTrackingListener, 79 | myTrackingListener 80 | }; 81 | -------------------------------------------------------------------------------- /src/components/mergeRectangles.js: -------------------------------------------------------------------------------- 1 | // onmessage = function(event) { 2 | // var disjointSet = event.data[1]; 3 | // //console.log("this should be an object", disjointSet); 4 | // disjointSet.find = function (i) { 5 | // if (this.parent[i]===i) { 6 | // return i; 7 | // }else { 8 | // return this.parent[i] = this.find(this.parent[i]); 9 | // } 10 | // }; 11 | 12 | // var rects = event.data[0]; 13 | // console.log(rects); 14 | // var intersectRect = function(x0, y0, x1, y1, x2, y2, x3, y3) { 15 | // return !(x2 > x1 || x3 < x0 || y2 > y1 || y3 < y0); 16 | // }; 17 | // // console.log("inside of merge rectangles disjointedSet", disjointSet); 18 | // for (var i = 0; i < rects.length; i++) { 19 | // var r1 = rects[i]; 20 | // for (var j = 0; j < rects.length; j++) { 21 | // var r2 = rects[j]; 22 | // if (intersectRect(r1.x, r1.y, r1.x + r1.width, r1.y + r1.height, r2.x, r2.y, r2.x + r2.width, r2.y + r2.height)) { 23 | // var x1 = Math.max(r1.x, r2.x); 24 | // var y1 = Math.max(r1.y, r2.y); 25 | // var x2 = Math.min(r1.x + r1.width, r2.x + r2.width); 26 | // var y2 = Math.min(r1.y + r1.height, r2.y + r2.height); 27 | // var overlap = (x1 - x2) * (y1 - y2); 28 | // var area1 = (r1.width * r1.height); 29 | // var area2 = (r2.width * r2.height); 30 | 31 | // if ((overlap / (area1 * (area1 / area2)) >= this.REGIONS_OVERLAP) && 32 | // (overlap / (area2 * (area1 / area2)) >= this.REGIONS_OVERLAP)) { 33 | // disjointSet.union(i, j); 34 | // } 35 | // } 36 | // } 37 | // } 38 | 39 | // var map = {}; 40 | // for (var k = 0; k < disjointSet.length; k++) { 41 | // console.log(disjointSet.length); 42 | // var rep = disjointSet.find(k); 43 | // if (!map[rep]) { 44 | // map[rep] = { 45 | // total: 1, 46 | // width: rects[k].width, 47 | // height: rects[k].height, 48 | // x: rects[k].x, 49 | // y: rects[k].y 50 | // }; 51 | // continue; 52 | // } 53 | // map[rep].total++; 54 | // map[rep].width += rects[k].width; 55 | // map[rep].height += rects[k].height; 56 | // map[rep].x += rects[k].x; 57 | // map[rep].y += rects[k].y; 58 | // } 59 | 60 | // var result = []; 61 | // Object.keys(map).forEach(function(key) { 62 | // var rect = map[key]; 63 | // result.push({ 64 | // total: rect.total, 65 | // width: (rect.width / rect.total + 0.5) | 0, 66 | // height: (rect.height / rect.total + 0.5) | 0, 67 | // x: (rect.x / rect.total + 0.5) | 0, 68 | // y: (rect.y / rect.total + 0.5) | 0 69 | // }); 70 | // }); 71 | // console.log("rectangle array", result); 72 | // // console.log("merge rectangle results", result); 73 | // postMessage({ 74 | // rectangle: result 75 | // }); 76 | // } 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/components/evalStagesCalcs.js: -------------------------------------------------------------------------------- 1 | onmessage = function(event) { 2 | 3 | 4 | var data = event.data[0]; 5 | var integralImage = event.data[1]; 6 | var integralImageSquare = event.data[2]; 7 | var tiltedIntegralImage = event.data[3]; 8 | var i = event.data[4]; 9 | var j = event.data[5]; 10 | var width = event.data[6]; 11 | var blockWidth = event.data[7]; 12 | var blockHeight = event.data[8]; 13 | var scale = event.data[9]; 14 | var inverseArea = 1.0 / (blockWidth * blockHeight); 15 | var wbA = i * width + j; 16 | var wbB = wbA + blockWidth; 17 | var wbD = wbA + blockHeight * width; 18 | var wbC = wbD + blockWidth; 19 | var mean = (integralImage[wbA] - integralImage[wbB] - integralImage[wbD] + integralImage[wbC]) * inverseArea; 20 | var variance = (integralImageSquare[wbA] - integralImageSquare[wbB] - integralImageSquare[wbD] + integralImageSquare[wbC]) * inverseArea - mean * mean; 21 | 22 | var standardDeviation = 1; 23 | if (variance > 0) { 24 | standardDeviation = Math.sqrt(variance); 25 | } 26 | 27 | var length = data.length; 28 | 29 | for (var w = 2; w < length; ) { 30 | var stageSum = 0; 31 | var stageThreshold = data[w++]; 32 | var nodeLength = data[w++]; 33 | 34 | while (nodeLength--) { 35 | var rectsSum = 0; 36 | var tilted = data[w++]; 37 | var rectsLength = data[w++]; 38 | 39 | for (var r = 0; r < rectsLength; r++) { 40 | var rectLeft = (j + data[w++] * scale + 0.5) | 0; 41 | var rectTop = (i + data[w++] * scale + 0.5) | 0; 42 | var rectWidth = (data[w++] * scale + 0.5) | 0; 43 | var rectHeight = (data[w++] * scale + 0.5) | 0; 44 | var rectWeight = data[w++]; 45 | 46 | var w1; 47 | var w2; 48 | var w3; 49 | var w4; 50 | if (tilted) { 51 | // RectSum(r) = RSAT(x-h+w, y+w+h-1) + RSAT(x, y-1) - RSAT(x-h, y+h-1) - RSAT(x+w, y+w-1) 52 | w1 = (rectLeft - rectHeight + rectWidth) + (rectTop + rectWidth + rectHeight - 1) * width; 53 | w2 = rectLeft + (rectTop - 1) * width; 54 | w3 = (rectLeft - rectHeight) + (rectTop + rectHeight - 1) * width; 55 | w4 = (rectLeft + rectWidth) + (rectTop + rectWidth - 1) * width; 56 | rectsSum += (tiltedIntegralImage[w1] + tiltedIntegralImage[w2] - tiltedIntegralImage[w3] - tiltedIntegralImage[w4]) * rectWeight; 57 | } else { 58 | // RectSum(r) = SAT(x-1, y-1) + SAT(x+w-1, y+h-1) - SAT(x-1, y+h-1) - SAT(x+w-1, y-1) 59 | w1 = rectTop * width + rectLeft; 60 | w2 = w1 + rectWidth; 61 | w3 = w1 + rectHeight * width; 62 | w4 = w3 + rectWidth; 63 | rectsSum += (integralImage[w1] - integralImage[w2] - integralImage[w3] + integralImage[w4]) * rectWeight; 64 | // TODO: Review the code below to analyze performance when using it instead. 65 | // w1 = (rectLeft - 1) + (rectTop - 1) * width; 66 | // w2 = (rectLeft + rectWidth - 1) + (rectTop + rectHeight - 1) * width; 67 | // w3 = (rectLeft - 1) + (rectTop + rectHeight - 1) * width; 68 | // w4 = (rectLeft + rectWidth - 1) + (rectTop - 1) * width; 69 | // rectsSum += (integralImage[w1] + integralImage[w2] - integralImage[w3] - integralImage[w4]) * rectWeight; 70 | } 71 | } 72 | 73 | var nodeThreshold = data[w++]; 74 | var nodeLeft = data[w++]; 75 | var nodeRight = data[w++]; 76 | 77 | if (rectsSum * inverseArea < nodeThreshold * standardDeviation) { 78 | //console.log("plus nodeLeft", stageSum, nodeLeft); 79 | stageSum += nodeLeft; 80 | } else { 81 | //console.log("plus nodeRight", stageSum, nodeRight); 82 | stageSum += nodeRight; 83 | } 84 | } 85 | 86 | if (stageSum < stageThreshold) { 87 | //console.log("eval false", stageSum, stageThreshold); 88 | postMessage({ 89 | status: false 90 | }); 91 | 92 | } 93 | } 94 | //console.log("eval true"); 95 | postMessage({ 96 | status: true 97 | }); 98 | 99 | 100 | }; 101 | -------------------------------------------------------------------------------- /mirageSockets.js: -------------------------------------------------------------------------------- 1 | module.exports = function(server) { 2 | 'use strict'; 3 | const io = require('socket.io').listen(server); 4 | 5 | const connections = []; 6 | const rooms = []; 7 | 8 | //room constructor 9 | function Room(obj) { 10 | this.id = obj.roomId; 11 | this.members = []; 12 | this.addMember = function(member) { 13 | this.members.push(member); 14 | }; 15 | } 16 | 17 | //member constructor 18 | function Member(socketId, roomId, initiator) { 19 | this.id = socketId; 20 | this.roomId = roomId; 21 | this.initiator = initiator; 22 | this.signalId = null; 23 | } 24 | 25 | 26 | io.sockets.on('connection', function(socket) { 27 | connections.push(socket); 28 | console.log(socket.id + ' joined!'); 29 | 30 | //disconnecting users 31 | socket.once('disconnect', function() { 32 | console.log('disconnect triggered'); 33 | 34 | // console.log('room: ', rooms[0]) 35 | 36 | let member, 37 | room, 38 | otherMem; 39 | // index; 40 | 41 | rooms.forEach(function(ele, idx) { 42 | member = ele.members.filter(client => client.id === socket.id)[0]; 43 | if (member) { 44 | 45 | if (rooms[idx].members.length > 0) { 46 | rooms[idx].members.forEach((el, id) => { 47 | io.to(el.id).emit('updateChatters', member); 48 | socket.disconnect(); 49 | }); 50 | } 51 | 52 | ele.members.splice(ele.members.indexOf(member), 1); 53 | if (!rooms[idx].members.length) rooms.splice(idx, 1); 54 | } 55 | 56 | }); 57 | 58 | // console.log('after going through and disconnecting sockets',rooms) 59 | connections.splice(connections.indexOf(socket), 1); 60 | // console.log(socket.id + ' left room ' + member.roomId) 61 | 62 | // console.log('room: ', rooms[0]) 63 | }); 64 | 65 | //join room logic 66 | socket.on('joinRoom', (payload) => { 67 | 68 | payload = JSON.parse(payload); 69 | let roomCheck = rooms.filter(room => room.id === payload); 70 | if (roomCheck.length > 0) { 71 | if (roomCheck[0].members > 1) { 72 | io.to(socket.id).emit('process', JSON.stringify(false)); 73 | } else { 74 | io.to(socket.id).emit('process', JSON.stringify(true)); 75 | } 76 | } else { 77 | io.to(socket.id).emit('process', JSON.stringify(true)); 78 | } 79 | 80 | }); 81 | 82 | //initiate 83 | socket.on('initiate', (payload) => { 84 | 85 | payload = JSON.parse(payload); 86 | 87 | let existingRoom = rooms.filter(room => room.id === payload.roomId), 88 | room, 89 | member; 90 | 91 | if (existingRoom.length === 0) { 92 | 93 | room = new Room(payload); 94 | member = new Member(socket.id, payload.roomId, true); 95 | room.addMember(member); 96 | rooms.push(room); 97 | 98 | } else if (existingRoom[0].members.length === 1) { 99 | 100 | member = new Member(socket.id, payload.roomId, false); 101 | existingRoom[0].addMember(member); 102 | existingRoom[0].members.forEach((ele, idx) => { 103 | io.to(ele.id).emit('readyConnect', JSON.stringify('both connected')); 104 | }); 105 | } 106 | io.to(socket.id).emit('initiated', JSON.stringify(member)); 107 | 108 | }); 109 | 110 | //beginning of signaling 111 | socket.on('message', function(payload) { 112 | 113 | let sharedRoom = rooms.filter(room => room.id === payload.roomID)[0]; 114 | 115 | // not used for anything yet but maybe for chatroom fallback? 116 | // if (payload.who === 'all') { 117 | // sharedRoom.members.forEach((ele, idx) => { 118 | // io.to(ele.id).emit('message', payload.data); 119 | // }); 120 | // } else 121 | if (payload.who === 'other') { 122 | sharedRoom.members.forEach((ele, idx) => { 123 | if (ele.id !== socket.id) { 124 | io.to(ele.id).emit('message', payload.data); 125 | } 126 | }); 127 | } 128 | 129 | }); //end of signaling 130 | 131 | }); 132 | }; 133 | -------------------------------------------------------------------------------- /public/css/main.css: -------------------------------------------------------------------------------- 1 | .btmRight { 2 | bottom: 0; 3 | right: 0; 4 | } 5 | 6 | #fixed { 7 | position: fixed; 8 | width: 61px; 9 | height: 61px; 10 | bottom: 10px; 11 | right: 20px; 12 | } 13 | 14 | #materialBtn { 15 | z-index: 2147483647; 16 | position: fixed; 17 | overflow: visible; 18 | bottom: 10px; 19 | right: 20px; 20 | background-color: #F44336; 21 | width: 60px; 22 | height: 60px; 23 | border-radius: 100%; 24 | background: #F44336; 25 | border: none; 26 | outline: none; 27 | color: #FFF; 28 | font-size: 36px; 29 | box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); 30 | /* transition: .3s; */ 31 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 32 | } 33 | 34 | .emoji { 35 | display: inline; 36 | height: 50%; 37 | width: 50%; 38 | border-radius: 5px; 39 | } 40 | 41 | .elementToFadeInAndOut { 42 | -webkit-animation: fadeinout 1s linear forwards infinite; 43 | animation: fadeinout 1s linear forwards infinite; 44 | opacity: 0; 45 | } 46 | 47 | #emojiButtons { 48 | height: 22%; 49 | width: 13%; 50 | margin: 0 auto; 51 | } 52 | 53 | #optionBtns { 54 | display: inline-block; 55 | width: 100%; 56 | margin-left: -22%; 57 | } 58 | 59 | #animeBtn { 60 | position: absolute; 61 | } 62 | 63 | #filterBtns { 64 | display: inline-flex; 65 | position: relative; 66 | vertical-align: middle; 67 | } 68 | 69 | .logo { 70 | font-size: 75px; 71 | } 72 | 73 | .hidden { 74 | display: none; 75 | } 76 | 77 | .fixPos { 78 | position: fixed; 79 | } 80 | 81 | #demo { 82 | z-index: 2147483646; 83 | position: absolute; 84 | display: block; 85 | overflow: hidden; 86 | bottom: 0; 87 | right: 0; 88 | text-align: center; 89 | height: 700px; 90 | width: 900px; 91 | padding-top: 75%; 92 | border: 1px solid; 93 | margin-bottom: 80%; 94 | margin-right: 80%; 95 | border-radius: 10px; 96 | background-color: white; 97 | } 98 | 99 | #boothApp { 100 | text-align: center; 101 | height: inherit; 102 | width: inherit; 103 | } 104 | 105 | #booth { 106 | height: 80%; 107 | width: 100%; 108 | position: relative; 109 | display: inline-flex; 110 | } 111 | 112 | #selectImg { 113 | margin: 5px 20px 5px 5px; 114 | text-align: center; 115 | font-weight: bold; 116 | font-size: 16px; 117 | } 118 | 119 | #vidContainer { 120 | height: 100%; 121 | /*45em;*/ 122 | width: 75%; 123 | /*60em;*/ 124 | } 125 | 126 | #myBooth { 127 | margin-left: 5%; 128 | position: absolute; 129 | } 130 | 131 | #peerBooth { 132 | position: absolute; 133 | margin-left: 5%; 134 | } 135 | 136 | #myVideo { 137 | text-align: center; 138 | position: absolute; 139 | z-index: 2; 140 | visibility: visible; 141 | } 142 | 143 | #peerVideo { 144 | text-align: center; 145 | position: absolute; 146 | z-index: 2; 147 | visibility: visible; 148 | } 149 | 150 | #myCanvas { 151 | text-align: center; 152 | z-index: 2147483000; 153 | position: absolute; 154 | visibility: visible; 155 | } 156 | 157 | #peerCanvas { 158 | text-align: center; 159 | z-index: 2147483000; 160 | position: absolute; 161 | visibility: visible; 162 | } 163 | 164 | #connectivity-buttons { 165 | width: 100%; 166 | margin: 15px; 167 | } 168 | 169 | .connectButton { 170 | display: inline; 171 | } 172 | 173 | 174 | /*////// room selection start ///////////////////*/ 175 | 176 | #roomApp { 177 | height: 100%; 178 | width: 100%; 179 | background-color: white; 180 | } 181 | 182 | #recent-rooms-list { 183 | list-style-type: none; 184 | padding: 0 15px; 185 | } 186 | 187 | .roombtn { 188 | background-color: #4285F4; 189 | border: none; 190 | border-radius: 2px; 191 | color: white; 192 | font-size: 0.8em; 193 | margin: 0 5px 20px 5px; 194 | width: 8em; 195 | height: 2.75em; 196 | padding: 0.5em 0.7em 0.5em 0.7em; 197 | -webkit-box-shadow: 1px 1px 5px 0 rgba(0, 0, 0, .5); 198 | -moz-box-shadow: 1px 1px 5px 0 rgba(0, 0, 0, .5); 199 | box-shadow: 1px 1px 5px 0 rgba(0, 0, 0, .5); 200 | } 201 | 202 | .roombtn:active { 203 | background-color: #3367D6; 204 | } 205 | 206 | .roombtn:hover { 207 | background-color: #3B78E7; 208 | } 209 | 210 | .roombtn:focus { 211 | outline: none; 212 | -webkit-box-shadow: 0 10px 15px 0 rgba(0, 0, 0, .5); 213 | -moz-box-shadow: 0 10px 15px 0 rgba(0, 0, 0, .5); 214 | box-shadow: 0 10px 15px 0 rgba(0, 0, 0, .5); 215 | } 216 | 217 | .roombtn[disabled] { 218 | color: rgb(76, 76, 76); 219 | color: rgba(255, 255, 255, 0.3); 220 | background-color: rgb(30, 30, 30); 221 | background-color: rgba(255, 255, 255, 0.12); 222 | } 223 | 224 | #room-selection input[type=text] { 225 | border: none; 226 | border-bottom: solid 1px #4c4c4f; 227 | font-size: 1em; 228 | background-color: transparent; 229 | color: #ccc; 230 | padding: .4em 0; 231 | margin-right: 20px; 232 | width: 100%; 233 | display: block; 234 | } 235 | 236 | #room-selection input[type="text"]:focus { 237 | border-bottom: solid 2px #4285F4; 238 | outline: none; 239 | } 240 | 241 | #room-selection input[type="text"].invalid { 242 | border-bottom: solid 2px #F44336; 243 | } 244 | 245 | #room-selection label.error-label { 246 | color: #F44336; 247 | font-size: .85em; 248 | font-weight: 200; 249 | margin: 0; 250 | } 251 | 252 | #room-id-input-div { 253 | margin: 15px; 254 | } 255 | 256 | #room-id-input-buttons { 257 | margin: 15px; 258 | } 259 | 260 | #room-selection h1 { 261 | font-weight: 300; 262 | margin: 0 0 0.8em 0; 263 | padding: 0 0 0.2em 0; 264 | } 265 | 266 | div#room-selection { 267 | margin: 3em auto 0 auto; 268 | width: 25em; 269 | padding: 1em 1.5em 1.3em 1.5em; 270 | } 271 | 272 | #room-selection p { 273 | color: #eee; 274 | font-weight: 300; 275 | line-height: 1.6em; 276 | } 277 | 278 | 279 | /*////// room selection end /////////////////////*/ 280 | 281 | @media screen only and (max-width: 600px) { 282 | #myBooth, 283 | #peerBooth { 284 | width: 100%; 285 | } 286 | } 287 | 288 | .app { 289 | margin: 15px; 290 | } 291 | 292 | @keyframes fade { 293 | 0%, 294 | 100% { 295 | opacity: 0 296 | } 297 | 50% { 298 | opacity: 1 299 | } 300 | } 301 | 302 | @-webkit-keyframes fadeinout { 303 | 0%, 304 | 100% { 305 | opacity: 0; 306 | } 307 | 50% { 308 | opacity: 1; 309 | } 310 | } 311 | 312 | .blckQt { 313 | z-index: -1; 314 | } 315 | 316 | .aboutUs { 317 | z-index: -1; 318 | } 319 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mirage-js 2 | 3 |
4 | **Mirage-JS** is a lightweight developer library that gives developers access to a customizable and interactive video chat component. 5 | With a short setup process, a developer can write custom animations and filters or use our robust presets, and can integrate that with a video chat component built on peer to peer webRTC technology. This enables clients access to real time multimedia interactions drawn on 2d canvas overlays. 6 | 7 | 8 | ## Setup 9 | 10 | 1. Install it from npm 11 | ```bash 12 | npm install --save mirage-js 13 | ``` 14 | 2. In client-side Javascript file require mirage-js. 15 | ```bash 16 | import { Mirage } from 'mirage-js'; 17 | ``` 18 | 3. On the server, navigate to our socket-logic file on node_modules (if server is in root directory, require from './node_modules/server/mirageSocket.js'); 19 | ```bash 20 | const mirageSocket = require('./node_modules/server/socketLogic.js'); 21 | ``` 22 | 23 | ## Client side API methods 24 | 25 | ### Begin component logic, this method should be coded after all the other methods have been coded 26 | ```bash 27 | mirage.startApp(); 28 | ``` 29 | 30 | ### Gain access to Mirage object 31 | ```bash 32 | const mirage = new Mirage(); 33 | ``` 34 | 35 | ### Insert CSS styles for component 36 | ```bash 37 | mirage.insertCss(); 38 | ``` 39 | 40 | ### Insert component onto fixed position on DOM 41 | ```bash 42 | mirage.insertChunk(); 43 | ``` 44 | 45 | ### Add your own filters 46 | ```bash 47 | mirage.putFilters = ['blur(5px)', 'saturate(20)']; 48 | ``` 49 | 50 | ### Add your own images/emoji 51 | ```bash 52 | mirage.putImages = ['https://cdnjs.cloudflare.com/ajax/libs/emojione/2.2.6/assets/png/1f4a9.png', 53 | 'https://cdnjs.cloudflare.com/ajax/libs/emojione/2.2.6/assets/png/1f4af.png']; 54 | ``` 55 | 56 | ## Run your own functions and animations within select events. They take no input, but 'state' is accessible, see below 57 | 58 | ### 'initial' event triggers immediately after mirage process begins 59 | ```bash 60 | mirage.on('initial', (state, filters, images) => { 61 | //console.log(state); 62 | }); 63 | ``` 64 | 65 | ### 'preStream' event triggers immediately before local stream becomes available 66 | ```bash 67 | mirage.on('preStream', (state, filters, images) => { 68 | //console.log(state); 69 | }); 70 | ``` 71 | 72 | ### 'localStream' event triggers immediately after local stream becomes available 73 | ```bash 74 | mirage.on('localStream', (state) => { 75 | //console.log(state); 76 | }); 77 | ``` 78 | 79 | ### 'streams' event triggers (both local and remote streams accessible) 80 | ```bash 81 | mirage.on('streams', (state) => { 82 | // console.log(state); 83 | }); 84 | ``` 85 | 86 | ### 'onData' event triggers (data channel becomes accessible for sending data) 87 | ```bash 88 | mirage.on('onData', (state) => { 89 | // console.log(state); 90 | state.rtcState.dataChannel.send(JSON.stringify({onData: "yo you're up in the data channels"})); 91 | }); 92 | ``` 93 | 94 | ### 'onMessage' event triggers (incoming data becomes accessible) 95 | ```bash 96 | mirage.on('onMessage', (state, dataObj) => { 97 | // console.log(state, dataObj); 98 | }); 99 | ``` 100 | ### Data becomes available to 'non-initiator' client (available via state.rtcState.dataChannel property) 101 | ```bash 102 | mirage.on('nonInitiatorData', (state) => { 103 | // console.log(state); 104 | }); 105 | ``` 106 | 107 | ## States 108 | 109 | ### As of now there are five states, which are properties on a state object that is accessible within the event methods 110 | 111 | ### Filter State 112 | ```bash 113 | state.filterState = { 114 | filters: ['blur(5px)', 'brightness(0.4)', 'contrast(200%)', 'grayscale(100%)', 'hue-rotate(90deg)', 'invert(100%)', 'sepia(100%)', 'saturate(20)', 'none']; //default filters 115 | addFilters: (filterArr) => { //add filters, suggested to do through putFilters method 116 | if (filterArr != undefined || filterArr.length > 0) { 117 | filterArr.forEach((ele, idx) => { 118 | if(this.filters.indexOf(ele) < 0){ 119 | this.filters.push(ele); 120 | } 121 | }); 122 | } 123 | }; 124 | ``` 125 | 126 | ### Room State (access chat client or room information) 127 | ```bash 128 | state.roomState = { 129 | vendorUrl: //window url 130 | chattersClient: // you and peer's socket ID 131 | chatterThisClient: //your socket ID 132 | roomID: //shared room object on server 133 | } 134 | ``` 135 | 136 | ### Media State 137 | ```bash 138 | state.mediaState = { 139 | peerBooth: //DOM element container for remote canvas and video 140 | peerVideo: //remote video 141 | peerCanvas: //remote canvas overlay 142 | peerContext: //remote context of canvas 143 | myBooth: //DOM element container for local canvas and video 144 | myVideo: //local video 145 | myCanvas: //local canvas overlay 146 | myContext: //local canvas context 147 | } 148 | ``` 149 | 150 | ### Anime State (animation data) 151 | ```bash 152 | state.animeState = { 153 | anime: //object of default functions, paste, bounce, orbit 154 | animeBtn: //DOM button with event listener to choose animation 155 | currAnime: //DOM display element for current animation 156 | currentAnimation: current chosen animation function 157 | raf: //request animation frame variable 158 | rafObj: //object collection of rafs 159 | currentImg: //current selected image 160 | emojis: //array collection of images 161 | emoBtns: //DOM buttons for image selections 162 | } 163 | ``` 164 | 165 | ### RTC State 166 | ```bash 167 | state.rtcState = { 168 | localStream: //local video stream 169 | remoteStream: //remote video stream 170 | dataChannel: //data channel shared between clients 171 | } 172 | ``` 173 | 174 | ### Element State (DOM nodes) 175 | ```bash 176 | state.elementState = { 177 | clearButton: document.getElementById(clearId); 178 | joinButton: document.getElementById(joinId); 179 | materialBtn: document.getElementById(materialId); 180 | demo: document.getElementById(demoId); 181 | fixedComponent: document.getElementById(fixedId); 182 | boothComponent: document.getElementById(boothId); 183 | connectElement: document.getElementById(connectId); 184 | disconnectElement: document.getElementById(disconnectId); 185 | } 186 | 187 | ## Our Team 188 | * Kevin Liu - [github.com/kevoutthebox](https://github.com/kevoutthebox) 189 | * Morgan Christison - [github.com/MorganChristison](https://github.com/MorganChristison) 190 | * Blake Watkins - [github.com/blkwtkns](https://github.com/blkwtkns) 191 | 192 | ## License 193 | ISC 194 | -------------------------------------------------------------------------------- /src/components/mirageWebRTC.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function connectEvents(state, handRemStream, func2, socket, events, mediaGenerator) { 4 | // console.log('connectEvent function: ' + rtcState); 5 | startSetup(state, handRemStream, socket, events, mediaGenerator); 6 | //data channel creation 7 | 8 | //create data channel 9 | state.rtcState.dataChannel = state.rtcState.peerConn.createDataChannel('interact'); 10 | // audio/video creation 11 | // console.log(rtcState.peerConn); 12 | func2(state.rtcState.dataChannel, state, events); 13 | doCall(state.rtcState, state.roomState, socket); 14 | } 15 | 16 | function startSetup(state, handRemStream, socket, events, mediaGenerator) { 17 | // console.log('startSetup? ', rtcState.isStarted, rtcState.localStream); 18 | if (!state.rtcState.isStarted && typeof state.rtcState.localStream !== 'undefined') { 19 | // console.log('creating peer connection'); 20 | createPeerConnection(state, handRemStream, socket, events, mediaGenerator); 21 | state.rtcState.peerConn.addStream(state.rtcState.localStream); 22 | state.rtcState.isStarted = true; 23 | } 24 | } 25 | 26 | function createPeerConnection(state, handRemStream, socket, events, mediaGenerator) { 27 | try { 28 | state.rtcState.peerConn = new RTCPeerConnection(state.rtcState.pcConfig); 29 | state.rtcState.peerConn.onicecandidate = () => handleIceCandidate(event, state.roomState, socket); 30 | state.rtcState.peerConn.onaddstream = () => handRemStream(event, state, events, mediaGenerator); //handleRemoteStreamAdded; 31 | state.rtcState.peerConn.onremovestream = handleRemoteStreamRemoved; 32 | state.rtcState.peerConn.oniceconnectionstatechange = () => handleIceConnStateChange(event, state.rtcState); 33 | } catch (err) { 34 | console.log('Failed to connect. Error: ' + err); 35 | return; 36 | } 37 | } 38 | 39 | 40 | function otherDataChannel(event, state, func1, events) { 41 | state.rtcState.peerConn.ondatachannel = (event) => { 42 | 43 | // console.log('not initiator data channel start', event.channel); 44 | state.rtcState.dataChannel = event.channel; 45 | func1(state.rtcState.dataChannel, state, events); 46 | // func2(state.mediaState, state.animeState, state.rtcState); 47 | 48 | //user input event for nonInitiator data channel method 49 | events.nonInitiatorData(state); 50 | }; 51 | } 52 | 53 | //misc webRTC helper functions 54 | 55 | function sendMessage(data, who, state, socket) { 56 | let message = { 57 | roomID: state.roomID, 58 | who: who, 59 | data: data 60 | }; 61 | // console.log('Client Sending Message: ', message); 62 | socket.emit('message', message); 63 | } 64 | 65 | function handleIceCandidate(event, roomState, socket) { 66 | // console.log('icecandidate event ', event); 67 | if (event.candidate) { 68 | sendMessage({ 69 | type: 'candidate', 70 | label: event.candidate.sdpMLineIndex, 71 | id: event.candidate.sdpMid, 72 | candidate: event.candidate.candidate 73 | }, 'other', roomState, socket); 74 | } else { 75 | 76 | // console.log('Finished adding candidates'); 77 | } 78 | } 79 | 80 | function handleIceConnStateChange(event, rtcState) { 81 | if (rtcState.peerConn.iceConnectionState === 'disconnected') { 82 | // console.log('Disconnected'); 83 | rtcState.peerConn.close(); 84 | 85 | // console.log('iceConn state change remove rtc', rtcState.peerConn); 86 | } 87 | } 88 | 89 | function handleRemoteStreamRemoved(event) { 90 | // console.log('Remote Stream removed, event: ', event); 91 | // socket.emit('disconnect'); 92 | // location.reload(); 93 | } 94 | 95 | function doCall(rtcState, roomState, socket) { 96 | // console.log('sending offer to peer', 'state in doCall: ' + rtcState.peerConn); 97 | rtcState.peerConn.createOffer().then((result) => { 98 | setLocalAndSendMessage(result, rtcState, roomState, socket); 99 | }).catch(err => { 100 | console.log('create offer error: ' + err); 101 | }); 102 | } 103 | 104 | function doAnswer(rtcState, roomState, socket) { 105 | // console.log('Sending answer to peer.'); 106 | rtcState.peerConn.createAnswer().then((result) => { 107 | setLocalAndSendMessage(result, rtcState, roomState, socket); 108 | }).catch(err => { 109 | console.log('create offer error: ' + err); 110 | }); 111 | } 112 | 113 | 114 | function setLocalAndSendMessage(sessionDescription, rtcState, roomState, socket) { 115 | rtcState.peerConn.setLocalDescription(sessionDescription); 116 | // console.log('setLocalAndSendMessage. Sending Message', sessionDescription); 117 | sendMessage(sessionDescription, 'other', roomState, socket); 118 | } //close misc webRTC helper function 119 | 120 | function endCall(socket, state, events) { 121 | socket.disconnect(); 122 | state.rtcState.peerConn.close(); 123 | state.rtcState.dataChannel.close(); 124 | state.rtcState.localStream.getTracks().forEach((track) => { 125 | track.stop(); 126 | }); 127 | 128 | for (var k in state) { 129 | state[k] = null; 130 | } 131 | 132 | events.end(state); 133 | } 134 | 135 | function handleRemoteStreamAdded(event, state, events, mediaGenerator) { 136 | // after adding remote sream, set the source of peer video to their stream 137 | state.rtcState.remoteStream = event.stream; 138 | 139 | mediaGenerator(state.rtcState.remoteStream, false, state); 140 | //user input for streams event listener 141 | events.streams(state); 142 | } 143 | 144 | 145 | function onDataChannelCreated(channel, state, events) { 146 | //data channel stuff 147 | channel.onopen = () => { 148 | 149 | // console.log('data channel onopen method triggered'); 150 | 151 | //user function for onData event listener 152 | events.onData(state); 153 | 154 | }; //end onopen method 155 | 156 | // for messaging if we want to integrate later 157 | //looks for click event on the send button// 158 | // document.getElementById('MRGsend').addEventListener('click', () => { 159 | // post message in text context on your side 160 | // send message object to the data channel 161 | // let yourMessageObj = JSON.stringify({ 162 | // message: "them:" + " " + document.getElementById('MRGyourMessage').value 163 | // }); 164 | // creates a variable with the same information to display on your side 165 | // let yourMessage = "me:" + " " + document.getElementById('MRGyourMessage').value; 166 | // post message in text context on your side 167 | // document.getElementById('messages').textContent += yourMessage + '\n'; 168 | // channel.send(yourMessageObj) 169 | // }) //end send click event// 170 | 171 | 172 | //on data event 173 | channel.onmessage = event => { 174 | 175 | let data = event.data; 176 | //conditionally apply or remove filter 177 | let dataObj = JSON.parse(data); 178 | 179 | //user input for onMessage event 180 | events.onMessage(state, dataObj); 181 | }; 182 | } 183 | 184 | export { 185 | connectEvents, 186 | startSetup, 187 | createPeerConnection, 188 | otherDataChannel, 189 | sendMessage, 190 | handleIceCandidate, 191 | handleRemoteStreamRemoved, 192 | doCall, 193 | doAnswer, 194 | setLocalAndSendMessage, 195 | endCall, 196 | handleRemoteStreamAdded, 197 | onDataChannelCreated 198 | }; 199 | -------------------------------------------------------------------------------- /src/components/mirageApp.js: -------------------------------------------------------------------------------- 1 | import adapter from 'webrtc-adapter'; 2 | import io from 'socket.io-client'; 3 | 4 | import { 5 | mediaGenerator 6 | } from './mediaGenerator'; 7 | 8 | import { 9 | roomStore, 10 | mediaStore, 11 | rtcStore, 12 | elementStore 13 | } from './mirageStore'; 14 | 15 | import { 16 | connectEvents, 17 | startSetup, 18 | createPeerConnection, 19 | otherDataChannel, 20 | sendMessage, 21 | handleIceCandidate, 22 | handleRemoteStreamRemoved, 23 | doCall, 24 | doAnswer, 25 | setLocalAndSendMessage, 26 | endCall, 27 | handleRemoteStreamAdded, 28 | onDataChannelCreated 29 | } from './mirageWebRTC'; 30 | 31 | // require('./tracking'); 32 | // need to abstract a little bit more, and then make tracking data available 33 | // to user manipulation 34 | 35 | 36 | function mirageApp(events, state, domIds) { 37 | 38 | domIds = domIds || ['MRGjoin-button', 'MRGconnect', 'MRGdisconnect', 'MRGroom-id-input', 'MRGmyBooth', 'MRGmyVideo', 'MRGmyCanvas', 'MRGpeerBooth', 'MRGpeerVideo', 'MRGpeerCanvas']; 39 | 40 | state.elementState = new elementStore(domIds); 41 | 42 | //initial user input event 43 | events.initial(state); 44 | 45 | let promisifiedOldGUM = function(constraints) { 46 | 47 | // First get ahold of getUserMedia, if present 48 | let getUserMedia = (navigator.getUserMedia || 49 | navigator.webkitGetUserMedia || 50 | navigator.mozGetUserMedia); 51 | 52 | // Return a rejected promise with an error 53 | // to keep a consistent interface 54 | if (!getUserMedia) { 55 | return Promise.reject(new Error('getUserMedia is not implemented in this browser')); 56 | } 57 | 58 | // Otherwise, wrap the call to the old navigator.getUserMedia with a Promise 59 | return new Promise(function(resolve, reject) { 60 | getUserMedia.call(navigator, constraints, resolve, reject); 61 | }); 62 | 63 | }; 64 | 65 | // For older browsers 66 | if (navigator.mediaDevices === undefined) { 67 | navigator.mediaDevices = {}; 68 | } 69 | 70 | //add the getUserMedia property if it's missing. 71 | if (navigator.mediaDevices.getUserMedia === undefined) { 72 | navigator.mediaDevices.getUserMedia = promisifiedOldGUM; 73 | } 74 | 75 | // Prefer camera resolution nearest to 1280x720. 76 | var constraints = { 77 | audio: false, 78 | video: true 79 | }; 80 | 81 | state.elementState.joinButton.addEventListener('click', () => { 82 | 83 | // state.mediaState = new mediaStore('MRGmyBooth', 'MRGpeerBooth'); 84 | // state.filterState = new filterStore('MRGfilterDisp', 'MRGfilter'); 85 | // state.animeState = new animeStore('MRGanimation', 'MRGanimateDisp', 'MRGemoji', [paste, bounce, orbit]); 86 | state.rtcState = new rtcStore(); 87 | state.roomState = new roomStore(window.URL); 88 | state.mediaState = new mediaStore(state.elementState.localBoothId, state.elementState.remoteBoothId); 89 | 90 | 91 | //add input filters or images 92 | // state.filterState.addFilters(filters); 93 | // state.animeState.addEmoji(images); 94 | 95 | //make user input for room id input field 96 | const socket = io.connect(); //io.connect('https://463505aa.ngrok.io/') 97 | state.roomState.roomID = document.getElementById(state.elementState.roomInputId).value; 98 | 99 | 100 | socket.emit('joinRoom', JSON.stringify(state.roomState.roomID)); 101 | socket.on('process', (payload) => { 102 | payload = JSON.parse(payload); 103 | if (!payload) { 104 | alert('Try a different room!'); 105 | } else { 106 | 107 | //start streaming right after inserting user preStream input 108 | events.preStream(state); 109 | 110 | navigator.mediaDevices.getUserMedia(constraints) 111 | .then(stream => { 112 | 113 | //test for positioning user input event listener 114 | //immediately after local stream is available 115 | events.localStream(state); 116 | 117 | 118 | //make initiate event happen automatically when streaming begins 119 | socket.emit('initiate', JSON.stringify({ 120 | streamId: stream.id, 121 | roomId: state.roomState.roomID 122 | })); 123 | 124 | socket.on('readyConnect', (payload) => { 125 | 126 | //user input for readyConnect event 127 | events.readyConnect(state); 128 | }); 129 | 130 | //disconnect event 131 | state.elementState.disconnectElement.addEventListener('click', (event) => { 132 | socket.emit('disconnect'); 133 | endCall(socket, state, events); 134 | state.elementState = new elementStore(domIds); 135 | }); //end of disconnect click event// 136 | 137 | socket.on('updateChatters', (chatter) => { 138 | socket.emit('disconnect'); 139 | endCall(socket, state, events); 140 | state.elementState = new elementStore(domIds); 141 | // document.getElementById('MRGmessages').textContent += 'notification: ' + chatter + ' has left.' + '\n'; 142 | state.roomState.chattersClient.splice(state.roomState.chattersClient.indexOf(chatter), 1); 143 | // document.getElementById('connect').disabled = false; 144 | }); 145 | 146 | socket.on('initiated', (member) => { 147 | 148 | member = JSON.parse(member); 149 | mediaGenerator(stream, true, state); 150 | 151 | //sets up local stream reference 152 | state.rtcState.localStream = stream; 153 | //set room ID shared between clients 154 | state.roomState.roomID = member.roomId; 155 | 156 | if (state.roomState.chattersClient.filter(clientChatter => clientChatter.id !== member.id).length || !state.roomState.chattersClient.length) { 157 | state.roomState.chattersClient.push(member); 158 | state.roomState.chatterThisClient = member.id; 159 | } 160 | 161 | 162 | state.elementState.connectElement.addEventListener('click', () => { 163 | connectEvents(state, handleRemoteStreamAdded, onDataChannelCreated, socket, events, mediaGenerator); 164 | 165 | //user input for connectTriggered event 166 | events.connectTriggered(state); 167 | }); 168 | 169 | socket.on('message', (message) => { 170 | if (message.type === 'offer') { 171 | if (!state.rtcState.isStarted) { 172 | startSetup(state, handleRemoteStreamAdded, socket, events, mediaGenerator); 173 | otherDataChannel(event, state, onDataChannelCreated, events); 174 | } 175 | state.rtcState.peerConn.setRemoteDescription(new RTCSessionDescription(message)); 176 | doAnswer(state.rtcState, state.roomState, socket); 177 | } else if (message.type === 'answer' && state.rtcState.isStarted) { 178 | state.rtcState.peerConn.setRemoteDescription(new RTCSessionDescription(message)); 179 | } else if (message.type === 'candidate' && state.rtcState.isStarted) { 180 | let candidate = new RTCIceCandidate({ 181 | sdpMLineIndex: message.label, 182 | candidate: message.candidate 183 | }); 184 | state.rtcState.peerConn.addIceCandidate(candidate); 185 | } 186 | }); 187 | 188 | }); //end of socket.on('initiated') 189 | 190 | 191 | }, //end of stream// 192 | (err) => { 193 | console.error(err); 194 | }); 195 | 196 | } //end of boolean in socket 'process' event 197 | 198 | }); //end of socket 'process' event 199 | 200 | }, false); //end of 'join' event 201 | 202 | } 203 | 204 | export { 205 | mirageApp 206 | }; 207 | -------------------------------------------------------------------------------- /src/utils/domFunctions.js: -------------------------------------------------------------------------------- 1 | function hiddenToggle(ele1, ele2) { 2 | let args = [...arguments]; 3 | args.forEach((ele, idx) => { 4 | let tag = document.getElementById(ele); 5 | if (tag.classList.contains('MRGhidden')) { 6 | tag.classList.remove('MRGhidden'); 7 | } else { 8 | tag.classList.add('MRGhidden'); 9 | } 10 | }); 11 | } 12 | 13 | //element manipulation 14 | function disableToggle(ele1, ele2) { 15 | let args = [...arguments]; 16 | args.forEach((ele, idx) => { 17 | document.getElementById(ele).disabled ? document.getElementById(ele).disabled = false : document.getElementById(ele).disabled = true; 18 | }); 19 | } 20 | 21 | //element manipulation 22 | function setVendorCss(element, style) { 23 | element.style.webkitFilter = style; 24 | element.style.mozFilter = style; 25 | element.style.filter = style; 26 | } 27 | 28 | //video and canvas dimensional manipulation 29 | function resizeMedia(win, state, container, func1, func2, func3) { 30 | 31 | let styleWidth1 = func1(state.myVideo, win).vidWidth, 32 | styleWidth2 = func1(state.peerVideo, win).vidWidth, 33 | targetDims = func2(container, win); 34 | 35 | if (styleWidth1 >= styleWidth2) { 36 | func3(state.myVideo, state.myCanvas, state.myContext, state.peerVideo, state.peerCanvas, state.peerContext, targetDims); 37 | } else { 38 | func3(state.peerVideo, state.peerCanvas, state.peerContext, state.myVideo, state.myCanvas, state.myContext, targetDims); 39 | } 40 | container.style.height = func1(container, win).vidHeight + 'px'; 41 | } 42 | 43 | //video and canvas dimensional manipulation 44 | function toggleVidSize(win, state, localCanvasId, remoteCanvasId, func1, func2, func3, func4) { 45 | console.log('toggle vid size triggered'); 46 | let arr, 47 | styleWidth1 = func1(state.myVideo, win).vidWidth, 48 | styleWidth2 = func1(state.peerVideo, win).vidWidth, 49 | booths = [localCanvasId, remoteCanvasId]; 50 | 51 | if (styleWidth1 >= styleWidth2) { 52 | let dims = func2(state.myVideo, win); 53 | 54 | func4(state.peerVideo, state.peerCanvas, state.peerContext, state.myVideo, state.myCanvas, state.myContext, dims); 55 | arr = [state.myVideo, state.myCanvas, state.peerVideo, state.peerCanvas]; 56 | 57 | booths.forEach((ele, idx) => { 58 | func3(ele, 'MRGpointerToggle'); 59 | }); 60 | 61 | } else { 62 | let dims = func2(state.peerVideo, win); 63 | 64 | func4(state.myVideo, state.myCanvas, state.myContext, state.peerVideo, state.peerCanvas, state.peerContext, dims); 65 | arr = [state.peerVideo, state.peerCanvas, state.myVideo, state.myCanvas]; 66 | 67 | booths.forEach((ele, idx) => { 68 | func3(ele, 'MRGpointerToggle'); 69 | }); 70 | } 71 | 72 | arr.forEach((ele, idx) => { 73 | if (idx < 2) { 74 | ele.style.zIndex = '3'; 75 | } else { 76 | ele.style.zIndex = '2'; 77 | } 78 | }); 79 | 80 | } 81 | 82 | //video and canvas dimensional manipulation 83 | function setSizes(upVid, upCanvas, upContext, downVid, downCanvas, downContext, dims) { 84 | upVid.setAttribute('width', '' + dims.bigVidWidth); 85 | upVid.setAttribute('height', '' + dims.bigVidHeight); 86 | 87 | upContext = upCanvas.getContext('2d'); 88 | upCanvas.width = dims.bigVidWidth; 89 | upCanvas.height = dims.bigVidHeight; 90 | upContext.strokeRect(0, 0, upCanvas.width, upCanvas.height); 91 | 92 | downVid.setAttribute('width', '' + dims.smallVidWidth); 93 | downVid.setAttribute('height', '' + dims.smallVidHeight); 94 | 95 | downContext = downCanvas.getContext('2d'); 96 | downCanvas.width = dims.smallVidWidth; 97 | downCanvas.height = dims.smallVidHeight; 98 | downContext.scale(.25, .25); 99 | downContext.strokeRect(0, 0, downCanvas.width, downCanvas.height); 100 | } 101 | 102 | //video and canvas dimensional manipulation 103 | function generateDims(container, win) { 104 | let containerStyle = win.getComputedStyle(container); 105 | let styleWidth = containerStyle.getPropertyValue('width'); 106 | let videoWidth = Math.round(+styleWidth.substring(0, styleWidth.length - 2)); 107 | let videoHeight = Math.round((videoWidth / 4) * 3); 108 | 109 | return { 110 | vidWidth: videoWidth, 111 | vidHeight: videoHeight 112 | }; 113 | } 114 | 115 | //video and canvas dimensional manipulation 116 | function vidDims(bigVid, win) { 117 | let vidStyle = win.getComputedStyle(bigVid); 118 | let styleWidth = vidStyle.getPropertyValue('width'); 119 | let bigVidWidth = Math.round(+styleWidth.substring(0, styleWidth.length - 2)); 120 | let bigVidHeight = Math.round((bigVidWidth / 4) * 3); 121 | let smallVidWidth = bigVidWidth / 4; 122 | let smallVidHeight = Math.round((smallVidWidth / 4) * 3); 123 | 124 | return { 125 | bigVidWidth: bigVidWidth, 126 | bigVidHeight: bigVidHeight, 127 | smallVidWidth: smallVidWidth, 128 | smallVidHeight: smallVidHeight 129 | }; 130 | } 131 | 132 | //not used 133 | function scaleToFill(videoTag, height, width) { 134 | let video = videoTag, 135 | videoRatio = 4 / 3, 136 | tagRatio = width / height; 137 | if (videoRatio < tagRatio) { 138 | video.setAttribute('style', '-webkit-transform: scaleX(' + tagRatio / videoRatio + ')'); 139 | } else if (tagRatio < videoRatio) { 140 | video.setAttribute('style', '-webkit-transform: scaleY(' + videoRatio / tagRatio + ')'); 141 | } 142 | } 143 | 144 | //not used 145 | function scaleElement(vid, height, width) { 146 | let video = vid; 147 | let actualRatio = 4 / 3; 148 | let targetRatio = width / height; 149 | let adjustmentRatio = targetRatio / actualRatio; 150 | let scale = actualRatio < targetRatio ? targetRatio / actualRatio : actualRatio / targetRatio; 151 | video.setAttribute('style', '-webkit-transform: scale(' + scale + ')'); 152 | } 153 | 154 | //element manipulator 155 | function classToggle(btnEleId, classType) { 156 | if (document.getElementById(btnEleId).classList.contains(classType)) { 157 | document.getElementById(btnEleId).classList.remove(classType); 158 | } else { 159 | document.getElementById(btnEleId).classList.add(classType); 160 | } 161 | } 162 | 163 | //element manipulator 164 | function appendConnectButtons(state) { 165 | //creating buttons will replace everytime so eventlistener is good. Will pull out of file 166 | let connectivityBtns = document.getElementById('MRGconnectivityBtns'); 167 | let conButton = document.createElement('button'); 168 | let disconButton = document.createElement('button'); 169 | conButton.setAttribute('class', 'MRGbtn'); 170 | disconButton.setAttribute('class', 'MRGbtn'); 171 | conButton.setAttribute('id', 'MRGconnect'); 172 | disconButton.setAttribute('id', 'MRGdisconnect'); 173 | // conButton.innerHTML = 'Connect'; 174 | // disconButton.innerHTML = 'Disconnect'; 175 | conButton.disabled = true; 176 | disconButton.disabled = true; 177 | disconButton.classList.add('MRGhidden'); 178 | connectivityBtns.appendChild(conButton); 179 | connectivityBtns.appendChild(disconButton); 180 | state.elementState.connectElement = conButton; 181 | state.elementState.disconnectElement = disconButton; 182 | } 183 | 184 | //remove child element of passed in argument from dom 185 | //element manipulator 186 | function removeChildren(el) { 187 | let element = document.getElementById(el); 188 | 189 | while (element.firstChild) { 190 | element.removeChild(element.firstChild); 191 | } 192 | } 193 | 194 | 195 | //this should stop the request animation frame recursive calls and also clear the canvas 196 | //element manipulator 197 | function clearFunc(animeSt, mediaSt) { 198 | for (let rafID in animeSt.rafObj) { 199 | cancelAnimationFrame(animeSt.rafObj[rafID]); 200 | } 201 | 202 | mediaSt.myContext.clearRect(0, 0, 10000, 10000); 203 | mediaSt.peerContext.clearRect(0, 0, 10000, 10000); 204 | } 205 | 206 | //make a new function store of element manipulators 207 | function toggleZindex() { 208 | // toggle Z index of non MRG elements to have Mirage component always show 209 | // only if can access dom elements 210 | if (document.querySelectorAll) { 211 | let domElements = document.body.getElementsByTagName('*'); 212 | for (let i = 0; i < domElements.length; i++) { 213 | 214 | if (domElements[i].id.substring(0, 3) !== 'MRG') { 215 | //give fixed elements z index of 1 and non fixed elements z index of -1 to keep positionality 216 | window.getComputedStyle(domElements[i]).getPropertyValue('position') === 'fixed' ? domElements[i].classList.toggle('notMirageFixed') : domElements[i].classList.toggle('notMirage'); 217 | } 218 | } 219 | } 220 | } 221 | 222 | export { 223 | disableToggle, 224 | hiddenToggle, 225 | setVendorCss, 226 | toggleVidSize, 227 | toggleZindex, 228 | clearFunc, 229 | removeChildren, 230 | resizeMedia, 231 | setSizes, 232 | generateDims, 233 | vidDims, 234 | classToggle, 235 | appendConnectButtons 236 | }; 237 | -------------------------------------------------------------------------------- /src/utils/funcStore.js: -------------------------------------------------------------------------------- 1 | //function store// 2 | 3 | function bounce(cv, ctx, evt, pos, emoImg, animate, array, rafObj) { 4 | let onload = emoImg.onload; 5 | //this object keeps track of the movement, loads the images, and determines 6 | //the velocity 7 | let dim = 50; 8 | let emoticon = { 9 | x: pos.x, 10 | y: pos.y, 11 | vx: 5, 12 | vy: 2, 13 | onload: function() { 14 | ctx.drawImage(emoImg, this.x - dim / 2, this.y - dim / 2, dim, dim); 15 | } 16 | }; 17 | //initial image load on canvas 18 | emoticon.onload(); 19 | let callBack = function() { 20 | array[0](emoticon, ctx, cv, callBack, emoImg, animate, rafObj, evt); 21 | }; 22 | //start drawing movement 23 | animate = requestAnimationFrame(callBack); 24 | //put evt timestamp as key and update val with most recent raf id from velocity vunction 25 | rafObj[evt.timeStamp.toString()] = animate; 26 | } //end bounce// 27 | 28 | function paste(cv, ctx, evt, pos, emoImg) { 29 | let onload = emoImg.onload; 30 | //this object keeps track of the movement, loads the images, and determines 31 | //the velocity 32 | let dim = 50; 33 | let emoticon = { 34 | x: pos.x, 35 | y: pos.y, 36 | vx: 5, 37 | vy: 2, 38 | onload: function() { 39 | ctx.drawImage(emoImg, this.x - dim / 2, this.y - dim / 2, dim, dim); 40 | } 41 | }; 42 | //initial image load on canvas 43 | emoticon.onload(); 44 | } //end staticPaste// 45 | 46 | //orbit func// 47 | function orbit(cv, ctx, evt, pos, emoImg, animate, array, rafObj) { 48 | let onload = emoImg.onload; 49 | //this object keeps track of the movement, loads the images, and determines 50 | //the angular veloctiy. We're keeping track of frequency of refreshes to 51 | //imcrement the degrees 52 | let dim = 50; 53 | let movement = .0349066; 54 | let emoticon = { 55 | x: pos.x, 56 | y: pos.y, 57 | r: 5, 58 | rotateCount: 1, 59 | wx: movement, 60 | wy: movement, 61 | onload: function() { 62 | ctx.drawImage(emoImg, this.x - dim / 2, this.y - dim / 2, dim, dim); 63 | } 64 | }; 65 | //initial image load on canvas 66 | emoticon.onload(); 67 | let callBack = function() { 68 | array[1](emoticon, ctx, cv, callBack, emoImg, animate, rafObj, evt); 69 | }; 70 | animate = requestAnimationFrame(callBack); 71 | //put evt timestamp as key and update val with most recent raf id from angularVelocity vunction 72 | rafObj[evt.timeStamp.toString()] = animate; 73 | 74 | } //end velocity// 75 | 76 | function trackFace(video, canvas, context, trackingObj, videoStream, img, channel) { 77 | //console.log("data channel", channel); 78 | 79 | // var video = video; 80 | // var canvas = canvas; 81 | // var context = context; 82 | //console.log("tracking object", trackingObj); 83 | var emoji = new Image(); 84 | if (img.src === undefined) { 85 | emoji.src = img; 86 | } else { 87 | emoji.src = img.src; 88 | } 89 | // console.log(emoji); 90 | var tracker = new trackingObj.ObjectTracker('face'); 91 | tracker.canvasOverlay = canvas; 92 | var faceRect = { 93 | x: 100, 94 | y: 100, 95 | width: emoji.width * 3, 96 | height: emoji.height * 3 97 | }; 98 | 99 | //console.log('face track has been called'); 100 | tracker.setInitialScale(4); 101 | tracker.setStepSize(2); 102 | tracker.setEdgesDensity(0.1); 103 | tracker.canvasOverlay = canvas; 104 | tracker.canvasContext = context; 105 | 106 | // if(isTrackingBool) { 107 | 108 | trackingObj.track(video, tracker, { 109 | camera: true 110 | }); 111 | tracker.on('track', function(event) { 112 | context.clearRect(faceRect.x - 50, faceRect.y - img.height * 3 - 50, faceRect.width + 50, faceRect.height + img.width * 3 + 50); 113 | //mediaState.myContext.clearRect 114 | event.data.forEach(function(rect) { 115 | //console.log("rect", rect, "emoji", emoji); 116 | faceRect.x = rect.x; 117 | faceRect.y = rect.y; 118 | hat(canvas, context, faceRect, emoji); 119 | 120 | let trackingDataObj = JSON.stringify({ 121 | tracking: 'yes', 122 | image: emoji.src, 123 | faceRect: faceRect 124 | }); 125 | channel.send(trackingDataObj); 126 | }); 127 | }); 128 | } 129 | 130 | // var pastRect = (function() { 131 | // var store = {x:0, y:0, width: 100, height: 100}; 132 | // function rememberFace(rect) { 133 | // store.x = rect.x; 134 | // store.y = rect.y; 135 | // store.width = rect.width; 136 | // store.height = rect.height; 137 | // } 138 | // return { 139 | // previous: function() { 140 | // console.log("store inside of previous", store); 141 | // return store; 142 | // }, 143 | // updateStore: function(faceRect) { 144 | // console.log("store was updated"); 145 | // rememberFace(faceRect); 146 | // } 147 | // }; 148 | // })(); 149 | 150 | function hat(cv, ctx, rect, img) { 151 | //console.log("rect in hat", rect, "cv in hat", cv); 152 | ctx.clearRect(0, 0, 20000, 20000); 153 | ctx.drawImage(img, rect.x, rect.y - 5, rect.height, rect.width); 154 | 155 | } 156 | 157 | //doesnt work yet, but would provide a way to erase drawn 158 | //objects in circular fashion rather than rectangular 159 | function cutCircle(context, x, y, radius) { 160 | context.globalCompositeOperation = 'destination-out'; 161 | context.arc(x, y, radius, 0, Math.PI * 2, true); 162 | context.fill(); 163 | } //end cutCircle// 164 | 165 | 166 | //paste object to canvas 167 | // function pasteImg(video, context, width, height, x, y, source) { 168 | // context.drawImage(video, 0, 0, width, height); 169 | // baseImg = new Image(); 170 | // baseImg.src = source; // needs to be path ie --> 'assets/weird.png'; 171 | // baseImg.onload = function() { 172 | // context.drawImage(baseImg, x - baseImg.width / 2, y - baseImg.height / 2); 173 | //setTimeout for pasted images// 174 | // var time = window.setTimeout(function() { 175 | // context.clearRect(x - baseImg.width / 2, y - baseImg.height / 2, baseImg.width, baseImg.height); 176 | // }, 5000); 177 | // } 178 | // } //end paste// 179 | 180 | 181 | //gets cursor position upon mouse click that places 182 | //an object or starts object movement 183 | function getCursorPosition(canvas, event) { 184 | let rect = canvas.getBoundingClientRect(); 185 | let x = event.clientX - rect.left; 186 | let y = event.clientY - rect.top; 187 | let pos = { 188 | x: x, 189 | y: y 190 | }; 191 | return pos; 192 | } //end getCursorPosition// 193 | 194 | 195 | //draws video on canvas 196 | function drawVideo(v, c, w, h) { 197 | if (v.paused || v.ended) return false; 198 | c.drawImage(v, 0, 0, w, h); 199 | setTimeout(drawVideo, 20, v, c, w, h); 200 | } //end drawVideo// 201 | 202 | //canvas draw function for velocity motion 203 | function velocity(obj, ctx, cv, cb, emoImg, animate, rafObj, evt) { 204 | ctx.clearRect(obj.x - emoImg.width / 2 - 5, obj.y - emoImg.height / 2 - 5, emoImg.width + 8, emoImg.height + 8); 205 | obj.onload(); 206 | obj.x += obj.vx; 207 | obj.y += obj.vy; 208 | if (obj.y + obj.vy > cv.height || obj.y + obj.vy < 0) { 209 | obj.vy = -obj.vy; 210 | } 211 | if (obj.x + obj.vx > cv.width || obj.x + obj.vx < 0) { 212 | obj.vx = -obj.vx; 213 | } 214 | animate = window.requestAnimationFrame(cb); 215 | //update the raf of latest draw of velocity recursive call for clearing purposes 216 | rafObj[evt.timeStamp.toString()] = animate; 217 | 218 | } //end velocity// 219 | 220 | //angularVelocity func// 221 | function angularVelocity(obj, ctx, cv, cb, emoImg, animate, rafObj, evt) { 222 | ctx.clearRect(obj.x - emoImg.width / 2 - 5, obj.y - emoImg.height / 2 - 5, emoImg.width + 10, emoImg.height + 10); 223 | obj.onload(); 224 | 225 | obj.x += Math.sin(obj.wx * obj.rotateCount) * obj.r; 226 | obj.y += Math.cos(obj.wy * obj.rotateCount) * obj.r; 227 | obj.rotateCount++; 228 | 229 | animate = window.requestAnimationFrame(cb); 230 | //update the raf of latest draw of velocity recursive call for clearing purposes 231 | rafObj[evt.timeStamp.toString()] = animate; 232 | } //end angularVelocity// 233 | 234 | 235 | function receivedAnimation(localBool, animeState, mediaState, event, dataObj, func1, func2) { 236 | let emoImg = new Image(); 237 | emoImg.src = dataObj.currentImg; 238 | 239 | animeState.temp = animeState.currentAnimation; 240 | animeState.currentAnimation = animeState.anime[dataObj.animation]; 241 | if (localBool) { 242 | animeState.currentAnimation(mediaState.myCanvas, mediaState.myContext, event, dataObj.position, emoImg, animeState.raf, [func1, func2], animeState.rafObj); 243 | } else { 244 | animeState.currentAnimation(mediaState.peerCanvas, mediaState.peerContext, event, dataObj.position, emoImg, animeState.raf, [func1, func2], animeState.rafObj); 245 | } 246 | animeState.currentAnimation = animeState.temp; 247 | 248 | } 249 | 250 | export { 251 | receivedAnimation, 252 | cutCircle, 253 | angularVelocity, 254 | velocity, 255 | drawVideo, 256 | getCursorPosition, 257 | orbit, 258 | paste, 259 | bounce, 260 | trackFace, 261 | hat 262 | }; 263 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | DEMO 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 47 | 48 | 49 | 50 | 51 |
52 |
53 |

A DEMONSTRATION OF OUR LIBRARY

54 |

Augmented Reality Video Chat

55 |
56 | 57 |
58 | 59 | 60 |
61 | 62 | 63 | 64 |
65 |
ChatBox
66 |

 67 |       
68 | 69 |
70 |
71 | 72 | 73 |
74 | 75 | 76 | 77 |
78 |
79 |
80 |

Subscribe for updates to mirageJS

81 |

Enter your name and email

82 | 83 |
84 |
85 | 86 | 87 |
88 |
89 | 90 | 91 |
92 | 93 |
94 |
95 |
96 | 97 |
98 | 99 |
100 | 101 | 102 | 103 |
104 |
105 |
106 |
107 |

About mirageJS

108 |

MirageJS is a next-generation video-chat platform built on webRTC 109 | technology where users can interact with both 2d and 3d objects 110 | in a augmented reality based interface.

111 |

Integrating applied physics and movement tracking functionalities, 112 | this is the video chat app to use in the 21st century

113 |
114 |
115 |
116 |
117 | 118 | 119 | 120 | 121 | 122 |
123 |
124 | 127 | 128 | 129 |
130 |
131 |
132 | 137 | 138 |
139 |
140 | We expect to launch our fully featured service in early September, but a beta version 141 | is already out on our github. 142 |
143 |
144 | 145 |
146 |
147 | 148 |
149 |
150 | 155 | 156 |
157 |
158 | https://github.com/kevoutthebox/streamar 159 |
160 |
161 | 162 |
163 |
164 | 165 |
166 |
167 | 172 | 173 |
174 |
175 | Everything is fantastic 176 |
177 |
178 | 179 |
180 | 181 |
182 |
183 | 184 | 185 |
186 |
187 | 188 | 189 |
190 |
191 | 194 | 195 |
196 |
197 |
198 |
199 | 200 |

Hello I am Morgan Christison

201 | Visit my Github 202 |
203 |
204 |
205 |
206 | 207 |

Hello I am Blake Watkins

208 | Visit my Github 209 |
210 |
211 |
212 |
213 | 214 |

Hello I am Kevin Liu

215 | Visit my Github 216 |
217 |
218 |
219 | 220 |
221 | 222 | 223 | 224 |
225 |
226 | 229 | 230 |
231 |
232 | 233 | 234 | 235 |
236 |
237 |
238 | 239 |
240 | 241 | 242 |
243 | 244 | 245 | 247 | 248 | 249 | 250 | 251 | 252 | -------------------------------------------------------------------------------- /public/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | Mirage 3 | } from './../src/index'; 4 | import { 5 | domReady 6 | } from './../src/utils/domReady'; 7 | 8 | import { 9 | filterListener, 10 | animationListener, 11 | clearListener, 12 | myTrackingListener, 13 | peerTrackingListener 14 | } from './../src/utils/listenerFuncs'; 15 | 16 | import { 17 | receivedAnimation, 18 | cutCircle, 19 | angularVelocity, 20 | velocity, 21 | drawVideo, 22 | getCursorPosition, 23 | orbit, 24 | paste, 25 | bounce, 26 | trackFace, 27 | hat 28 | } from './../src/utils/funcStore'; 29 | 30 | import { 31 | disableToggle, 32 | hiddenToggle, 33 | setVendorCss, 34 | toggleVidSize, 35 | toggleZindex, 36 | clearFunc, 37 | removeChildren, 38 | resizeMedia, 39 | setSizes, 40 | generateDims, 41 | vidDims, 42 | classToggle, 43 | appendConnectButtons 44 | } from './../src/utils/domFunctions'; 45 | 46 | import { 47 | filterStore, 48 | animeStore 49 | } from './../src/utils/utilStore'; 50 | 51 | require('./../src/components/tracking'); 52 | 53 | // wait for dom to load 54 | domReady(() => { 55 | 56 | // instantiate mirage object 57 | const mirage = new Mirage(); 58 | 59 | mirage.insertCss(); //mount styles on DOM for component 60 | 61 | mirage.insertChunk(); // mount mirage chunk on DOM 62 | 63 | // mirage.domIds = ['MRGjoin-button', 'MRGconnect', 'MRGdisconnect', 'MRGroom-id-input', 'MRGmyBooth', 'MRGmyVideo', 'MRGmyCanvas', 'MRGpeerBooth', 'MRGpeerVideo', 'MRGpeerCanvas']; 64 | 65 | ///beginning of manipulation/event access methods/// 66 | //manipulation at beginning of mirage process 67 | mirage.on('initial', (state) => { 68 | 69 | document.getElementById('MRGmaterialBtn').addEventListener('click', () => { 70 | // need to parse through stylesheets and set z-indexes of elements to -1 with 71 | // each toggle 72 | document.getElementById('MRGdemo').classList.toggle('MRGhidden'); 73 | toggleZindex(); 74 | }); 75 | 76 | document.getElementById('MRGmaterialBtn').addEventListener('drag', (event) => { 77 | // console.log(event); 78 | }); 79 | 80 | document.getElementById('MRGmaterialBtn').addEventListener('dragend', (event) => { 81 | // console.log('drag over', event.clientX); 82 | document.getElementById('MRGmaterialBtn').style.left = event.clientX + 'px'; 83 | document.getElementById('MRGmaterialBtn').style.top = (event.clientY - 60) + 'px'; 84 | }); 85 | }); 86 | 87 | 88 | //manipulation right before MediaDevices process starts 89 | mirage.on('preStream', (state) => { 90 | 91 | state.filterState = new filterStore('MRGfilterDisp', 'MRGfilter'); 92 | state.animeState = new animeStore('MRGanimation', 'MRGanimateDisp', 'MRGemoji', [paste, bounce, orbit]); 93 | 94 | 95 | //add input filters or images 96 | // state.filterState.addFilters(filters); 97 | // state.animeState.addEmoji(images); 98 | 99 | appendConnectButtons(state); 100 | 101 | state.animeState.emojis.forEach((ele, idx) => { 102 | let btn = document.createElement('button'); 103 | btn.classList.add('MRGemojibtn'); 104 | btn.classList.add('MRGemoji'); 105 | let emoj = document.createElement('img'); 106 | emoj.src = ele; 107 | emoj.style.height = '25px'; 108 | emoj.style.width = '25px'; 109 | btn.appendChild(emoj); 110 | document.getElementById('MRGemojiButtons').appendChild(btn); 111 | }); 112 | 113 | hiddenToggle('MRGroomApp', 'MRGboothApp'); 114 | 115 | document.getElementById('MRGbooth').addEventListener('drag', (event) => { 116 | 117 | }); 118 | 119 | document.getElementById('MRGbooth').addEventListener('dragend', (event) => { 120 | document.getElementById('MRGfixed').style.left = event.clientX + 'px'; 121 | document.getElementById('MRGfixed').style.top = event.clientY + 'px'; 122 | }); 123 | }); 124 | 125 | 126 | //manipulation immediately after local stream becomes available 127 | mirage.on('localStream', (state) => { 128 | console.log('localStream test', state); 129 | }); 130 | 131 | //manipulation during socket 'readyConnect' event which signals clients that connection is ready 132 | mirage.on('readyConnect', (state) => { 133 | state.elementState.connectElement.disabled = false; 134 | classToggle(state.elementState.connectId, 'MRGelementToFadeInAndOut'); 135 | }); 136 | 137 | //manipulation right after connection event happens 138 | mirage.on('connectTriggered', (state) => { 139 | classToggle(state.elementState.connectId, 'MRGelementToFadeInAndOut'); 140 | }); 141 | 142 | 143 | //manipulation right after remote stream becomes available 144 | mirage.on('streams', (state) => { 145 | 146 | state.mediaState.peerCanvas.classList.add('MRGpointerToggle'); 147 | 148 | toggleVidSize(window, state.mediaState, 'MRGmyCanvas', 'MRGpeerCanvas', generateDims, vidDims, classToggle, setSizes); 149 | 150 | hiddenToggle('MRGconnect', 'MRGdisconnect'); 151 | }); 152 | 153 | 154 | //manipulation right after data channel becomes available 155 | mirage.on('onData', (state) => { 156 | 157 | animationListener(state.mediaState.peerCanvas, state.animeState.emoImg, state.animeState.anime, state.animeState.currAnime, state.mediaState.peerContext, state.animeState.raf, [velocity, angularVelocity], state.rtcState.dataChannel, false, getCursorPosition, state.animeState.rafObj); //remote 158 | 159 | animationListener(state.mediaState.myCanvas, state.animeState.emoImg, state.animeState.anime, state.animeState.currAnime, state.mediaState.myContext, state.animeState.raf, [velocity, angularVelocity], state.rtcState.dataChannel, true, getCursorPosition, state.animeState.rafObj); //local 160 | 161 | filterListener(state.mediaState.myVideo, 'MRGmyFilter', state.filterState.currFilter, true, state.rtcState.dataChannel, setVendorCss); 162 | 163 | filterListener(state.mediaState.peerVideo, 'MRGpeerFilter', state.filterState.currFilter, false, state.rtcState.dataChannel, setVendorCss); 164 | 165 | clearListener(state.rtcState.dataChannel, clearFunc, document.getElementById('MRGclear'), state.animeState, state.mediaState); 166 | 167 | myTrackingListener(state.mediaState.myVideo, state.mediaState.myCanvas, state.mediaState.myContext, state.animeState.emoImg, tracking, state.rtcState.dataChannel); 168 | 169 | peerTrackingListener(state.mediaState.peerVideo, state.mediaState.peerCanvas, state.mediaState.peerContext, state.animeState.emoImg, state.rtcState.dataChannel, trackFace, tracking, state.rtcState.remoteStream); 170 | 171 | document.getElementById('MRGvideoToggle').addEventListener('click', () => { 172 | toggleVidSize(window, state.mediaState, 'MRGmyCanvas', 'MRGpeerCanvas', generateDims, vidDims, classToggle, setSizes); 173 | }); 174 | 175 | // changing this because the multi event listener is retoggling 176 | disableToggle('MRGconnect', 'MRGdisconnect'); 177 | 178 | window.onresize = () => { 179 | resizeMedia(window, state.mediaState, document.getElementById('MRGvidContainer'), generateDims, vidDims, setSizes); 180 | }; 181 | 182 | //changing filters// 183 | state.filterState.filterBtn.addEventListener('click', () => { 184 | state.filterState.currFilter.innerHTML = state.filterState.filters[state.filterState.idx++]; 185 | if (state.filterState.idx >= state.filterState.filters.length) state.filterState.idx = 0; 186 | }, false); //end of filter test// 187 | 188 | //changing animations// 189 | state.animeState.animeBtn.addEventListener('click', () => { 190 | state.animeState.currAnime.innerHTML = state.animeState.animeKeys[state.animeState.idx]; 191 | state.animeState.currentAnimation = state.animeState.anime[state.animeState.animeKeys[state.animeState.idx++]]; 192 | if (state.animeState.idx >= state.animeState.animeKeys.length) state.animeState.idx = 0; 193 | }, false); 194 | 195 | //adding click handler for active emoji selection 196 | Array.from(state.animeState.emoBtns, (ele) => { 197 | ele.addEventListener('click', (event) => { 198 | state.animeState.currentImg = ele.querySelectorAll('img')[0].getAttribute('src'); 199 | state.animeState.emoImg.src = state.animeState.currentImg; 200 | }, false); 201 | }); 202 | }); 203 | 204 | 205 | //manipulation on receipt of data through data channel 206 | mirage.on('onMessage', (state, dataObj) => { 207 | 208 | if (dataObj.hasOwnProperty('filter')) { 209 | if (dataObj.filter) { 210 | setVendorCss(state.mediaState.peerVideo, dataObj.filterType); 211 | } else { 212 | setVendorCss(state.mediaState.myVideo, dataObj.filterType); 213 | } 214 | } 215 | 216 | if (dataObj.hasOwnProperty('localEmoji')) { 217 | if (dataObj.localEmoji) { 218 | //remote display bounce animation! 219 | 220 | receivedAnimation(false, state.animeState, state.mediaState, event, dataObj, velocity, angularVelocity); 221 | } else if (!dataObj.localEmoji) { 222 | //local display bounce animation! 223 | 224 | receivedAnimation(true, state.animeState, state.mediaState, event, dataObj, velocity, angularVelocity); 225 | } 226 | } 227 | 228 | if (dataObj.hasOwnProperty('tracking')) { 229 | state.mediaState.myContext.clearRect(0, 0, state.mediaState.myCanvas.width, state.mediaState.myCanvas.height); 230 | 231 | if (dataObj.tracking === 'yes') { 232 | state.mediaState.myContext.clearRect(0, 0, state.mediaState.myCanvas.width, state.mediaState.myCanvas.height); 233 | var emoji = new Image(); 234 | emoji.src = dataObj.image; 235 | // console.log(emoji); 236 | //console.log(dataObj.faceRect); 237 | var adjustedRect = { 238 | x: dataObj.faceRect.x, 239 | y: dataObj.faceRect.y, 240 | width: dataObj.faceRect.width / 2, 241 | height: dataObj.faceRect.height / 2 242 | }; 243 | hat(state.mediaState.myCanvas, state.mediaState.myContext, adjustedRect, emoji); 244 | } 245 | 246 | } 247 | 248 | if (dataObj.hasOwnProperty('myTrack')) { 249 | if (dataObj.myTrack === state.mediaState.myVideo) { 250 | //console.log("track me fired off"); 251 | var myEmoji = new Image(); 252 | myEmoji.src = dataObj.image; 253 | var tracking = dataObj.tracking; 254 | var channel = dataObj.channel; 255 | trackFace(state.mediaState.peerCanvas, state.mediaState.peerContext, tracking, state.rtcState.remoteStream, myEmoji, channel); 256 | } 257 | } 258 | 259 | if (dataObj.type === 'clear') { 260 | clearFunc(state.animeState, state.mediaState); 261 | } 262 | 263 | }); 264 | 265 | 266 | //manipulation of nonInitializer client logic when data channel 267 | //becomes available for them 268 | mirage.on('nonInitiatorData', (state) => { 269 | 270 | animationListener(state.mediaState.peerCanvas, state.animeState.emoImg, state.animeState.anime, state.animeState.currAnime, state.mediaState.peerContext, state.animeState.raf, [velocity, angularVelocity], state.rtcState.dataChannel, false, getCursorPosition, state.animeState.rafObj); 271 | 272 | }); 273 | 274 | mirage.on('end', (state) => { 275 | let parentNodes = [ 276 | 'MRGmyBooth', 277 | 'MRGpeerBooth', 278 | 'MRGconnectivityBtns', 279 | 'MRGemojiButtons' 280 | ]; 281 | 282 | parentNodes.forEach((ele) => { 283 | removeChildren(ele); 284 | }); 285 | 286 | hiddenToggle('MRGroomApp', 'MRGboothApp'); 287 | 288 | }); 289 | ///end of event access methods/// 290 | 291 | mirage.startApp(); // start mirage logic 292 | 293 | }); 294 | -------------------------------------------------------------------------------- /tests/client/funcStore.test.js: -------------------------------------------------------------------------------- 1 | import * as funcStore from '../../src/components/funcStore'; 2 | import { expect } from 'chai'; 3 | // import fs from 'fs'; 4 | import sinon from 'sinon'; 5 | 6 | 7 | 8 | describe('functions should exist', () => { 9 | it('paste should exist', () => { 10 | expect(typeof funcStore.paste).to.equal('function'); 11 | }); 12 | it('bounce should exist', () => { 13 | expect(typeof funcStore.bounce).to.equal('function'); 14 | }); 15 | it('orbit should exist', () => { 16 | expect(typeof funcStore.orbit).to.equal('function'); 17 | }); 18 | it('getCursorPosition should exist', () => { 19 | expect(typeof funcStore.getCursorPosition).to.equal('function'); 20 | }); 21 | it('setVendorCss should exist', () => { 22 | expect(typeof funcStore.setVendorCss).to.equal('function'); 23 | }); 24 | it('drawVideo should exist', () => { 25 | expect(typeof funcStore.drawVideo).to.equal('function'); 26 | }); 27 | it('velocity should exist', () => { 28 | expect(typeof funcStore.velocity).to.equal('function'); 29 | }); 30 | it('angularVelocity should exist', () => { 31 | expect(typeof funcStore.angularVelocity).to.equal('function'); 32 | }); 33 | 34 | it('appendConnectButtons should exist', () => { 35 | expect(typeof funcStore.appendConnectButtons).to.equal('function'); 36 | }); 37 | it('removeChildren should exist', () => { 38 | expect(typeof funcStore.removeChildren).to.equal('function'); 39 | }); 40 | it('clearFunc should exist', () => { 41 | expect(typeof funcStore.clearFunc).to.equal('function'); 42 | }); 43 | it('toggleZindex should exist', () => { 44 | expect(typeof funcStore.toggleZindex).to.equal('function'); 45 | }); 46 | it('toggleVidSize should exist', () => { 47 | expect(typeof funcStore.toggleVidSize).to.equal('function'); 48 | }); 49 | it('vidDims should exist', () => { 50 | expect(typeof funcStore.vidDims).to.equal('function'); 51 | }); 52 | it('generateDims should exist', () => { 53 | expect(typeof funcStore.generateDims).to.equal('function'); 54 | }); 55 | it('scaleToFill should exist', () => { 56 | expect(typeof funcStore.scaleToFill).to.equal('function'); 57 | }); 58 | it('scaleElement should exist', () => { 59 | expect(typeof funcStore.scaleElement).to.equal('function'); 60 | }); 61 | it('classToggle should exist', () => { 62 | expect(typeof funcStore.classToggle).to.equal('function'); 63 | }); 64 | it('cutCircle should exist', () => { 65 | expect(typeof funcStore.cutCircle).to.equal('function'); 66 | }); 67 | it('resizeMedia should exist', () => { 68 | expect(typeof funcStore.resizeMedia).to.equal('function'); 69 | }); 70 | it('setSizes should exist', () => { 71 | expect(typeof funcStore.setSizes).to.equal('function'); 72 | }); 73 | }); 74 | 75 | describe('paste functionality', () => { 76 | let canvas, context, event, position, emoImg, sandbox; 77 | beforeEach(() => { 78 | canvas = document.createElement('canvas'); 79 | canvas.width = 600; 80 | canvas.height = 450; 81 | 82 | context = canvas.getContext('2d'); 83 | 84 | position = {x: 500, y:300}; 85 | emoImg = new Image(); 86 | emoImg.src = 'http://getemoji.com/assets/ico/favicon.png'; 87 | 88 | //new sandbox 89 | sandbox = sinon.sandbox.create(); 90 | afterEach(function () { 91 | //clear spies 92 | sandbox.restore(); 93 | }); 94 | }); 95 | 96 | it('should be callable', () => { 97 | let pasteSpy = sandbox.spy(funcStore, 'paste'); 98 | funcStore.paste(canvas, context, event, position, emoImg); 99 | expect(pasteSpy.called).to.be.true; 100 | 101 | }); 102 | it('should not throw an error', () => { 103 | let pasteSpy = sandbox.spy(funcStore, 'paste'); 104 | funcStore.paste(canvas, context, event, position, emoImg); 105 | expect(pasteSpy.threw()).to.be.false; 106 | 107 | }); 108 | }); 109 | 110 | describe('appendConnectButtons functionality', () => { 111 | let MRGconnectivityBtns, sandbox, conButton, disconButton; 112 | beforeEach(() => { 113 | MRGconnectivityBtns = document.createElement('div'); 114 | MRGconnectivityBtns.setAttribute('id', 'MRGconnectivityBtns'); 115 | document.body.appendChild(MRGconnectivityBtns); 116 | 117 | //new sandbox 118 | sandbox = sinon.sandbox.create(); 119 | }); 120 | afterEach(function () { 121 | document.body.removeChild(MRGconnectivityBtns); 122 | //clear spies 123 | sandbox.restore(); 124 | }); 125 | 126 | it('should add elements to the dom', () => { 127 | //there should be none of these buttons on dom before appendConnectButtons is called 128 | expect(document.getElementById('MRGconnect')).to.equal(null); 129 | expect(document.getElementById('MRGdisconnect')).to.equal(null); 130 | funcStore.appendConnectButtons(); 131 | expect(document.getElementById('MRGconnect')).to.not.equal(null); 132 | expect(document.getElementById('MRGdisconnect')).to.not.equal(null); 133 | }); 134 | it('should create elements with button type', () => { 135 | funcStore.appendConnectButtons(); 136 | expect(document.getElementById('MRGconnect').nodeName).to.equal('BUTTON'); 137 | expect(document.getElementById('MRGdisconnect').nodeName).to.equal('BUTTON'); 138 | }); 139 | it('should set the buttons initially as disabled', () => { 140 | funcStore.appendConnectButtons(); 141 | expect(document.getElementById('MRGconnect').disabled).to.be.true; 142 | expect(document.getElementById('MRGdisconnect').disabled).to.be.true; 143 | }); 144 | }); 145 | 146 | describe('removeChildren functionality', () => { 147 | let MRGconnectivityBtns, sandbox, conButton, disconButton; 148 | beforeEach(() => { 149 | MRGconnectivityBtns = document.createElement('div'); 150 | MRGconnectivityBtns.setAttribute('id', 'MRGconnectivityBtns'); 151 | conButton = document.createElement('button'); 152 | conButton.setAttribute('id', 'conButton'); 153 | disconButton = document.createElement('button'); 154 | disconButton.setAttribute('id', 'disconButton'); 155 | MRGconnectivityBtns.appendChild(conButton); 156 | MRGconnectivityBtns.appendChild(disconButton); 157 | document.body.appendChild(MRGconnectivityBtns); 158 | }); 159 | afterEach(() => { 160 | document.body.removeChild(MRGconnectivityBtns); 161 | }); 162 | 163 | it('should remove one child if it exists', () => { 164 | expect(document.getElementById('conButton')).to.not.equal(null); 165 | funcStore.removeChildren('MRGconnectivityBtns'); 166 | expect(document.getElementById('conButton')).to.equal(null); 167 | }); 168 | it('should remove two child if it exists', () => { 169 | expect(document.getElementById('conButton')).to.not.equal(null); 170 | expect(document.getElementById('disconButton')).to.not.equal(null); 171 | funcStore.removeChildren('MRGconnectivityBtns'); 172 | expect(document.getElementById('conButton')).to.equal(null); 173 | expect(document.getElementById('disconButton')).to.equal(null); 174 | }); 175 | it('should work if the children does not exist', () => { 176 | //first to remove existing children 177 | funcStore.removeChildren('MRGconnectivityBtns'); 178 | funcStore.removeChildren('MRGconnectivityBtns'); 179 | expect(true).to.be.true; 180 | }); 181 | }); 182 | 183 | describe('clearFunc functionality', () => { 184 | it ('should stop all raf', () => { 185 | //generic code to generate a raf id to clear. 186 | let requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || 187 | window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; 188 | let cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame; 189 | let start = window.mozAnimationStartTime; // Only supported in FF. Other browsers can use something like Date.now(). 190 | let animeSt = {}; 191 | let mediaSt = {}; 192 | 193 | mediaSt.myContext = document.createElement('canvas').getContext('2d'); 194 | mediaSt.peerContext = document.createElement('canvas').getContext('2d'); 195 | 196 | function step(timestamp) { 197 | let progress = timestamp - start; 198 | if (progress < 2000) { 199 | animeSt.myReq = requestAnimationFrame(step); 200 | } 201 | } 202 | animeSt.myReq = requestAnimationFrame(step); 203 | funcStore.clearFunc(animeSt, mediaSt); 204 | }); 205 | }); 206 | 207 | describe('toggleZindex functionality', () => { 208 | beforeEach(() => { 209 | let mirageEl = document.createElement('div'); 210 | mirageEl.setAttribute('id', 'MRG-hello'); 211 | let nonMirageEl = document.createElement('div'); 212 | nonMirageEl.setAttribute('id', 'hello'); 213 | document.body.appendChild(mirageEl); 214 | document.body.appendChild(nonMirageEl); 215 | }); 216 | afterEach(() => { 217 | document.body.removeChild(document.getElementById('MRG-hello')); 218 | document.body.removeChild(document.getElementById('hello')); 219 | }); 220 | 221 | it('should add the notMirage class if class does not contain MRG', () => { 222 | expect(document.getElementById('hello').classList.contains('notMirage')).to.be.false; 223 | funcStore.toggleZindex(); 224 | expect(document.getElementById('hello').classList.contains('notMirage')).to.be.true; 225 | }); 226 | it('should not add the notMirage class if class does not contain MRG', () => { 227 | expect(document.getElementById('MRG-hello').classList.contains('notMirage')).to.be.false; 228 | funcStore.toggleZindex(); 229 | expect(document.getElementById('MRG-hello').classList.contains('notMirage')).to.be.false; 230 | }); 231 | }); 232 | 233 | describe('classToggle functionality', () => { 234 | beforeEach(() => { 235 | let mirageEl = document.createElement('div'); 236 | mirageEl.setAttribute('id', 'MRG-hello'); 237 | mirageEl.setAttribute('class', 'MRG-hello-class'); 238 | let nonMirageEl = document.createElement('div'); 239 | nonMirageEl.setAttribute('id', 'hello'); 240 | document.body.appendChild(mirageEl); 241 | document.body.appendChild(nonMirageEl); 242 | }); 243 | afterEach(() => { 244 | document.body.removeChild(document.getElementById('MRG-hello')); 245 | document.body.removeChild(document.getElementById('hello')); 246 | }); 247 | 248 | it('should remove the MRG-hello class if contains it first time', () => { 249 | expect(document.getElementById('MRG-hello').classList.contains('MRG-hello-class')).to.be.true; 250 | funcStore.classToggle('MRG-hello', 'MRG-hello-class'); 251 | expect(document.getElementById('MRG-hello').classList.contains('MRG-hello-class')).to.be.false; 252 | }); 253 | it('should re-add the MRG-hello class after 2 calls if contains it first time', () => { 254 | expect(document.getElementById('MRG-hello').classList.contains('MRG-hello-class')).to.be.true; 255 | funcStore.classToggle('MRG-hello', 'MRG-hello-class'); 256 | expect(document.getElementById('MRG-hello').classList.contains('MRG-hello-class')).to.be.false; 257 | funcStore.classToggle('MRG-hello', 'MRG-hello-class'); 258 | expect(document.getElementById('MRG-hello').classList.contains('MRG-hello-class')).to.be.true; 259 | }); 260 | it('should remove the MRG-hello class after 3 calls if contains it first time', () => { 261 | expect(document.getElementById('MRG-hello').classList.contains('MRG-hello-class')).to.be.true; 262 | funcStore.classToggle('MRG-hello', 'MRG-hello-class'); 263 | expect(document.getElementById('MRG-hello').classList.contains('MRG-hello-class')).to.be.false; 264 | funcStore.classToggle('MRG-hello', 'MRG-hello-class'); 265 | expect(document.getElementById('MRG-hello').classList.contains('MRG-hello-class')).to.be.true; 266 | funcStore.classToggle('MRG-hello', 'MRG-hello-class'); 267 | expect(document.getElementById('MRG-hello').classList.contains('MRG-hello-class')).to.be.false; 268 | }); 269 | }); 270 | 271 | 272 | describe('setVendorCss functionality', () => { 273 | beforeEach(() => { 274 | // let mirageEl = document.createElement('div'); 275 | // mirageEl.setAttribute('id', 'MRG-hello'); 276 | // document.body.appendChild(mirageEl); 277 | }); 278 | afterEach(() => { 279 | // document.body.removeChild(document.getElementById('MRG-hello')); 280 | }); 281 | 282 | it('should set style.webkitfilter to passed in style', () => { 283 | let mirageEl = document.createElement('div') 284 | mirageEl.setAttribute('id', 'MRG-hello'); 285 | 286 | funcStore.setVendorCss(mirageEl,'contrast(200%)'); 287 | expect(mirageEl.style.mozFilter).to.equal('contrast(200%)'); 288 | }); 289 | it('should set style.webkitFilter and style.mozFilter to passed in style', () => { 290 | let mirageEl = document.createElement('div') 291 | mirageEl.setAttribute('id', 'MRG-hello'); 292 | 293 | funcStore.setVendorCss(mirageEl,'contrast(200%)'); 294 | expect(mirageEl.style.webkitFilter).to.equal('contrast(200%)'); 295 | expect(mirageEl.style.mozFilter).to.equal('contrast(200%)'); 296 | }); 297 | }); 298 | -------------------------------------------------------------------------------- /src/components/cssChunk.js: -------------------------------------------------------------------------------- 1 | let cssChunk = ``; 359 | 360 | export { 361 | cssChunk 362 | }; 363 | --------------------------------------------------------------------------------