├── venv
├── pip-selfcheck.json
└── Include
│ └── site
│ └── python2.7
│ └── greenlet
│ └── greenlet.h
├── requirements.txt
├── .gitignore
├── ChatServerWorkSample.pdf
├── static
├── js
│ ├── dispatcher.js
│ ├── actions
│ │ ├── RoomMsgActions.js
│ │ ├── UsersListActions.js
│ │ └── RoomsActions.js
│ ├── main.js
│ ├── components
│ │ ├── Layout.js
│ │ ├── Header.js
│ │ ├── Rooms.js
│ │ ├── Name.js
│ │ ├── UserList.js
│ │ └── Room.js
│ └── stores
│ │ ├── RoomMsgStore.js
│ │ ├── RoomsStore.js
│ │ └── UsersStore.js
├── index.html
└── css
│ └── style.css
├── run.sh
├── templates
└── index.html
├── webpack.config.js
├── package.json
├── run.py
└── readme.md
/venv/pip-selfcheck.json:
--------------------------------------------------------------------------------
1 | {"last_check":"2016-11-30T18:37:09Z","pypi_version":"9.0.1"}
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Flask==0.11.1
2 | Flask_SocketIO==2.7.2
3 | eventlet==0.19.0
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /venv
2 | .DS_Store
3 | *.pyc
4 | /node_modules
5 | /static/main.min.js
6 |
--------------------------------------------------------------------------------
/ChatServerWorkSample.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shisaq/chat-server/HEAD/ChatServerWorkSample.pdf
--------------------------------------------------------------------------------
/static/js/dispatcher.js:
--------------------------------------------------------------------------------
1 | import { Dispatcher } from 'flux';
2 |
3 | export default new Dispatcher;
4 |
--------------------------------------------------------------------------------
/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Chat Server Startup
3 |
4 | virtualenv venv
5 | . ./venv/bin/activate
6 | pip install -r requirements.txt
7 | export FLASK_APP=run.py
8 | flask run
9 |
--------------------------------------------------------------------------------
/static/js/actions/RoomMsgActions.js:
--------------------------------------------------------------------------------
1 | import dispatcher from '../dispatcher';
2 |
3 | export function sendMessage(data) {
4 | dispatcher.dispatch({
5 | type: 'SEND_MESSAGE',
6 | data
7 | })
8 | }
9 |
--------------------------------------------------------------------------------
/static/js/actions/UsersListActions.js:
--------------------------------------------------------------------------------
1 | import dispatcher from '../dispatcher';
2 |
3 | export function pushName(name) {
4 | dispatcher.dispatch({
5 | type: 'PUSH_USERNAME',
6 | name
7 | });
8 | }
9 |
10 | export function popName() {
11 | dispatcher.dispatch({
12 | type: 'POP_USERNAME'
13 | });
14 | }
15 |
--------------------------------------------------------------------------------
/static/js/actions/RoomsActions.js:
--------------------------------------------------------------------------------
1 | import dispatcher from '../dispatcher';
2 |
3 | export function matchUser(data) {
4 | dispatcher.dispatch({
5 | type: 'MATCH_USER',
6 | data
7 | })
8 | }
9 |
10 | export function updateStatus(room) {
11 | dispatcher.dispatch({
12 | type: 'UPDATE_STATUS',
13 | room
14 | })
15 | }
16 |
--------------------------------------------------------------------------------
/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Chat Server
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {% block head %}
5 |
6 | Chat Server
7 |
8 |
9 |
10 |
11 | {% endblock %}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/static/js/main.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
4 | import injectTapEventPlugin from 'react-tap-event-plugin';
5 | injectTapEventPlugin();
6 | import { SocketProvider } from 'socket.io-react';
7 | import io from 'socket.io-client';
8 |
9 | import Layout from './components/Layout';
10 |
11 | const socket = io.connect(process.env.SOCKET_URL);
12 |
13 | const App = () => (
14 |
15 |
16 |
17 |
18 |
19 | );
20 |
21 | const app = document.getElementById('app');
22 |
23 | ReactDOM.render(, app);
24 |
--------------------------------------------------------------------------------
/static/js/components/Layout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {grey100} from 'material-ui/styles/colors';
3 |
4 | import Header from './Header';
5 | import Rooms from "./Rooms";
6 |
7 | const styles = {
8 | containerStyle: {
9 | width: '100%',
10 | margin: '20px auto',
11 | backgroundColor: grey100
12 | },
13 | titleStyle: {
14 | paddingLeft: 20
15 | }
16 | };
17 |
18 | export default class Layout extends React.Component {
19 | constructor() {
20 | super();
21 | }
22 |
23 | render() {
24 | return(
25 |
26 |
Chat Server
27 |
28 |
29 |
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var debug = process.env.NODE_ENV !== "production";
2 | var webpack = require('webpack');
3 | var path = require('path');
4 |
5 | module.exports = {
6 | context: path.join(__dirname, "static"),
7 | devtool: debug ? "inline-sourcemap" : null,
8 | entry: "./js/main.js",
9 | module: {
10 | loaders: [
11 | {
12 | test: /\.jsx?$/,
13 | exclude: /(node_modules|bower_components)/,
14 | loader: 'babel-loader',
15 | query: {
16 | presets: ['react', 'es2015', 'stage-0'],
17 | plugins: ['react-html-attrs', 'transform-decorators-legacy', 'transform-class-properties']
18 | }
19 | }
20 | ]
21 | },
22 | output: {
23 | path: __dirname + "/static/",
24 | filename: "main.min.js"
25 | },
26 | plugins: debug ? [] : [
27 | new webpack.optimize.DedupePlugin(),
28 | new webpack.optimize.OccurenceOrderPlugin(),
29 | new webpack.optimize.UglifyJsPlugin({ mangle: false, sourcemap: false }),
30 | ]
31 | };
32 |
--------------------------------------------------------------------------------
/static/js/stores/RoomMsgStore.js:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from 'events';
2 |
3 | import dispatcher from '../dispatcher';
4 |
5 | class RoomMsgStore extends EventEmitter {
6 | constructor() {
7 | super();
8 | // read messages from localStorage, or build an object if there's no data
9 | this.msgs = JSON.parse(localStorage.getItem('msgs')) || {};
10 | }
11 |
12 | getAll() {
13 | return this.msgs;
14 | }
15 |
16 | updateMsg(data) {
17 | this.msgs[data.room] = this.msgs[data.room] || [];
18 | this.msgs[data.room].push(data.msg);
19 | localStorage.setItem('msgs', JSON.stringify(this.msgs));
20 | this.emit('pushNewMsg', data.room);
21 | }
22 |
23 | handleActions(action) {
24 | switch(action.type) {
25 | case 'SEND_MESSAGE': {
26 | this.updateMsg(action.data);
27 | }
28 | }
29 | }
30 | }
31 |
32 | const roomMsgStore = new RoomMsgStore;
33 | dispatcher.register(roomMsgStore.handleActions.bind(roomMsgStore));
34 | export default roomMsgStore;
35 |
--------------------------------------------------------------------------------
/static/js/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Paper from 'material-ui/Paper';
3 | import { socketConnect } from 'socket.io-react';
4 | import {cyan500} from 'material-ui/styles/colors';
5 |
6 | import Name from './Name';
7 | import UserList from './UserList';
8 |
9 | const styles = {
10 | paperStyle: {
11 | height: 70,
12 | marginTop: 20
13 | },
14 | headerStyle: {
15 | display: 'flex',
16 | flexWrap: 'wrap',
17 | padding: '0 20px',
18 | backgroundColor: cyan500
19 | }
20 | };
21 |
22 |
23 | @socketConnect
24 | export default class Header extends React.Component {
25 | constructor() {
26 | super();
27 | this.state = {
28 | name: ''
29 | };
30 | }
31 |
32 | pushName(name) {
33 | this.setState({name});
34 | }
35 |
36 | render() {
37 | return(
38 |
39 |
43 |
44 | );
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chat-server-frontend",
3 | "version": "0.0.1",
4 | "description": "",
5 | "main": "webpack.config.js",
6 | "dependencies": {
7 | "babel-core": "^6.20.0",
8 | "babel-loader": "^6.2.9",
9 | "babel-plugin-add-module-exports": "^0.1.4",
10 | "babel-plugin-react-html-attrs": "^2.0.0",
11 | "babel-plugin-transform-class-properties": "^6.19.0",
12 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
13 | "babel-preset-es2015": "^6.18.0",
14 | "babel-preset-react": "^6.16.0",
15 | "babel-preset-stage-0": "^6.16.0",
16 | "flux": "^3.1.0",
17 | "material-ui": "^0.16.5",
18 | "react": "^0.14.8",
19 | "react-dom": "^0.14.8",
20 | "react-tap-event-plugin": "^0.2.2",
21 | "react-timeout": "^1.0.0",
22 | "socket.io-client": "^1.7.2",
23 | "socket.io-react": "^1.1.2",
24 | "webpack": "^1.14.0",
25 | "webpack-dev-server": "^1.16.2"
26 | },
27 | "devDependencies": {},
28 | "scripts": {
29 | "dev": "webpack-dev-server --content-base static --inline --hot",
30 | "test": "echo \"Error: no test specified\" && exit 1",
31 | "build": "NODE_ENV=production webpack"
32 | },
33 | "author": "Haiquan Li",
34 | "license": "MIT"
35 | }
36 |
--------------------------------------------------------------------------------
/static/css/style.css:
--------------------------------------------------------------------------------
1 | *,
2 | :before,
3 | :after {
4 | box-sizing: border-box;
5 | }
6 |
7 | html,
8 | body,
9 | div,
10 | h1,
11 | h2,
12 | h3,
13 | h4,
14 | h5,
15 | h6,
16 | p,
17 | ul,
18 | li {
19 | margin: 0;
20 | padding: 0;
21 | font-family: 'Roboto', sans-serif;
22 | }
23 |
24 | body {
25 | background-color: #F5F5F5;
26 | }
27 |
28 | h1,h2,h3,h4,h5,h6 {
29 | line-height: 1.2;
30 | }
31 |
32 | h1 {
33 | font-size: 48px;
34 | font-weight: 200;
35 | color: #1c0656;
36 | }
37 |
38 | h2 {
39 | font-size: 40px;
40 | font-weight: 200;
41 | color: #1c0656;
42 | margin: .5em 0;
43 | }
44 |
45 | h3 {
46 | font-size: 32px;
47 | letter-spacing: -0.5px;
48 | font-weight: 300;
49 | color: #1c0656;
50 | margin: 1em 0;
51 | }
52 |
53 | h4 {
54 | font-size: 18px;
55 | font-weight: 600;
56 | letter-spacing: -0.125px;
57 | color: #1c0656;
58 | }
59 |
60 | h4:not(:first-child) {
61 | margin: 3em 0 1.2em;
62 | }
63 |
64 | h5 {
65 | font-weight: 600;
66 | font-size: 16px;
67 | margin-bottom: .4em;
68 | }
69 |
70 | h6 {
71 | font-size: 14px;
72 | font-weight: 600;
73 | margin-bottom: .4em;
74 | }
75 |
76 | .hide {
77 | display: none !important;
78 | }
79 |
--------------------------------------------------------------------------------
/static/js/stores/RoomsStore.js:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from 'events';
2 |
3 | import dispatcher from '../dispatcher';
4 |
5 | class RoomsStore extends EventEmitter {
6 | constructor() {
7 | super();
8 | this.rooms = [];
9 | }
10 |
11 | getAll() {
12 | return this.rooms;
13 | }
14 |
15 | matchUser(data) {
16 | for (var i = 0; i < this.rooms.length; i++) {
17 | if (data.room === this.rooms[i].room) {
18 | // console.log('This room has already been there.');
19 | this.rooms[i].isActive = true;
20 | this.emit('addNewRoom', data);
21 | return;
22 | }
23 | }
24 | if (sessionStorage.name === data.inviter ||
25 | sessionStorage.name === data.guest) {
26 | // console.log('Ready to make a new room out!!!');
27 | this.rooms.push(data);
28 | this.emit('addNewRoom', data);
29 | }
30 | }
31 |
32 | updateStatus(room) {
33 | for (var i = 0; i < this.rooms.length; i++) {
34 | if (room === this.rooms[i].room) {
35 | // console.log(this.rooms[i].isActive);
36 | this.rooms[i].isActive = false;
37 | this.emit('updateRooms');
38 | }
39 | }
40 | }
41 |
42 | handleActions(action) {
43 | switch(action.type) {
44 | case 'MATCH_USER': {
45 | this.matchUser(action.data);
46 | }
47 | case 'UPDATE_STATUS': {
48 | this.updateStatus(action.room);
49 | }
50 | }
51 | }
52 | }
53 |
54 | const roomsStore = new RoomsStore;
55 | dispatcher.register(roomsStore.handleActions.bind(roomsStore));
56 | export default roomsStore;
57 |
--------------------------------------------------------------------------------
/static/js/components/Rooms.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { socketConnect } from 'socket.io-react';
3 |
4 | import Room from './Room';
5 | import RoomsStore from '../stores/RoomsStore';
6 | import * as RoomsActions from '../actions/RoomsActions';
7 |
8 | const roomsStyle = {
9 | width: '100%',
10 | margin: '20px auto',
11 | padding: '20px',
12 | listStyle: 'none'
13 | };
14 |
15 | @socketConnect
16 | export default class Rooms extends React.Component {
17 | constructor() {
18 | super();
19 | this.state = {
20 | rooms: RoomsStore.getAll()
21 | };
22 | }
23 |
24 | componentWillMount() {
25 | RoomsStore.on('addNewRoom', (data) => {
26 | this.setState({
27 | rooms: RoomsStore.getAll()
28 | });
29 | // 1 room and 2 users have been comfirmed. Now join them into the room.
30 | this.props.socket.emit('join_private_room', data);
31 | });
32 |
33 | RoomsStore.on('updateRooms', () => {
34 | this.setState({
35 | rooms: RoomsStore.getAll()
36 | });
37 | });
38 |
39 | this.props.socket.on('invite_match_user', (data) => {
40 | // Let's go and see if the username belongs to the room!
41 | RoomsActions.matchUser(data);
42 | });
43 | }
44 |
45 | render() {
46 | const {rooms} = this.state;
47 | const roomComponents = rooms.map((data) => {
48 | return ;
49 | });
50 |
51 | return(
52 |
53 |
54 | {roomComponents}
55 |
56 |
57 | );
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/static/js/stores/UsersStore.js:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from 'events';
2 |
3 | import dispatcher from '../dispatcher';
4 |
5 | class UsersStore extends EventEmitter {
6 | constructor() {
7 | super();
8 | this.usersList = [
9 | {
10 | name: 'Select a user...',
11 | timeStamp: null
12 | }
13 | ];
14 | }
15 |
16 | getUsername(name) {
17 | for(var i = 1; i < this.usersList.length; i++) {
18 | if (name === this.usersList[i].name) {
19 | this.usersList[i].timeStamp = Date.now();
20 | this.emit('updateUsersList');
21 | return;
22 | }
23 | }
24 | // console.log('name does not equal, it will be added.');
25 | this.usersList.push({
26 | name,
27 | timeStamp: Date.now()
28 | });
29 |
30 | this.emit('updateUsersList');
31 | }
32 |
33 | popUsername() {
34 | setInterval(
35 | () => {
36 | for(var i = 1; i < this.usersList.length; i++) {
37 | if (Date.now() - this.usersList[i].timeStamp > 3500) {
38 | const invalidName = this.usersList[i].name;
39 | // console.log('User: [' + invalidName + '] is offline.');
40 | this.usersList.splice(i, 1);
41 | this.emit('updateUsersList', invalidName);
42 | }
43 | }
44 | },
45 | 3000);
46 | }
47 |
48 | getAll() {
49 | return this.usersList;
50 | }
51 |
52 | handleActions(action) {
53 | switch(action.type) {
54 | case 'PUSH_USERNAME': {
55 | this.getUsername(action.name);
56 | }
57 | case 'POP_USERNAME': {
58 | this.popUsername();
59 | }
60 | }
61 | }
62 | }
63 |
64 | const usersStore = new UsersStore;
65 | dispatcher.register(usersStore.handleActions.bind(usersStore));
66 | export default usersStore;
67 |
--------------------------------------------------------------------------------
/run.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import eventlet
3 | eventlet.monkey_patch()
4 | from flask import Flask, render_template
5 | from flask_socketio import SocketIO, emit, join_room, leave_room, rooms
6 | from datetime import datetime
7 | # to generate a random secret key
8 | import os
9 | # refresh pyc everytime I change code
10 | import sys
11 | sys.dont_write_bytecode = True
12 |
13 | # init
14 | async_mode = 'eventlet'
15 |
16 | app = Flask(__name__)
17 | app.secret_key = os.urandom(24)
18 | socketio = SocketIO(app, async_mode=async_mode)
19 |
20 |
21 | # index
22 | @app.route('/')
23 | def index():
24 | return render_template('index.html')
25 |
26 |
27 | # socketio events
28 | # on connect
29 | @socketio.on('connect')
30 | def user_connect():
31 | print(datetime.now().strftime("%Y-%m-%d %H:%M:%S") + ' - Server connected successfully.')
32 |
33 | # offline
34 | @socketio.on('disconnect')
35 | def user_disconnect():
36 | print(datetime.now().strftime("%Y-%m-%d %H:%M:%S") + ' - Client disconnected.')
37 |
38 | # a user press enter after input a valid username
39 | @socketio.on('is_online')
40 | def user_joined(name):
41 | emit('online_name', name, broadcast=True)
42 |
43 | # `inviter` selects `guest`, sending 2 usernames
44 | @socketio.on('build_private_room')
45 | def creat_private_room(names):
46 | print('The inviter is: [' + names['inviter'] + '], the guest is: [' + names['guest'] + ']')
47 | r = [names['inviter'], names['guest']]
48 | r.sort()
49 | room = ''.join(r)
50 | emit('invite_match_user', {
51 | 'inviter': names['inviter'],
52 | 'guest': names['guest'],
53 | 'room': room,
54 | 'isActive': 'true'
55 | }, broadcast=True)
56 |
57 | # 2 filtered users emit this event, then join room
58 | @socketio.on('join_private_room')
59 | def the_private_room(data):
60 | join_room(data['room'])
61 | print('Users: ' + data['inviter'] + ', ' + data['guest'] + ' joined the room [' + data['room'] + '].')
62 |
63 | # send private messages
64 | @socketio.on('private_message')
65 | def handle_message(data):
66 | emit('room_message', data, room=data['room'])
67 |
68 | # startup
69 | if __name__ == '__main__':
70 | socketio.run(app, debug=True)
71 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Chat server
2 |
3 | > This is a simple sample of an online chat server.
4 |
5 | ## Requirement
6 | 1. Have Python 2.7 installed globally;
7 | 2. Have [pip](https://pip.pypa.io/en/stable/installing/) and [virtualenv](http://docs.python-guide.org/en/latest/dev/virtualenvs/) installed globally.
8 |
9 | ## Installation
10 | 1. Download this repository
11 | `$ git clone https://github.com/shisaq/chat-server.git`
12 | 2. On the root of this repository, open your terminal
13 | 3. Get the permission to read, write and execute: `$ chmod 755 ./run.sh`
14 | 4. Run `$ ./run.sh` to install dependencies
15 | 
16 | 5. Open your browser, type `localhost:5000` or `127.0.0.1:5000`
17 | 
18 | 6. (optional) If you prefer, you can run `npm install` to customize the front-end works
19 |
20 | ## How it looks
21 |
22 | ### Hit enter to register
23 | 
24 |
25 | ### Users list
26 | 
27 |
28 | ### Popup private room
29 | 
30 |
31 | ### Caller and callee popup together
32 | 
33 |
34 | ### Hit enter to send message
35 | 
36 |
37 | ### Private messages
38 | 
39 |
40 | ### Users list refresh dynamically
41 | 
42 |
43 | ## Technique stack
44 |
45 | * [Flask](http://flask.pocoo.org/)
46 | * [SocketIO](http://socket.io/)
47 | * [React](https://facebook.github.io/react/)
48 |
49 | ## Known issues
50 |
51 | * Scorll bar cannot scroll to bottom automatically when finished initially loading the room messages.
52 | * Messages actually store in localStorage. They may conflate when another user login at the same computer with the prior one.
53 | * It has not fitted for multiple sizes of screens.
54 | * Sometimes it may judge a user offline, due to unstable network.
55 | * Rooms list is an array now. But it should be an object, including every item named by the room name. Just like Room messages object.
56 |
57 | ## Liscense
58 |
59 | MIT
60 |
--------------------------------------------------------------------------------
/static/js/components/Name.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TextField from 'material-ui/TextField';
3 | import ReactTimeout from 'react-timeout';
4 | import { socketConnect } from 'socket.io-react';
5 | import {cyan100, cyan500} from 'material-ui/styles/colors';
6 |
7 | const styles = {
8 | nameStyle: {
9 | width: '50%',
10 | paddingLeft: 10,
11 | position: 'relative',
12 | top: -15
13 | },
14 | underlineStyle: {
15 | borderColor: cyan500
16 | },
17 | underlineFocusStyle: {
18 | borderColor: cyan100
19 | },
20 | floatingLabelStyle: {
21 | color: cyan100
22 | }
23 | };
24 |
25 | @ReactTimeout
26 | @socketConnect
27 | export default class Name extends React.Component {
28 | constructor() {
29 | super();
30 | this.state = {
31 | disabled: false,
32 | floatingText: 'Press enter to submit'
33 | };
34 | }
35 |
36 | handleChange(e) {
37 | sessionStorage.name = e.target.value;
38 | this.props.pushName(sessionStorage.name);
39 | }
40 |
41 | handleKeyPress(e) {
42 | // when press enter, we send the name with invalidate input
43 | if (e.key === 'Enter' && e.target.value) {
44 | this.setState({
45 | disabled: true,
46 | floatingText: ' '
47 | });
48 | const self = this;
49 | this.props.setInterval(
50 | () => {self.props.socket.emit(
51 | 'is_online',
52 | self.props.name);
53 | },
54 | 3000);
55 | }
56 | }
57 |
58 | render() {
59 | return(
60 |
61 |
62 |
76 |
77 | );
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/static/js/components/UserList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import DropDownMenu from 'material-ui/DropDownMenu';
3 | import MenuItem from "material-ui/MenuItem";
4 | import { socketConnect } from 'socket.io-react';
5 |
6 | import * as UsersListActions from '../actions/UsersListActions';
7 | import UsersStore from '../stores/UsersStore';
8 |
9 | const styles = {
10 | userListStyle: {
11 | width: '50%',
12 | position: 'relative',
13 | top: -15,
14 | right: 10
15 | },
16 | dropDownStyles: {
17 | width: 240,
18 | top: 20
19 | }
20 | };
21 |
22 | @socketConnect
23 | export default class UserList extends React.Component {
24 | constructor() {
25 | super();
26 | this.state = {
27 | currentName: 'Select a user...',
28 | usersList: UsersStore.getAll()
29 | };
30 | }
31 |
32 | handleChange = (event, index, value) => {
33 | this.setState({currentName: value});
34 | if (value !== sessionStorage.name && value !== 'Select a user...') {
35 | this.props.socket.emit('build_private_room', {
36 | inviter: sessionStorage.name,
37 | guest: value
38 | });
39 | } else {
40 | console.log('You can talk to yourself directly.');
41 | }
42 | }
43 |
44 | componentWillMount() {
45 | UsersStore.on('updateUsersList', (invalidName) => {
46 | if (invalidName === this.state.currentName) {
47 | this.setState({
48 | currentName: 'Select a user...',
49 | usersList: UsersStore.getAll()
50 | });
51 | } else {
52 | this.setState({
53 | usersList: UsersStore.getAll()
54 | });
55 | }
56 |
57 | });
58 |
59 | this.props.socket.on('connect', () => {
60 | UsersListActions.popName();
61 | });
62 |
63 | this.props.socket.on('online_name', (name) => {
64 | UsersListActions.pushName(name);
65 | });
66 |
67 | }
68 |
69 |
70 | render() {
71 | const { usersList } = this.state;
72 | const self = this;
73 | const { socket } = self.props;
74 | const usernamesComponents = usersList.map((user) => {
75 | return ;
80 | });
81 |
82 | return (
83 |
84 |
85 |
91 | {usernamesComponents}
92 |
93 |
94 | );
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/venv/Include/site/python2.7/greenlet/greenlet.h:
--------------------------------------------------------------------------------
1 | /* vim:set noet ts=8 sw=8 : */
2 |
3 | /* Greenlet object interface */
4 |
5 | #ifndef Py_GREENLETOBJECT_H
6 | #define Py_GREENLETOBJECT_H
7 |
8 | #include
9 |
10 | #ifdef __cplusplus
11 | extern "C" {
12 | #endif
13 |
14 | #define GREENLET_VERSION "0.4.10"
15 |
16 | typedef struct _greenlet {
17 | PyObject_HEAD
18 | char* stack_start;
19 | char* stack_stop;
20 | char* stack_copy;
21 | intptr_t stack_saved;
22 | struct _greenlet* stack_prev;
23 | struct _greenlet* parent;
24 | PyObject* run_info;
25 | struct _frame* top_frame;
26 | int recursion_depth;
27 | PyObject* weakreflist;
28 | PyObject* exc_type;
29 | PyObject* exc_value;
30 | PyObject* exc_traceback;
31 | PyObject* dict;
32 | } PyGreenlet;
33 |
34 | #define PyGreenlet_Check(op) PyObject_TypeCheck(op, &PyGreenlet_Type)
35 | #define PyGreenlet_MAIN(op) (((PyGreenlet*)(op))->stack_stop == (char*) -1)
36 | #define PyGreenlet_STARTED(op) (((PyGreenlet*)(op))->stack_stop != NULL)
37 | #define PyGreenlet_ACTIVE(op) (((PyGreenlet*)(op))->stack_start != NULL)
38 | #define PyGreenlet_GET_PARENT(op) (((PyGreenlet*)(op))->parent)
39 |
40 | #if (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 7) || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 1) || PY_MAJOR_VERSION > 3
41 | #define GREENLET_USE_PYCAPSULE
42 | #endif
43 |
44 | /* C API functions */
45 |
46 | /* Total number of symbols that are exported */
47 | #define PyGreenlet_API_pointers 8
48 |
49 | #define PyGreenlet_Type_NUM 0
50 | #define PyExc_GreenletError_NUM 1
51 | #define PyExc_GreenletExit_NUM 2
52 |
53 | #define PyGreenlet_New_NUM 3
54 | #define PyGreenlet_GetCurrent_NUM 4
55 | #define PyGreenlet_Throw_NUM 5
56 | #define PyGreenlet_Switch_NUM 6
57 | #define PyGreenlet_SetParent_NUM 7
58 |
59 | #ifndef GREENLET_MODULE
60 | /* This section is used by modules that uses the greenlet C API */
61 | static void **_PyGreenlet_API = NULL;
62 |
63 | #define PyGreenlet_Type (*(PyTypeObject *) _PyGreenlet_API[PyGreenlet_Type_NUM])
64 |
65 | #define PyExc_GreenletError \
66 | ((PyObject *) _PyGreenlet_API[PyExc_GreenletError_NUM])
67 |
68 | #define PyExc_GreenletExit \
69 | ((PyObject *) _PyGreenlet_API[PyExc_GreenletExit_NUM])
70 |
71 | /*
72 | * PyGreenlet_New(PyObject *args)
73 | *
74 | * greenlet.greenlet(run, parent=None)
75 | */
76 | #define PyGreenlet_New \
77 | (* (PyGreenlet * (*)(PyObject *run, PyGreenlet *parent)) \
78 | _PyGreenlet_API[PyGreenlet_New_NUM])
79 |
80 | /*
81 | * PyGreenlet_GetCurrent(void)
82 | *
83 | * greenlet.getcurrent()
84 | */
85 | #define PyGreenlet_GetCurrent \
86 | (* (PyGreenlet * (*)(void)) _PyGreenlet_API[PyGreenlet_GetCurrent_NUM])
87 |
88 | /*
89 | * PyGreenlet_Throw(
90 | * PyGreenlet *greenlet,
91 | * PyObject *typ,
92 | * PyObject *val,
93 | * PyObject *tb)
94 | *
95 | * g.throw(...)
96 | */
97 | #define PyGreenlet_Throw \
98 | (* (PyObject * (*) \
99 | (PyGreenlet *self, PyObject *typ, PyObject *val, PyObject *tb)) \
100 | _PyGreenlet_API[PyGreenlet_Throw_NUM])
101 |
102 | /*
103 | * PyGreenlet_Switch(PyGreenlet *greenlet, PyObject *args)
104 | *
105 | * g.switch(*args, **kwargs)
106 | */
107 | #define PyGreenlet_Switch \
108 | (* (PyObject * (*)(PyGreenlet *greenlet, PyObject *args, PyObject *kwargs)) \
109 | _PyGreenlet_API[PyGreenlet_Switch_NUM])
110 |
111 | /*
112 | * PyGreenlet_SetParent(PyObject *greenlet, PyObject *new_parent)
113 | *
114 | * g.parent = new_parent
115 | */
116 | #define PyGreenlet_SetParent \
117 | (* (int (*)(PyGreenlet *greenlet, PyGreenlet *nparent)) \
118 | _PyGreenlet_API[PyGreenlet_SetParent_NUM])
119 |
120 | /* Macro that imports greenlet and initializes C API */
121 | #ifdef GREENLET_USE_PYCAPSULE
122 | #define PyGreenlet_Import() \
123 | { \
124 | _PyGreenlet_API = (void**)PyCapsule_Import("greenlet._C_API", 0); \
125 | }
126 | #else
127 | #define PyGreenlet_Import() \
128 | { \
129 | PyObject *module = PyImport_ImportModule("greenlet"); \
130 | if (module != NULL) { \
131 | PyObject *c_api_object = PyObject_GetAttrString( \
132 | module, "_C_API"); \
133 | if (c_api_object != NULL && PyCObject_Check(c_api_object)) { \
134 | _PyGreenlet_API = \
135 | (void **) PyCObject_AsVoidPtr(c_api_object); \
136 | Py_DECREF(c_api_object); \
137 | } \
138 | Py_DECREF(module); \
139 | } \
140 | }
141 | #endif
142 |
143 | #endif /* GREENLET_MODULE */
144 |
145 | #ifdef __cplusplus
146 | }
147 | #endif
148 | #endif /* !Py_GREENLETOBJECT_H */
149 |
--------------------------------------------------------------------------------
/static/js/components/Room.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { socketConnect } from 'socket.io-react';
3 | import {Card, CardHeader} from 'material-ui/Card';
4 | import TextField from 'material-ui/TextField';
5 | import FlatButton from 'material-ui/FlatButton';
6 | import IconButton from 'material-ui/IconButton';
7 | import CloseButton from 'material-ui/svg-icons//navigation/close';
8 | import Paper from 'material-ui/Paper';
9 | import {cyan100, cyan500, grey100} from 'material-ui/styles/colors';
10 |
11 | import RoomMsgStore from '../stores/RoomMsgStore';
12 | import * as RoomMsgActions from '../actions/RoomMsgActions';
13 | import * as RoomsActions from '../actions/RoomsActions';
14 |
15 | const styles = {
16 | roomStyle: {
17 | width: '33.3%',
18 | display: 'inline-block',
19 | minWidth: 300,
20 | padding: 10
21 | },
22 | paperStyle: {
23 | height: 400,
24 | position: 'relative',
25 | backgroundColor: grey100
26 | },
27 | cardHeaderStyle: {
28 | backgroundColor: cyan100,
29 | paddingTop: 10,
30 | paddingBottom: 10
31 | },
32 | closeStyle: {
33 | position: 'absolute',
34 | top: 0,
35 | right: 0
36 | },
37 | chatRecord: {
38 | height: 290,
39 | padding: 10,
40 | backgroundColor: grey100,
41 | overflowY: 'auto',
42 | overflowX: 'hidden'
43 | },
44 | singleRecord: {
45 | margin: '5px 0'
46 | },
47 | inputArea: {
48 | position: 'absolute',
49 | bottom: 0,
50 | padding: '0 10px',
51 | width: 'calc(100% - 20px)'
52 | }
53 | };
54 |
55 | @socketConnect
56 | export default class Room extends React.Component {
57 | constructor() {
58 | super();
59 | this.state = {
60 | messages: RoomMsgStore.getAll()
61 | };
62 | }
63 |
64 | handleKeyPress(e) {
65 | if (e.key === 'Enter' && e.target.value) {
66 | // console.log('I will send this message to the server.');
67 | this.props.socket.emit('private_message', {
68 | msg: sessionStorage.name + ': ' + e.target.value,
69 | room: this.props.info.room
70 | });
71 | e.target.value = '';
72 | }
73 | }
74 |
75 | show() {
76 | const shouldShow = this.props.info.isActive;
77 | // console.log(shouldShow);
78 | if (shouldShow) {
79 | return '';
80 | } else {
81 | return 'hide';
82 | }
83 | }
84 |
85 | handleClose() {
86 | // console.log('We should make room[' + this.props.info.room + '] disappear.');
87 | RoomsActions.updateStatus(this.props.info.room);
88 | }
89 |
90 | componentWillMount() {
91 | this.props.socket.on('room_message', (data) => {
92 | // console.log('I have received the message.');
93 | if (this.props.info.room === data.room) {
94 | RoomMsgActions.sendMessage(data);
95 | }
96 | });
97 |
98 | RoomMsgStore.on('pushNewMsg', (room) => {
99 | this.setState({
100 | messages: RoomMsgStore.getAll()
101 | });
102 | if (this.props.info.room === room) {
103 | const node = document.getElementById(room);
104 | node.scrollTop = node.scrollHeight;
105 | // console.log('Scroll bar has reached to the bottom!');
106 | }
107 | });
108 | }
109 |
110 | render() {
111 | const { messages } = this.state;
112 | const { info } = this.props;
113 | const oppositeName = (info.inviter === sessionStorage.name) ?
114 | info.guest : info.inviter;
115 | messages[info.room] = messages[info.room] || [];
116 | const msgComponent = messages[info.room].map((msg, index) => {
117 | return {msg}
;
118 | });
119 |
120 | return(
121 |
122 |
123 |
124 |
129 |
132 |
133 |
134 |
135 | {msgComponent}
136 |
137 |
144 |
145 |
146 |
147 | );
148 | }
149 | }
150 |
--------------------------------------------------------------------------------