diff --git a/.gitignore b/.gitignore index 1f69570..3a1b68d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ node_modules -dist bower_components .tmp .publish/ diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..11da079 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,23 @@ +'use strict'; +var express = require('express'); +var compression = require('compression'); +var app = express(); +var http = require('http'); +var ExpressPeerServer = require('peer').ExpressPeerServer; +var wsServer = require('./server/ws-server.js'); + +var server = http.createServer(app); + +// Serve up content from public directory +app.use(compression()); +app.use(express.static(__dirname + '/public')); + +var port = process.env.PORT || 3002; +server.listen(port); +wsServer.create(server); +app.use('/peerjs', ExpressPeerServer(server, { + debug: true +})); + + +console.log('listening on port ' + port); diff --git a/dist/package.json b/dist/package.json new file mode 100644 index 0000000..f6c84c6 --- /dev/null +++ b/dist/package.json @@ -0,0 +1,14 @@ +{ + "private": true, + "engines": { + "node": ">=0.10.0" + }, + "dependencies": { + "binaryjs": "^0.2.1", + "compression": "^1.6.0", + "express": "^4.13.3", + "peer": "^0.2.8", + "ua-parser-js": "^0.7.10", + "ws": "^1.1.1" + } +} diff --git a/dist/public/elements/elements.html b/dist/public/elements/elements.html new file mode 100644 index 0000000..a37e388 --- /dev/null +++ b/dist/public/elements/elements.html @@ -0,0 +1,22671 @@ + + + + + + \ No newline at end of file diff --git a/dist/public/index.html b/dist/public/index.html new file mode 100644 index 0000000..3fc7401 --- /dev/null +++ b/dist/public/index.html @@ -0,0 +1,18 @@ + +Snapdrop + diff --git a/dist/public/manifest.json b/dist/public/manifest.json new file mode 100644 index 0000000..05ab83b --- /dev/null +++ b/dist/public/manifest.json @@ -0,0 +1,29 @@ +{ + "name": "Snapdrop", + "short_name": "Snapdrop", + "icons": [{ + "src": "images/touch/icon-128x128.png", + "sizes": "128x128", + "type": "image/png" + }, { + "src": "images/touch/apple-touch-icon.png", + "sizes": "152x152", + "type": "image/png" + }, { + "src": "images/touch/ms-touch-icon-144x144-precomposed.png", + "sizes": "144x144", + "type": "image/png" + }, { + "src": "images/touch/chrome-touch-icon-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, { + "src": "images/touch/chrome-splashscreen-icon-384x384.png", + "sizes": "384x384", + "type": "image/png" + }], + "background_color": "#3367d6", + "start_url": "index.html", + "display": "standalone", + "theme_color": "#3367d6" +} diff --git a/dist/public/scripts/app.js b/dist/public/scripts/app.js new file mode 100644 index 0000000..27ba397 --- /dev/null +++ b/dist/public/scripts/app.js @@ -0,0 +1 @@ +!function(e){"use strict";var o=e.querySelector("#app");o.baseUrl="/",""===window.location.port,o.displayInstalledToast=function(){Polymer.dom(e).querySelector("platinum-sw-cache").disabled||Polymer.dom(e).querySelector("#caching-complete").show()},o.displayToast=function(o){var t=Polymer.dom(e).querySelector("#toast");t.text=o,t.show()},o.addEventListener("dom-change",function(){console.log("Our app is ready to rock!"),o.conn=e.querySelector("connection-wrapper")}),window.addEventListener("WebComponentsReady",function(){}),o._showAbout=function(){e.querySelector("#pages").select(1)},o._showAbout=function(){e.querySelector("#pages").select(0)}}(document); \ No newline at end of file diff --git a/dist/public/sounds/blop.mp3 b/dist/public/sounds/blop.mp3 new file mode 100755 index 0000000..28a6244 Binary files /dev/null and b/dist/public/sounds/blop.mp3 differ diff --git a/dist/public/sounds/blop.ogg b/dist/public/sounds/blop.ogg new file mode 100644 index 0000000..d1ce0c2 Binary files /dev/null and b/dist/public/sounds/blop.ogg differ diff --git a/dist/public/styles/main.css b/dist/public/styles/main.css new file mode 100644 index 0000000..6e71009 --- /dev/null +++ b/dist/public/styles/main.css @@ -0,0 +1 @@ +body,html{height:100%;width:100%;padding:0;margin:0}body{background:#fafafa;font-family:Roboto,'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333;-webkit-font-smoothing:antialiased;overflow-x:hidden}#ads,#ads2{display:none}@media screen and (min-width:520px){#ads{display:block;position:absolute;top:8px;left:50%;margin-left:-150px}}@media screen and (min-width:720px){#ads{display:none}#ads2{display:block;position:absolute;bottom:4px;left:4px}} \ No newline at end of file diff --git a/dist/readme.md b/dist/readme.md new file mode 100644 index 0000000..6c01c5f --- /dev/null +++ b/dist/readme.md @@ -0,0 +1,5 @@ +# Run a Snapdrop Server +- `npm install` +- `node index.js` +- TODO: SSL connection (i.e nginx) + - ( Please do a PR if you've build an alternative index.js with a self-signed cert ) \ No newline at end of file diff --git a/dist/server/ws-server.js b/dist/server/ws-server.js new file mode 100644 index 0000000..c067053 --- /dev/null +++ b/dist/server/ws-server.js @@ -0,0 +1,154 @@ +'use strict'; +var parser = require('ua-parser-js'); + +// Start Binary.js server +var BinaryServer = require('binaryjs').BinaryServer; + +exports.create = function(server) { + + // link it to express + var bs = BinaryServer({ + server: server, + path: '/binary' + }); + + function guid() { + function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + } + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + + s4() + '-' + s4() + s4() + s4(); + } + + function getDeviceName(req) { + var ua = parser(req.headers['user-agent']); + return { + model: ua.device.model, + os: ua.os.name, + browser: ua.browser.name, + type: ua.device.type + }; + } + + function hash(text) { + // A string hashing function based on Daniel J. Bernstein's popular 'times 33' hash algorithm. + var h = 5381, + index = text.length; + while (index) { + h = (h * 33) ^ text.charCodeAt(--index); + } + return h >>> 0; + } + + function getIP(socket) { + return socket.upgradeReq.headers['x-forwarded-for'] || socket.upgradeReq.connection.remoteAddress; + } + // Wait for new user connections + bs.on('connection', function(client) { + + client.uuidRaw = guid(); + //ip is hashed to prevent injections by spoofing the 'x-forwarded-for' header + // client.hashedIp = 1; //use this to test locally + client.hashedIp = hash(getIP(client._socket)); + + client.deviceName = getDeviceName(client._socket.upgradeReq); + + // Incoming stream from browsers + client.on('stream', function(stream, meta) { + if (meta && meta.serverMsg === 'rtc-support') { + client.uuid = (meta.rtc ? 'rtc_' : '') + client.uuidRaw; + client.send({ + isSystemEvent: true, + type: 'handshake', + name: client.deviceName, + uuid: client.uuid + }); + return; + } + if (meta && meta.serverMsg === 'device-name') { + //max name length = 40 + if (meta.name && meta.name.length > 40) { + return; + } + client.name = meta.name; + return; + } + + meta.from = client.uuid; + + // broadcast to the other client + for (var id in bs.clients) { + if (bs.clients.hasOwnProperty(id)) { + var otherClient = bs.clients[id]; + if (otherClient !== client && meta.toPeer === otherClient.uuid) { + var send = otherClient.createStream(meta); + stream.pipe(send, meta); + } + } + } + }); + }); + + function forEachClient(fn) { + for (var id in bs.clients) { + if (bs.clients.hasOwnProperty(id)) { + var client = bs.clients[id]; + fn(client); + } + } + } + + + + + + function notifyBuddies() { + var locations = {}; + //group all clients by location (by public ip address) + forEachClient(function(client) { + var ip = client.hashedIp; + locations[ip] = locations[ip] || []; + locations[ip].push({ + socket: client, + contact: { + peerId: client.uuid, + name: client.name || client.deviceName, + device: client.name ? client.deviceName : undefined + } + }); + }); + //notify every location + Object.keys(locations).forEach(function(locationKey) { + //notify every client of all other clients in this location + var location = locations[locationKey]; + location.forEach(function(client) { + //all other clients + var buddies = location.reduce(function(result, otherClient) { + if (otherClient !== client) { + result.push(otherClient.contact); + } + return result; + }, []); + var currState = hash(JSON.stringify(buddies)); + console.log(currState); + var socket = client.socket; + //protocol + var msg = { + buddies: buddies, + isSystemEvent: true, + type: 'buddies' + }; + //send only if state changed + if (currState !== socket.lastState) { + socket.send(msg); + socket.lastState = currState; + return; + } + }); + }); + } + + setInterval(notifyBuddies, 3000); +};