Merge branch 'firebase'

This commit is contained in:
Szymon Nowak 2014-03-18 19:46:24 +01:00
commit dc91e3653b
14 changed files with 283 additions and 1227 deletions

View File

@ -2,3 +2,6 @@ HOST=localhost
PORT=8000
WEB_PORT=8000
SECRET=35b725ff3c3b48fc889c55d886f21aa1
FIREBASE_SECRET=qwerty
NEW_RELIC_ENABLED=false
NEW_RELIC_LICENSE_KEY=qwerty

2
.gitignore vendored
View File

@ -4,3 +4,5 @@
.env
.tmp
dist
newrelic_agent.log

View File

@ -1,7 +1,8 @@
<!doctype html>
<html lang="en">
<head>
<title>P2P file sharing</title>
<title>ShareDrop</title>
<meta name="description" content="ShareDrop is a peer-to-peer file sharing app powered by HTML5 WebRTC.">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="mobile-web-app-capable" content="yes">
@ -38,6 +39,7 @@
<script src="//cdnjs.cloudflare.com/ajax/libs/ember.js/1.4.0/ember.min.js"></script>
<!-- @endif -->
<script src="https://cdn.firebase.com/js/client/1.0.6/firebase.js"></script>
<script src="https://login.persona.org/include.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js"></script>

View File

