├── .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 |
2 | #app
3 | #messages_scroll
4 | #messages_pool
5 | div(v-for="m in messages")
6 | p.msg.title(v-if="!hide_date")
7 | span.msg.datetime(v-if="m.__time") {{datetime(m.__time)}}
8 | span.msg.user(v-if="m.__user") {{m.__user}}
9 | p.msg.content(v-html="emoji(m.msg)")
10 | #send_input_div
11 | .link(v-html="emoji('🙂')")
12 | .link(v-html="emoji('📎')")
13 | .ui.transparent.inverted.fluid.input
14 | input#send_input(type='text', placeholder='Enter Message...', autocomplete='off', v-model='inputs', @keyup.enter='send()')
15 |
16 |
17 |
63 |
64 |
177 |
--------------------------------------------------------------------------------