first working version

This commit is contained in:
Robin Linus 2015-12-26 13:33:16 +01:00
parent f1ad168e40
commit bda1a15750
24 changed files with 543 additions and 206 deletions

View file

@ -1,5 +1,5 @@
![](https://cloud.githubusercontent.com/assets/110953/7877439/6a69d03e-0590-11e5-9fac-c614246606de.png)
## Share With Me
## Snapdrop
> A starting point for building web applications with Polymer 1.0
@ -14,11 +14,11 @@
* [Recipes](/docs/README.md/) for ES2015 support, Polymer performance, using Chrome Dev Editor, Deploying to GitHub Pages, Deploying to Firebase, and Mobile Chrome Apps
### Demo
See latest Share With Me Demo (from master) at http://polymerelements.github.io/polymer-starter-kit
See latest Snapdrop Demo (from master) at http://polymerelements.github.io/polymer-starter-kit
### Tutorials
Check out the Share With Me tutorials on [polymer-project.org](https://polymer-project.org):
Check out the Snapdrop tutorials on [polymer-project.org](https://polymer-project.org):
* [Set up the PSK](https://www.polymer-project.org/1.0/docs/start/psk/set-up.html)
* [Create a page](https://www.polymer-project.org/1.0/docs/start/psk/create-a-page.html)
@ -26,7 +26,7 @@ Check out the Share With Me tutorials on [polymer-project.org](https://polymer-p
## Getting Started
To take advantage of Share With Me you need to:
To take advantage of Snapdrop you need to:
1. Get a copy of the code.
2. Install the dependencies if you don't already have them.
@ -35,21 +35,21 @@ To take advantage of Share With Me you need to:
### Get the code
[Download](https://github.com/polymerelements/polymer-starter-kit/releases/latest) and extract Share With Me to where you want to work. The project comes in two flavours - Light and Full.
[Download](https://github.com/polymerelements/polymer-starter-kit/releases/latest) and extract Snapdrop to where you want to work. The project comes in two flavours - Light and Full.
**Beginners**: Try Share With Me Light. This doesn't require any extra dependencies nor knowledge of modern front-end tooling. This option is good for prototyping if you haven't build a Polymer app before.
**Beginners**: Try Snapdrop Light. This doesn't require any extra dependencies nor knowledge of modern front-end tooling. This option is good for prototyping if you haven't build a Polymer app before.
**Intermediate - Advanced**: Use the full version of Share With Me. This comes with all the build tools you'll need for testing and productionising your app so it's nice and lean. You'll need to run a few extra commands to install the tools we recommend but it's worth it to make sure your final app is super optimised.
**Intermediate - Advanced**: Use the full version of Snapdrop. This comes with all the build tools you'll need for testing and productionising your app so it's nice and lean. You'll need to run a few extra commands to install the tools we recommend but it's worth it to make sure your final app is super optimised.
:warning: **Important**: Share With Me, and Share With Me Light, both contain dotfiles (files starting with a `.`). If you're copying the contents of the Starter Kit to a new location make sure you bring along these dotfiles as well! On Mac, [enable showing hidden files](http://ianlunn.co.uk/articles/quickly-showhide-hidden-files-mac-os-x-mavericks/), then try extracting/copying Share With Me again. This time the dotfiles needed should be visible so you can copy them over without issues.
:warning: **Important**: Snapdrop, and Snapdrop Light, both contain dotfiles (files starting with a `.`). If you're copying the contents of the Starter Kit to a new location make sure you bring along these dotfiles as well! On Mac, [enable showing hidden files](http://ianlunn.co.uk/articles/quickly-showhide-hidden-files-mac-os-x-mavericks/), then try extracting/copying Snapdrop again. This time the dotfiles needed should be visible so you can copy them over without issues.
Rob Dodson has a fantastic [PolyCast video](https://www.youtube.com/watch?v=xz-yixRxZN8) available that walks through using Share With Me. An [end-to-end with Polymer](https://www.youtube.com/watch?v=1f_Tj_JnStA) and Share With Me talk is also available.
Rob Dodson has a fantastic [PolyCast video](https://www.youtube.com/watch?v=xz-yixRxZN8) available that walks through using Snapdrop. An [end-to-end with Polymer](https://www.youtube.com/watch?v=1f_Tj_JnStA) and Snapdrop talk is also available.
### Install dependencies
#### Quick-start (for experienced users)
With Node.js installed, run the following one liner from the root of your Share With Me download:
With Node.js installed, run the following one liner from the root of your Snapdrop download:
```sh
npm install -g gulp bower && npm install && bower install
@ -154,7 +154,7 @@ These style files are located in the [styles folder](app/styles/).
## Unit Testing
Web apps built with Share With Me come configured with support for [Web Component Tester](https://github.com/Polymer/web-component-tester) - Polymer's preferred tool for authoring and running unit tests. This makes testing your element based applications a pleasant experience.
Web apps built with Snapdrop come configured with support for [Web Component Tester](https://github.com/Polymer/web-component-tester) - Polymer's preferred tool for authoring and running unit tests. This makes testing your element based applications a pleasant experience.
[Read more](https://github.com/Polymer/web-component-tester#html-suites) about using Web Component tester.
@ -181,13 +181,13 @@ Components installed by Bower live in the `app/bower_components` directory. This
## Service Worker
Share With Me offers an optional offline experience thanks to Service Worker and the [Platinum Service Worker elements](https://github.com/PolymerElements/platinum-sw). New to Service Worker? Read the following [introduction](http://www.html5rocks.com/en/tutorials/service-worker/introduction/) to understand how it works.
Snapdrop offers an optional offline experience thanks to Service Worker and the [Platinum Service Worker elements](https://github.com/PolymerElements/platinum-sw). New to Service Worker? Read the following [introduction](http://www.html5rocks.com/en/tutorials/service-worker/introduction/) to understand how it works.
Our optional offline setup should work well for relatively simple applications. For more complex apps, we recommend learning how Service Worker works so that you can make the most of the Platinum Service Worker element abstractions.
### Enable Service Worker support?
To enable Service Worker support for Share With Me project use these 3 steps:
To enable Service Worker support for Snapdrop project use these 3 steps:
1. Uncomment Service Worker code in index.html
```HTML
@ -272,16 +272,16 @@ If you find anything to still be stale, you can also try navigating to `chrome:s
#### Disable Service Worker support after you enabled it
If for any reason you need to disable Service Worker support after previously enabling it, you can remove it from your Share With Me project using these 4 steps:
If for any reason you need to disable Service Worker support after previously enabling it, you can remove it from your Snapdrop project using these 4 steps:
1. Remove references to the platinum-sw elements from your application [index](https://github.com/PolymerElements/polymer-starter-kit/blob/master/app/index.html).
2. Remove the two Platinum Service Worker elements (platinum-sw/..) in [app/elements/elements.html](https://github.com/PolymerElements/polymer-starter-kit/blob/master/app/elements/elements.html)
3. Remove 'precache' from the list in the 'default' gulp task ([gulpfile.js](https://github.com/PolymerElements/polymer-starter-kit/blob/master/gulpfile.js))
4. Navigate to `chrome://serviceworker-internals` and unregister any Service Workers registered by Share With Me for your app just in case there's a copy of it cached.
4. Navigate to `chrome://serviceworker-internals` and unregister any Service Workers registered by Snapdrop for your app just in case there's a copy of it cached.
## Yeoman support
[generator-polymer](https://github.com/yeoman/generator-polymer/releases) now includes support for Share With Me out of the box.
[generator-polymer](https://github.com/yeoman/generator-polymer/releases) now includes support for Snapdrop out of the box.
## Frequently Asked Questions
@ -308,7 +308,7 @@ own local setup.
### Where can I find the application layouts from your Google I/O 2015 talk?
App layouts live in a separate repository called [app-layout-templates](https://github.com/PolymerElements/app-layout-templates).
You can select a template and copy over the relevant parts you would like to reuse to Share With Me.
You can select a template and copy over the relevant parts you would like to reuse to Snapdrop.
You will probably need to change paths to where your Iron and Paper dependencies can be found to get everything working.
This can be done by adding them to the [`elements.html`](https://github.com/PolymerElements/polymer-starter-kit/blob/master/app/elements/elements.html) import.
@ -366,14 +366,14 @@ If you are not using the build-blocks, but still wish for additional files (e.g
### I'm finding the installation/tooling here overwhelming. What should I do?
Don't worry! We've got your covered. Share With Me tries to offer everything you need to build and optimize your apps for production, which is why we include the tooling we do. We realise however that our tooling setup may not be for everyone.
Don't worry! We've got your covered. Snapdrop tries to offer everything you need to build and optimize your apps for production, which is why we include the tooling we do. We realise however that our tooling setup may not be for everyone.
If you find that you just want the simplest setup possible, we recommend using Share With Me light, which is available from the [Releases](https://github.com/PolymerElements/polymer-starter-kit/releases) page. This takes next to no time to setup.
If you find that you just want the simplest setup possible, we recommend using Snapdrop light, which is available from the [Releases](https://github.com/PolymerElements/polymer-starter-kit/releases) page. This takes next to no time to setup.
## Licensing
Like other Google projects, Share With Me includes Google license headers at the top of several of our source files. Google's open-source licensing requires that this header be kept in place (sorry!), however we acknowledge that you may need to add your own licensing to files you modify. This can be done by appending your own extensions to these headers.
Like other Google projects, Snapdrop includes Google license headers at the top of several of our source files. Google's open-source licensing requires that this header be kept in place (sorry!), however we acknowledge that you may need to add your own licensing to files you modify. This can be done by appending your own extensions to these headers.
## Contributing
Share With Me is a new project and is an ongoing effort by the Web Component community. We welcome your bug reports, PRs for improvements, docs and anything you think would improve the experience for other Polymer developers.
Snapdrop is a new project and is an ongoing effort by the Web Component community. We welcome your bug reports, PRs for improvements, docs and anything you think would improve the experience for other Polymer developers.

View file

@ -52,12 +52,10 @@
</file-input>
</template>
</div>
<div hidden$="{{buddies.length}}" class="explanation">
Open this page on another device
<wbr>to share files.
<div hidden$="{{buddies.0}}" class="explanation">
Open this page on other devices<br> to send files.
</div>
<personal-avatar class="me"></personal-avatar>
<!-- <iron-ajax id="ajax" auto url="https://yawim.com/findbuddies/{{me}}" handle-as="json" last-response="{{buddies}}"></iron-ajax> -->
</template>
<script>
'use strict';
@ -66,30 +64,11 @@
properties: {
buddies: {
type: Array,
value: []
notify: true
},
me: {
type: String,
}
},
attached: function() {
//Ask server every second for changes
var ajax = this.$.ajax;
function request() {
//ajax.generateRequest();
}
var intervalId = setInterval(request, 1000);
document.addEventListener('visibilitychange', function() {
if (document.hidden) {
clearInterval(intervalId);
intervalId = 0;
} else {
if (!intervalId) {
intervalId = setInterval(request, 1000);
}
}
});
},
},
_fileSelected: function(e) {
var peerId = e.model.item.peerId;

View file

@ -13,11 +13,11 @@
width: 80px;
height: 80px;
color: #4285f4;
margin-bottom: 6px;
}
.paper-font-body1 {
font-size: 13px;
margin-top: 6px;
}
.discover {
@ -26,7 +26,7 @@
</style>
<iron-icon icon="chat:wifi-tethering"></iron-icon>
<div class="paper-font-body1">
SnapDrop lets you share instantly with people near by.
Snapdrop lets you share instantly with people near by.
</div>
<div class="paper-font-body1 discover">
Allow me to be discovered by: Everyone in this network.

View file

@ -7,7 +7,7 @@
@apply(--layout-vertical);
@apply(--layout-center);
width: 120px;
height: 120px;
height: 152px;
}
paper-icon-button {
@ -45,7 +45,7 @@
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
margin: 4px;
margin-top: 4px;
}
</style>
<paper-icon-button icon="{{_displayIcon}}"></paper-icon-button>
@ -85,7 +85,6 @@
if (contact.type === 'tablet') {
return 'chat:tablet-mac';
}
return 'chat:desktop-mac';
},
attached: function() {

View file

@ -2,14 +2,14 @@
<link rel="import" href="../bower_components/platinum-sw/platinum-sw-register.html">
<link rel="import" href="../bower_components/paper-toast/paper-toast.html">
<link rel="import" href="../bower_components/paper-progress/paper-progress.html">
<link rel="import" href="../bower_components/neon-animation/neon-animated-pages.html">
<!-- Configure your routes here
<link rel="import" href="routing.html">
-->
<!-- Add your elements here -->
<link rel="import" href="../styles/app-theme.html">
<link rel="import" href="x-cards/x-card.html">
<link rel="import" href="x-cards/x-cards.html">
<link rel="import" href="buddy-finder/buddy-finder.html">
<link rel="import" href="p2p-network/connection-wrapper.html">
<link rel="import" href="file-sharing/file-receiver.html">

View file

@ -17,13 +17,14 @@
z-index: 101;
}
b {
.filename {
word-break: break-all;
word-break: break-word;
}
</style>
<paper-dialog id="dialog" entry-animation="scale-up-animation" exit-animation="fade-out-animation" with-backdrop modal>
<h2>Download File</h2>
<p><b>{{file.name}}</b></p>
<p><b class="filename">{{file.name}}</b></p>
<div class="buttons">
<paper-button dialog-dismiss on-tap="_decline">Discard</paper-button>
<paper-button dialog-confirm on-tap="_accept" autofocus>Download</paper-button>
@ -31,11 +32,11 @@
</paper-dialog>
<paper-dialog id="download" entry-animation="scale-up-animation" exit-animation="fade-out-animation" with-backdrop modal>
<h2>File Received</h2>
<p>Right Click and "Save as"...</p>
<p>Open File or Right Click and "Save as"...</p>
<div class="buttons">
<paper-button dialog-dismiss>Discard</paper-button>
<a href="{{dataUri}}" target="_blank">
<paper-button dialog-confirm autofocus>Download</paper-button>
<paper-button dialog-confirm autofocus>Open File</paper-button>
</a>
</div>
</paper-dialog>

File diff suppressed because one or more lines are too long

View file

@ -7,8 +7,11 @@ Chat.FileSelectionBehavior = {
console.log('no files selected...');
return;
}
for (var i = 0; i < files.length; i++) {
var file = files[i];
this._fileSelected(files[0]); //single select
//files.forEach(this._fileSelected.bind(this)); //multi-select
},
_fileSelected: function(file) {
if (file) {
this.fire('file-selected', {
file: file,
name: file.name

View file

@ -8,27 +8,17 @@
<script>
'use strict';
(function() {
function guid() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4();
}
var webRTCSupported = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection || window.webkitRTCPeerConnection;
window.webRTCSupported = !!(window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection || window.webkitRTCPeerConnection);
function rtcConnectionSupported(peerId) {
return webRTCSupported && (peerId.indexOf('rtc_') === 0);
return window.webRTCSupported && (peerId.indexOf('rtc_') === 0);
}
Polymer({
is: 'connection-wrapper',
properties: {
me: {
notify: true,
value: (webRTCSupported ? 'rtc_' : 'ws_') + guid()
}
notify: true
},
},
behaviors: [Chat.FileTransferProtocol],
_sendFile: function(toPeer, file) {
@ -50,9 +40,17 @@
if (!rtcConnectionSupported(toPeer)) {
callback();
} else {
this.$.p2p.connectToPeer(toPeer,callback);
this.$.p2p.connectToPeer(toPeer, callback);
}
},
_onHandshake: function(event) {
var me = event.uuid;
console.log('i am');
this.set('me', me);
if (window.webRTCSupported) {
this.$.p2p.initialize();
}
}
});
})();
</script>

View file

@ -22,6 +22,9 @@ Chat.FileTransferProtocol = {
console.log('FTP received sysMsg:', msg);
switch (msg.type) {
case 'handshake':
this._onHandshake(msg);
break;
case 'offer':
this._onOffered(msg);
break;

View file

@ -9,8 +9,7 @@
is: 'p2p-network',
properties: {
me: {
type: String,
notify: true,
type: String
}
},
attached: function() {
@ -22,16 +21,24 @@
this._peer.destroy();
}
}.bind(this);
this._initialize();
},
_initialize: function() {
var options = {
host: 'yawim.com',
port: 443,
path: 'peerjs',
secure: true
};
this._peer = new Peer(this.me,options);
initialize: function() {
var options;
if (window.debug) {
options = {
host: window.location.hostname,
port: 3002,
path: 'peerjs'
};
} else {
options = {
host: 'snapdrop.net',
port: 443,
path: 'peerjs',
secure: true
};
}
this._peer = new Peer(this.me, options);
this._peer.on('open', function(id) {
console.log('My peer ID is: ' + id);
this.set('me', id);
@ -53,12 +60,10 @@
if (err.message.indexOf('Lost connection to server') > -1) {
this._peer.destroy();
this.set('me', this.me);
this._initialize();
this.async(this._initialize, 3000);
return;
}
}.bind(this));
},
connect: function(c) {
@ -140,7 +145,7 @@
conns.forEach(function(conn) {
if (conn.label === 'file') {
conn.send(file);
console.log('file send');
console.log('file send via WebRTC');
}
}.bind(this));
}

View file

@ -15,13 +15,13 @@
this.init();
},
init: function() {
var websocketUrl = (window.location.protocol === 'https:' ? 'wss://' : 'ws://') + document.location.hostname + ':9001';
var websocketUrl = (window.debug ? 'ws://' + window.location.hostname + ':3002' : 'wss://snapdrop.net') + '/binary';
this.client = new BinaryClient(websocketUrl);
this.client.on('stream', function(stream, meta) {
// collect stream data
var parts = [];
stream.on('data', function(data) {
console.log('part received', meta, data);
//console.log('part received', meta, data);
if (data.isSystemEvent) {
if (meta) {
data.from = meta.from;
@ -45,22 +45,22 @@
}.bind(this));
}.bind(this));
this.client.on('open', function(e) {
this.cancelAsync(this.reconnectTimer);
console.log(e);
this.client.send({}, {
handshake: this.me
serverMsg: 'rtc-support',
rtc: window.webRTCSupported
});
}.bind(this));
this.client.on('error', function(e) {
console.log(e);
});
this._reconnect(e);
}.bind(this));
this.client.on('close', function(e) {
console.log(e);
//try to reconnect after 3s
this.async(this.init, 3000);
this._reconnect(e);
}.bind(this));
},
_sendFile: function(toPeer, file) {
console.log('send file!', file);
console.log('send file via WebSocket', file);
this.client.send(file.file, {
name: file.file.name,
type: file.file.type,
@ -76,6 +76,11 @@
this.client.send(event, {
toPeer: toPeer
});
},
_reconnect: function(e) {
console.log('disconnected', e);
//try to reconnect after 3s
this.reconnectTimer = this.async(this.init, 3000);
}
});
</script>

View file

@ -0,0 +1,138 @@
<link rel="import" href="../../bower_components/iron-flex-layout/iron-flex-layout.html">
<link rel="import" href="../../bower_components/neon-animation/neon-shared-element-animatable-behavior.html">
<link rel="import" href="../../bower_components/neon-animation/neon-animations.html">
<link rel="import" href="../../bower_components/paper-styles/paper-styles-classes.html">
<link rel="import" href="../../bower_components/iron-icon/iron-icon.html">
<dom-module id="x-card">
<template>
<style>
:host {
display: block;
overflow: hidden;
color: white;
z-index: 3
}
#placeholder {
opacity: 0;
background-color: #4285f4;
@apply(--layout-fit);
}
paper-icon-button {
position: absolute;
top: 16px;
right: 16px;
z-index: 2;
}
#container {
@apply(--layout-fit);
@apply(--layout-vertical);
@apply(--layout-center-center);
background-color: #4285f4;
padding: 64px 32px 64px 32px;
box-sizing: border-box;
}
iron-icon {
width: 80px;
height: 80px;
}
.paper-font-subhead {
text-align: center;
}
a {
text-decoration: none;
color: white;
@apply(--layout-self-end);
}
.center {
@apply(--layout-vertical);
@apply(--layout-center-center);
}
#footer {
position: absolute;
left: 50%;
margin-left: -160px;
width: 320px;
bottom: 24px;
text-align: center;
}
</style>
<paper-icon-button id="btn" icon="chat:close" on-tap="_switch"></paper-icon-button>
<div id="placeholder"></div>
<div id="container">
<div class="center">
<iron-icon icon="chat:wifi-tethering"></iron-icon>
<div class="paper-font-headline">Snapdrop</div>
<div class="paper-font-subhead">The easiest way to send files across devices.</div>
</div>
<span id="footer">Built with &#9829; by <a href="mailto:robin@capira.de">Robin Linus</a></span>
</div>
</template>
</dom-module>
<script>
(function() {
Polymer({
is: 'x-card',
behaviors: [
Polymer.NeonSharedElementAnimatableBehavior
],
properties: {
animationConfig: {
value: function() {
return {
'entry': [{
name: 'ripple-animation',
id: 'ripple',
toPage: this
}, {
name: 'fade-out-animation',
node: this.$.placeholder,
timing: {
delay: 250
}
}, {
name: 'fade-in-animation',
node: this.$.container,
timing: {
delay: 50
}
}],
'exit': [{
name: 'opaque-animation',
node: this.$.placeholder
}, {
name: 'fade-out-animation',
node: this.$.container,
timing: {
duration: 0
}
}, {
name: 'reverse-ripple-animation',
id: 'reverse-ripple',
fromPage: this
}]
};
}
},
sharedElements: {
value: function() {
return {
'ripple': this.$.placeholder,
'reverse-ripple': this.$.placeholder
};
}
}
},
_switch: function() {
document.querySelector('#pages').select(0);
}
});
})();
</script>

View file

@ -0,0 +1,83 @@
<link rel="import" href="../../bower_components/iron-flex-layout/iron-flex-layout.html">
<link rel="import" href="../../bower_components/neon-animation/neon-shared-element-animatable-behavior.html">
<link rel="import" href="../../bower_components/neon-animation/neon-animations.html">
<link rel="import" href="../../bower_components/paper-icon-button/paper-icon-button.html">
<dom-module id="x-cards">
<template>
<style>
:host {
display: block;
overflow: hidden;
}
#placeholder {
opacity: 0;
background-color: grey;
@apply(--layout-fit);
}
paper-icon-button {
position: absolute;
top: 16px;
right: 16px;
z-index: 2;
color: #313131;
}
paper-icon-button:hover {
color: #4285f4;
}
</style>
<div id="placeholder"></div>
<div id="container">
<paper-icon-button id="btn" icon="chat:info-outline" on-tap="_switch"></paper-icon-button>
<content select="div"></content>
</div>
</template>
</dom-module>
<script>
(function() {
Polymer({
is: 'x-cards',
behaviors: [
Polymer.NeonSharedElementAnimatableBehavior
],
properties: {
animationConfig: {
value: function() {
return {
'entry': [{
name: 'reverse-ripple-animation',
id: 'reverse-ripple',
toPage: this
}],
'exit': [{
name: 'fade-out-animation',
node: this.$.container,
timing: {
delay: 150,
duration: 0
}
}, {
name: 'ripple-animation',
id: 'ripple',
fromPage: this
}]
};
}
},
sharedElements: {
value: function() {
return {
'ripple': this.$.btn,
'reverse-ripple': this.$.btn
};
}
}
},
_switch: function() {
document.querySelector('#pages').select(1);
}
});
})();
</script>

View file

@ -3,10 +3,11 @@
<head>
<meta charset="utf-8">
<meta name="description" content="">
<meta name="description" content="Snapdrop lets you instantly share files with people near by. It is a web-based clone of Apple's Airdrop.">
<meta name="viewport" content="initial-scale=1,user-scalable=no,maximum-scale=1">
<meta name="generator" content="SnapDrop!">
<title>SnapDrop!</title>
<meta name="generator" content="Snapdrop">
<title>Snapdrop</title>
<link rel="shortcut icon" href="favicon.ico?v=2" />
<!-- Place favicon.ico in the `app/` directory -->
<!-- Chrome for Android theme color -->
<meta name="theme-color" content="#3367d6">
@ -18,10 +19,12 @@
<meta name="mobile-web-app-capable" content="yes">
<meta name="application-name" content="PSK">
<link rel="icon" sizes="192x192" href="images/touch/chrome-touch-icon-192x192.png">
<link rel="fluid-icon" type="image/png" href="images/touch/chrome-touch-icon-192x192.png">
<meta property="og:image" content="https://snapdrop.net/images/touch/chrome-touch-icon-192x192.png" />
<!-- Add to homescreen for Safari on iOS -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="SnapDrop!">
<meta name="apple-mobile-web-app-title" content="Snapdrop">
<link rel="apple-touch-icon" href="images/touch/apple-touch-icon.png">
<!-- Tile icon for Win8 (144x144) -->
<meta name="msapplication-TileImage" content="images/touch/ms-touch-icon-144x144-precomposed.png">
@ -29,25 +32,34 @@
<link rel="stylesheet" href="styles/main.css">
<!-- endbuild-->
<!-- build:js bower_components/webcomponentsjs/webcomponents-lite.min.js -->
<script src="bower_components/webcomponentsjs/webcomponents-lite.js" async></script>
<script src="bower_components/webcomponentsjs/webcomponents-lite.js" async="1"></script>
<!-- endbuild -->
<!-- Because this project uses vulcanize this should be your only html import
in this file. All other imports should go in elements.html -->
<link rel="import" href="elements/elements.html" async>
<meta name="description" content="SnapDrop lets you instantly share files with people near by. It is a web-based clone of Apple's Airdrop.">
</head>
<body class="fullbleed layout vertical" loading>
<script src="scripts/animated-bg.js" inline></script>
<script>
window.debug = true;
</script>
<span id="browser-sync-binding"></span>
<template is="dom-bind" id="app">
<paper-progress indeterminate hidden$="{{!loading}}"></paper-progress>
<buddy-finder me="{{me}}" active$="{{loading}}" buddies="{{buddies}}"></buddy-finder>
<connection-wrapper me="{{me}}" loading="{{loading}}" buddies="{{buddies}}"></connection-wrapper>
<neon-animated-pages id="pages" selected="0">
<x-cards on-switch="_showAbout">
<div>
<paper-progress indeterminate hidden$="{{!loading}}"></paper-progress>
<buddy-finder me="{{me}}" active$="{{loading}}" buddies="{{buddies}}"></buddy-finder>
</div>
</x-cards>
<x-card on-switch="_showApp">
</x-card>
</neon-animated-pages>
<file-receiver></file-receiver>
<paper-toast id="toast" duration="6000">
</paper-toast>
<!-- Uncomment next block to enable Service Worker support (1/2) -->
<paper-toast id="caching-complete" duration="6000" text="Caching complete! This app will work offline.">
</paper-toast>
<platinum-sw-register auto-register clients-claim skip-waiting base-uri="bower_components/platinum-sw/bootstrap" on-service-worker-installed="displayInstalledToast">
@ -58,6 +70,22 @@
<!-- build:js scripts/app.js -->
<script src="scripts/app.js"></script>
<!-- endbuild-->
<script>
(function(i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r;
i[r] = i[r] || function() {
(i[r].q = i[r].q || []).push(arguments)
}, i[r].l = 1 * new Date();
a = s.createElement(o),
m = s.getElementsByTagName(o)[0];
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m)
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
ga('create', 'UA-71686975-1', 'auto');
ga('send', 'pageview');
</script>
</body>
</html>

View file

@ -1,6 +1,6 @@
{
"name": "SnapDrop",
"short_name": "SnapDrop",
"name": "Snapdrop",
"short_name": "Snapdrop",
"icons": [{
"src": "images/touch/icon-128x128.png",
"sizes": "128x128",

View file

@ -40,6 +40,10 @@
});
app._showAbout=function(){
document.querySelector('#pages').select(1);
};
app._showAbout=function(){
document.querySelector('#pages').select(0);
};
})(document);

View file

@ -28,4 +28,7 @@ paper-progress {
position: absolute;
top: 0;
}
neon-animated-pages{
height: 100%;
}
</style>

View file

@ -32,6 +32,12 @@
<g id="tablet-mac">
<path d="M18.5 0h-14C3.12 0 2 1.12 2 2.5v19C2 22.88 3.12 24 4.5 24h14c1.38 0 2.5-1.12 2.5-2.5v-19C21 1.12 19.88 0 18.5 0zm-7 23c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm7.5-4H4V3h15v16z" />
</g>
<g id="info-outline">
<path d="M11 17h2v-6h-2v6zm1-15C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zM11 9h2V7h-2v2z" />
</g>
<g id="close">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" />
</g>
</defs>
</svg>
</iron-iconset-svg>

27
domains.txt Normal file
View file

@ -0,0 +1,27 @@
syncas.com
nearbyc.com
websynca.com
websyncr.com
syncronr.com
syncronify.com
syncats.com
websyncr.com
geofilebox.com
geomiao.com
geofilenet.com geofile.net
geoshareit.com
geobitbox.com
geodropr.com
localsyncr.com
localfiledrop.com
geobitbin.com
localbitbox.com
geodropx.com
geobytedrop.com
geochatbox.com
geoblinc.com
geosyncit.com
geowebdrop.com
localmeow.com
mybitbox.com
geodropme.com

View file

@ -16,6 +16,9 @@ var packageJson = require('./package.json');
var crypto = require('crypto');
var ensureFiles = require('./tasks/ensure-files.js');
var inlinesource = require('gulp-inline-source');
var proxy = require('proxy-middleware');
var url = require('url');
var minifyHTML = require('gulp-minify-html');
// var ghPages = require('gulp-gh-pages');
@ -190,6 +193,7 @@ gulp.task('vulcanize', function() {
inlineCss: true,
inlineScripts: true
}))
.pipe(minifyHTML({ empty: true }))
.pipe(gulp.dest(dist('elements')))
.pipe($.size({
title: 'vulcanize'
@ -240,6 +244,10 @@ gulp.task('clean', function() {
// Watch files for changes & reload
gulp.task('serve', ['styles', 'elements', 'images'], function() {
var peerjsProxy = url.parse('http://localhost:3002/peerjs');
peerjsProxy.route = '/peerjs';
var websocketProxy = url.parse('http://localhost:3002/binary');
websocketProxy.route = '/binary';
browserSync({
port: 5000,
notify: false,
@ -259,7 +267,7 @@ gulp.task('serve', ['styles', 'elements', 'images'], function() {
// https: true,
server: {
baseDir: ['.tmp', 'app'],
middleware: [historyApiFallback()]
middleware: [proxy(peerjsProxy),proxy(websocketProxy), historyApiFallback()]
}
});

22
index.js Normal file
View file

@ -0,0 +1,22 @@
'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);

View file

@ -1,11 +1,11 @@
{
"private": true,
"devDependencies": {
"browser-sync": "^2.7.7",
"browser-sync": "^2.10.1",
"connect-history-api-fallback": "^1.1.0",
"del": "^2.0.2",
"glob-all": "^3.0.1",
"gulp": "^3.8.5",
"gulp": "^3.9.0",
"gulp-autoprefixer": "^3.1.0",
"gulp-cache": "^0.4.0",
"gulp-changed": "^1.0.0",
@ -19,7 +19,7 @@
"gulp-jshint": "^1.6.3",
"gulp-load-plugins": "^1.1.0",
"gulp-minify-css": "^1.2.1",
"gulp-minify-html": "^1.0.2",
"gulp-minify-html": "^1.0.5",
"gulp-rename": "^1.2.0",
"gulp-replace": "^0.5.4",
"gulp-size": "^2.0.0",
@ -28,8 +28,10 @@
"gulp-vulcanize": "^6.0.0",
"jshint-stylish": "^2.0.0",
"merge-stream": "^1.0.0",
"proxy-middleware": "^0.15.0",
"require-dir": "^0.3.0",
"run-sequence": "^1.0.2",
"url": "^0.11.0",
"vulcanize": ">= 1.4.2",
"web-component-tester": "^4.0.0"
},
@ -43,7 +45,9 @@
},
"dependencies": {
"binaryjs": "^0.2.1",
"compression": "^1.6.0",
"express": "^4.13.3",
"peer": "^0.2.8",
"ua-parser-js": "^0.7.10",
"ws": "^0.8.1"
}

View file

@ -1,106 +1,131 @@
'use strict';
var fs = require('fs');
var parser = require('ua-parser-js');
// Serve client side statically
var express = require('express');
var app = express();
app.use(express.static(__dirname + '/public'));
// var https = require('https');
// var server = https.createServer({
// key: fs.readFileSync('/var/www/sharewithme/ssl/privkey.pem').toString(),
// cert: fs.readFileSync('/var/www/sharewithme/ssl/fullchain.pem').toString()
// }, app);
var http = require('http');
var server = http.createServer(app);
// Start Binary.js server
var BinaryServer = require('binaryjs').BinaryServer;
// link it to express
var bs = BinaryServer({
server: server
});
exports.create = function(server) {
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
};
}
// Wait for new user connections
bs.on('connection', function(client) {
console.log('connection received!');
// link it to express
var bs = BinaryServer({
server: server,
path: '/binary'
});
client.deviceName = getDeviceName(client._socket.upgradeReq);
// Incoming stream from browsers
client.on('stream', function(stream, meta) {
console.log('stream received!', meta);
if (meta.handshake) {
client.uuid = meta.handshake;
return;
function guid() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
meta.from = client.uuid;
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4();
}
// broadcast to all other clients
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 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
};
}
// Wait for new user connections
bs.on('connection', function(client) {
console.log('connection received!', client._socket.upgradeReq.connection.remoteAddress);
client.uuidRaw = guid();
client.deviceName = getDeviceName(client._socket.upgradeReq);
// Incoming stream from browsers
client.on('stream', function(stream, meta) {
console.log('stream received!', meta);
if (meta && meta.serverMsg === 'rtc-support') {
client.uuid = (meta.rtc ? 'rtc_' : '') + client.uuidRaw;
client.send({
isSystemEvent: true,
type: 'handshake',
uuid: client.uuid
});
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 forEachClient(fn) {
for (var id in bs.clients) {
if (bs.clients.hasOwnProperty(id)) {
var client = bs.clients[id];
fn(client);
}
}
}
}
function getIP(socket) {
return socket.upgradeReq.headers['x-forwarded-for'] || socket.upgradeReq.connection.remoteAddress;
}
function getIP(socket) {
return socket.upgradeReq.headers['x-forwarded-for'] || socket.upgradeReq.connection.remoteAddress;
}
function notifyBuddies() {
//TODO: This should be possible in linear time
forEachClient(function(client1) {
var buddies = [];
var myIP = getIP(client1._socket);
forEachClient(function(client2) {
var otherIP = getIP(client2._socket);
console.log(myIP, otherIP);
if (client1 !== client2 && myIP === otherIP) {
buddies.push({
peerId: client2.uuid,
name: client2.deviceName
});
}
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 notifyBuddiesX() {
var locations = {};
//group all clients by location (by public ip address)
forEachClient(function(client) {
//ip is hashed to prevent injections by spoofing the 'x-forwarded-for' header
var ip = hash(getIP(client._socket));
locations[ip] = locations[ip] || [];
locations[ip].push({
socket: client,
contact: {
peerId: client.uuid,
name: client.deviceName,
}
});
});
var msg = {
buddies: buddies,
isSystemEvent: true,
type: 'buddies'
};
client1.send(msg);
});
}
setInterval(notifyBuddies, 4000);
//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;
}, []);
//protocol
var msg = {
buddies: buddies,
isSystemEvent: true,
type: 'buddies'
};
client.socket.send(msg);
});
});
}
server.listen(9001);
console.log('HTTP and BinaryJS server started on port 9001');
setInterval(notifyBuddiesX, 5000);
};