├── src ├── common │ ├── config │ │ ├── locale │ │ │ └── en.js │ │ ├── env │ │ │ ├── production.js │ │ │ ├── testing.js │ │ │ └── development.js │ │ ├── route.js │ │ ├── config.js │ │ ├── view.js │ │ ├── db.js │ │ └── websocket.js │ ├── bootstrap │ │ ├── hook.js │ │ └── start.js │ └── controller │ │ └── error.js └── home │ ├── config │ └── config.js │ ├── model │ └── index.js │ ├── controller │ ├── base.js │ ├── index.js │ └── socket.js │ └── logic │ └── index.js ├── www ├── static │ ├── img │ │ ├── bg.jpg │ │ └── dialog.png │ ├── libs │ │ ├── record │ │ │ ├── recorder.js │ │ │ └── recorderWorker.js │ │ ├── custombox │ │ │ ├── legacy.min.js │ │ │ ├── custombox.min.js │ │ │ └── custombox.min.css │ │ ├── caret.js │ │ ├── jquery.qrcode.min.js │ │ └── twemoji.min.js │ ├── css │ │ └── chat.css │ └── js │ │ └── chat.js ├── testing.js ├── production.js ├── index.js └── README.md ├── .thinkjsrc ├── pm2.json ├── README.md ├── package.json ├── .gitignore ├── view ├── home │ ├── index_test.html │ ├── index_login.html │ └── index_index.html └── common │ ├── error_404.html │ └── error_400.html └── nginx.conf /src/common/config/locale/en.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default { 4 | 5 | }; -------------------------------------------------------------------------------- /src/common/config/env/production.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default { 4 | 5 | }; -------------------------------------------------------------------------------- /src/common/config/env/testing.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default { 4 | 5 | }; -------------------------------------------------------------------------------- /src/common/config/env/development.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default { 4 | 5 | }; -------------------------------------------------------------------------------- /www/static/img/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizheming/anychat/HEAD/www/static/img/bg.jpg -------------------------------------------------------------------------------- /www/static/img/dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lizheming/anychat/HEAD/www/static/img/dialog.png -------------------------------------------------------------------------------- /.thinkjsrc: -------------------------------------------------------------------------------- 1 | { 2 | "createAt": "2016-01-24 13:48:41", 3 | "mode": "module", 4 | "es6": true 5 | } 6 | -------------------------------------------------------------------------------- /src/common/config/route.js: -------------------------------------------------------------------------------- 1 | // export default { 2 | // [/^chat\/(\d+)$/, "home/index/chat?r=:1"] 3 | // } -------------------------------------------------------------------------------- /src/home/config/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * config 4 | */ 5 | export default { 6 | //key: value 7 | }; -------------------------------------------------------------------------------- /src/home/model/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * model 4 | */ 5 | export default class extends think.model.base { 6 | 7 | } -------------------------------------------------------------------------------- /src/common/bootstrap/hook.js: -------------------------------------------------------------------------------- 1 | /** 2 | * this file will be loaded before server started 3 | * you can register app hook 4 | */ 5 | -------------------------------------------------------------------------------- /src/home/controller/base.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default class extends think.controller.base { 4 | /** 5 | * some base method in here 6 | */ 7 | } -------------------------------------------------------------------------------- /src/common/config/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * config 4 | */ 5 | export default { 6 | //cluster_on: true //socket.io not support cluster mode 7 | //key: value 8 | route_on: false 9 | }; 10 | -------------------------------------------------------------------------------- /src/common/config/view.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * template config 4 | */ 5 | export default { 6 | content_type: 'text/html', 7 | file_ext: '.html', 8 | file_depr: '_', 9 | root_path: think.ROOT_PATH + '/view', 10 | type: 'ejs', 11 | options: {} 12 | }; -------------------------------------------------------------------------------- /pm2.json: -------------------------------------------------------------------------------- 1 | { 2 | "apps": [{ 3 | "name": "websocket-socket.io", 4 | "script": "production.js", 5 | "cwd": "/var/www/anychat/www", 6 | "max_memory_restart": "1G", 7 | "autorestart": true, 8 | "node_args": [], 9 | "args": [], 10 | "env": { 11 | 12 | } 13 | }] 14 | } 15 | -------------------------------------------------------------------------------- /src/common/bootstrap/start.js: -------------------------------------------------------------------------------- 1 | /** 2 | * this file will be loaded before server started 3 | * you can define global functions used in controllers, models, templates 4 | */ 5 | 6 | /** 7 | * use global.xxx to define global functions 8 | * 9 | * global.fn1 = function(){ 10 | * 11 | * } 12 | */ -------------------------------------------------------------------------------- /www/testing.js: -------------------------------------------------------------------------------- 1 | var thinkjs = require('thinkjs'); 2 | var path = require('path'); 3 | 4 | var rootPath = path.dirname(__dirname); 5 | 6 | var instance = new thinkjs({ 7 | APP_PATH: rootPath + '/app', 8 | ROOT_PATH: rootPath, 9 | RESOURCE_PATH: __dirname, 10 | env: 'testing' 11 | }); 12 | 13 | instance.run(); -------------------------------------------------------------------------------- /www/production.js: -------------------------------------------------------------------------------- 1 | var thinkjs = require('thinkjs'); 2 | var path = require('path'); 3 | 4 | var rootPath = path.dirname(__dirname); 5 | 6 | var instance = new thinkjs({ 7 | APP_PATH: rootPath + '/app', 8 | ROOT_PATH: rootPath, 9 | RESOURCE_PATH: __dirname, 10 | env: 'production' 11 | }); 12 | 13 | instance.run(); -------------------------------------------------------------------------------- /www/index.js: -------------------------------------------------------------------------------- 1 | var thinkjs = require('thinkjs'); 2 | var path = require('path'); 3 | 4 | var rootPath = path.dirname(__dirname); 5 | 6 | var instance = new thinkjs({ 7 | APP_PATH: rootPath + '/app', 8 | ROOT_PATH: rootPath, 9 | RESOURCE_PATH: __dirname, 10 | env: 'development' 11 | }); 12 | instance.compile(true); 13 | instance.run(); -------------------------------------------------------------------------------- /src/common/config/db.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * db config 4 | * @type {Object} 5 | */ 6 | export default { 7 | type: 'mysql', 8 | host: '127.0.0.1', 9 | port: '', 10 | name: '', 11 | user: '', 12 | pwd: '', 13 | prefix: 'think_', 14 | encoding: 'utf8', 15 | nums_per_page: 10, 16 | log_sql: true, 17 | log_connect: true, 18 | cache: { 19 | on: true, 20 | type: '', 21 | timeout: 3600 22 | } 23 | }; -------------------------------------------------------------------------------- /src/common/config/websocket.js: -------------------------------------------------------------------------------- 1 | export default { 2 | on: true, //是否开启 WebSocket 3 | type: "socket.io", 4 | allow_origin: "", 5 | sub_protocal: "", 6 | adapter: undefined, 7 | path: "", //url path for websocket 8 | messages: { 9 | open: 'home/socket/open', 10 | close: 'home/socket/close', 11 | chat: 'home/socket/chat', 12 | adduser: 'home/socket/adduser', 13 | voice: '/home/socket/voice' 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## README 2 | 3 | application created by [ThinkJS](http://www.thinkjs.org) 4 | 5 | ## install dependencies 6 | 7 | ``` 8 | npm install 9 | ``` 10 | 11 | ## start server 12 | 13 | ``` 14 | npm start 15 | ``` 16 | 17 | ## deploy with pm2 18 | 19 | ``` 20 | pm2 startOrReload pm2.json 21 | ``` 22 | 23 | ## TODO 24 | 25 | - [X] 消息声音通知以及标签栏闪烁通知 26 | - [ ] 聊天表情和图片 27 | - [X] @ 支持 28 | - [X] 命令模式 29 | - [X] 昵称修改 30 | - [ ] 聊天记录保存 31 | - [X] 增加二维码网址分享 32 | - [X] 语音 33 | - [X] 字母头像 34 | -------------------------------------------------------------------------------- /src/home/controller/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Base from './base.js'; 4 | 5 | export default class extends Base { 6 | /** 7 | * index action 8 | * @return {Promise} [] 9 | */ 10 | indexAction() { 11 | //'http://localhost:8360' 12 | this.assign('socketUrl', 'http://' + this.http.host); 13 | return this.display(); 14 | } 15 | 16 | loginAction() { 17 | return this.display(); 18 | } 19 | 20 | testAction() { 21 | return this.display(); 22 | } 23 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "anychat", 3 | "description": "project created by thinkjs", 4 | "version": "1.0.1", 5 | "scripts": { 6 | "start": "node www/index.js", 7 | "compile": "babel --loose all --optional runtime --stage 0 --modules common src/ --out-dir app/", 8 | "watch-compile": "npm run compile -- --watch" 9 | }, 10 | "dependencies": { 11 | "babel": "5.8.21", 12 | "babel-runtime": "5.6.17", 13 | "socket.io": "^1.3.7", 14 | "thinkjs": "2.0.x" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/home/logic/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * logic 4 | * @param {} [] 5 | * @return {} [] 6 | */ 7 | export default class extends think.logic.base { 8 | /** 9 | * index action logic 10 | * @return {} [] 11 | */ 12 | indexAction(){ 13 | this.checkRoom(); 14 | } 15 | 16 | chatAction() { 17 | this.checkRoom(); 18 | } 19 | 20 | loginAction() { 21 | this.checkRoom(); 22 | } 23 | 24 | checkRoom() { 25 | /** 必须选择一个聊天室否则随机 **/ 26 | if( !this.get('room') ) { 27 | this.http.redirect('?room=' + think.md5(Date.now()).slice(0,8) ); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage/ 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Dependency directory 23 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 24 | node_modules/ 25 | 26 | # IDE config 27 | .idea 28 | 29 | # output 30 | app/ 31 | output/ 32 | output.tar.gz 33 | -------------------------------------------------------------------------------- /view/home/index_test.html: -------------------------------------------------------------------------------- 1 | hello 2 | 3 | 4 | 34 | -------------------------------------------------------------------------------- /www/README.md: -------------------------------------------------------------------------------- 1 | ## application 2 | 3 | ### start server 4 | 5 | *development* 6 | 7 | ```js 8 | node www/index.js 9 | ``` 10 | 11 | *testing* 12 | 13 | ```js 14 | node www/testing.js 15 | ``` 16 | 17 | *production* 18 | 19 | ```js 20 | node www/production.js 21 | ``` 22 | 23 | or use pm2 to manage node: 24 | 25 | ``` 26 | pm2 start www/production.js 27 | ``` 28 | 29 | ### compile es6 code 30 | 31 | ``` 32 | npm run compile 33 | ``` 34 | 35 | watch file change: 36 | 37 | ``` 38 | npm run wacth-compile 39 | ``` 40 | 41 | ### how to link resource 42 | 43 | *in template file* 44 | 45 | ```html 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | ``` 54 | 55 | *link image in css* 56 | 57 | ```css 58 | .a{ 59 | background: url(../img/a.png) no-repeat; 60 | } 61 | ``` -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost1; 4 | root /Users/welefen/Develop/git/thinkjs2-demos/websocket-socket.io/www; 5 | set $node_port 8360; 6 | 7 | index index.js index.html index.htm; 8 | if ( -f $request_filename/index.html ){ 9 | rewrite (.*) $1/index.html break; 10 | } 11 | if ( !-f $request_filename ){ 12 | rewrite (.*) /index.js; 13 | } 14 | location = /index.js { 15 | proxy_http_version 1.1; 16 | proxy_set_header Connection ""; 17 | proxy_set_header X-Real-IP $remote_addr; 18 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 19 | proxy_set_header Host $http_host; 20 | proxy_set_header X-NginX-Proxy true; 21 | proxy_set_header Upgrade $http_upgrade; 22 | proxy_set_header Connection "upgrade"; 23 | proxy_pass http://127.0.0.1:$node_port$request_uri; 24 | proxy_redirect off; 25 | } 26 | location ~ /static/ { 27 | etag on; 28 | expires max; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/common/controller/error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * error controller 4 | */ 5 | export default class extends think.controller.base { 6 | /** 7 | * display error page 8 | * @param {Number} status [] 9 | * @return {Promise} [] 10 | */ 11 | displayErrorPage(status){ 12 | let module = 'common'; 13 | if(think.mode !== think.mode_module){ 14 | module = this.config('default_module'); 15 | } 16 | let file = `${module}/error/${status}.html`; 17 | let options = this.config('tpl'); 18 | options = think.extend({}, options, {type: 'ejs'}); 19 | return this.display(file, options); 20 | } 21 | /** 22 | * Bad Request 23 | * @return {Promise} [] 24 | */ 25 | _400Action(){ 26 | return this.displayErrorPage(400); 27 | } 28 | /** 29 | * Forbidden 30 | * @return {Promise} [] 31 | */ 32 | _403Action(){ 33 | return this.displayErrorPage(403); 34 | } 35 | /** 36 | * Not Found 37 | * @return {Promise} [] 38 | */ 39 | _404Action(){ 40 | return this.displayErrorPage(404); 41 | } 42 | /** 43 | * Internal Server Error 44 | * @return {Promise} [] 45 | */ 46 | _500Action(){ 47 | return this.displayErrorPage(500); 48 | } 49 | /** 50 | * Service Unavailable 51 | * @return {Promise} [] 52 | */ 53 | _503Action(){ 54 | return this.displayErrorPage(503); 55 | } 56 | } -------------------------------------------------------------------------------- /view/home/index_login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | anychat 6 | 64 | 65 | 66 |
67 | 68 | 69 | 70 |
71 | 72 | 88 | 89 | -------------------------------------------------------------------------------- /src/home/controller/socket.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Base from './base.js'; 4 | 5 | var chatrooms = {}; 6 | 7 | export default class extends Base { 8 | openAction() { 9 | 10 | } 11 | 12 | chatAction(self) { 13 | var { 14 | room, userId, message 15 | } = self.http.data; 16 | var members = chatrooms[room]; 17 | 18 | if (!members[userId]) return false; 19 | 20 | this.broadTo('chat', { 21 | displayName: members[userId].displayName, 22 | message 23 | }, room, userId); 24 | } 25 | 26 | voiceAction(self) { 27 | var { 28 | room, userId, message 29 | } = self.http.data; 30 | var members = chatrooms[room]; 31 | 32 | if (!members[userId]) return false; 33 | 34 | this.broadTo('chat:voice', { 35 | displayName: members[userId].displayName, 36 | message 37 | }, room, userId); 38 | } 39 | 40 | closeAction(self) { 41 | var socket = self.http.socket; 42 | var logoutRoom, logoutUserId, logoutUser; 43 | for (var room in chatrooms) { 44 | var members = chatrooms[room]; 45 | for (var userId in members) { 46 | if (members[userId].socket === socket) { 47 | logoutRoom = room; 48 | logoutUserId = userId; 49 | logoutUser = members[userId]; 50 | } 51 | } 52 | } 53 | 54 | if (!logoutUserId) return true; 55 | 56 | var displayName = logoutUser.displayName; 57 | delete chatrooms[logoutRoom][logoutUserId]; 58 | 59 | /**通知其他用户有用户离开并更新成员列表**/ 60 | this.broadTo('user:exit', { 61 | exit: displayName, 62 | users: this.getUsers(logoutRoom) 63 | }, logoutRoom); 64 | } 65 | 66 | adduserAction(self) { 67 | var socket = self.http.socket; 68 | var { 69 | room, userId, displayName 70 | } = self.http.data; 71 | if (!chatrooms[room]) { 72 | chatrooms[room] = {}; 73 | } 74 | 75 | var members = chatrooms[room]; 76 | while (members[userId]) { 77 | userId += 1; 78 | } 79 | 80 | /**通知其他用户有新用户加入,并更新用户列表**/ 81 | this.broadTo('user:join', { 82 | join: displayName, 83 | users: this.getUsers(room).concat([displayName]) 84 | }, room); 85 | 86 | members[userId] = { 87 | displayName, socket 88 | }; 89 | 90 | /**告诉本用户加入成功并更新用户 Id**/ 91 | this.emit('user:login', { 92 | userId, users: this.getUsers(room) 93 | }); 94 | } 95 | 96 | broadTo(event, data, room, filter = false) { 97 | var members = chatrooms[room] || {}; 98 | for (var i in members) { 99 | if (i === filter) continue; 100 | let member = members[i]; 101 | member.socket.emit(event, data); 102 | } 103 | } 104 | 105 | getUsers(room) { 106 | var members = chatrooms[room]; 107 | 108 | return Object.keys(members).map(member => ({id: member, name: members[member].displayName})); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /www/static/libs/record/recorder.js: -------------------------------------------------------------------------------- 1 | /*License (MIT) 2 | 3 | Copyright © 2013 Matt Diamond 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 6 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation 7 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and 8 | to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of 11 | the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 14 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 16 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 17 | DEALINGS IN THE SOFTWARE. 18 | */ 19 | 20 | (function(window){ 21 | 22 | var WORKER_PATH = '/static/libs/record/recorderWorker.js'; 23 | 24 | var Recorder = function(source, cfg){ 25 | var config = cfg || {}; 26 | var bufferLen = config.bufferLen || 4096; 27 | this.context = source.context; 28 | if(!this.context.createScriptProcessor){ 29 | this.node = this.context.createJavaScriptNode(bufferLen, 2, 2); 30 | } else { 31 | this.node = this.context.createScriptProcessor(bufferLen, 2, 2); 32 | } 33 | 34 | var worker = new Worker(config.workerPath || WORKER_PATH); 35 | worker.postMessage({ 36 | command: 'init', 37 | config: { 38 | sampleRate: this.context.sampleRate 39 | } 40 | }); 41 | var recording = false, 42 | currCallback; 43 | 44 | this.node.onaudioprocess = function(e){ 45 | if (!recording) return; 46 | worker.postMessage({ 47 | command: 'record', 48 | buffer: [ 49 | e.inputBuffer.getChannelData(0), 50 | e.inputBuffer.getChannelData(1) 51 | ] 52 | }); 53 | } 54 | 55 | this.configure = function(cfg){ 56 | for (var prop in cfg){ 57 | if (cfg.hasOwnProperty(prop)){ 58 | config[prop] = cfg[prop]; 59 | } 60 | } 61 | } 62 | 63 | this.record = function(){ 64 | recording = true; 65 | } 66 | 67 | this.stop = function(){ 68 | recording = false; 69 | } 70 | 71 | this.clear = function(){ 72 | worker.postMessage({ command: 'clear' }); 73 | } 74 | 75 | this.getBuffers = function(cb) { 76 | currCallback = cb || config.callback; 77 | worker.postMessage({ command: 'getBuffers' }) 78 | } 79 | 80 | this.exportWAV = function(cb, type){ 81 | currCallback = cb || config.callback; 82 | type = type || config.type || 'audio/wav'; 83 | if (!currCallback) throw new Error('Callback not set'); 84 | worker.postMessage({ 85 | command: 'exportWAV', 86 | type: type 87 | }); 88 | } 89 | 90 | this.exportMonoWAV = function(cb, type){ 91 | currCallback = cb || config.callback; 92 | type = type || config.type || 'audio/wav'; 93 | if (!currCallback) throw new Error('Callback not set'); 94 | worker.postMessage({ 95 | command: 'exportMonoWAV', 96 | type: type 97 | }); 98 | } 99 | 100 | worker.onmessage = function(e){ 101 | var blob = e.data; 102 | currCallback(blob); 103 | } 104 | 105 | source.connect(this.node); 106 | this.node.connect(this.context.destination); // if the script node is not connected to an output the "onaudioprocess" event is not triggered in chrome. 107 | }; 108 | 109 | Recorder.setupDownload = function(blob, filename){ 110 | var url = (window.URL || window.webkitURL).createObjectURL(blob); 111 | var link = document.getElementById("save"); 112 | link.href = url; 113 | link.download = filename || 'output.wav'; 114 | } 115 | 116 | window.Recorder = Recorder; 117 | 118 | })(window); 119 | -------------------------------------------------------------------------------- /view/home/index_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | anychat 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 26 |
27 | 36 | 50 | 65 | 75 | 76 | 81 |
82 |
83 |
84 |
85 | 86 |
87 |
88 |

 89 | 				
按住空格键(Space)说话
90 | 91 |
92 |
93 |
94 | 98 |