From d8bae84fff7977844fa2b57b15b86a831b73aed2 Mon Sep 17 00:00:00 2001
From: Pacien TRAN-GIRARD
Date: Thu, 15 May 2014 16:00:45 +0200
Subject: First version
---
.project | 18 +++++
Procfile | 1 +
README.md | 8 ++
package.json | 33 ++++++++
views/channel.html | 28 +++++++
views/client.html | 83 ++++++++++++++++++++
views/home.html | 31 ++++++++
views/layout.html | 11 +++
webcastor.js | 216 +++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 429 insertions(+)
create mode 100644 .project
create mode 100644 Procfile
create mode 100644 package.json
create mode 100644 views/channel.html
create mode 100644 views/client.html
create mode 100644 views/home.html
create mode 100644 views/layout.html
create mode 100644 webcastor.js
diff --git a/.project b/.project
new file mode 100644
index 0000000..45abfac
--- /dev/null
+++ b/.project
@@ -0,0 +1,18 @@
+
+
+ Webcastor
+
+
+
+
+
+ com.eclipsesource.jshint.ui.builder
+
+
+
+
+
+ org.nodeclipse.ui.NodeNature
+ org.eclipse.wst.jsdt.core.jsNature
+
+
diff --git a/Procfile b/Procfile
new file mode 100644
index 0000000..d1c50f6
--- /dev/null
+++ b/Procfile
@@ -0,0 +1 @@
+web: node webcastor.js
\ No newline at end of file
diff --git a/README.md b/README.md
index 0e2474a..4f55f2a 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,10 @@
Webcastor
=========
+
+Webcastor is a simple websocket broadcaster based on socket.io and node.js.
+
+When a broadcaster sends a message to a channel, it is broadcast to all other clients (including other broadcasters) connected to the channel.
+
+Channels are created with a generated ID and an optional broadcaster password.
+
+This application is currently running on [https://webcastor.herokuapp.com/](https://webcastor.herokuapp.com/).
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..da6dad6
--- /dev/null
+++ b/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "Webcastor",
+ "version": "0.1.0",
+ "description": "Webcastor",
+ "main": "webcastor.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified! Configure in package.json\" && exit 1"
+ },
+ "repository": "",
+ "keywords": [
+ "node.js",
+ "eclipse",
+ "nodeclipse"
+ ],
+ "author": "",
+ "license": "MIT",
+ "readmeFilename": "README.md",
+ "dependencies": {
+ "body-parser": "^1.1.1",
+ "express": "^4.1.2",
+ "hogan-express": "^0.5.2",
+ "http": "0.0.0",
+ "logfmt": "^1.1.2",
+ "password-hash": "^1.2.2",
+ "redis": "^0.10.1",
+ "socket.io": "^0.9.16",
+ "socket.io-wildcard": "^0.1.1",
+ "url": "^0.10.1"
+ },
+ "engines": {
+ "node": "0.10.x"
+ }
+}
diff --git a/views/channel.html b/views/channel.html
new file mode 100644
index 0000000..a1c13b5
--- /dev/null
+++ b/views/channel.html
@@ -0,0 +1,28 @@
+
Create a channel
+
+Channel created with given password
+
+
+
+
+
+
diff --git a/views/client.html b/views/client.html
new file mode 100644
index 0000000..c322042
--- /dev/null
+++ b/views/client.html
@@ -0,0 +1,83 @@
+Test client
+
+
+
+
+
+
+Log
+
+
+
+
\ No newline at end of file
diff --git a/views/home.html b/views/home.html
new file mode 100644
index 0000000..f6fef09
--- /dev/null
+++ b/views/home.html
@@ -0,0 +1,31 @@
+Create a channel
+
+
+
+
+
+
+Test client - Sources
+
+
diff --git a/views/layout.html b/views/layout.html
new file mode 100644
index 0000000..c929bc9
--- /dev/null
+++ b/views/layout.html
@@ -0,0 +1,11 @@
+
+
+
+
+ Webcastor
+
+
+ Webcastor
+ {{{ yield }}}
+
+
\ No newline at end of file
diff --git a/webcastor.js b/webcastor.js
new file mode 100644
index 0000000..67b96d3
--- /dev/null
+++ b/webcastor.js
@@ -0,0 +1,216 @@
+/*
+ * This file is part of Webcastor .
+
+ * Webcastor is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * Webcastor is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with Webcastor. If not, see .
+ */
+
+var imports = {
+ http : require('http'),
+ url : require('url'),
+
+ express : require('express'),
+ logfmt : require('logfmt'),
+ bodyParser : require('body-parser'),
+ hogan : require('hogan-express'),
+
+ socketio : require('socket.io'),
+ socketioWildcard : require('socket.io-wildcard'),
+
+ redis : require('redis'),
+
+ passwordHash : require('password-hash'),
+};
+
+var params = {
+ database : {
+ persistent : process.env.REDISCLOUD_URL !== undefined
+ && process.env.REDISCLOUD_URL !== null,
+ server : process.env.REDISCLOUD_URL,
+ },
+
+ server : {
+ port : (process.env.PORT !== undefined) ? process.env.PORT : '8080',
+ },
+
+ messageSizeLimit : 8000,
+};
+
+var VolatileKeyValueStore = {
+ create : function() {
+ var self = Object.create(this);
+ this.store = {};
+ return self;
+ },
+
+ set : function(key, value) {
+ this.store[key] = value;
+ },
+
+ get : function(key, callback) {
+ var value = this.store[key];
+ callback(null, value);
+ },
+};
+
+var Channel = {
+ create : function(password) {
+ var uniqueName = this.generateUniqueName();
+ this.open(uniqueName, password);
+ return uniqueName;
+ },
+
+ open : function(name, password) {
+ var hashedPassword = imports.passwordHash.generate(password);
+ Server.db.set(name, hashedPassword);
+ },
+
+ generateUniqueName : function() {
+ var uniqueName;
+ while (uniqueName === this.previousName) {
+ uniqueName = (+new Date()).toString(36).toUpperCase();
+ }
+ this.previousName = uniqueName;
+ return uniqueName;
+ },
+
+ getPassword : function(name, callback) {
+ Server.db.get(name, function(err, hashedPassword) {
+ callback(hashedPassword);
+ });
+ },
+};
+
+var Server = {
+ init : function() {
+ if (params.database.persistent) {
+ this.db = this.connectToDb();
+ } else {
+ this.db = VolatileKeyValueStore.create();
+ }
+
+ this.app = this.createApp();
+ this.server = imports.http.createServer(this.app);
+ this.io = imports.socketioWildcard(imports.socketio)
+ .listen(this.server);
+
+ this.addHandlers(this.app, this.io);
+
+ this.server.listen(params.server.port);
+ },
+
+ createApp : function() {
+ var app = imports.express();
+
+ app.use(imports.logfmt.requestLogger());
+ app.use(imports.bodyParser());
+ app.set('view engine', 'html');
+ app.set('layout', 'layout');
+ app.enable('view cache');
+ app.engine('html', imports.hogan);
+
+ return app;
+ },
+
+ connectToDb : function() {
+ console.log(params.database.server);
+ var redisURL = imports.url.parse(params.database.server);
+ console.log(redisURL);
+ var db = imports.redis.createClient(redisURL.port, redisURL.hostname, {
+ no_ready_check : true
+ });
+ db.auth(redisURL.auth.split(':')[1]);
+ db.on('error', function(err) {
+ console.log('Redis error encountered: ', err);
+ });
+
+ return db;
+ },
+
+ addHandlers : function(app, io) {
+ app.get('/client', function(req, res) {
+ res.render('client');
+ });
+
+ app.get('/', function(req, res) {
+ res.render('home');
+ });
+
+ app.post('/', function(req, res) {
+ var password = req.body.password;
+ if (password === null) {
+ password = '';
+ }
+
+ var channelName = Channel.create(password);
+
+ console.log('Created new channel ' + channelName);
+
+ res.render('channel', {
+ channel : channelName,
+ url : req.protocol + '://' + req.host + '/' + channelName,
+ });
+ });
+
+ io.sockets.on('connection', function(socket) {
+ var query = imports.url.parse(socket.handshake.url, true).query;
+ var channel = query.channel;
+ var password = query.password;
+
+ console.log('Incomming connection on ' + channel);
+ Server.handleSocket(socket, channel, password);
+ });
+ },
+
+ handleSocket : function(socket, channel, password) {
+ Channel.getPassword(channel, function(hashedPassword) {
+
+ if (hashedPassword === null) {
+ console.log('Client joined and unknown channel');
+ return;
+ }
+
+ console.log(hashedPassword);
+
+ socket.join(channel);
+
+ if (!imports.passwordHash.verify(password, hashedPassword)) {
+ console.log('Client joined ' + channel);
+ return;
+ }
+
+ console.log('Broadcaster joined ' + channel);
+
+ socket.on('*', function(event) {
+ Server.broadcast(socket, channel, event);
+ });
+ });
+ },
+
+ broadcast : function(socket, channel, event) {
+ var JSONmessage = JSON.stringify(event);
+
+ var messageSize = encodeURI(JSONmessage).split(/%..|./).length - 1;
+ if (messageSize > params.messageSizeLimit) {
+ console.log('Not broadcasting ' + JSONmessage + ' (' + messageSize
+ + ' bytes)');
+ return;
+ }
+
+ console.log('Broadcasting ' + JSONmessage + ' (' + messageSize
+ + ' bytes)');
+ socket.broadcast.to(channel).emit(event.name, event.args);
+ },
+};
+
+Server.init();
--
cgit v1.2.3