├── .gitignore ├── README.md ├── build.js ├── bundle-server.js ├── config └── setupDb.sql ├── index.html ├── index.js ├── modules ├── dbQuery.js └── hasher.js ├── package.json ├── public ├── ico-security.svg └── profile_pic_def │ ├── def_profilePic_1.svg │ ├── def_profilePic_10.svg │ ├── def_profilePic_11.svg │ ├── def_profilePic_12.svg │ ├── def_profilePic_2.svg │ ├── def_profilePic_3.svg │ ├── def_profilePic_4.svg │ ├── def_profilePic_5.svg │ ├── def_profilePic_6.svg │ ├── def_profilePic_7.svg │ ├── def_profilePic_8.svg │ └── def_profilePic_9.svg ├── readme ├── p2p_chat-find_friends.gif ├── p2p_chat-group_chat.gif ├── p2p_chat-login.gif ├── p2p_chat-manage_friendship .gif ├── p2p_chat-private_chat.gif ├── p2p_chat-profile_pic.gif └── p2p_chat-register.gif ├── routes ├── api.js ├── root.js ├── webSocket-bk.js └── webSocket.js ├── src ├── actions │ └── actions.js ├── components │ ├── chat-private.js │ ├── chat-public.js │ ├── chat-secure.js │ ├── friendshipButton.js │ ├── friendships-blocked.js │ ├── friendships-current.js │ ├── friendships-pending.js │ ├── login.js │ ├── logo.js │ ├── online-friends.js │ ├── profile-pic-upload.js │ ├── profile-pic.js │ ├── profile-self-bio.js │ ├── profile-self.js │ ├── profilePicOther.js │ ├── registration.js │ ├── users.js │ └── welcome.js ├── containers │ ├── app.js │ ├── chat-children-container.js │ ├── chat-list-container.js │ ├── chat-private-container.js │ ├── chat-public-container.js │ ├── chat-secure-container.js │ ├── friends-container.js │ ├── friendshipButtonContainer.js │ ├── online-friends-container.js │ ├── profileOther.js │ └── users-container.js ├── reducers │ └── reducers.js ├── shell.js └── utils │ ├── axios.js │ ├── formWrapper.js │ ├── peer-bk.js │ ├── peer.js │ └── socketIo.js ├── uploads └── .gitignore └── users ├── 04ec7e10980947.560efb9b62284.png ├── 4a2d8e10980947.560ef9a45fc34.png ├── 8e44e310980947.560efbe769094.png ├── Aquaman - .png ├── Batman - Bruce Wayne.png ├── Black Widow - Natasha Romanoff.png ├── Captain America.png ├── Flash - Barry Allen.png ├── Green Lantern - .png ├── Hell Boy.png ├── Hulk.png ├── Invisible Woman.png ├── Iron Man - Tony Stark.png ├── Litz - .png ├── Mr Fantastic - .png ├── Nick Fury.png ├── Superman -Clark Kent.png ├── The Human Torch - .png ├── The Thing - .png ├── Thor - .png ├── Wonder Woman.png ├── db151110980947.560efb243ae6e.png └── e3afac10980947.560ef9a483f53.png /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | bundle.js 4 | package-lock.json 5 | secrets.json 6 | .tern-project 7 | .eslintrc.json 8 | /tests 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # P2P-Chat 2 | A fun and easy messaging app that allows private conversations through P2P. 3 | 4 | ### Table of contents 5 | * [Context](#context) 6 | * [Summary](#summary) 7 | * [Tech Stack](#tech-stack) 8 | * [Installation](#installation) 9 | * [Features](#features) 10 | * [Todos](#todos-of-additional-features) 11 | * [Contact && License](#contact) 12 | 13 | ## Context 14 | Between July and October 2017, I attend **[SPICED Academy]**, an intensive 12-week coding program focused on Full Stack JavaScript Web Development in Berlin. 15 | During this program, I built a portfolio of web applications. 16 | 1. **[Reichstag]** - a static landing page 17 | 2. **[Kitty Carousel]** - a carousel/slideshow element that displays kitties picture 18 | 3. **[Resizable Panes]** - an element to display before and after photos 19 | 4. **[Incremental Search]** - search field that allows users to select matching results 20 | 5. **[Connect Four]** - the vertical checkers game 21 | 6. **[Spotify API Search]** 22 | 7. **[Github API Search]** 23 | 8. **[Ticker Twitter API]** - a sliding twitter news feed element 24 | 9. **[Petition]** - A server-side render app for collecting signatures for a pledge. 25 | 10. **[Imageboard]** - An app that allows users to upload images, comment, and like images. 26 | 11. **[Social Network]** - Mock social network project for superheroes and villains. 27 | 12. Final Project - one-week coding challenge - **[P2P Chat]** 28 | 29 | As my **Final Project** I was required to come up with and develop a project of my own to showcase what I had learned and to test new ideas and technologies. 30 | The constraints were the following: 31 | - it had to be completed in just one week 32 | - it had to be substantial but also achievable. 33 | ###### Timeframe: _**One week**_ 34 | ###### New Technologies: 35 | - PeerJs (WebRTC) 36 | 37 | ## Summary: 38 | Recently I've participated in a one-week coding challenge where I set myself to build a **secure and decentralized chat application**. 39 | **My goal** with this application **was to test the architecture and tech needed to make it happen**. 40 | The user has the ability to register, login, look up people to befriend, manage friendships, use a global chatroom, use a private chatroom or switch to a “secure” channel to protect the privacy of a conversation. To make all of this happen, I had to build a node.js backend that used WebSocket to emit real-time events to all the relevant clients while on a React/Redux frontend I had to handle the secure chat by enabling two clients to speak directly to each other through webRtc protocol (p2p connection). 41 | I especially enjoyed how React in combination with Redux allowed me to explicitly implement the separation of concerns principle and also write clean, functional and maintainable code. 42 | 43 | ### Tech Stack: 44 | | **Frontend** | **Backend** | **Database** | 45 | | ------ | ------ | ------ | 46 | **[React.js]** | **[Node.js]** | **[PostgreSQL]** 47 | **[Redux.js]** | **[Express.js]** | **[AWS S3]** 48 | **[Socket.io - client]** | **[Socket.io - server]** 49 | **[PeerJs - WebRTC]** | 50 | **[Material-UI]** | 51 | ## Installation 52 | ```bash 53 | $ git clone https://github.com/suddenlyGiovanni/p2p-chat.git 54 | $ cd p2p-chat 55 | $ npm install 56 | $ cd config && touch secrets.json 57 | ``` 58 | ##### Secret.json 59 | Paste in the following code and remember to configure [PostgreSQL] and [AWS S3] it accordingly... 60 | ```javascript 61 | { 62 | "psqlConfig": "postgres:postgres:postgres@localhost:5432/p2p-chat", 63 | "sessionSecret": "this is a secret!!", 64 | "bcryptSalt": "this is a secret!!", 65 | "AWS_KEY": "XXXXXXX", 66 | "AWS_SECRET": "XXXXXXX/XXXXXXX/", 67 | "AWS_BUCKET": "p2p-chat", 68 | "s3Url": "https://s3.amazonaws.com/XXXXXXX/" 69 | } 70 | ``` 71 | 72 | ## Features: 73 | > As a user, I can **register and login**. If I am already login, I can skip this step. 74 | 75 | The user can create or submit its credentials: Passwords are hashed using the bcrypt library. 76 | Forms include CSRF protection using the csurf npm package. 77 | 78 | ![p2p_chat-register] 79 | ![p2p_chat-login] 80 | 81 | > As a user, I can **personalize my profile picture**. 82 | 83 | ![p2p_chat-profile_pic] 84 | 85 | > As a user, I can **see who of my friends is online now**. 86 | 87 | > As a user, I can **find friends using the search box**. 88 | 89 | ![p2p_chat-find_friends] 90 | 91 | This Feature is implemented as an incremental search field. 92 | Input events result in ajax requests, and the route hit does a database queries with pattern matching to find matches. 93 | 94 | > As a user, I can **see a list of all of my friends**. I can also **manage friendship status**: 95 | I can send a friendship request, 96 | I can cancel ann erroneous friendship request, 97 | I can accept friends requests, 98 | I can terminate friendships 99 | 100 | ![p2p_chat-manage_friendship] 101 | 102 | > As a user, I can **use the group chat** feature to chat with everyone that is online. 103 | 104 | ![p2p_chat-group_chat] 105 | 106 | > As a user, I can **use the private chat** to talk to other friends that can be **either online or offline**. 107 | 108 | ![p2p_chat-private_chat] 109 | 110 | > As a user, I can **use the secure chat** to talk to other friends. 111 | 112 | This feature is achieved by enabling **two clients to speak directly to each other through the webRTC** protocol (p2p connection). 113 | The **messages** payload **are stored only locally** in the redux store of each client. They are also not persistent. 114 | 115 | ## Todos of additional features: 116 | - [ ] **Temp:** 117 | 118 | ## Contact 119 | * e-mail: ravalico.giovanni@gmail.com 120 | * Twitter: [@superspacezova](https://twitter.com/superspacezova "twitterhandle on twitter") 121 | * LinkdeIn: [/giovanni-ravalico] 122 | 123 | License 124 | ---- 125 | MIT © [suddenlyGiovanni] 126 | **Free Software, Hell Yeah!** 127 | 128 | [//]: # (These are reference links used in the body of this note and get stripped out when the markdown processor does its job. There is no need to format nicely because it shouldn't be seen. Thanks SO - http://stackoverflow.com/questions/4823468/store-comments-in-markdown-syntax) 129 | 130 | [//]: # (Contact references:) 131 | [Spiced Academy]: 132 | [suddenlyGiovanni]: 133 | [/giovanni-ravalico]: 134 | [@superspacezova]: 135 | 136 | [//]: # (Context references:) 137 | [Reichstag]: 138 | [Kitty Carousel]: 139 | [Resizable Panes]: 140 | [Incremental Search]: 141 | [Connect Four]: 142 | [Spotify API Search]: 143 | [Github API Search]: 144 | [Ticker Twitter API]: 145 | [Petition]: 146 | [Imageboard]: 147 | [Social Network]: 148 | [P2P Chat]: 149 | 150 | [//]: # (Tech Stack references:) 151 | [React.js]: 152 | [Node.js]: 153 | [PostgreSQL]: 154 | [Redux.js]: 155 | [Express.js]: 156 | [AWS S3]: 157 | [Socket.io - client]: 158 | [Socket.io - server]: 159 | [PeerJs - WebRTC]: 160 | [Material-UI]: 161 | [suddenlyGiovanni]: 162 | 163 | [//]: # (Picture references:) 164 | [p2p_chat-register]: 165 | [p2p_chat-login]: 166 | [p2p_chat-profile_pic]: 167 | [p2p_chat-find_friends]: 168 | [p2p_chat-manage_friendship]: 169 | [p2p_chat-group_chat]: 170 | [p2p_chat-private_chat]: 171 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | const webpack = require( 'webpack' ); 2 | 3 | const plugins = [ 4 | new webpack.DefinePlugin( { 5 | "process.env": { 6 | NODE_ENV: JSON.stringify( "production" ) 7 | } 8 | } ) 9 | ]; 10 | 11 | if ( process.env.NODE_ENV == 'production' ) { 12 | plugins.push( new webpack.optimize.UglifyJsPlugin( { 13 | compress: { 14 | warnings: false 15 | } 16 | } ) ); 17 | } 18 | 19 | const conf = { 20 | entry: [ 'babel-polyfill', __dirname + '/src/shell.js' ], 21 | output: { 22 | path: __dirname + '/public/', 23 | filename: 'bundle.js' 24 | }, 25 | plugins: plugins, 26 | module: { 27 | loaders: [ { 28 | test: /\.js$/, 29 | loader: 'babel-loader', 30 | query: { 31 | presets: [ [ 'es2015' ], [ 'react' ], [ 'stage-2' ] ], 32 | plugins: [ 'transform-async-to-generator' ] 33 | } 34 | } ] 35 | } 36 | }; 37 | 38 | if ( require.main == module ) { 39 | webpack( conf, function ( err, info ) { 40 | if ( err ) { 41 | console.log( err ); 42 | } 43 | if ( info && info.compilation.errors.length ) { 44 | console.log( info.compilation.errors ); 45 | } 46 | } ); 47 | } else { 48 | module.exports = require( 'webpack-dev-middleware' )( webpack( conf ), { 49 | watchOptions: { 50 | aggregateTimeout: 300 51 | }, 52 | publicPath: '/' 53 | } ); 54 | } 55 | -------------------------------------------------------------------------------- /bundle-server.js: -------------------------------------------------------------------------------- 1 | const express = require( 'express' ); 2 | const app = express(); 3 | 4 | app.use( require( './build' ) ); 5 | 6 | app.listen( 8081, () => { 7 | console.log( 'Ready to compile and serve bundle.js' ); 8 | } ); 9 | -------------------------------------------------------------------------------- /config/setupDb.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS messages; 2 | DROP TABLE IF EXISTS friendships; 3 | DROP TABLE IF EXISTS users; 4 | 5 | CREATE TABLE users( 6 | uid SERIAL PRIMARY KEY, 7 | email VARCHAR(300) NOT NULL UNIQUE, 8 | password VARCHAR(200) NOT NULL, 9 | "firstName" VARCHAR(200) NOT NULL, 10 | "lastName" VARCHAR (200) NOT NULL, 11 | "profilePic" VARCHAR(300), 12 | bio VARCHAR(300), 13 | timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP 14 | ); 15 | 16 | CREATE TABLE friendships( 17 | fid SERIAL PRIMARY KEY, 18 | "fromUserId" INTEGER NOT NULL REFERENCES users(uid), 19 | status VARCHAR (200) NOT NULL, 20 | "toUserId" INTEGER NOT NULL REFERENCES users(uid) 21 | ); 22 | 23 | CREATE TABLE messages( 24 | mid SERIAL PRIMARY KEY, 25 | "fromUserId" INTEGER NOT NULL REFERENCES users(uid), 26 | "toUserId" INTEGER REFERENCES users(uid), 27 | "toAll" BIT DEFAULT B'0'::"bit", 28 | "messageBody" VARCHAR (300) NOT NULL, 29 | timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 30 | read BIT DEFAULT B'0'::"bit" 31 | ); 32 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | P2P-Chat 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // REQUIRED MODULES_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 2 | const express = require( 'express' ), 3 | // morgan = require( 'morgan' ), 4 | path = require( 'path' ), 5 | bodyParser = require( 'body-parser' ), 6 | cookieSession = require( 'cookie-session' ), 7 | csrf = require( 'csurf' ), 8 | compression = require( 'compression' ); 9 | // db = require( './modules/dbQuery' ); 10 | // favicon = require( 'serve-favicon' ); 11 | 12 | 13 | // EXPRESS 14 | const app = express(); 15 | 16 | // SOCKETio 17 | const server = require( 'http' ).Server( app ); 18 | const io = require( 'socket.io' )( server ); 19 | module.exports.io = io; 20 | 21 | // PEERJS 22 | const ExpressPeerServer = require( 'peer' ).ExpressPeerServer; 23 | const options = { debug: true }; 24 | 25 | // MIDDLEWARE __________________________________________________________________ 26 | 27 | // HTTP request logger middleware 28 | // app.use( morgan( 'dev' ) ); 29 | // _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 30 | 31 | 32 | // compression gZip response before sending them 33 | app.use( compression() ); 34 | // _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 35 | 36 | 37 | // BODY PARSER 38 | app.use( bodyParser.json() ); 39 | // _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 40 | 41 | 42 | // COOKIE SESSION 43 | app.use( cookieSession( { 44 | secret: require( './config/secrets.json' ).sessionSecret, 45 | maxAge: 1000 * 60 * 60 * 24 * 14 46 | } ) ); 47 | // _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 48 | 49 | 50 | if ( process.env.NODE_ENV != 'production' ) { 51 | app.use( '/bundle.js', require( 'http-proxy-middleware' )( { 52 | target: 'http://localhost:8081/' 53 | } ) ); 54 | } 55 | 56 | 57 | // set the public folder where client stuff lives 58 | // app.use( express.static( './public' ) ); 59 | app.use( express.static( path.join( __dirname, '/public' ) ) ); 60 | 61 | 62 | // CSURF 63 | // app.use( csrf() ); 64 | 65 | app.use( ( req, res, next ) => { 66 | // res.cookie( '__csrf__', req.csrfToken() ); 67 | next(); 68 | } ); 69 | // _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 70 | 71 | 72 | // ROUTING _____________________________________________________________________ 73 | // Connect all our routes to our application 74 | app.use( '/', require( './routes/root' ) ); 75 | app.use( '/api/', require( './routes/api' ) ); 76 | // load webSocket.js and pass it the socket.io object 77 | app.use( '/ws/', require( './routes/webSocket' ) ); 78 | 79 | 80 | // PEERJS ROUTING 81 | app.use( '/peerjs', ExpressPeerServer( server, options ) ); 82 | 83 | // if no route match then.. 84 | app.get( '*', function ( req, res ) { 85 | res.sendFile( path.join( __dirname, 'index.html' ) ); 86 | } ); 87 | 88 | 89 | 90 | // PEERJS EVENTS LISTENING 91 | server.on( 'connection', peer => { 92 | console.log( 'Peerjs - server - Event - "connection" - id:', peer.id ); 93 | } ); 94 | server.on( 'disconnect', peer => { 95 | console.log( 'Peerjs - server - Event - "disconnect" - id:', peer.id ); 96 | } ); 97 | 98 | 99 | 100 | // ERROR: 101 | // catch 404 and forward to error handler 102 | app.use( function ( req, res, next ) { 103 | var err = new Error( 'Not Found' ); 104 | err.status = 404; 105 | next( err ); 106 | } ); 107 | 108 | app.use( ( err, req, res, next ) => { 109 | // set locals, only providing error in development 110 | res.locals.message = err.message; 111 | res.locals.error = req.app.get( 'env' ) === 'development' ? err : {}; 112 | 113 | // render the error page 114 | res.status( err.status || 500 ); 115 | console.log(err); 116 | res.send( 'error' ); 117 | } ); 118 | // _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 119 | 120 | 121 | // SERVER ______________________________________________________________________ 122 | const listener = server.listen( process.env.PORT || 8080, () => { 123 | console.log( `listening on port ${listener.address().port}.` ); 124 | } ); 125 | -------------------------------------------------------------------------------- /modules/hasher.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require( 'bcryptjs' ); 2 | 3 | const salt = process.env.BCRYPTSALT || require( '../config/secrets.json' ).bcryptSalt; 4 | 5 | function hashPassword( plainTextPassword ) { 6 | return new Promise( function ( resolve, reject ) { 7 | bcrypt.genSalt( function ( err, salt ) { 8 | if ( err ) { 9 | return reject( err ); 10 | } 11 | bcrypt.hash( plainTextPassword, salt, function ( err, hash ) { 12 | if ( err ) { 13 | return reject( err ); 14 | } 15 | resolve( hash ); 16 | } ); 17 | } ); 18 | } ); 19 | } 20 | 21 | function checkPassword( textEnteredInLoginForm, hashedPasswordFromDatabase ) { 22 | return new Promise( function ( resolve, reject ) { 23 | bcrypt.compare( textEnteredInLoginForm, hashedPasswordFromDatabase, function ( err, doesMatch ) { 24 | if ( err ) { 25 | reject( err ); 26 | } else { 27 | resolve( doesMatch ); 28 | } 29 | } ); 30 | } ); 31 | } 32 | 33 | module.exports.hashPassword = hashPassword; 34 | module.exports.checkPassword = checkPassword; 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "p2p-chat", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "node build.js", 8 | "start": "npm run build && node index.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "engines": { 12 | "node": "8.4.0" 13 | }, 14 | "nodemonConfig": { 15 | "ignore": [ 16 | "src/*" 17 | ], 18 | "delay": "2500" 19 | }, 20 | "dependencies": { 21 | "axios": "^0.15.3", 22 | "babel-cli": "^6.18.0", 23 | "babel-core": "^6.20.0", 24 | "babel-loader": "^6.2.9", 25 | "babel-plugin-transform-async-to-generator": "^6.24.1", 26 | "babel-polyfill": "^6.26.0", 27 | "babel-preset-es2015": "^6.18.0", 28 | "babel-preset-latest": "^6.16.0", 29 | "babel-preset-react": "^6.16.0", 30 | "babel-preset-stage-2": "^6.24.1", 31 | "bcryptjs": "^2.4.3", 32 | "body-parser": "^1.15.2", 33 | "compression": "^1.7.0", 34 | "cookie-parser": "^1.4.3", 35 | "cookie-session": "^2.0.0-alpha.2", 36 | "csurf": "^1.9.0", 37 | "express": "^4.14.0", 38 | "express-session": "^1.15.5", 39 | "http-proxy-middleware": "^0.17.4", 40 | "knox": "^0.9.2", 41 | "material-design-icons": "^3.0.1", 42 | "material-ui": "^0.19.2", 43 | "morgan": "^1.8.2", 44 | "multer": "^1.3.0", 45 | "peer": "^0.2.8", 46 | "react": "^15.4.1", 47 | "react-dom": "^15.4.1", 48 | "react-redux": "^5.0.5", 49 | "react-router": "^3.0.0", 50 | "react-tap-event-plugin": "^2.0.1", 51 | "redux": "^3.7.1", 52 | "redux-devtools-extension": "^2.13.2", 53 | "redux-promise": "^0.5.3", 54 | "socket.io": "^2.0.3", 55 | "spiced-pg": "spicedacademy/spiced-pg", 56 | "styled-components": "^2.1.2", 57 | "tern-node-express": "^0.4.0", 58 | "uid-safe": "^2.1.4", 59 | "webpack": "^1.14.0", 60 | "webpack-dev-middleware": "^1.8.4" 61 | }, 62 | "keywords": [], 63 | "author": "", 64 | "license": "ISC", 65 | "devDependencies": { 66 | "eslint": "^4.6.1", 67 | "eslint-plugin-react": "^7.3.0" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /public/ico-security.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/profile_pic_def/def_profilePic_1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 10 | 13 | 16 | 21 | 24 | 25 | 26 | 27 | 29 | 30 | 32 | 40 | 44 | 47 | 51 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /public/profile_pic_def/def_profilePic_10.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 10 | 12 | 13 | 14 | 19 | 22 | 23 | 24 | 28 | 30 | 31 | 33 | 35 | 36 | 37 | 42 | 44 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /public/profile_pic_def/def_profilePic_11.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 10 | 15 | 18 | 21 | 23 | 25 | 27 | 29 | 31 | 33 | 35 | 38 | 41 | 44 | 47 | 49 | 50 | 52 | 55 | 56 | 57 | 58 | 59 | 60 | 63 | 64 | 65 | 66 | 67 | 69 | 71 | 74 | 77 | 80 | 83 | 89 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /public/profile_pic_def/def_profilePic_12.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 10 | 15 | 18 | 21 | 23 | 25 | 26 | 28 | 29 | 32 | 35 | 36 | 40 | 43 | 45 | 46 | 50 | 53 | 63 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /public/profile_pic_def/def_profilePic_2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 10 | 13 | 15 | 20 | 23 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 37 | 38 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /public/profile_pic_def/def_profilePic_3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 10 | 14 | 15 | 18 | 20 | 21 | 26 | 29 | 31 | 33 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /public/profile_pic_def/def_profilePic_4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 10 | 14 | 17 | 22 | 25 | 27 | 29 | 31 | 33 | 36 | 38 | 41 | 45 | 48 | 51 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /public/profile_pic_def/def_profilePic_5.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 10 | 14 | 17 | 22 | 25 | 27 | 29 | 31 | 33 | 38 | 41 | 43 | 45 | 47 | 49 | 51 | 53 | 54 | 56 | 58 | 59 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /public/profile_pic_def/def_profilePic_6.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 11 | 13 | 14 | 15 | 16 | 21 | 24 | 28 | 31 | 33 | 35 | 37 | 40 | 43 | 47 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /public/profile_pic_def/def_profilePic_7.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 10 | 15 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 33 | 36 | 40 | 43 | 46 | 48 | 52 | 55 | 57 | 59 | 61 | 63 | 64 | 67 | 69 | 70 | 72 | 73 | 75 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /public/profile_pic_def/def_profilePic_8.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 10 | 12 | 14 | 16 | 17 | 18 | 22 | 25 | 30 | 33 | 34 | 35 | 37 | 39 | 42 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /public/profile_pic_def/def_profilePic_9.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 17 | 20 | 25 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 43 | 45 | 47 | 49 | 50 | 55 | 59 | 60 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /readme/p2p_chat-find_friends.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suddenlyGiovanni/spiced_academy--p2p_chat/a64d852d68888778f407edd741eb922be90749cb/readme/p2p_chat-find_friends.gif -------------------------------------------------------------------------------- /readme/p2p_chat-group_chat.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suddenlyGiovanni/spiced_academy--p2p_chat/a64d852d68888778f407edd741eb922be90749cb/readme/p2p_chat-group_chat.gif -------------------------------------------------------------------------------- /readme/p2p_chat-login.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suddenlyGiovanni/spiced_academy--p2p_chat/a64d852d68888778f407edd741eb922be90749cb/readme/p2p_chat-login.gif -------------------------------------------------------------------------------- /readme/p2p_chat-manage_friendship .gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suddenlyGiovanni/spiced_academy--p2p_chat/a64d852d68888778f407edd741eb922be90749cb/readme/p2p_chat-manage_friendship .gif -------------------------------------------------------------------------------- /readme/p2p_chat-private_chat.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suddenlyGiovanni/spiced_academy--p2p_chat/a64d852d68888778f407edd741eb922be90749cb/readme/p2p_chat-private_chat.gif -------------------------------------------------------------------------------- /readme/p2p_chat-profile_pic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suddenlyGiovanni/spiced_academy--p2p_chat/a64d852d68888778f407edd741eb922be90749cb/readme/p2p_chat-profile_pic.gif -------------------------------------------------------------------------------- /readme/p2p_chat-register.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suddenlyGiovanni/spiced_academy--p2p_chat/a64d852d68888778f407edd741eb922be90749cb/readme/p2p_chat-register.gif -------------------------------------------------------------------------------- /routes/root.js: -------------------------------------------------------------------------------- 1 | // ROUTE: --> / 2 | const router = require( 'express' ).Router(); 3 | const path = require( 'path' ); 4 | 5 | 6 | 7 | // router.get( '/', ( req, res ) => { 8 | // res.sendFile( path.join( __dirname, '../index.html' ) ); 9 | // } ); 10 | 11 | 12 | 13 | router.get( '/', function ( req, res ) { 14 | if ( !req.session.user ) { 15 | return res.redirect( '/welcome/' ); 16 | } 17 | res.sendFile( path.join( __dirname, '../index.html' ) ); 18 | } ); 19 | 20 | 21 | 22 | router.get( '/welcome/', function ( req, res ) { 23 | if ( req.session.user ) { 24 | return res.redirect( '/' ); 25 | } 26 | res.sendFile( path.join( __dirname, '../index.html' ) ); 27 | } ); 28 | 29 | 30 | 31 | /* MODULE EXPORTS */ 32 | module.exports = router; 33 | -------------------------------------------------------------------------------- /routes/webSocket-bk.js: -------------------------------------------------------------------------------- 1 | // ROUTE: --> /ws/ 2 | const router = require( 'express' ).Router(), 3 | db = require( '../modules/dbQuery' ), 4 | io = require( '../index' ).io; 5 | 6 | 7 | // SOCKET.IO WS: 8 | // define constructor function that gets `io` send to it 9 | // function( io ) { 10 | // }; 11 | io.on( 'connection', socket => { 12 | console.log( `socket with the id ${socket.id} is now connected` ); 13 | 14 | socket.on( 'disconnect', () => { 15 | console.log( `SocketIo - on: "disconnect" - socket with the id ${socket.id} is now disconnected` ); 16 | // remove here the disconnected socket from the list of online users: 17 | /* 18 | it is possible for a single user to appear in the list more than once. 19 | it is important to only remove the item from the list that has the 20 | matching socket id when 'disconnect' event occurs 21 | */ 22 | const disconnectedUserSocket = onlineUsers.find( user => user.socketId == socket.id ); 23 | // console.log( 'disconnectedUserSocket: ', disconnectedUserSocket ); 24 | 25 | onlineUsers.splice( onlineUsers.indexOf( disconnectedUserSocket ), 1 ); 26 | // console.log( 'onlineUsers - after removing disconnectedUserSocket: ', onlineUsers ); 27 | /* 28 | When a user disconnects, after removing the user with the id of the 29 | socket that disconnected from the list, you should determine if that 30 | user is no longer in your list at all. If the user is gone, the server 31 | should send a message to all connected clients with the id of the 32 | disconnected user in the payload so that they can update their lists of 33 | online users accordingly. Let's call this event 'userLeft'. 34 | */ 35 | const disconnectedUserId = onlineUsers.filter( user => { 36 | // remember that filter return an array 37 | return user.userId == disconnectedUserSocket.userId; 38 | } ); 39 | // console.log( 'disconnectedUserId: ', disconnectedUserId ); 40 | 41 | if ( disconnectedUserId.length === 0 ) { 42 | const userId = disconnectedUserSocket.userId; 43 | console.log(`last user with id ${userId} has gone offline`); 44 | io.sockets.emit( 'userLeft', userId ); 45 | } 46 | 47 | } ); 48 | 49 | socket.on( 'chatMessage', ( messageBody ) => { 50 | const messengerId = onlineUsers.find( user => user.socketId == socket.id ).userId; 51 | console.log( `SocketIo - on: "chatMessage" - messengerId: ${messengerId} - payload:`, messageBody ); 52 | /* When the server receives this event, it should broadcast 53 | a 'chatMessage' event to all of the connected sockets. 54 | The payload for this event should include the message the user sent 55 | as well as the user's id, first name, last name, and profile pic.*/ 56 | return db.createPublicMessage( messengerId, messageBody ) 57 | .then( newPublicMessage => io.sockets.emit( 'chatMessage', newPublicMessage ) ) 58 | .catch( err => console.error( err.stack ) ); 59 | } ); 60 | 61 | 62 | socket.on( 'chatMessagePrivate', privateMessage => { 63 | const fromUserId = onlineUsers.find( user => user.socketId == socket.id ).userId; 64 | const { toUserId, messageBody } = privateMessage; 65 | 66 | const toUserIdOnline = onlineUsers.find( user => user.userId == toUserId ); 67 | 68 | const fromUserSocketId = socket.id; 69 | const toUserSocketId = toUserIdOnline && toUserIdOnline.socketId; 70 | 71 | console.log( `SocketIo - on: "chatMessagePrivate" 72 | - fromUserId: ${fromUserId} - toUserId: ${toUserId} 73 | - payload:`, privateMessage ); 74 | return db.createPrivateMessage( fromUserId, toUserId, messageBody ) 75 | 76 | .then( newPrivateMessage => { 77 | console.log( 'emitting private messaging - fromUserSocketId', fromUserSocketId ); 78 | io.sockets.sockets[ fromUserSocketId ].emit( 'privateChatMessage', newPrivateMessage ); 79 | toUserSocketId && io.sockets.sockets[ toUserSocketId ].emit( 'privateChatMessage', newPrivateMessage ); 80 | } ) 81 | 82 | .catch( err => console.error( err.stack ) ); 83 | } ); 84 | 85 | } ); 86 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 87 | 88 | function makeSureUserIsLoggedIn( req, res, next ) { 89 | console.log( 'webSocket.js - fn: makeSureUserIsLoggedIn' ); 90 | next(); 91 | } 92 | 93 | 94 | let onlineUsers = []; 95 | let mapUsersIdToPeerId = []; 96 | /* exemple... 97 | onlineUsers = [ 98 | { 99 | socketId: 'wJdwDQAKhUuXxZ2vAAAA', 100 | userId: 1 101 | } 102 | ]; 103 | 104 | mapUsersIdToPeerId = [ 105 | { 106 | peerId: 'lololololllolloo', 107 | userId: 1 108 | } 109 | ] 110 | */ 111 | 112 | // SOCKET.IO ROUTES 113 | /* make a route that the client can hit after the socket connects. 114 | The /ws/connected/:socketId route can then read the user's id from 115 | the session and the socket id from req.params */ 116 | router.post( '/connected/:socketId', makeSureUserIsLoggedIn, ( req, res ) => { 117 | const uid = req.session.user.uid; 118 | const socketId = req.params.socketId; 119 | 120 | const socketAlreadyThere = onlineUsers.find( user => user.socketId == socketId ); 121 | const userAlreadyThere = onlineUsers.find( user => user.userId == uid ); 122 | 123 | console.log( `API: method: POST /ws/connected/:${socketId} - uid: ${uid} - 124 | onlineUsers: `, onlineUsers, '\n' ); 125 | 126 | if ( !socketAlreadyThere && io.sockets.sockets[ socketId ] ) { 127 | /* Every time a logged in user makes a request, push 128 | an object representing that user to an array after confirming that 129 | that user is not already in the list */ 130 | onlineUsers.push( { userId: uid, socketId } ); 131 | 132 | /* When a user is added to the list of online users, the server should 133 | send a message to that user with the list of all online users 134 | as the payload: event 'onlineUsers' */ 135 | return db.readAllUsersByIds( onlineUsers.map( user => user.userId ) ) 136 | 137 | .then( onlineUsers => io.sockets.sockets[ socketId ].emit( 'onlineUsers', onlineUsers ) ) 138 | 139 | .then( () => { 140 | return db.readAllPublicMessages() 141 | .then( publicMessageList => io.sockets.sockets[ socketId ].emit( 'publicChatMessages', publicMessageList ) ); 142 | } ) 143 | 144 | .then( () => { 145 | return db.readAllPrivateMessages( uid ) 146 | .then( privateMessageList => io.sockets.sockets[ socketId ].emit( 'privateChatMessages', privateMessageList ) ); 147 | } ) 148 | 149 | .then( () => { 150 | res.json( { success: true } ); 151 | /* Also when a user is added to the list of online users, 152 | the server should send a message to all online users with information 153 | about the user who just came online as the payload, allowing all clients 154 | to keep their list of online users updated: event 'userJoined' */ 155 | if ( !userAlreadyThere ) { 156 | return db.readUser( uid ) 157 | .then( userJoined => { 158 | // FIXME: this should not be done here!!!!! 159 | userJoined.online = true; 160 | return io.sockets.emit( 'userJoined', userJoined ); 161 | } ); 162 | } 163 | } ) 164 | 165 | .catch( err => console.log( err ) ); 166 | } 167 | } ); 168 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 169 | 170 | // PEER.JS ROUTES 171 | /* make a route that the client can hit after the socket connects. 172 | The /ws/storeIdToServer/:peerId route can then read the user's id from 173 | the session and the socket id from req.params 174 | */ 175 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 176 | 177 | router.post( '/storeIdToServer/:peerId', makeSureUserIsLoggedIn, ( req, res ) => { 178 | const uid = req.session.user.uid; 179 | const peerId = req.params.peerId; 180 | 181 | // FIXME: eventually in the future send the peerId info to just the clients of the the user's friend 182 | 183 | const peerAlreadyThere = mapUsersIdToPeerId.find( user => user.peerId == peerId ); 184 | const userAlreadyThere = mapUsersIdToPeerId.find( user => user.userId == uid ); 185 | 186 | console.log( `API: method: POST /ws/storeIdToServer/:${peerId} - uid: ${uid} - 187 | onlineUsers: `, onlineUsers, '\n' ); 188 | 189 | if ( !peerAlreadyThere ) { 190 | /* Every time a logged in user makes a request, push 191 | an object representing that user to an array after confirming that 192 | that user is not already in the list */ 193 | mapUsersIdToPeerId.push( { userId: uid, peerId } ); 194 | 195 | /* When a user is added to the list of mapUsersIdToPeerId, the server should 196 | send a message to that user with the list of all online users linked to their peerId 197 | as the payload: event 'onlineUsers' */ 198 | return db.readAllUsersByIds( mapUsersIdToPeerId.map( user => user.userId ) ) 199 | 200 | .then( onlinePeers => { 201 | // find the socket that belongs to the user 202 | const socketId = onlineUsers.find( user => user.userId == uid ).socketId; 203 | // set update the user list with their respecting peerId 204 | const updatedUsers = onlinePeers.map( user => { 205 | const peerIdToAdd = mapUsersIdToPeerId.find( peer => peer.userId === user.uid ).peerId; 206 | return { ...user, peerId: peerIdToAdd }; 207 | } ); 208 | return io.sockets.sockets[ socketId ].emit( 'onlinePeers', updatedUsers ); 209 | } ) 210 | 211 | .then( () => { 212 | res.json( { success: true } ); 213 | /* Also when a user is added to the list of online users, 214 | the server should send a message to all online users with information 215 | about the user who just came online as the payload, allowing all clients 216 | to keep their list of online users updated: event 'userJoined' */ 217 | if ( !userAlreadyThere ) { 218 | return db.readUser( uid ) 219 | .then( userData => { 220 | // FIXME: this should not be done here!!!!! 221 | // userData.peerId = peerId; 222 | return io.sockets.emit( 'peerJoined', { ...userData, peerId: peerId } ); 223 | } ); 224 | } 225 | } ) 226 | 227 | .catch( err => console.log( err ) ); 228 | } 229 | } ); 230 | 231 | 232 | /* MODULE EXPORTS */ 233 | module.exports = router; 234 | // module.exports.io = io; 235 | -------------------------------------------------------------------------------- /routes/webSocket.js: -------------------------------------------------------------------------------- 1 | // ROUTE: --> /ws/ 2 | const router = require( 'express' ).Router(), 3 | db = require( '../modules/dbQuery' ), 4 | io = require( '../index' ).io; 5 | 6 | let onlineUsers = {}; 7 | /* example... 8 | onlineUsers = { 9 | uid:{ 10 | socketId: ['wJdwDQAKhUuXxZ2vAAAA', 'yolloyllyo'], 11 | peerId: 'lololololllolloo', 12 | } 13 | } 14 | */ 15 | 16 | // SOCKET.IO WS: 17 | // define constructor function that gets `io` send to it 18 | // function( io ) { 19 | // }; 20 | io.on( 'connection', socket => { 21 | console.log( `socket with the id ${socket.id} is now connected` ); 22 | 23 | socket.on( 'disconnect', () => { 24 | console.log( `SocketIo - on: "disconnect" - socket with the id ${socket.id} is now disconnected` ); 25 | // remove here the disconnected socket from the list of online users: 26 | /* 27 | it is possible for a single user to appear in the list more than once. 28 | it is important to only remove the item from the list that has the 29 | matching socket id when 'disconnect' event occurs 30 | */ 31 | 32 | let userId; 33 | 34 | for ( let uid in onlineUsers ) { 35 | const socketIds = onlineUsers[ uid ].socketIds; 36 | const matchSocket = socketIds.find( socketId => socketId === socket.id ); 37 | if ( matchSocket ) { 38 | userId = uid; 39 | } 40 | } 41 | 42 | if ( !onlineUsers[ userId ] ) { 43 | return 44 | } 45 | 46 | const socketIdIndex = onlineUsers[ userId ].socketIds.indexOf( socket.id ); 47 | onlineUsers[ userId ].socketIds.splice( socketIdIndex, 1 ); 48 | 49 | 50 | // console.log( 'onlineUsers - after removing disconnectedUserSocket: ', onlineUsers ); 51 | /* 52 | When a user disconnects, after removing the user with the id of the 53 | socket that disconnected from the list, you should determine if that 54 | user is no longer in your list at all. If the user is gone, the server 55 | should send a message to all connected clients with the id of the 56 | disconnected user in the payload so that they can update their lists of 57 | online users accordingly. Let's call this event 'userLeft'. 58 | */ 59 | 60 | if ( onlineUsers[ userId ].socketIds.length === 0 ) { 61 | console.log( `last user with id ${userId} has gone offline` ); 62 | io.sockets.emit( 'userLeft', userId ); 63 | delete onlineUsers[ userId ]; 64 | } 65 | 66 | } ); 67 | 68 | socket.on( 'chatMessage', messageBody => { 69 | let userId; 70 | for ( let uid in onlineUsers ) { 71 | const socketIds = onlineUsers[ uid ].socketIds; 72 | const matchSocket = socketIds.find( socketId => socketId === socket.id ); 73 | if ( matchSocket ) { 74 | userId = uid; 75 | } 76 | } 77 | 78 | console.log( `SocketIo - on: "chatMessage" - messengerId: ${userId} - payload:`, messageBody ); 79 | /* When the server receives this event, it should broadcast 80 | a 'chatMessage' event to all of the connected sockets. 81 | The payload for this event should include the message the user sent 82 | as well as the user's id, first name, last name, and profile pic.*/ 83 | return db.createPublicMessage( userId, messageBody ) 84 | .then( newPublicMessage => io.sockets.emit( 'chatMessage', newPublicMessage ) ) 85 | .catch( err => console.error( err.stack ) ); 86 | } ); 87 | 88 | 89 | socket.on( 'chatMessagePrivate', privateMessage => { 90 | let fromUserId; 91 | for ( let uid in onlineUsers ) { 92 | const socketIds = onlineUsers[ uid ].socketIds; 93 | const matchSocket = socketIds.find( socketId => socketId === socket.id ); 94 | if ( matchSocket ) { 95 | fromUserId = uid; 96 | } 97 | } 98 | const { toUserId, messageBody } = privateMessage; 99 | const toUserIdOnline = Object.keys( onlineUsers ).find( userId => userId == toUserId ); 100 | 101 | const fromUserSocketId = socket.id; 102 | const toUserSocketId = toUserIdOnline && onlineUsers[ toUserIdOnline ].socketIds.slice( -1 )[ 0 ]; 103 | 104 | console.log( `SocketIo - on: "chatMessagePrivate" 105 | - fromUserId: ${fromUserId} - toUserId: ${toUserId} 106 | - payload:`, privateMessage ); 107 | return db.createPrivateMessage( fromUserId, toUserId, messageBody ) 108 | 109 | .then( newPrivateMessage => { 110 | console.log( 'emitting private messaging - fromUserSocketId', fromUserSocketId ); 111 | io.sockets.sockets[ fromUserSocketId ].emit( 'privateChatMessage', newPrivateMessage ); 112 | toUserSocketId && io.sockets.sockets[ toUserSocketId ].emit( 'privateChatMessage', newPrivateMessage ); 113 | } ) 114 | 115 | .catch( err => console.error( err.stack ) ); 116 | } ); 117 | 118 | } ); 119 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 120 | 121 | function makeSureUserIsLoggedIn( req, res, next ) { 122 | console.log( 'webSocket.js - fn: makeSureUserIsLoggedIn' ); 123 | next(); 124 | } 125 | 126 | 127 | 128 | 129 | // SOCKET.IO ROUTES 130 | /* make a route that the client can hit after the socket connects. 131 | The /ws/connected/:socketId route can then read the user's id from 132 | the session and the socket id from req.params */ 133 | router.post( '/connected/:socketId', makeSureUserIsLoggedIn, ( req, res ) => { 134 | const uid = req.session.user.uid; 135 | const socketId = req.params.socketId; 136 | console.log( `API: method: POST /ws/connected/:${socketId} - uid: ${uid} - 137 | onlineUsers: `, onlineUsers, '\n' ); 138 | let userAlreadyThere = true; 139 | if ( !onlineUsers[ uid ] ) { 140 | userAlreadyThere = false; 141 | onlineUsers[ uid ] = { 142 | socketIds: [ socketId ] 143 | }; 144 | } 145 | 146 | const socketAlreadyThere = onlineUsers[ uid ].socketIds.find( socket => socket == socketId ); 147 | 148 | /* Every time a logged in user makes a request, push 149 | an object representing that user to an array after confirming that 150 | that user is not already in the list */ 151 | 152 | // if the obj with key of uid is present => then update it's property 153 | 154 | // if the obj with key of uid is present => then update it's property 155 | // if the socket is not in the user's socketsIds list, than add it to he list. 156 | if ( !socketAlreadyThere && io.sockets.sockets[ socketId ] ) { 157 | onlineUsers[ uid ].socketIds.push( socketId ); 158 | } 159 | // else, there can't be any sockets ids duplication's, so break 160 | 161 | 162 | // THEN SEND the list of all online users TO THE USER HIMSELF 163 | /* When a user is added to the list of online users, the server should 164 | send a message to that user with the list of all online users 165 | as the payload: event 'onlineUsers' */ 166 | return db.readAllUsersByIds( Object.keys( onlineUsers ) ) 167 | 168 | .then( onlineUsers => io.sockets.sockets[ socketId ].emit( 'onlineUsers', onlineUsers ) ) 169 | 170 | .then( () => { 171 | return db.readAllPublicMessages().then( publicMessageList => io.sockets.sockets[ socketId ].emit( 'publicChatMessages', publicMessageList ) ); 172 | } ) 173 | 174 | .then( () => { 175 | return db.readAllPrivateMessages( uid ).then( privateMessageList => io.sockets.sockets[ socketId ].emit( 'privateChatMessages', privateMessageList ) ); 176 | } ) 177 | 178 | // then send TO ALL ONLINE USERS the data and socketId of the user himself. 179 | .then( () => { 180 | res.json( { success: true } ); 181 | /* Also when a user is added to the list of online users, 182 | the server should send a message to all online users with information 183 | about the user who just came online as the payload, allowing all clients 184 | to keep their list of online users updated: event 'userJoined' */ 185 | if ( !userAlreadyThere ) { 186 | return db.readUser( uid ) 187 | .then( userJoined => { 188 | // FIXME: this should not be done here!!!!! 189 | userJoined.online = true; 190 | return io.sockets.emit( 'userJoined', userJoined ); 191 | } ); 192 | } 193 | } ) 194 | 195 | .catch( err => console.log( err ) ); 196 | 197 | } ); 198 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 199 | 200 | // PEER.JS ROUTES 201 | /* make a route that the client can hit after the socket connects. 202 | The /ws/storeIdToServer/:peerId route can then read the user's id from 203 | the session and the socket id from req.params 204 | */ 205 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 206 | 207 | router.post( '/storeIdToServer/:peerId', makeSureUserIsLoggedIn, ( req, res ) => { 208 | const uid = req.session.user.uid; 209 | const peerId = req.params.peerId; 210 | 211 | // FIXME: eventually in the future send the peerId info to just the clients of the the user's friend 212 | 213 | // const peerAlreadyThere = mapUsersIdToPeerId.find( user => user.peerId == peerId ); 214 | const peerAlreadyThere = onlineUsers[ uid ].peerId === peerId; 215 | // const userAlreadyThere = mapUsersIdToPeerId.find( user => user.userId == uid ); 216 | // const userAlreadyThere = onlineUsers.hasOwnProperty( uid ); 217 | 218 | console.log( `API: method: POST /ws/storeIdToServer/:${peerId} - uid: ${uid} - 219 | onlineUsers: `, onlineUsers, '\n' ); 220 | 221 | /* Every time a logged in user makes a request, push 222 | an object representing that user to an array after confirming that 223 | that user is not already in the list */ 224 | 225 | // if user is not yet a prop of the onlineUsers obj 226 | if ( !onlineUsers.hasOwnProperty( uid ) ) { 227 | // then add a new user to the onlineUsers and set it's peerId prop accordingly 228 | onlineUsers[ uid ] = { peerId: peerId }; 229 | } else if ( onlineUsers.hasOwnProperty( uid ) ) { 230 | if ( !peerAlreadyThere ) { 231 | // if the obj with key of uid is present => then update it's property 232 | // if the peerId is not already there, than add it to the prop. 233 | onlineUsers[ uid ].peerId = peerId; 234 | } 235 | } 236 | 237 | /* When a user is added to the list of mapUsersIdToPeerId, the server should 238 | send a message to that user with the list of all online users linked to their peerId 239 | as the payload: event 'onlineUsers' */ 240 | return db.readAllUsersByIds( Object.keys( onlineUsers ) ) 241 | 242 | .then( onlinePeers => { 243 | // find the socket that belongs to the user 244 | const socketId = onlineUsers[ uid ].socketIds.slice( -1 )[ 0 ]; 245 | 246 | // set update the user list with their respecting peerId 247 | const updatedUsers = onlinePeers.map( user => { 248 | // const peerIdToAdd = mapUsersIdToPeerId.find( peer => peer.userId === user.uid ).peerId; 249 | for ( let uid in onlineUsers ) { 250 | if ( uid == user.uid ) { 251 | return { ...user, peerId: onlineUsers[ uid ].peerId }; 252 | } 253 | } 254 | } ); 255 | return io.sockets.sockets[ socketId ].emit( 'onlinePeers', updatedUsers ); 256 | } ) 257 | 258 | .then( () => { 259 | res.json( { success: true } ); 260 | /* Also when a user is added to the list of online users, 261 | the server should send a message to all online users with information 262 | about the user who just came online as the payload, allowing all clients 263 | to keep their list of online users updated: event 'userJoined' */ 264 | return db.readUser( uid ) 265 | .then( userData => { 266 | // FIXME: this should not be done here!!!!! 267 | // userData.peerId = peerId; 268 | return io.sockets.emit( 'peerJoined', { ...userData, peerId: peerId } ); 269 | } ); 270 | 271 | } ) 272 | 273 | .catch( err => console.log( err ) ); 274 | } ); 275 | 276 | 277 | /* MODULE EXPORTS */ 278 | module.exports = router; 279 | // module.exports.io = io; 280 | -------------------------------------------------------------------------------- /src/actions/actions.js: -------------------------------------------------------------------------------- 1 | import axios from '../utils/axios'; 2 | 3 | 4 | 5 | 6 | export function logOutUser() { 7 | console.log( 'REDUX - ACTION - fn: logOutUser' ); 8 | return axios.get( '/api/logout' ) 9 | 10 | .then( result => { 11 | console.log( 'REDUX - ACTION - fn: logOutUser - data', result.data ); 12 | if ( result.data.success ) { 13 | window.location.href = '/'; 14 | } 15 | } ) 16 | 17 | .catch( err => console.log( err ) ); 18 | } 19 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 20 | 21 | 22 | export function loadUserData() { 23 | console.log( 'REDUX - ACTION - fn: loadUserData' ); 24 | return axios.get( '/api/user' ) 25 | .then( result => { 26 | console.log( 'REDUX - ACTION - fn: loadUserData - data', result.data ); 27 | return { 28 | type: 'LOAD_USER_DATA', 29 | user: result.data.userData 30 | }; 31 | 32 | } ) 33 | 34 | .catch( err => { 35 | console.log( err ); 36 | return { type: 'ERROR' }; 37 | } ); 38 | } 39 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 40 | 41 | 42 | export function updateProfilePic( formData ) { 43 | console.log( 'REDUX - ACTION - fn: updateProfilePic' ); 44 | return axios.put( '/api/user/profile_pic', formData ) 45 | 46 | .then( result => { 47 | if ( result.data.success ) { 48 | return { 49 | type: 'UPDATE_USER_DATA', 50 | user: result.data.userData 51 | }; 52 | } 53 | } ) 54 | 55 | .catch( err => console.log( err ) ); 56 | 57 | } 58 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 59 | 60 | 61 | export function loadLatestUsers() { 62 | console.log( 'REDUX - ACTION - fn: loadLatestUsers' ); 63 | return axios.get( '/api/users' ) 64 | 65 | .then( result => { 66 | console.log( 'REDUX - ACTION - fn: loadLatestUsers - data', result.data ); 67 | return { 68 | type: 'LOAD_LATEST_USERS', 69 | users: result.data.users 70 | }; 71 | } ) 72 | 73 | .catch( err => console.log( err ) ); 74 | } 75 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 76 | 77 | export function loadSearchedUsers( search ) { 78 | console.log( 'REDUX - ACTION - fn: loadSearchedUsers', search ); 79 | // TODO: switch to socketio at some point 80 | 81 | return axios.post( '/api/users/search', { search } ) 82 | .then( results => { 83 | console.log( 'REDUX - ACTION - fn: loadSearchedUsers - data', results.data.users ); 84 | return { 85 | type: 'LOAD_SEARCHED_USERS', 86 | users: results.data.users 87 | }; 88 | } ) 89 | 90 | .catch( err => console.log( err ) ); 91 | } 92 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 93 | 94 | 95 | 96 | 97 | export function clearSearchedUsers() { 98 | console.log( 'REDUX - ACTION - fn: clearSearchedUsers' ); 99 | return { type: 'CLEAR_SEARCHED_USERS' }; 100 | } 101 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 102 | 103 | 104 | 105 | 106 | export function loadFriends() { 107 | console.log( 'REDUX - ACTION - fn: loadFriends' ); 108 | 109 | return axios.get( '/api/friends' ) 110 | 111 | .then( result => { 112 | console.log( 'REDUX - ACTION - fn: loadFriends - data', result.data.friends ); 113 | return { 114 | type: 'LOAD_FRIENDS', 115 | friends: result.data.friends 116 | }; 117 | } ) 118 | 119 | .catch( err => { 120 | console.log( err ); 121 | return { type: 'ERROR' }; 122 | } ); 123 | } 124 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 125 | 126 | 127 | 128 | export function requestFriendship( fromUserId, toUserId, status ) { 129 | 130 | console.log( `REDUX - ACTION - fn: requestFriendship 131 | fromUserId: ${fromUserId}, 132 | toUserId: ${toUserId}, 133 | status: ${status}` ); 134 | 135 | return axios.post( `/api/friends/${fromUserId}/${toUserId}/new`, { status: status } ) 136 | 137 | .then( result => { 138 | console.log( 'REDUX - ACTION - fn: requestFriendship - data', result.data ); 139 | return { 140 | type: 'REQUEST_FRIENDSHIP', 141 | newFriendshipStatus: result.data.newFriendshipStatus 142 | }; 143 | } ) 144 | 145 | .catch( err => { 146 | console.log( err ); 147 | return { type: 'ERROR' }; 148 | } ); 149 | } 150 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 151 | 152 | 153 | 154 | 155 | export function updateFriendship( fromUserId, toUserId, status ) { 156 | 157 | console.log( `REDUX - ACTION - fn: updateFriendship 158 | fromUserId: ${fromUserId}, 159 | toUserId: ${toUserId}, 160 | status: ${status}` ); 161 | 162 | return axios.put( `/api/friends/${fromUserId}/${toUserId}`, { status: status } ) 163 | 164 | .then( result => { 165 | console.log( 'REDUX - ACTION - fn: updateFriendship - data', result.data ); 166 | return { 167 | type: 'UPDATE_FRIENDSHIP', 168 | newFriendshipStatus: result.data.newFriendshipStatus 169 | }; 170 | } ) 171 | 172 | .catch( err => { 173 | console.log( err ); 174 | return { type: 'ERROR' }; 175 | } ); 176 | } 177 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 178 | 179 | 180 | 181 | 182 | export function connectUser( socketId ) { 183 | console.log( 'REDUX - ACTION - fn: connectUser' ); 184 | return axios.post( `/ws/connected/${socketId}` ) 185 | 186 | .then( () => { return { type: 'CONNECT_USER' }; } ) 187 | 188 | .catch( err => console.log( err ) ); 189 | } 190 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 191 | 192 | 193 | 194 | 195 | export function sendPeerIdToServer( peerId ) { 196 | console.log( 'REDUX - ACTION - fn: sendPeerIdToServer' ); 197 | return axios.post( `/ws/storeIdToServer/${peerId}` ) 198 | .then( () => { 199 | return { type: 'ADD_PEERID_TO_USER_DATA', peerId }; 200 | } ) 201 | .catch( err => console.log( err ) ); 202 | } 203 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 204 | 205 | 206 | 207 | 208 | export function createOnlineUsers( onlineUsers ) { 209 | console.log( 'REDUX - ACTION - fn: createOnlineUsers' ); 210 | return { 211 | type: 'CREATE_ONLINE_USERS', 212 | onlineUsers 213 | }; 214 | } 215 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 216 | 217 | 218 | 219 | 220 | export function createOnlinePeers( onlinePeers ) { 221 | console.log( 'REDUX - ACTION - fn: createOnlinePeers' ); 222 | return { 223 | type: 'CREATE_ONLINE_PEERS', 224 | onlinePeers 225 | }; 226 | } 227 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 228 | 229 | 230 | 231 | 232 | export function addOnlineUser( userJoined ) { 233 | console.log( 'REDUX - ACTION - fn: addOnlineUser' ); 234 | return { 235 | type: 'ADD_ONLINE_USER', 236 | userJoined 237 | }; 238 | } 239 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 240 | 241 | 242 | 243 | 244 | export function addOnlinePeer( peerJoined ) { 245 | console.log( 'REDUX - ACTION - fn: addOnlinePeer' ); 246 | return { 247 | type: 'ADD_ONLINE_PEER', 248 | peerJoined 249 | }; 250 | } 251 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 252 | 253 | 254 | 255 | 256 | export function removeOnlineUser( uid ) { 257 | console.log( 'REDUX - ACTION - fn: removeOnlineUser - uid:', uid ); 258 | return { 259 | type: 'REMOVE_ONLINE_USER', 260 | uid 261 | }; 262 | } 263 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 264 | 265 | 266 | 267 | 268 | export function createPublicMessageList( publicMessageList ) { 269 | console.log( 'REDUX - ACTION - fn: createPublicMessageList' ); 270 | return { 271 | type: 'CREATE_PUBLIC_MESSAGE_LIST', 272 | publicMessageList 273 | }; 274 | } 275 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 276 | 277 | 278 | 279 | 280 | export function createPrivateMessageList( privateMessageList ) { 281 | console.log( 'REDUX - ACTION - fn: createPrivateMessageList' ); 282 | return { 283 | type: 'CREATE_PRIVATE_MESSAGE_LIST', 284 | privateMessageList 285 | }; 286 | } 287 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 288 | 289 | 290 | 291 | 292 | export function addNewPublicMessage( newPublicMessage ) { 293 | console.log( 'REDUX - ACTION - fn: addNewPublicMessage' ); 294 | return { 295 | type: 'ADD_NEW_PUBLIC_MESSAGE', 296 | newPublicMessage 297 | }; 298 | } 299 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 300 | 301 | 302 | 303 | 304 | export function addNewPrivateMessage( newPrivateMessage ) { 305 | console.log( 'REDUX - ACTION - fn: addNewPrivateMessage', newPrivateMessage ); 306 | return { 307 | type: 'ADD_NEW_PRIVATE_MESSAGE', 308 | newPrivateMessage 309 | }; 310 | } 311 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 312 | 313 | 314 | 315 | 316 | export function persistOtherUid( otherUid ) { 317 | console.log( 'REDUX - ACTION - fn: persistOtherUid' ); 318 | return { 319 | type: 'PERSIST_OTHER_UID', 320 | otherUid 321 | }; 322 | } 323 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 324 | 325 | 326 | 327 | 328 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 329 | -------------------------------------------------------------------------------- /src/components/chat-private.js: -------------------------------------------------------------------------------- 1 | // REACT 2 | import React, { Component } from 'react'; 3 | // UTILS 4 | import getSocket from '../utils/socketIo'; 5 | 6 | // MATERIAL UI 7 | import { Card, CardHeader, CardTitle, CardText } from 'material-ui/Card'; 8 | import Avatar from 'material-ui/Avatar'; 9 | import { Toolbar } from 'material-ui/Toolbar'; 10 | import TextField from 'material-ui/TextField'; 11 | 12 | 13 | export default class ChatPrivate extends Component { 14 | constructor( props ) { 15 | super( props ); 16 | } 17 | 18 | handleSubmit( e ) { 19 | if ( e.keyCode === 13 ) { 20 | e.preventDefault(); 21 | console.log( e.target.value ); 22 | getSocket().emit( 'chatMessagePrivate', { 23 | toUserId: this.props.otherUser.uid, 24 | messageBody: e.target.value 25 | } ); 26 | e.target.value = ''; 27 | } 28 | } 29 | 30 | componentDidUpdate() { 31 | this.messageArea.scrollTop = this.messageArea.scrollHeight; 32 | } 33 | 34 | render() { 35 | console.log( 'ChatPrivate - RENDER - this.props: ', this.props ); 36 | const { currentUser, otherUser, messages } = this.props; 37 | 38 | const chatMessages = messages && messages.map( message => { 39 | const { mid, sender, msg, timestamp } = message; 40 | 41 | 42 | 43 | if ( sender ) { 44 | // MESSAGE FROM THE USER 45 | const avatar = 46 | return ( 47 | 55 | 60 | {msg} 61 | 62 | ); 63 | } else { 64 | // MESSAGE FORM THE OTHE USER 65 | const avatar = 66 | return ( 67 | 75 | 79 | {msg} 80 | 81 | ); 82 | } 83 | 84 | } ); 85 | 86 | return ( 87 |
88 |
this.messageArea = elem} 90 | style={{ 91 | overflow: 'scroll', 92 | height: 400, 93 | display: 'flex', 94 | flexDirection: 'column', 95 | justifyContent: 'space-evenly', 96 | alignItems: 'center' 97 | }}> 98 | 99 | {chatMessages} 100 | 101 |
102 | 103 | this.newMessage = newMessage} 109 | onKeyDown={ e => this.handleSubmit(e) } 110 | /> 111 | 112 |
113 | ); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/components/chat-public.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import getSocket from '../utils/socketIo'; 3 | import ProfilePicOther from './profilePicOther'; 4 | 5 | // MATERIAL UI 6 | import { Card, CardHeader, CardTitle, CardText } from 'material-ui/Card'; 7 | import Avatar from 'material-ui/Avatar'; 8 | import { Toolbar } from 'material-ui/Toolbar'; 9 | import TextField from 'material-ui/TextField'; 10 | 11 | 12 | 13 | export default class ChatPublic extends Component { 14 | constructor( props ) { 15 | super( props ); 16 | } 17 | 18 | handleSubmit( e ) { 19 | if ( e.keyCode === 13 ) { 20 | e.preventDefault(); 21 | console.log( e.target.value ); 22 | getSocket().emit( 'chatMessage', e.target.value ); 23 | e.target.value = ''; 24 | } 25 | } 26 | 27 | componentDidUpdate() { 28 | this.messageArea.scrollTop = this.messageArea.scrollHeight; 29 | } 30 | 31 | render() { 32 | console.log( 'ChatPublic - RENDER - this.props: ', this.props ); 33 | const { currentUser } = this.props; 34 | 35 | const chatMessages = this.props.publicMessagesList && this.props.publicMessagesList.map( message => { 36 | const { 37 | mid, 38 | fromUserId, 39 | firstName, 40 | lastName, 41 | profilePic, 42 | messageBody, 43 | timestamp 44 | } = message; 45 | 46 | const sender = ( currentUser.uid == message.uid ) ? true : false 47 | 48 | const avatar = 49 | 50 | if ( sender ) { 51 | return ( 52 | 53 | 61 | 66 | {messageBody} 67 | 68 | ); 69 | } else { 70 | return ( 71 | 79 | 83 | {messageBody} 84 | 85 | ); 86 | } 87 | 88 | 89 | } ); 90 | 91 | return ( 92 |
93 |
this.messageArea = elem} 95 | style={{ 96 | overflow: 'scroll', 97 | height: 400, 98 | display: 'flex', 99 | flexDirection: 'column', 100 | justifyContent: 'space-evenly', 101 | alignItems: 'center' 102 | }}> 103 | 104 | {chatMessages} 105 | 106 |
107 | 108 | this.newMessage = newMessage} 114 | onKeyDown={ e => this.handleSubmit(e) } 115 | /> 116 | 117 |
118 | ); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/components/chat-secure.js: -------------------------------------------------------------------------------- 1 | // REACT 2 | import React, { Component } from 'react'; 3 | // UTILS 4 | import getSocket from '../utils/socketIo'; 5 | import getPeers from '../utils/peer'; 6 | 7 | // MATERIAL UI 8 | import { Card, CardHeader, CardTitle, CardText } from 'material-ui/Card'; 9 | import Avatar from 'material-ui/Avatar'; 10 | import { Toolbar } from 'material-ui/Toolbar'; 11 | import TextField from 'material-ui/TextField'; 12 | 13 | let conn; 14 | 15 | export default class ChatSecure extends Component { 16 | constructor( props ) { 17 | super( props ); 18 | } 19 | 20 | componentDidMount() { 21 | conn = getPeers().connect( this.props.otherUser.peerId ); 22 | console.log( 'ChatSecure - componentDidMount - conn: ', conn ); 23 | } 24 | 25 | handleSubmit( e ) { 26 | if ( e.keyCode === 13 ) { 27 | e.preventDefault(); 28 | console.log( e.target.value ); 29 | conn.send( { 30 | fromUserId: this.props.currentUser.uid, 31 | toUserId: this.props.otherUser.uid, 32 | messageBody: e.target.value 33 | } ); 34 | // getSocket().emit( 'chatMessagePrivate', { 35 | // toUserId: this.props.otherUser.uid, 36 | // messageBody: e.target.value 37 | // } ); 38 | e.target.value = ''; 39 | } 40 | } 41 | 42 | componentDidUpdate() { 43 | this.messageArea.scrollTop = this.messageArea.scrollHeight; 44 | } 45 | 46 | render() { 47 | console.log( 'ChatSecure - RENDER - this.props: ', this.props ); 48 | const { currentUser, otherUser, messages } = this.props; 49 | 50 | const chatMessages = messages && messages.map( message => { 51 | const { mid, sender, msg, timestamp } = message; 52 | 53 | let uid, 54 | firstName, 55 | lastName, 56 | profilePic; 57 | 58 | 59 | if ( sender ) { 60 | uid = otherUser.uid; 61 | firstName = otherUser.firstName; 62 | lastName = otherUser.lastName; 63 | profilePic = otherUser.profilePic; 64 | } else { 65 | uid = currentUser.uid; 66 | firstName = currentUser.firstName; 67 | lastName = currentUser.lastName; 68 | profilePic = currentUser.profilePic; 69 | } 70 | 71 | const avatar = 72 | 73 | 74 | if ( sender ) { 75 | 76 | 81 | 82 | {msg} 83 | 84 | } 85 | 86 | return ( 87 |
  • 88 | 89 | 93 | {msg} 94 | 95 |
  • 96 | ); 97 | } ); 98 | 99 | return ( 100 |
    101 |
    this.messageArea = elem} 103 | style={{ 104 | overflow: 'scroll', 105 | height: 400 106 | }}> 107 | messages go here 108 |
      109 | {chatMessages} 110 |
    111 |
    112 | 113 | this.newMessage = newMessage} 119 | onKeyDown={ e => this.handleSubmit(e) } 120 | /> 121 | 122 |
    123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/components/friendshipButton.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const FriendshipButton = (props) => { 4 | return( 5 | 6 | ); 7 | }; 8 | 9 | export default FriendshipButton; 10 | -------------------------------------------------------------------------------- /src/components/friendships-blocked.js: -------------------------------------------------------------------------------- 1 | // REACT 2 | import React from 'react'; 3 | 4 | // MATERIAL-UI 5 | import Avatar from 'material-ui/Avatar'; 6 | import { List, ListItem } from 'material-ui/List'; 7 | import Subheader from 'material-ui/Subheader'; 8 | import Divider from 'material-ui/Divider'; 9 | import { grey400 } from 'material-ui/styles/colors'; 10 | import IconButton from 'material-ui/IconButton'; 11 | import IconMenu from 'material-ui/IconMenu'; 12 | import MenuItem from 'material-ui/MenuItem'; 13 | 14 | // ICONS 15 | import SocialPersonAdd from 'material-ui/svg-icons/social/person-add'; 16 | import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert'; 17 | 18 | const iconButtonElement = ( 19 | 24 | 25 | 26 | ); 27 | 28 | const BlockedFriendships = ( props ) => { 29 | console.log( 'BlockedFriendships - RENDER - this.props: ', props ); 30 | const { handleFriendshipChange } = props; 31 | 32 | const listBlockedFriendships = props.blockedFriendships.map( ( blockedFriend ) => { 33 | const { uid, firstName, lastName, profilePic } = blockedFriend; 34 | 35 | const rightIconMenu = ( 36 | handleFriendshipChange( uid, 'PENDING' ) }> 39 | }/> 43 | 44 | ); 45 | 46 | return ( 47 |
    48 | } 51 | rightIconButton={rightIconMenu} 52 | /> 53 | 54 |
    55 | ); 56 | } ); 57 | 58 | return ( 59 | 60 | These people have been blocked 61 | {listBlockedFriendships} 62 | 63 | ); 64 | 65 | }; 66 | 67 | export default BlockedFriendships; 68 | -------------------------------------------------------------------------------- /src/components/friendships-current.js: -------------------------------------------------------------------------------- 1 | // REACT 2 | import React from 'react'; 3 | import ProfilePicOther from './profilePicOther'; 4 | import { browserHistory } from 'react-router'; 5 | 6 | // MATERIAL-UI 7 | import Avatar from 'material-ui/Avatar'; 8 | import { List, ListItem } from 'material-ui/List'; 9 | import Subheader from 'material-ui/Subheader'; 10 | import Divider from 'material-ui/Divider'; 11 | import { grey400 } from 'material-ui/styles/colors'; 12 | import IconButton from 'material-ui/IconButton'; 13 | import IconMenu from 'material-ui/IconMenu'; 14 | import MenuItem from 'material-ui/MenuItem'; 15 | import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert'; 16 | // ICONS 17 | import ContentRemoveCircle from 'material-ui/svg-icons/content/remove-circle'; 18 | 19 | 20 | 21 | const handleTouchCurrentFriend = ( uid ) => { 22 | console.log( 'App - fn: handleTouchCurrentFriend' ); 23 | browserHistory.push( `/chat/private/${uid}` ); 24 | }; 25 | 26 | 27 | const iconButtonElement = ( 28 | 33 | 34 | 35 | ); 36 | 37 | 38 | const CurrentFriendship = ( props ) => { 39 | console.log( 'CurrentFriendship - RENDER - this.props: ', props ); 40 | const { handleFriendshipChange, uidSelf } = props; 41 | 42 | 43 | const listCurrentFriendships = props.currentFriendships.map( ( currentFriend ) => { 44 | const { uid, firstName, lastName, profilePic } = currentFriend; 45 | const rightIconMenu = ( 46 | handleFriendshipChange( uid, 'TERMINATED' ) }> 49 | }/> 53 | 54 | ); 55 | 56 | return ( 57 |
    58 | } 61 | rightIconButton={rightIconMenu} 62 | onClick={ () => handleTouchCurrentFriend( uid ) } 63 | /> 64 | 65 |
    66 | 67 | ); 68 | } ); 69 | 70 | return ( 71 | 72 | These people are currently your friend 73 | {listCurrentFriendships} 74 | 75 | ); 76 | 77 | }; 78 | 79 | export default CurrentFriendship; 80 | -------------------------------------------------------------------------------- /src/components/friendships-pending.js: -------------------------------------------------------------------------------- 1 | // REACT 2 | import React from 'react'; 3 | 4 | // MATERIAL-UI 5 | import Avatar from 'material-ui/Avatar'; 6 | import { List, ListItem } from 'material-ui/List'; 7 | import Subheader from 'material-ui/Subheader'; 8 | import Divider from 'material-ui/Divider'; 9 | import { grey400 } from 'material-ui/styles/colors'; 10 | import IconButton from 'material-ui/IconButton'; 11 | import IconMenu from 'material-ui/IconMenu'; 12 | import MenuItem from 'material-ui/MenuItem'; 13 | import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert'; 14 | // ICONS 15 | import SocialPersonAdd from 'material-ui/svg-icons/social/person-add'; 16 | 17 | 18 | const iconButtonElement = ( 19 | 24 | 25 | 26 | ); 27 | 28 | const PendingFriendships = props => { 29 | console.log( 'PendingFriendships - RENDER - this.props: ', props ); 30 | const { handleFriendshipChange } = props; 31 | 32 | const listPendingFriendships = props.pendingFriendships.map( ( pendingFriend ) => { 33 | const { uid, firstName, lastName, profilePic } = pendingFriend; 34 | const rightIconMenu = ( 35 | handleFriendshipChange( uid, 'ACCEPTED' ) }> 38 | }/> 42 | 43 | ); 44 | return ( 45 |
    46 | } 49 | rightIconButton={rightIconMenu} 50 | /> 51 | 52 |
    53 | ); 54 | } ); 55 | 56 | 57 | return ( 58 | 59 | These people want to be your friend 60 | {listPendingFriendships} 61 | 62 | ); 63 | }; 64 | 65 | 66 | export default PendingFriendships; 67 | -------------------------------------------------------------------------------- /src/components/login.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Link } from 'react-router'; 4 | import FormWrapper from '../utils/formWrapper'; 5 | // MATERIAL-UI 6 | import TextField from 'material-ui/TextField'; 7 | import RaisedButton from 'material-ui/RaisedButton'; 8 | 9 | const style = { margin: 12 }; 10 | 11 | const LoginForm = ( { handleInput, handleSubmit, error } ) => { 12 | 13 | console.log( 'LoginForm - RENDER'); 14 | 15 | return ( 16 |
    17 |

    Log in

    18 | { error &&
    Something went wrong. Please try again!
    } 19 |
    20 | 21 | 22 | 32 |
    33 | 34 | 35 | 45 |
    46 | 47 | {/* */} 48 | 49 | 50 | 51 | {/*

    Not a member? Register

    */} 52 |
    53 | ); 54 | }; 55 | 56 | 57 | LoginForm.propTypes = { 58 | handleInput : PropTypes.func, 59 | handleSubmit: PropTypes.func, 60 | error: PropTypes.bool, 61 | // success: PropTypes.bool 62 | }; 63 | 64 | export default FormWrapper(LoginForm, '/api/login'); 65 | -------------------------------------------------------------------------------- /src/components/logo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Logo = () => { 4 | return ( 5 |
    Logo
    6 | ); 7 | }; 8 | 9 | export default Logo; 10 | -------------------------------------------------------------------------------- /src/components/online-friends.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link, browserHistory } from 'react-router'; 3 | 4 | 5 | // MATERIAL-UI 6 | import Avatar from 'material-ui/Avatar'; 7 | import { List, ListItem } from 'material-ui/List'; 8 | import Subheader from 'material-ui/Subheader'; 9 | import Divider from 'material-ui/Divider'; 10 | 11 | const handleTouchOnlineFriend = ( uid ) => { 12 | console.log( 'OnlineFriend - fn: handleTouchOnlineFriend' ); 13 | browserHistory.push( `/chat/secure/${uid}` ); 14 | }; 15 | 16 | const OnlineFriend = props => { 17 | console.log( 'OnlineFriend - RENDER - this.props: ', props ); 18 | 19 | const listOnlineFriends = props.onlineFriends.map( onlineFriend => { 20 | const { uid, firstName, lastName, profilePic } = onlineFriend; 21 | 22 | const avatar = ( 23 | 24 | 25 | 26 | ); 27 | 28 | return ( 29 |
    30 | handleTouchOnlineFriend( uid ) } 34 | /> 35 | 36 |
    37 | ); 38 | } ); 39 | 40 | return ( 41 | 42 | Online Friends you can secure chat with: 43 | {listOnlineFriends} 44 | 45 | ); 46 | }; 47 | 48 | export default OnlineFriend; 49 | -------------------------------------------------------------------------------- /src/components/profile-pic-upload.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class ProfilePicUpload extends React.Component { 4 | 5 | constructor( props ) { 6 | super( props ); 7 | this.state = { 8 | hover: false 9 | }; 10 | this.toggleHover = this.toggleHover.bind( this ); 11 | } 12 | 13 | 14 | toggleHover() { 15 | this.setState( { hover: !this.state.hover } ); 16 | } 17 | 18 | render() { 19 | console.log( 'ProfilePicUpload - RENDER - this.prop: ', this.prop ); 20 | 21 | const { 22 | hideProfilePicUpload, 23 | uploadProfilePic 24 | } = this.props; 25 | 26 | const modalStyle = { 27 | position: 'absolute', 28 | top: '50%', 29 | left: '50%', 30 | transform: 'translate(-50%, -50%)', 31 | zIndex: '9999', 32 | background: '#fff' 33 | }; 34 | 35 | const backdropStyle = { 36 | position: 'absolute', 37 | width: '100%', 38 | height: '100%', 39 | top: '0px', 40 | left: '0px', 41 | zIndex: '9998', 42 | background: 'rgba(0, 0, 0, 0.3)' 43 | }; 44 | 45 | const inputStyle = { 46 | width: '0.1px', 47 | height: '0.1px', 48 | opacity: '0', 49 | overflow: 'hidden', 50 | position: 'absolute', 51 | zIndex: '-1' 52 | }; 53 | 54 | let labelStyle; 55 | 56 | if ( this.state.hover ) { 57 | labelStyle = { 58 | fontSize: '1.25em', 59 | fontWeight: '700', 60 | color: 'white', 61 | backgroundColor: 'red', 62 | display: 'inline-block', 63 | cursor: 'pointer' 64 | }; 65 | } else { 66 | labelStyle = { 67 | fontSize: '1.25em', 68 | fontWeight: '700', 69 | color: 'white', 70 | backgroundColor: 'black', 71 | display: 'inline-block', 72 | cursor: 'pointer' 73 | }; 74 | } 75 | 76 | return ( 77 | 78 |
    79 | 80 |
    81 | 82 |
    83 |

    ProfilePicUploader

    84 | 85 | 89 | 96 | 97 |

    98 |
    99 | 100 |
    101 | 102 |
    103 | 104 |
    105 | ); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/components/profile-pic.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const ProfilePic = ( props ) => { 5 | // console.log( 'ProfilePic - RENDER - this.props: ', props ); 6 | const { showProfilePicUpload, src, alt } = props; 7 | return ( 8 |
    11 | 12 | {alt} 16 | 17 |
    18 | ); 19 | }; 20 | 21 | 22 | ProfilePic.propTypes = { 23 | showProfilePicUpload: PropTypes.func, 24 | src: PropTypes.string, 25 | alt: PropTypes.string 26 | }; 27 | 28 | export default ProfilePic; 29 | -------------------------------------------------------------------------------- /src/components/profile-self-bio.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import axios from '../utils/axios'; 4 | 5 | export default class ProfileSelfBio extends React.Component { 6 | 7 | constructor( props ) { 8 | super( props ); 9 | this.state = { 10 | bio: '', 11 | editBioIsVisible: false 12 | 13 | }; 14 | } 15 | 16 | componentDidMount() { 17 | if ( this.props.bio ) { 18 | this.setState( { bio: this.props.bio } ); 19 | } 20 | } 21 | 22 | 23 | 24 | handleInput( e ) { 25 | this.setState( { 26 | [ e.target.name ]: e.target.value 27 | } ); 28 | } 29 | 30 | handleSubmit( e ) { 31 | e.preventDefault(); 32 | // make POST request to this.url and handle response 33 | console.log( 'ProfileSelfBio - fn: Axios.put - data: ', this.state ); 34 | 35 | axios.put( `/api/user/${this.props.uid}/bio`, this.state ) 36 | 37 | .then( resp => { 38 | const data = resp.data; 39 | !data.success ? 40 | this.setState( { error: true } ) : 41 | this.setState( { data, editBioIsVisible: false } ); 42 | } ) 43 | 44 | .catch( err => console.error( err.stack ) ); 45 | } 46 | 47 | setBioVisibility( boolean ) { 48 | console.log( 'fn: editBioIsVisible ' ); 49 | this.setState( { editBioIsVisible: boolean } ); 50 | } 51 | 52 | 53 | render() { 54 | console.log( 'React Component: ProfileSelfBio - RENDER - this.props: ', JSON.stringify( this.props ) ); 55 | 56 | const { bio, editBioIsVisible, error } = this.state; 57 | // const { bio } = this.props; 58 | 59 | // if no bio found.. 60 | const noBioData = ( 61 |

    this.setBioVisibility(true)}>Add your bio now

    62 | ); 63 | 64 | // if bio is found.. 65 | const bioData = ( 66 |
    67 | 68 |