diff options
Diffstat (limited to 'webcastor.js')
-rw-r--r-- | webcastor.js | 216 |
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 | |||
18 | var 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 | |||
35 | var 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 | |||
49 | var 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 | |||
66 | var 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 | |||
94 | var 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 | |||
216 | Server.init(); | ||