aboutsummaryrefslogtreecommitdiff
path: root/webcastor.js
diff options
context:
space:
mode:
Diffstat (limited to 'webcastor.js')
-rw-r--r--webcastor.js216
1 files changed, 216 insertions, 0 deletions
diff --git a/webcastor.js b/webcastor.js
new file mode 100644
index 0000000..67b96d3
--- /dev/null
+++ b/webcastor.js
@@ -0,0 +1,216 @@
1/*
2 * This file is part of Webcastor <https://github.com/Pacien/Webcastor>.
3
4 * Webcastor is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8
9 * Webcastor is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13
14 * You should have received a copy of the GNU General Public License
15 * along with Webcastor. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18var imports = {
19 http : require('http'),
20 url : require('url'),
21
22 express : require('express'),
23 logfmt : require('logfmt'),
24 bodyParser : require('body-parser'),
25 hogan : require('hogan-express'),
26
27 socketio : require('socket.io'),
28 socketioWildcard : require('socket.io-wildcard'),
29
30 redis : require('redis'),
31
32 passwordHash : require('password-hash'),
33};
34
35var params = {
36 database : {
37 persistent : process.env.REDISCLOUD_URL !== undefined
38 && process.env.REDISCLOUD_URL !== null,
39 server : process.env.REDISCLOUD_URL,
40 },
41
42 server : {
43 port : (process.env.PORT !== undefined) ? process.env.PORT : '8080',
44 },
45
46 messageSizeLimit : 8000,
47};
48
49var VolatileKeyValueStore = {
50 create : function() {
51 var self = Object.create(this);
52 this.store = {};
53 return self;
54 },
55
56 set : function(key, value) {
57 this.store[key] = value;
58 },
59
60 get : function(key, callback) {
61 var value = this.store[key];
62 callback(null, value);
63 },
64};
65
66var Channel = {
67 create : function(password) {
68 var uniqueName = this.generateUniqueName();
69 this.open(uniqueName, password);
70 return uniqueName;
71 },
72
73 open : function(name, password) {
74 var hashedPassword = imports.passwordHash.generate(password);
75 Server.db.set(name, hashedPassword);
76 },
77
78 generateUniqueName : function() {
79 var uniqueName;
80 while (uniqueName === this.previousName) {
81 uniqueName = (+new Date()).toString(36).toUpperCase();
82 }
83 this.previousName = uniqueName;
84 return uniqueName;
85 },
86
87 getPassword : function(name, callback) {
88 Server.db.get(name, function(err, hashedPassword) {
89 callback(hashedPassword);
90 });
91 },
92};
93
94var Server = {
95 init : function() {
96 if (params.database.persistent) {
97 this.db = this.connectToDb();
98 } else {
99 this.db = VolatileKeyValueStore.create();
100 }
101
102 this.app = this.createApp();
103 this.server = imports.http.createServer(this.app);
104 this.io = imports.socketioWildcard(imports.socketio)
105 .listen(this.server);
106
107 this.addHandlers(this.app, this.io);
108
109 this.server.listen(params.server.port);
110 },
111
112 createApp : function() {
113 var app = imports.express();
114
115 app.use(imports.logfmt.requestLogger());
116 app.use(imports.bodyParser());
117 app.set('view engine', 'html');
118 app.set('layout', 'layout');
119 app.enable('view cache');
120 app.engine('html', imports.hogan);
121
122 return app;
123 },
124
125 connectToDb : function() {
126 console.log(params.database.server);
127 var redisURL = imports.url.parse(params.database.server);
128 console.log(redisURL);
129 var db = imports.redis.createClient(redisURL.port, redisURL.hostname, {
130 no_ready_check : true
131 });
132 db.auth(redisURL.auth.split(':')[1]);
133 db.on('error', function(err) {
134 console.log('Redis error encountered: ', err);
135 });
136
137 return db;
138 },
139
140 addHandlers : function(app, io) {
141 app.get('/client', function(req, res) {
142 res.render('client');
143 });
144
145 app.get('/', function(req, res) {
146 res.render('home');
147 });
148
149 app.post('/', function(req, res) {
150 var password = req.body.password;
151 if (password === null) {
152 password = '';
153 }
154
155 var channelName = Channel.create(password);
156
157 console.log('Created new channel ' + channelName);
158
159 res.render('channel', {
160 channel : channelName,
161 url : req.protocol + '://' + req.host + '/' + channelName,
162 });
163 });
164
165 io.sockets.on('connection', function(socket) {
166 var query = imports.url.parse(socket.handshake.url, true).query;
167 var channel = query.channel;
168 var password = query.password;
169
170 console.log('Incomming connection on ' + channel);
171 Server.handleSocket(socket, channel, password);
172 });
173 },
174
175 handleSocket : function(socket, channel, password) {
176 Channel.getPassword(channel, function(hashedPassword) {
177
178 if (hashedPassword === null) {
179 console.log('Client joined and unknown channel');
180 return;
181 }
182
183 console.log(hashedPassword);
184
185 socket.join(channel);
186
187 if (!imports.passwordHash.verify(password, hashedPassword)) {
188 console.log('Client joined ' + channel);
189 return;
190 }
191
192 console.log('Broadcaster joined ' + channel);
193
194 socket.on('*', function(event) {
195 Server.broadcast(socket, channel, event);
196 });
197 });
198 },
199
200 broadcast : function(socket, channel, event) {
201 var JSONmessage = JSON.stringify(event);
202
203 var messageSize = encodeURI(JSONmessage).split(/%..|./).length - 1;
204 if (messageSize > params.messageSizeLimit) {
205 console.log('Not broadcasting ' + JSONmessage + ' (' + messageSize
206 + ' bytes)');
207 return;
208 }
209
210 console.log('Broadcasting ' + JSONmessage + ' (' + messageSize
211 + ' bytes)');
212 socket.broadcast.to(channel).emit(event.name, event.args);
213 },
214};
215
216Server.init();