├── chatServer ├── .eslintignore ├── .eslintrc ├── .yo-rc.json ├── README.md ├── client │ └── README.md ├── common │ └── models │ │ ├── message.js │ │ ├── profile.js │ │ ├── thread.js │ │ ├── thread.json │ │ ├── profile.json │ │ └── message.json ├── server │ ├── component-config.json │ ├── middleware.development.json │ ├── datasources.json │ ├── boot │ │ ├── authentication.js │ │ └── root.js │ ├── config.json │ ├── middleware.json │ ├── server.js │ ├── model-config.json │ └── chat-server.js ├── .editorconfig └── package.json ├── chat ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── index.html ├── src │ ├── store │ │ ├── reducers │ │ │ ├── index.js │ │ │ ├── authReducer.js │ │ │ └── chatReducer.js │ │ ├── actions │ │ │ ├── authActions.js │ │ │ └── chatActions.js │ │ └── configureStore.js │ ├── setupTests.js │ ├── App.test.js │ ├── index.css │ ├── App.css │ ├── components │ │ ├── partials │ │ │ ├── Message.js │ │ │ ├── ChatInput.js │ │ │ ├── ThreadView.js │ │ │ ├── Sidebar.js │ │ │ ├── Login.js │ │ │ └── Signup.js │ │ └── pages │ │ │ ├── Messenger.js │ │ │ └── Auth.js │ ├── index.js │ ├── logo.svg │ ├── App.js │ ├── assets │ │ └── css │ │ │ └── swag.css │ └── serviceWorker.js ├── config │ ├── jest │ │ ├── cssTransform.js │ │ └── fileTransform.js │ ├── pnpTs.js │ ├── paths.js │ ├── env.js │ ├── modules.js │ ├── webpackDevServer.config.js │ └── webpack.config.js ├── scripts │ ├── test.js │ ├── start.js │ └── build.js └── package.json └── README.md /chatServer/.eslintignore: -------------------------------------------------------------------------------- 1 | /client/ -------------------------------------------------------------------------------- /chatServer/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "loopback" 3 | } -------------------------------------------------------------------------------- /chatServer/.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-loopback": {} 3 | } -------------------------------------------------------------------------------- /chat/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /chatServer/README.md: -------------------------------------------------------------------------------- 1 | # My Application 2 | 3 | The project is generated by [LoopBack](http://loopback.io). -------------------------------------------------------------------------------- /chatServer/client/README.md: -------------------------------------------------------------------------------- 1 | ## Client 2 | 3 | This is the place for your application front-end files. 4 | -------------------------------------------------------------------------------- /chatServer/common/models/message.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(Message) { 4 | 5 | }; 6 | -------------------------------------------------------------------------------- /chatServer/common/models/profile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(Profile) { 4 | 5 | }; 6 | -------------------------------------------------------------------------------- /chatServer/common/models/thread.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(Thread) { 4 | 5 | }; 6 | -------------------------------------------------------------------------------- /chat/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frozen-dev71/Chat-Application-vai-Web-Socket/main/chat/public/favicon.ico -------------------------------------------------------------------------------- /chat/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frozen-dev71/Chat-Application-vai-Web-Socket/main/chat/public/logo192.png -------------------------------------------------------------------------------- /chat/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frozen-dev71/Chat-Application-vai-Web-Socket/main/chat/public/logo512.png -------------------------------------------------------------------------------- /chatServer/server/component-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "loopback-component-explorer": { 3 | "mountPath": "/explorer", 4 | "generateOperationScopedModels": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /chat/src/store/reducers/index.js: -------------------------------------------------------------------------------- 1 | import {combineReducers} from 'redux'; 2 | import auth from './authReducer'; 3 | import chat from './chatReducer'; 4 | 5 | export default combineReducers({ 6 | auth, 7 | chat 8 | }) -------------------------------------------------------------------------------- /chatServer/server/middleware.development.json: -------------------------------------------------------------------------------- 1 | { 2 | "final:after": { 3 | "strong-error-handler": { 4 | "params": { 5 | "debug": true, 6 | "log": true 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /chat/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /chat/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /chatServer/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 2 10 | end_of_line = lf 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | -------------------------------------------------------------------------------- /chatServer/server/datasources.json: -------------------------------------------------------------------------------- 1 | { 2 | "db1": { 3 | "name": "db1", 4 | "connector": "memory" 5 | }, 6 | "db": { 7 | "host": "localhost", 8 | "port": 27017, 9 | "url": "", 10 | "database": "chatServer", 11 | "password": "", 12 | "name": "db", 13 | "user": "", 14 | "useNewUrlParser": true, 15 | "connector": "mongodb" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /chat/config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /chatServer/server/boot/authentication.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2016. All Rights Reserved. 2 | // Node module: loopback-workspace 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | 'use strict'; 7 | 8 | module.exports = function enableAuthentication(server) { 9 | // enable authentication 10 | server.enableAuth(); 11 | }; 12 | -------------------------------------------------------------------------------- /chat/src/store/actions/authActions.js: -------------------------------------------------------------------------------- 1 | export const loggedIn = (data) => { 2 | return dispatch => { 3 | dispatch({ 4 | type: 'LOGGEDIN', 5 | payload: data 6 | }) 7 | } 8 | } 9 | 10 | export const logout = () =>{ 11 | return dispatch => { 12 | dispatch ({ 13 | type: 'LOGGEDOUT', 14 | payload: null 15 | }) 16 | } 17 | } -------------------------------------------------------------------------------- /chat/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /chatServer/server/boot/root.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2016. All Rights Reserved. 2 | // Node module: loopback-workspace 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | 'use strict'; 7 | 8 | module.exports = function(server) { 9 | // Install a `/` route that returns server status 10 | const router = server.loopback.Router(); 11 | router.get('/', server.loopback.status()); 12 | server.use(router); 13 | }; 14 | -------------------------------------------------------------------------------- /chatServer/server/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "restApiRoot": "/api", 3 | "host": "0.0.0.0", 4 | "port": 3000, 5 | "remoting": { 6 | "context": false, 7 | "rest": { 8 | "handleErrors": false, 9 | "normalizeHttpPath": false, 10 | "xml": false 11 | }, 12 | "json": { 13 | "strict": false, 14 | "limit": "100kb" 15 | }, 16 | "urlencoded": { 17 | "extended": true, 18 | "limit": "100kb" 19 | }, 20 | "cors": false 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /chatServer/common/models/thread.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Thread", 3 | "base": "PersistedModel", 4 | "idInjection": true, 5 | "options": { 6 | "validateUpsert": true 7 | }, 8 | "properties": { 9 | "lastUpdated": { 10 | "type": "date" 11 | }, 12 | "users": { 13 | "type": [ 14 | "string" 15 | ] 16 | } 17 | }, 18 | "validations": [], 19 | "relations": { 20 | "Messages" : { 21 | "type": "hasMany", 22 | "model": "Message" 23 | } 24 | }, 25 | "acls": [], 26 | "methods": {} 27 | } 28 | -------------------------------------------------------------------------------- /chatServer/common/models/profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Profile", 3 | "base": "PersistedModel", 4 | "idInjection": true, 5 | "options": { 6 | "validateUpsert": true 7 | }, 8 | "properties": { 9 | "name": { 10 | "type": "string" 11 | }, 12 | "email": { 13 | "type": "string" 14 | }, 15 | "created": { 16 | "type": "date" 17 | } 18 | }, 19 | "validations": [], 20 | "relations": { 21 | "User": { 22 | "type": "belongsTo", 23 | "model": "User", 24 | "foreignKey": "userId" 25 | } 26 | }, 27 | "acls": [], 28 | "methods": {} 29 | } 30 | -------------------------------------------------------------------------------- /chat/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /chat/src/store/reducers/authReducer.js: -------------------------------------------------------------------------------- 1 | const defaultState = { 2 | token: null, 3 | user: {}, 4 | } 5 | 6 | const auth = (State = defaultState, action) => { 7 | switch(action.type){ 8 | case 'LOGGEDIN': 9 | return{ 10 | ...State, 11 | token: action.payload.data.session.id, 12 | user: action.payload.data.user 13 | } 14 | case 'LOGGEDOUT': 15 | return{ 16 | ...State, 17 | ...defaultState 18 | } 19 | default: 20 | return State 21 | } 22 | } 23 | 24 | export default auth; -------------------------------------------------------------------------------- /chat/src/store/configureStore.js: -------------------------------------------------------------------------------- 1 | import {createStore, applyMiddleware} from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import {persistStore, persistReducer} from 'redux-persist'; 4 | import storage from 'redux-persist/lib/storage'; 5 | import rootRoducer from './reducers'; 6 | 7 | const persistConfig = { 8 | key: 'root', 9 | storage, 10 | } 11 | 12 | const persistedReducer = persistReducer(persistConfig, rootRoducer); 13 | 14 | 15 | export default () => { 16 | let store = createStore(persistedReducer, applyMiddleware(thunk)); 17 | let persistor = persistStore(store); 18 | return { 19 | store, 20 | persistor 21 | } 22 | } -------------------------------------------------------------------------------- /chat/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /chat/config/pnpTs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { resolveModuleName } = require('ts-pnp'); 4 | 5 | exports.resolveModuleName = ( 6 | typescript, 7 | moduleName, 8 | containingFile, 9 | compilerOptions, 10 | resolutionHost 11 | ) => { 12 | return resolveModuleName( 13 | moduleName, 14 | containingFile, 15 | compilerOptions, 16 | resolutionHost, 17 | typescript.resolveModuleName 18 | ); 19 | }; 20 | 21 | exports.resolveTypeReferenceDirective = ( 22 | typescript, 23 | moduleName, 24 | containingFile, 25 | compilerOptions, 26 | resolutionHost 27 | ) => { 28 | return resolveModuleName( 29 | moduleName, 30 | containingFile, 31 | compilerOptions, 32 | resolutionHost, 33 | typescript.resolveTypeReferenceDirective 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /chatServer/common/models/message.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Message", 3 | "base": "PersistedModel", 4 | "idInjection": true, 5 | "options": { 6 | "validateUpsert": true 7 | }, 8 | "properties": { 9 | "content": { 10 | "type": "string" 11 | }, 12 | "date": { 13 | "type": "date" 14 | }, 15 | "seenBy": { 16 | "type": "object" 17 | }, 18 | "userId": { 19 | "type": "string" 20 | } 21 | }, 22 | "validations": [], 23 | "relations": { 24 | "Thread": { 25 | "type": "belongsTo", 26 | "model": "Thread", 27 | "foreignKey": "threadId" 28 | }, 29 | "User": { 30 | "type": "belongsTo", 31 | "model": "User", 32 | "foreignKey": "userId" 33 | } 34 | }, 35 | "acls": [], 36 | "methods": {} 37 | } 38 | -------------------------------------------------------------------------------- /chat/src/components/partials/Message.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {connect} from 'react-redux'; 3 | 4 | class Message extends Component{ 5 | render(){ 6 | return( 7 |
8 | 9 |
10 | {this.props.msg.content} 11 |
12 |
13 | ) 14 | } 15 | } 16 | 17 | const mapStateToProps = (state) => ({ 18 | ...state.auth, 19 | ...state.chat 20 | }) 21 | 22 | const mapDispatchToProps = (dispatch) => ({ 23 | 24 | }) 25 | 26 | export default connect( 27 | mapStateToProps, 28 | mapDispatchToProps 29 | )(Message); -------------------------------------------------------------------------------- /chat/src/components/pages/Messenger.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {withRouter, Link} from 'react-router-dom'; 3 | import {connect} from 'react-redux'; 4 | import ThreadView from '../partials/ThreadView'; 5 | import Sidebar from '../partials/Sidebar'; 6 | import ChatInput from '../partials/ChatInput'; 7 | class Messenger extends Component{ 8 | 9 | render(){ 10 | return( 11 |
12 | 13 | 14 | 15 |
16 | ); 17 | } 18 | } 19 | 20 | const mapStateToProps = state => ({ 21 | ...state.auth, 22 | ...state.chat 23 | }) 24 | 25 | const mapDispatchToProps = dispatch => ({ 26 | 27 | }) 28 | 29 | export default connect( 30 | mapStateToProps, 31 | mapDispatchToProps 32 | )(withRouter(Messenger)); -------------------------------------------------------------------------------- /chatServer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatServer", 3 | "version": "1.0.0", 4 | "main": "server/server.js", 5 | "engines": { 6 | "node": ">=6" 7 | }, 8 | "scripts": { 9 | "lint": "eslint .", 10 | "start": "node .", 11 | "posttest": "npm run lint" 12 | }, 13 | "dependencies": { 14 | "compression": "^1.0.3", 15 | "cors": "^2.5.2", 16 | "helmet": "^3.10.0", 17 | "loopback": "^3.22.0", 18 | "loopback-boot": "^2.6.5", 19 | "loopback-component-explorer": "^6.2.0", 20 | "loopback-connector-mongodb": "^4.2.0", 21 | "serve-favicon": "^2.0.1", 22 | "strong-error-handler": "^3.0.0", 23 | "ws": "^7.2.1" 24 | }, 25 | "devDependencies": { 26 | "eslint": "^4.18.2", 27 | "eslint-config-loopback": "^8.0.0" 28 | }, 29 | "repository": { 30 | "type": "", 31 | "url": "" 32 | }, 33 | "license": "UNLICENSED", 34 | "description": "chatServer" 35 | } 36 | -------------------------------------------------------------------------------- /chat/src/components/pages/Auth.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {withRouter, Link} from 'react-router-dom'; 3 | import * as AuthActions from '../../store/actions/authActions'; 4 | import Login from '../partials/Login'; 5 | import Signup from '../partials/Signup'; 6 | import {connect} from 'react-redux'; 7 | 8 | 9 | class Auth extends Component{ 10 | render(){ 11 | return( 12 |
13 | {this.props.match.path === '/signup' ? 14 | 15 | : 16 | 17 | } 18 |
19 | ) 20 | } 21 | } 22 | 23 | const mapStateToProps = state => ({ 24 | ...state.auth 25 | }) 26 | 27 | const mapDispatchToProps = dispatch => ({ 28 | 29 | }) 30 | 31 | export default connect( 32 | mapStateToProps, 33 | mapDispatchToProps 34 | )(withRouter(Auth)); -------------------------------------------------------------------------------- /chat/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | import rootReducer from './store/reducers'; 7 | import {Provider} from 'react-redux'; 8 | import configureStore from './store/configureStore'; 9 | import {PersistGate} from 'redux-persist/integration/react'; 10 | 11 | const {store, persistor} = configureStore(); 12 | 13 | ReactDOM.render( 14 | 15 | 16 | 17 | 18 | 19 | 20 | , document.getElementById('root')); 21 | 22 | // If you want your app to work offline and load faster, you can change 23 | // unregister() to register() below. Note this comes with some pitfalls. 24 | // Learn more about service workers: https://bit.ly/CRA-PWA 25 | serviceWorker.unregister(); 26 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chat-Application-vai-Web-Socket 2 | This is a realtime-chat-application. This application covered by web-socket for Realtime messaging. Whole system developed by Bootstrap, ReacJS, Loopback API 3 | 4 | ![Chat Application](https://i.ytimg.com/vi/ZwFA3YMfkoc/maxresdefault.jpg) 5 | 6 | ## Introduction 7 | This is a public code repository as open source for students and new learner about this things. 8 | 9 | I created a full Realtime Chat Application. I'm going to use React on the front end and Bootstrap, with NodeJS Loopback-API + Socket.io web socket library on the back end. 10 | 11 | # Setup: 12 | - Download Full Project 13 | - Copy all file from `src` directory from chat-application 14 | - Create new react app in your machine 15 | - Then replace new app src folder with `chat-application/src` files. 16 | - Use NodeJS + LoopBack to configure the backend. Try google to install both technology. 17 | - run ```npm i && npm start``` for both client and server side to start the development server 18 | -------------------------------------------------------------------------------- /chatServer/server/middleware.json: -------------------------------------------------------------------------------- 1 | { 2 | "initial:before": { 3 | "loopback#favicon": {} 4 | }, 5 | "initial": { 6 | "compression": {}, 7 | "cors": { 8 | "params": { 9 | "origin": true, 10 | "credentials": true, 11 | "maxAge": 86400 12 | } 13 | }, 14 | "helmet#xssFilter": {}, 15 | "helmet#frameguard": { 16 | "params": { 17 | "action": "deny" 18 | } 19 | }, 20 | "helmet#hsts": { 21 | "params": { 22 | "maxAge": 0, 23 | "includeSubDomains": true 24 | } 25 | }, 26 | "helmet#hidePoweredBy": {}, 27 | "helmet#ieNoOpen": {}, 28 | "helmet#noSniff": {}, 29 | "helmet#noCache": { 30 | "enabled": false 31 | } 32 | }, 33 | "session": {}, 34 | "auth": {}, 35 | "parse": {}, 36 | "routes": { 37 | "loopback#rest": { 38 | "paths": [ 39 | "${restApiRoot}" 40 | ] 41 | } 42 | }, 43 | "files": {}, 44 | "final": { 45 | "loopback#urlNotFound": {} 46 | }, 47 | "final:after": { 48 | "strong-error-handler": {} 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /chatServer/server/server.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2016. All Rights Reserved. 2 | // Node module: loopback-workspace 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | 'use strict'; 7 | 8 | const loopback = require('loopback'); 9 | const boot = require('loopback-boot'); 10 | 11 | const app = module.exports = loopback(); 12 | 13 | app.start = function() { 14 | // start the web server 15 | return app.listen(8080, function() { 16 | app.emit('started'); 17 | const baseUrl = app.get('url').replace(/\/$/, ''); 18 | console.log('Web server listening at: %s', baseUrl); 19 | if (app.get('loopback-component-explorer')) { 20 | const explorerPath = app.get('loopback-component-explorer').mountPath; 21 | console.log('Browse your REST API at %s%s', baseUrl, explorerPath); 22 | } 23 | }); 24 | }; 25 | 26 | // Bootstrap the application, configure models, datasources and middleware. 27 | // Sub-apps like REST API are mounted via boot scripts. 28 | boot(app, __dirname, function(err) { 29 | if (err) throw err; 30 | 31 | // start the server if `$ node server.js` 32 | if (require.main === module) 33 | app.start(); 34 | }); 35 | -------------------------------------------------------------------------------- /chatServer/server/model-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "sources": [ 4 | "loopback/common/models", 5 | "loopback/server/models", 6 | "../common/models", 7 | "./models" 8 | ], 9 | "mixins": [ 10 | "loopback/common/mixins", 11 | "loopback/server/mixins", 12 | "../common/mixins", 13 | "./mixins" 14 | ] 15 | }, 16 | "User": { 17 | "dataSource": "db", 18 | "relations": { 19 | "Profile": { 20 | "type": "hasOne", 21 | "model": "Profile", 22 | "foreignKey": "" 23 | }, 24 | "Messages": { 25 | "type": "hasMany", 26 | "model": "Message" 27 | } 28 | } 29 | }, 30 | "AccessToken": { 31 | "dataSource": "db", 32 | "public": false 33 | }, 34 | "ACL": { 35 | "dataSource": "db", 36 | "public": false 37 | }, 38 | "RoleMapping": { 39 | "dataSource": "db", 40 | "public": false, 41 | "options": { 42 | "strictObjectIDCoercion": true 43 | } 44 | }, 45 | "Role": { 46 | "dataSource": "db", 47 | "public": false 48 | }, 49 | "Thread": { 50 | "dataSource": "db", 51 | "public": true 52 | }, 53 | "Message": { 54 | "dataSource": "db", 55 | "public": true 56 | }, 57 | "Profile": { 58 | "dataSource": "db", 59 | "public": true 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /chat/config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const camelcase = require('camelcase'); 5 | 6 | // This is a custom Jest transformer turning file imports into filenames. 7 | // http://facebook.github.io/jest/docs/en/webpack.html 8 | 9 | module.exports = { 10 | process(src, filename) { 11 | const assetFilename = JSON.stringify(path.basename(filename)); 12 | 13 | if (filename.match(/\.svg$/)) { 14 | // Based on how SVGR generates a component name: 15 | // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6 16 | const pascalCaseFilename = camelcase(path.parse(filename).name, { 17 | pascalCase: true, 18 | }); 19 | const componentName = `Svg${pascalCaseFilename}`; 20 | return `const React = require('react'); 21 | module.exports = { 22 | __esModule: true, 23 | default: ${assetFilename}, 24 | ReactComponent: React.forwardRef(function ${componentName}(props, ref) { 25 | return { 26 | $$typeof: Symbol.for('react.element'), 27 | type: 'svg', 28 | ref: ref, 29 | key: null, 30 | props: Object.assign({}, props, { 31 | children: ${assetFilename} 32 | }) 33 | }; 34 | }), 35 | };`; 36 | } 37 | 38 | return `module.exports = ${assetFilename};`; 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /chat/scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | 19 | const jest = require('jest'); 20 | const execSync = require('child_process').execSync; 21 | let argv = process.argv.slice(2); 22 | 23 | function isInGitRepository() { 24 | try { 25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }); 26 | return true; 27 | } catch (e) { 28 | return false; 29 | } 30 | } 31 | 32 | function isInMercurialRepository() { 33 | try { 34 | execSync('hg --cwd . root', { stdio: 'ignore' }); 35 | return true; 36 | } catch (e) { 37 | return false; 38 | } 39 | } 40 | 41 | // Watch unless on CI or explicitly running all tests 42 | if ( 43 | !process.env.CI && 44 | argv.indexOf('--watchAll') === -1 && 45 | argv.indexOf('--watchAll=false') === -1 46 | ) { 47 | // https://github.com/facebook/create-react-app/issues/5210 48 | const hasSourceControl = isInGitRepository() || isInMercurialRepository(); 49 | argv.push(hasSourceControl ? '--watch' : '--watchAll'); 50 | } 51 | 52 | 53 | jest.run(argv); 54 | -------------------------------------------------------------------------------- /chat/src/components/partials/ChatInput.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {withRouter, Link} from 'react-router-dom'; 3 | import {connect} from 'react-redux'; 4 | 5 | class ChatInput extends Component{ 6 | constructor(props){ 7 | super(props); 8 | 9 | this.state={ 10 | content: '' 11 | } 12 | } 13 | 14 | sendMessage = (e) =>{ 15 | e.preventDefault(); 16 | 17 | const msg = { 18 | threadId: this.props.match.params.threadId, 19 | userId: this.props.user.id, 20 | content: this.state.content, 21 | date: new Date() 22 | } 23 | 24 | this.props.socket.send(JSON.stringify({ 25 | type: 'ADD_MESSAGE', 26 | threadId: msg.threadId, 27 | message: msg 28 | })); 29 | 30 | this.setState({content: ''}); 31 | } 32 | 33 | render(){ 34 | return( 35 |
this.sendMessage(e)}> 36 |
37 | this.setState({ 43 | content: e.target.value 44 | })} 45 | /> 46 | 47 |
48 | 49 | ); 50 | } 51 | } 52 | 53 | const mapStateToProps = state => ({ 54 | ...state.auth, 55 | ...state.chat 56 | }) 57 | 58 | const mapDispatchToProps = dispatch => ({ 59 | 60 | }) 61 | 62 | export default connect( 63 | mapStateToProps, 64 | mapDispatchToProps 65 | )(withRouter(ChatInput)); -------------------------------------------------------------------------------- /chat/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 19 | 28 | React App 29 | 30 | 31 | 32 |
33 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /chat/src/components/partials/ThreadView.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {withRouter, Link} from 'react-router-dom'; 3 | import {connect} from 'react-redux'; 4 | import Message from './Message'; 5 | 6 | class ThreadView extends Component{ 7 | 8 | componentDidMount(){ 9 | this.init(); 10 | } 11 | componentDidUpdate(props){ 12 | if(props.match.params.threadId !== this.props.match.params.threadId){ 13 | this.init(); 14 | } 15 | } 16 | 17 | init = () => { 18 | let currentThread = this.props.threads.filter(t => t.id === this.props.match.params.threadId)[0]; 19 | if(currentThread && this.props.socket.readyState){ 20 | let skip = currentThread.Messages || 0; 21 | this.props.socket.send(JSON.stringify({ 22 | type: 'THREAD_LOAD', 23 | data: { 24 | threadId: this.props.match.params.threadId, 25 | skip: skip 26 | } 27 | })) 28 | } 29 | } 30 | 31 | render(){ 32 | return( 33 |
34 | {this.props.threads.filter(thread => thread.id === this.props.match.params.threadId).map((thread, i) => { 35 | return( 36 |
37 | {thread.Messages.map((msg, mi) => { 38 | return( 39 | p.id === msg.userId)[0]}/> 40 | ) 41 | })} 42 |
43 | ) 44 | })} 45 |
46 | ); 47 | } 48 | } 49 | 50 | const mapStateToProps = state => ({ 51 | ...state.auth, 52 | ...state.chat 53 | }) 54 | 55 | const mapDispatchToProps = dispatch => ({ 56 | 57 | }) 58 | 59 | export default connect( 60 | mapStateToProps, 61 | mapDispatchToProps 62 | )(withRouter(ThreadView)); -------------------------------------------------------------------------------- /chat/src/store/reducers/chatReducer.js: -------------------------------------------------------------------------------- 1 | const defaultState = { 2 | socket: null, 3 | message: '', 4 | threads: [], 5 | currentThread: '', 6 | users: [] 7 | } 8 | 9 | const chat = (state = defaultState, action) =>{ 10 | switch(action.type){ 11 | case 'SETUP_SOCKET': 12 | return { 13 | ...state, 14 | socket: action.payload 15 | } 16 | case 'GOT_USERS': 17 | console.log('Getting User', action.payload); 18 | return { 19 | ...state, 20 | users: action.payload 21 | } 22 | case 'ADD_THREAD': 23 | return { 24 | ...state, 25 | threads: state.threads.filter(t => t.id === action.payload.id).length === 0 ? 26 | state.threads.concat(action.payload) 27 | : state.threads 28 | } 29 | 30 | case 'INITIAL_THREADS': 31 | return { 32 | ...state, 33 | threads: action.payload 34 | } 35 | case 'ADD_SINGLE_MESSAGE': 36 | return{ 37 | ...state, 38 | threads: state.threads.map(thread => { 39 | if(thread.id === action.payload.threadId){ 40 | return { 41 | ...thread, 42 | Messages: thread.Messages.concat(action.payload.message) 43 | } 44 | } 45 | else{ 46 | return thread 47 | } 48 | }) 49 | } 50 | case 'ADD_MESSAGES_TO_THREAD': 51 | return { 52 | ...state, 53 | threads: state.threads.map(t => { 54 | if(t.id === action.payload.threadId){ 55 | return { 56 | ...t, 57 | Messages: action.payload.messages.concat(t.Messages) 58 | } 59 | } 60 | else{ 61 | return t 62 | } 63 | }) 64 | } 65 | default: 66 | return state 67 | } 68 | } 69 | 70 | export default chat; -------------------------------------------------------------------------------- /chat/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /chat/src/store/actions/chatActions.js: -------------------------------------------------------------------------------- 1 | import * as AuthActions from './authActions'; 2 | 3 | export const setupSocket = (token, userId) => { 4 | return dispatch => { 5 | const socket = new WebSocket('ws://localhost:8080'); 6 | socket.onopen = () => { 7 | if(token){ 8 | socket.send(JSON.stringify({ 9 | type: 'CONNECT_WITH_TOKEN', 10 | data: { 11 | token: token, 12 | userId: userId 13 | } 14 | })) 15 | dispatch({ 16 | type: 'SETUP_SOCKET', 17 | payload: socket 18 | }); 19 | } 20 | else{ 21 | dispatch({ 22 | type: 'SETUP_SOCKET', 23 | payload: socket 24 | }); 25 | } 26 | } 27 | 28 | socket.onmessage = (message) => { 29 | console.log('Message', message); 30 | let data = JSON.parse(message.data); 31 | switch(data.type){ 32 | case 'LOGGEDIN': 33 | dispatch(AuthActions.loggedIn(data)); 34 | break; 35 | case 'GOT_USERS': 36 | dispatch({ 37 | type: 'GOT_USERS', 38 | payload: data.data.users 39 | }) 40 | break; 41 | case 'ADD_THREAD': 42 | dispatch({ 43 | type: 'ADD_THREAD', 44 | payload: data.data 45 | }); 46 | break; 47 | case 'INITIAL_THREADS': 48 | dispatch({ 49 | type: 'INITIAL_THREADS', 50 | payload: data.data 51 | }); 52 | break; 53 | case 'GOT_MESSAGES': 54 | dispatch({ 55 | type: 'ADD_MESSAGES_TO_THREAD', 56 | payload: { 57 | threadId: data.threadId, 58 | messages: data.messages 59 | } 60 | }) 61 | break; 62 | case 'ADD_MESSAGE_TO_THREAD': 63 | dispatch({ 64 | type: 'ADD_SINGLE_MESSAGE', 65 | payload: { 66 | threadId: data.threadId, 67 | message: data.message 68 | } 69 | }) 70 | document.getElementById('main-view').scrollTop = document.getElementById('main-view').scrollHeight; 71 | default: 72 | //do nothing 73 | } 74 | } 75 | 76 | } 77 | } -------------------------------------------------------------------------------- /chat/config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebook/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(inputPath, needsSlash) { 15 | const hasSlash = inputPath.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return inputPath.substr(0, inputPath.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${inputPath}/`; 20 | } else { 21 | return inputPath; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right