@ -1,5 +1,9 @@
window.ShareDrop.App = Ember.Application.create();
ShareDrop.App.config = {
FIREBASE_URL: "https://sharedrop.firebaseio.com/"
};
ShareDrop.App.deferReadiness();
// Check if everything we need is available
@ -9,6 +13,7 @@ ShareDrop.App.deferReadiness();
.catch(function (error) {
ShareDrop.App.error = error;
})
.then(authenticateToFirebase)
.then(function () {
ShareDrop.App.advanceReadiness();
});
@ -34,6 +39,21 @@ ShareDrop.App.deferReadiness();
});
});
}
function authenticateToFirebase() {
return new Promise(function (resolve, reject) {
var xhr = Ember.$.getJSON('/auth');
xhr.then(function (data) {
var ref = new Firebase(ShareDrop.App.config.FIREBASE_URL);
ShareDrop.App.ref = ref;
ShareDrop.App.userId = data.id;
ref.auth(data.token, function (error) {
error ? reject(error) : resolve();
});
});
});
}
})();
ShareDrop.App.IndexRoute = Ember.Route.extend({

View File

@ -2,9 +2,12 @@ ShareDrop.App.ApplicationController = Ember.Controller.extend({
init: function () {
this._super();
var you = ShareDrop.App.User.create({
var id = ShareDrop.App.userId,
you = ShareDrop.App.User.create({
uuid: id,
email: localStorage.email || null
});
you.set('peer.id', id);
this.set('you', you);
this.handlePersonaAuth();

View File

@ -7,13 +7,12 @@ ShareDrop.App.IndexController = Ember.ArrayController.extend({
init: function () {
// Handle room events
$.subscribe('connected.room', this._onRoomConnected.bind(this));
$.subscribe('user_list.room', this._onRoomUserList.bind(this));
$.subscribe('disconnected.room', this._onRoomDisconnected.bind(this));
$.subscribe('user_added.room', this._onRoomUserAdded.bind(this));
$.subscribe('user_changed.room', this._onRoomUserChanged.bind(this));
$.subscribe('user_removed.room', this._onRoomUserRemoved.bind(this));
// Handle peer events
$.subscribe('connected.server.peer', this._onPeerServerConnected.bind(this));
$.subscribe('incoming_connection.p2p.peer', this._onPeerP2PIncomingConnection.bind(this));
$.subscribe('outgoing_connection.p2p.peer', this._onPeerP2POutgoingConnection.bind(this));
$.subscribe('disconnected.p2p.peer', this._onPeerP2PDisconnected.bind(this));
@ -23,15 +22,17 @@ ShareDrop.App.IndexController = Ember.ArrayController.extend({
$.subscribe('file_received.p2p.peer', this._onPeerP2PFileReceived.bind(this));
$.subscribe('file_sent.p2p.peer', this._onPeerP2PFileSent.bind(this));
// Connect to PeerJS server first,
// so that we already have peer ID when later joining a room.
this.set('webrtc', new ShareDrop.WebRTC());
// Join the room
var room = new ShareDrop.Room(ShareDrop.App.ref);
room.join(this.get('you').serialize());
this.set('room', room);
this._super();
},
_onRoomConnected: function (event, data) {
var you = this.get('you');
var you = this.get('you'),
room = this.get('room');
you.get('peer').setProperties(data.peer);
delete data.peer;
@ -39,10 +40,17 @@ ShareDrop.App.IndexController = Ember.ArrayController.extend({
// Find and set your local IP
this._setUserLocalIP();
// Initialize WebRTC
this.set('webrtc', new ShareDrop.WebRTC(you.get('uuid'), {
room: room.name,
firebaseRef: ShareDrop.App.ref
}));
},
_onRoomUserList: function (event, data) {
data.forEach(this._addPeer.bind(this));
_onRoomDisconnected: function () {
this.clear();
this.set('webrtc', null);
},
_onRoomUserAdded: function (event, data) {
@ -80,18 +88,6 @@ ShareDrop.App.IndexController = Ember.ArrayController.extend({
this.removeObject(peer);
},
_onPeerServerConnected: function (event, data) {
var you = this.get('you');
// you.set('isConnected', true);
you.set('peer.id', data.id);
// Join room and broadcast your attributes
var room = new ShareDrop.Room();
room.join(you.serialize());
this.set('room', room);
},
_onPeerP2PIncomingConnection: function (event, data) {
var connection = data.connection,
peer = this.findBy('peer.id', connection.peer);
@ -216,7 +212,7 @@ ShareDrop.App.IndexController = Ember.ArrayController.extend({
rtc.setLocalDescription(offer);
var addr = grep(offer.sdp);
if (addr) {
if (addr && addr !== '0.0.0.0') {
console.log('Local IP found: ', addr);
you.set('local_ip', addr);
}
@ -267,7 +263,7 @@ ShareDrop.App.IndexController = Ember.ArrayController.extend({
var addr = this.get('you.local_ip'),
room = this.get('room');
if (room && addr !== undefined) {
if (room && addr) {
console.log('Broadcasting user\'s local IP: ', addr);
room.update({local_ip: addr});
}

View File

@ -1,6 +1,5 @@
ShareDrop.Room = function () {
var url = window.location.protocol + '//' + window.location.hostname;
this._socket = new io.connect(url);
ShareDrop.Room = function (firebaseRef) {
this._ref = firebaseRef;
this.name = null;
};
@ -12,58 +11,57 @@ ShareDrop.Room.prototype.join = function (user) {
// Join room and listen for changes
.then(function (data) {
var socket = self._socket;
self.name = data.name;
user.public_ip = data.public_ip;
self.name = data.name,
// Setup Firebase refs
self._connectionRef = self._ref.child('.info/connected');
self._roomRef = self._ref.child('rooms/' + self.name);
self._usersRef = self._roomRef.child('users');
self._userRef = self._usersRef.child(user.uuid);
$.extend(user, {
uuid: data.uuid,
public_ip: data.public_ip
});
socket.emit('join', {
room: self.name,
peer: user
});
console.log('Room:\t Connecting to: ', self.name);
socket.on('user_list', function (data) {
console.log('Room:\t Connected to: ', self.name);
$.publish('connected.room', user);
self._connectionRef.on('value', function (snapshot) {
// Once connected (or reconnected) to Firebase
if (snapshot.val() === true) {
console.log('Firebase: (Re)Connected');
console.log('Room:\t user_list: ', data);
$.publish('user_list.room', [data]);
});
// Remove yourself from the room when disconnected
self._userRef.onDisconnect().remove();
socket.on('user_added', function (user) {
console.log('Room:\t user_added: ', user);
$.publish('user_added.room', user);
});
// Join the room
self._userRef.set(user, function (error) {
console.log('Firebase: User added to the room');
$.publish('connected.room', user);
});
socket.on('user_changed', function (user) {
console.log('Room:\t user_changed: ', user);
$.publish('user_changed.room', user);
});
self._usersRef.on('child_added', function (snapshot) {
var user = snapshot.val();
socket.on('user_removed', function (user) {
console.log('Room:\t user_removed: ', user);
$.publish('user_removed.room', user);
});
console.log('Room:\t user_added: ', user);
$.publish('user_added.room', user);
});
socket.on('disconnect', function () {
console.log('Room:\t disconnect');
});
self._usersRef.on('child_removed', function (snapshot) {
var user = snapshot.val();
socket.on('error', function () {
console.log('Room:\t error');
});
console.log('Room:\t user_removed: ', user);
$.publish('user_removed.room', user);
});
socket.on('reconnecting', function () {
console.log('Room:\t reconnecting');
});
self._usersRef.on('child_changed', function (snapshot) {
var user = snapshot.val();
socket.on('reconnect', function () {
console.log('Room:\t reconnect');
console.log('Room:\t user_changed: ', user);
$.publish('user_changed.room', user);
});
} else {
console.log('Firebase: Disconnected');
$.publish('disconnected.room');
self._usersRef.off();
}
});
});
@ -71,8 +69,5 @@ ShareDrop.Room.prototype.join = function (user) {
};
ShareDrop.Room.prototype.update = function (attrs) {
this._socket.emit('update', {
room: this.name,
peer: attrs
});
this._userRef.update(attrs);
};

View File

@ -1,34 +1,20 @@
// TODO: provide TURN server config
// once it's possible to create rooms with custom names.
ShareDrop.WebRTC = function (options) {
this.conn = new Peer({ // PeerJS client library
host: 'file-drop-peer-server.herokuapp.com',
port: 80,
ShareDrop.WebRTC = function (id, options) {
var defaults = {
config: {'iceServers': [
{ url: 'stun:stun.l.google.com:19302' }
]},
debug: 3
});
};
this.conn = new Peer(id, $.extend(defaults, options));
this.files = {
outgoing: {},
incoming: {}
};
// When connected to PeerJS server
this.conn.on('open', function (id) {
var self = this;
$.publish('connected.server.peer', {id: id});
console.log('Peer:\t Connected to server with ID: ', id);
// TODO: cancel on error/disconnect
// Ping WebSocket server to prevent timeout on Heroku
window.setInterval(function () {
self.socket.send({type: 'ping'});
}, 5000);
});
// Listen for incoming connections
this.conn.on('connection', function (connection) {
$.publish('incoming_connection.p2p.peer', {connection: connection});
@ -42,13 +28,6 @@ ShareDrop.WebRTC = function (options) {
this.conn.on('error', function (error) {
console.log('Peer:\t Error while connecting to server: ', error);
});
// Make sure PeerJS connection is cleaned up
window.onunload = window.onbeforeunload = function () {
if (!!this.conn && !this.conn.destroyed) {
this.conn.destroy();
}
};
};
ShareDrop.WebRTC.CHUNKS_PER_ACK = 64;

View File

@ -4,10 +4,14 @@ ShareDrop.App.User = ShareDrop.App.Peer.extend({
local_ip = this.get('local_ip'),
label;
if (email) {
if (email && local_ip) {
label = email + ' (' + local_ip + ')';
} else {
} else if (local_ip) {
label = local_ip;
} else if (email) {
label = email;
} else {
label = null;
}
return label;

File diff suppressed because it is too large Load Diff

View File

@ -1,21 +1,22 @@
// TODO:
// - require process.env.SECRET
module.exports.server = function (options) {
'use strict';
require('newrelic');
// Room server
var http = require('http'),
path = require('path'),
express = require('express'),
uuid = require('node-uuid'),
crypto = require('crypto'),
extend = require('deep-extend'),
persona = require('express-persona'),
socketIo = require('socket.io'),
FirebaseTokenGenerator = require("firebase-token-generator"),
firebaseTokenGenerator = new FirebaseTokenGenerator(process.env.FIREBASE_SECRET),
app = express(),
host = process.env.HOST,
webPort = process.env.WEB_PORT, // 80 or 443
secret = process.env.SECRET,
server, io, base;
base;
options = options || {};
base = options.base || ['.'];
@ -23,7 +24,15 @@ module.exports.server = function (options) {
app.use(express.logger());
app.use(express.urlencoded());
app.use(express.cookieParser());
app.use(express.session({ secret: secret }));
app.use(express.cookieSession({
cookie: {
// secure: true,
httpOnly: true,
maxAge: 30 * 24 * 60 * 60 * 1000 // 30 days
},
secret: secret,
proxy: true
}));
app.use(express.compress());
app.use(express.json());
@ -49,54 +58,18 @@ module.exports.server = function (options) {
var ip = req.headers['x-forwarded-for'] || req.ip,
name = crypto.createHmac('md5', secret).update(ip).digest('hex');
res.json({ name: name, uuid: uuid.v1(), public_ip: ip });
res.json({name: name, public_ip: ip});
});
//
// Room server
//
server = http.createServer(app);
io = socketIo.listen(server);
app.get('/auth', function (req, res) {
var id = uuid.v1(),
token = firebaseTokenGenerator.createToken(
{id: id}, // will be available in Firebase security rules as 'auth'
{expires: 32503680000} // 01.01.3000 00:00
);
io.sockets.on('connection', function (client) {
// When a peer joins a room, send back list of other peers already there
client.on('join', function (data) {
var room = data.room,
peer = data.peer;
console.log('#join data: ', data);
client.peer = peer;
var clients = io.sockets.clients(room),
peers = clients.map(function (client) {return client.peer;});
// Send back list of other peers in the room
client.emit('user_list', peers);
// Join the room
client.join(room);
// Notify other peers that a new peer has joined the room
client.broadcast.to(room).emit('user_added', client.peer);
// Notify other peers when a peer leaves the room
client.on('disconnect', function () {
client.broadcast.to(room).emit('user_removed', client.peer);
});
console.log('#join peers already in the room: ', peers);
});
client.on('update', function (data) {
var room = data.room,
peer = data.peer;
extend(client.peer, peer);
client.broadcast.to(room).emit('user_changed', client.peer);
});
res.json({id: id, token: token});
});
return server;
return http.createServer(app);
};

21
newrelic.js Normal file
View File

@ -0,0 +1,21 @@
/**
* New Relic agent configuration.
*
* See lib/config.defaults.js in the agent distribution for a more complete
* description of configuration variables and their potential values.
*/
exports.config = {
/**
* Array of application names.
*/
app_name : ['ShareDrop'],
logging : {
/**
* Level at which to log. 'trace' is most useful to New Relic when diagnosing
* issues with the agent, 'info' and higher will impose the least overhead on
* production applications.
*/
level : 'info'
}
};

View File

@ -1,19 +1,24 @@
{
"name": "ShareDrop",
"version": "0.0.1",
"version": "1.0.0",
"description": "P2P file sharing",
"main": "index.js",
"repository" : {
"type" : "git",
"url" : "https://github.com/cowbell/sharedrop.git"
},
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Szymon Nowak",
"license": "MIT",
"dependencies": {
"socket.io": "~0.9.16",
"express": "~3.4.7",
"express-persona": "~0.1.1",
"node-uuid": "~1.4.1",
"deep-extend": "~0.2.6",
"firebase-token-generator": "^0.1.4",
"newrelic": "^1.4.0",
"grunt": "~0.4.2",
"grunt-cli": "~0.1.9",
"load-grunt-tasks": "~0.2.1",

View File

@ -1,19 +0,0 @@
# V1
- persona + email update
- fix broadcasting emails on page load
- ensure that socket.io has the most recent version with emails
- tooltips
# V2 (needs changes to PeerJS library)
- serialize files asynchronously
- show progress bar for sender and recipient
# V3:
- set email from Persona on server side to avoid faking it
- send multiple files (one after another)
- allow to get room name from URL (provided or generated) for wan connections; don't get public IP in this case
# Firebase (?)
- use Firebase instead of socket.io for room server
- initial vs new peers https://groups.google.com/forum/#!topic/firebase-talk/iZ3eLYAZBkU
- less hosting issues, but only max 50 connections on free plan...