├── .gitignore ├── .babelrc ├── TODO.md ├── README.md ├── src ├── client │ ├── main.js │ ├── scripts │ │ ├── utils.js │ │ ├── websocket.js │ │ └── actions.js │ └── App.vue └── server │ └── main.js ├── .jsbeautifyrc ├── public └── index.html ├── LICENSE ├── package.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["es2015", { "modules": false }] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | ## TODOs 2 | - [x] websocket.js 3 | - [x] ES2015/ES2016/babel 4 | - [ ] cross-env 5 | - [ ] webpack-dev-server 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Astchat.js 2 | An incognito chatting framework 3 | 4 | A Nodejs implementation of https://github.com/antfu/astchat 5 | -------------------------------------------------------------------------------- /src/client/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | 4 | new Vue({ 5 | el: '#app', 6 | render: h => h(App) 7 | }) 8 | -------------------------------------------------------------------------------- /.jsbeautifyrc: -------------------------------------------------------------------------------- 1 | { 2 | "indent_size": 2, 3 | "indent_char": " ", 4 | "other": " ", 5 | "indent_level": 0, 6 | "indent_with_tabs": false, 7 | "preserve_newlines": true, 8 | "max_preserve_newlines": 2, 9 | "jslint_happy": true, 10 | "indent_handlebars": true 11 | } 12 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/client/scripts/utils.js: -------------------------------------------------------------------------------- 1 | import Emoji from 'emojione/lib/js/emojione' 2 | 3 | export default { 4 | emoji(t) {return Emoji.toImage(t)}, 5 | datetime(time) { 6 | var a = new Date(time); 7 | var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; 8 | var year = a.getFullYear(); 9 | var month = months[a.getMonth()]; 10 | var date = a.getDate(); 11 | var hour = this.pad(a.getHours(),2); 12 | var min = this.pad(a.getMinutes(),2); 13 | var sec = this.pad(a.getSeconds(),2); 14 | var time = hour + ':' + min + ':' + sec + ', ' + date + ' ' + month; 15 | return time; 16 | }, 17 | pad(num, size) { 18 | var s = num+""; 19 | while (s.length < size) s = "0" + s; 20 | return s; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Anthony Fu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Astchat.js", 3 | "description": "An incognito chatting framework", 4 | "author": "Anthony Fu ", 5 | "private": true, 6 | "scripts": { 7 | "dev": "cross-env NODE_ENV=development webpack-dev-server --open --inline --hot", 8 | "build": "cross-env NODE_ENV=production webpack --progress --colors --hide-modules", 9 | "watch": "cross-env NODE_ENV=production webpack --progress --colors --watch --hide-modules", 10 | "node": "node ./src/server/main.js" 11 | }, 12 | "dependencies": { 13 | "emojione": "^2.2.6", 14 | "express": "^4.14.0", 15 | "vue": "latest", 16 | "ws": "^1.1.1" 17 | }, 18 | "devDependencies": { 19 | "babel-core": "latest", 20 | "babel-loader": "latest ", 21 | "babel-preset-es2015": "latest", 22 | "cross-env": "^3.0.0", 23 | "css-loader": "^0.25.0", 24 | "file-loader": "^0.9.0", 25 | "less": "^2.7.1", 26 | "less-loader": "^2.2.3", 27 | "pug": "^2.0.0-beta6", 28 | "pug-loader": "^2.3.0", 29 | "url-loader": "^0.5.7", 30 | "vue-loader": "latest", 31 | "webpack": "^2.1.0-beta.25", 32 | "webpack-dev-server": "^2.1.0-beta.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/client/scripts/websocket.js: -------------------------------------------------------------------------------- 1 | export default class Socket { 2 | constructor(url) { 3 | this.url = url || 'ws://' + location.host + location.pathname 4 | this.socket = new WebSocket(this.url) 5 | this.onfuncs = {} 6 | this.socket.onopen = e => this.call_on('open', e) 7 | this.socket.onerror = e => this.call_on('error', e) 8 | this.socket.onclose = e => this.call_on('close', e) 9 | this.socket.onmessage = e => { 10 | this.call_on('message', e) 11 | try { 12 | let json_obj = JSON.parse(e.data) 13 | this.call_on('json',json_obj) 14 | } catch (err) { 15 | console.error(err) 16 | } 17 | } 18 | } 19 | 20 | on(type, func) { 21 | this.onfuncs[type] = this.onfuncs[type] || [] 22 | this.onfuncs[type].push(func) 23 | return this 24 | } 25 | 26 | off(type) { 27 | this.onfuncs[type] = [] 28 | } 29 | 30 | call_on(type, e) { 31 | let funcs = this.onfuncs[type] || [] 32 | for (var f of funcs) 33 | try { 34 | f(e) 35 | } catch (err) { 36 | console.error(err) 37 | } 38 | } 39 | 40 | send(e) { 41 | this.socket.send(e) 42 | } 43 | 44 | json(obj) { 45 | this.send(JSON.stringify(obj)) 46 | } 47 | 48 | close(code, reason) { 49 | this.socket.close.apply(code, reason) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/server/main.js: -------------------------------------------------------------------------------- 1 | let express = require('express') 2 | let app = express() 3 | let url = require('url') 4 | let WebSocketServer = require('ws').Server 5 | 6 | let rooms = {} 7 | 8 | app.get('/', function (req, res) { 9 | res.redirect('/lobby'); 10 | }) 11 | 12 | app.use('/dist', express.static('public/dist')) 13 | 14 | app.get('/:room', function (req, res) { 15 | res.sendFile('index.html', {root: './public'}) 16 | }) 17 | 18 | let server = app.listen(8080, function () { 19 | console.log('Example app listening on port 8080!') 20 | }); 21 | 22 | let wss = new WebSocketServer({ 23 | server: server 24 | }) 25 | 26 | wss.on('connection', function (ws) { 27 | let location = url.parse(ws.upgradeReq.url, true) 28 | let name = location.path 29 | rooms[name] = rooms[name] || [] 30 | rooms[name].push(ws) 31 | 32 | console.log((new Date()) + ' Connection from ' + ' accepted. At ' + location.path) 33 | ws.on('message', function (message, flags) { 34 | console.log(message) 35 | for (var c of rooms[name]) 36 | c.send(message) 37 | }); 38 | ws.on('close', function (reasonCode, description) { 39 | let index = rooms[name].indexOf(ws) 40 | if (index != -1) 41 | rooms[name].splice(index, 1); 42 | console.log((new Date()) + ' Peer ' + ws.remoteAddress + ' disconnected.') 43 | }); 44 | 45 | ws.msg = (msg, action) => { 46 | action = action || 'chat' 47 | ws.send(JSON.stringify({ 48 | __user: '@Astchat', 49 | __time: new Date().getTime(), 50 | __action: action, 51 | msg: msg, 52 | })) 53 | } 54 | ws.msg('Welcome to Astchat! 🎊') 55 | }); 56 | -------------------------------------------------------------------------------- /src/client/scripts/actions.js: -------------------------------------------------------------------------------- 1 | export default class Actions { 2 | constructor(actions, base) { 3 | this.actions = {} 4 | for (let key in actions) 5 | { 6 | let value = actions[key] 7 | if (typeof value === "function") 8 | value = [value] 9 | this.actions[key] = value 10 | } 11 | this.base = base || {} 12 | } 13 | 14 | add_actions(name, func) { 15 | this.actions[name] = this.actions[name] || [] 16 | this.actions[name].push(func) 17 | } 18 | 19 | remove_actions(name, func) { 20 | if (!func) 21 | this.actions[name] = [] 22 | else { 23 | let index = this.actions[name].indexOf(func) 24 | if (index != -1) 25 | this.actions[name].splice(index, 1); 26 | } 27 | } 28 | 29 | onmessage(message) { 30 | if (typeof message === 'string') 31 | message = JSON.parse(message) 32 | let action = message.__action || '__else' 33 | for (let f of (this.actions[action]||[])) { 34 | try { 35 | f(message) 36 | } catch (err) { 37 | console.error(err) 38 | } 39 | } 40 | } 41 | 42 | make(message, action) { 43 | let result = { 44 | __action: action, 45 | __time: new Date().getTime() 46 | } 47 | for (let key in this.base) 48 | { 49 | let value = this.base[key] 50 | if (typeof value === "function") 51 | value = value() 52 | if (value) 53 | result[key] = value 54 | } 55 | for (let key in message) 56 | { 57 | let value = message[key] 58 | if (value) 59 | result[key] = value 60 | } 61 | return result 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | 4 | module.exports = { 5 | name: 'client', 6 | entry: './src/client/main.js', 7 | output: { 8 | path: path.resolve(__dirname, './public/dist'), 9 | publicPath: '/public/dist/', 10 | filename: 'build.js' 11 | }, 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.vue$/, 16 | loader: 'vue', 17 | options: { 18 | // vue-loader options go here 19 | } 20 | }, 21 | { 22 | test: /\.js$/, 23 | loader: 'babel', 24 | exclude: /node_modules/ 25 | }, 26 | { 27 | test: /\.[jade|pug]$/, 28 | loader: 'pug' 29 | }, 30 | { 31 | test: /\.less$/, 32 | loader: 'less' 33 | }, 34 | { 35 | test: /\.(png|jpg|gif|svg)$/, 36 | loader: 'file' 37 | } 38 | ] 39 | }, 40 | resolve: { 41 | alias: { 42 | 'vue$': 'vue/dist/vue' 43 | } 44 | }, 45 | devServer: { 46 | historyApiFallback: true, 47 | noInfo: true 48 | }, 49 | devtool: '#eval-source-map' 50 | } 51 | 52 | if (process.env.NODE_ENV === 'production') { 53 | module.exports.devtool = '#source-map' 54 | // http://vue-loader.vuejs.org/en/workflow/production.html 55 | module.exports.plugins = (module.exports.plugins || []).concat([ 56 | new webpack.DefinePlugin({ 57 | 'process.env': { 58 | NODE_ENV: '"production"' 59 | } 60 | }), 61 | new webpack.optimize.UglifyJsPlugin({ 62 | compress: { 63 | warnings: false 64 | } 65 | }), 66 | new webpack.LoaderOptionsPlugin({ 67 | minimize: true 68 | }) 69 | ]) 70 | } 71 | -------------------------------------------------------------------------------- /src/client/App.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 63 | 64 | 177 | --------------------------------------------------------------------------------