commit 22be7c5cb9237a81e1f6d7d9f72592d7e0548507
parent e5eab64c6b8770e4b82d20469595b24c251a165d
Author: Robin Linus <robin_woll@capira.de>
Date: Wed, 23 Dec 2015 13:57:13 +0100
Lots of small improvements, websockets fallback
Diffstat:
35 files changed, 2681 insertions(+), 923 deletions(-)
diff --git a/app/elements/buddy-finder/buddy-finder.html b/app/elements/buddy-finder/buddy-finder.html
@@ -1,97 +1,100 @@
-<link rel="import" href="../../../bower_components/iron-ajax/iron-ajax.html">
-<link rel="import" href="../../../bower_components/paper-styles/paper-styles.html">
+<link rel="import" href="../../bower_components/iron-ajax/iron-ajax.html">
+<link rel="import" href="../../bower_components/paper-styles/paper-styles.html">
<link rel="import" href="../file-sharing/file-input.html">
<link rel="import" href="user-avatar.html">
+<link rel="import" href="personal-avatar.html">
<dom-module id="buddy-finder">
<template>
<style>
:host {
- display: block;
- background-color: white;
+ background-color: transparent;
@apply(--layout-fit);
@apply(--layout-horizontal);
@apply(--layout-center-center);
overflow: hidden;
- }
-
- .paper-font-display1 {
- color: black;
- text-align: center;
- margin-bottom: 16px;
- display: none;
+ position: relative;
+ height: 100%;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ margin: 0;
}
.buddies {
z-index: 1;
@apply(--layout-horizontal);
+ @apply(--layout-center-center);
+ @apply(--layout-wrap);
}
.buddy {
cursor: pointer;
}
- .circles {
+ .me {
position: absolute;
- bottom: -50px;
+ bottom: 24px;
left: 50%;
- width: 1140px;
- margin-left: -570px;
- height: 700px;
- transform-origin: 570px 570px;
- animation: grow 1.5s ease-out;
- fill: transparent;
+ margin-left: -180px;
}
- .me {
- position: absolute;
- bottom: 30px;
- left: 50%;
- margin-left: -60px;
+ .explanation {
+ @apply(--paper-font-headline);
+ color: #4285f4;
+ text-align: center;
}
</style>
- <div class="paper-font-display1">People near by</div>
<div class="buddies">
<template is="dom-repeat" items="{{buddies}}">
- <file-input on-file-selected="_fileDropped">
- <user-avatar contact="{{item.peerId}}" class="buddy"></user-avatar>
+ <file-input on-file-selected="_fileSelected">
+ <user-avatar contact="{{item}}" class="buddy"></user-avatar>
</file-input>
</template>
</div>
- <user-avatar contact="{{me}}" class="me"></user-avatar>
- <svg class="circles" viewBox="-0.5 -0.5 1140 700">
- <circle class="circle" cx="570" cy="570" r="120" stroke="rgba(160,160,160,.15)"></circle>
- <circle class="circle" cx="570" cy="570" r="210" stroke="rgba(160,160,160,.2)"></circle>
- <circle class="circle" cx="570" cy="570" r="300" stroke="rgba(160,160,160,.3)"></circle>
- <circle class="circle" cx="570" cy="570" r="390" stroke="rgba(160,160,160,.35)"></circle>
- <circle class="circle" cx="570" cy="570" r="480" stroke="rgba(160,160,160,.4)"></circle>
- <circle class="circle" cx="570" cy="570" r="570" stroke="rgba(160,160,160,.43)"></circle>
- </svg>
- <iron-ajax id="ajax" auto url="https://yawim.com/findbuddies/{{me}}" handle-as="json" last-response="{{buddies}}"></iron-ajax>
+ <div hidden$="{{buddies.length}}" class="explanation">
+ Open this page on another device
+ <wbr>to share 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';
Polymer({
is: 'buddy-finder',
properties: {
- buddies: Array,
+ buddies: {
+ type: Array,
+ value: []
+ },
me: {
type: String,
}
},
attached: function() {
//Ask server every second for changes
- setInterval(function() {
- this.$.ajax.generateRequest();
- }.bind(this), 1000);
+ 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);
+ }
+ }
+ });
},
- _fileDropped: function(e) {
+ _fileSelected: function(e) {
var peerId = e.model.item.peerId;
var file = e.detail;
- app.p2p.connectToPeer(peerId, function() {
- app.p2p.sendFile(peerId, file);
- });
- console.log('Send:', file);
- console.log('To:', peerId);
+ app.p2p.sendFile(peerId, file);
}
});
</script>
diff --git a/app/elements/buddy-finder/personal-avatar.html b/app/elements/buddy-finder/personal-avatar.html
@@ -0,0 +1,41 @@
+<link rel="import" href="../../bower_components/iron-icon/iron-icon.html">
+<link rel="import" href="../../styles/icons.html">
+<dom-module id="personal-avatar">
+ <template>
+ <style>
+ :host {
+ @apply(--layout-vertical);
+ @apply(--layout-center);
+ width: 360px;
+ }
+
+ iron-icon {
+ width: 80px;
+ height: 80px;
+ color: #4285f4;
+ }
+
+ .paper-font-body1 {
+ font-size: 13px;
+ margin-top: 6px;
+ }
+
+ .discover {
+ color: #4285f4;
+ }
+ </style>
+ <iron-icon icon="chat:wifi-tethering"></iron-icon>
+ <div class="paper-font-body1">
+ 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.
+ </div>
+ </template>
+ <script>
+ 'use strict';
+ Polymer({
+ is: 'personal-avatar'
+ });
+ </script>
+</dom-module>
diff --git a/app/elements/buddy-finder/user-avatar.html b/app/elements/buddy-finder/user-avatar.html
@@ -1,4 +1,4 @@
-<link rel="import" href="../contact-item/anonymous-contact-behavior.html">
+<link rel="import" href="../../bower_components/paper-icon-button/paper-icon-button.html">
<dom-module id="user-avatar">
<template>
<style>
@@ -9,35 +9,121 @@
width: 120px;
height: 120px;
}
-
- .avatar {
+
+ paper-icon-button {
display: inline-block;
- width: 52px;
- height: 52px;
+ width: 64px !important;
+ height: 64px !important;
border-radius: 50%;
overflow: hidden;
- background: #ccc;
- @apply(--shadow-elevation-2dp);
+ padding: 12px;
+ margin-bottom: 4px;
+ background-color: #4285f4;
+ color: white;
+ }
+
+ :host:hover paper-icon-button {
+ transform: scale(1.05);
+ }
+
+ .paper-font-subhead {
+ text-align: center;
}
- .paper-font-subhead{
- text-align: center;
+
+ .paper-font-body1 {
+ text-align: center;
+ width: 100%;
+ font-size: 13px;
+ color: grey;
+ margin-top: 2px;
+ }
+
+ :host,
+ .paper-font-subhead,
+ .paper-font-body1 {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ margin: 4px;
}
</style>
- <div class="avatar" id="avatar" item-icon></div>
+ <paper-icon-button icon="{{_displayIcon}}"></paper-icon-button>
<div class="paper-font-subhead">{{_displayName}}</div>
+ <div class="paper-font-body1">{{status}}</div>
</template>
<script>
'use strict';
Polymer({
is: 'user-avatar',
- behaviors:[Chat.AnonymousContactBehavior],
- observers:['_computeBackgroundImg(contact.*)'],
- _computeBackgroundImg:function(){
- console.log('avatar changed');
- var avatar = this.anonymousAccount(this.contact).avatar;
- var style = this.$.avatar.style;
- style.backgroundImage='url('+avatar.url+')';
- style.backgroundPosition=avatar.left+'px '+avatar.top+'px';
+ properties: {
+ contact: Object,
+ _displayName: {
+ computed: '_computeDisplayName(contact)'
+ },
+ _displayIcon: {
+ computed: '_computeDisplayIcon(contact)'
+ },
+ status: {
+ type: String,
+ value: ''
+ }
+ },
+ _computeDisplayName: function(contact) {
+ contact = contact.name;
+ if (contact.model) {
+ return contact.os + ' ' + contact.model;
+ }
+ contact.os = contact.os.replace('Mac OS', 'Mac');
+ return contact.os + ' ' + contact.browser;
+ },
+ _computeDisplayIcon: function(contact) {
+ contact = contact.name;
+ if (contact.type === 'mobile') {
+ return 'chat:phone-iphone';
+ }
+ if (contact.type === 'tablet') {
+ return 'chat:tablet-mac';
+ }
+
+ return 'chat:desktop-mac';
+ },
+ attached: function() {
+ this.async(function() {
+ app.p2p.addEventListener('file-offered', function(e) {
+ if (e.detail.to === this.contact.peerId) {
+ this.status = 'Waiting to accept...';
+ }
+ }.bind(this), false);
+ app.p2p.addEventListener('upload-started', function(e) {
+ if (e.detail.to === this.contact.peerId) {
+ this.status = 'Uploading...';
+ }
+ }.bind(this), false);
+ app.p2p.addEventListener('download-started', function(e) {
+ if (e.detail.from === this.contact.peerId) {
+ this.status = 'Downloading...';
+ }
+ }.bind(this), false);
+ app.p2p.addEventListener('upload-complete', function(e) {
+ if (e.detail.from === this.contact.peerId) {
+ this.status = '';
+ }
+ }.bind(this), false);
+ app.p2p.addEventListener('download-complete', function(e) {
+ if (e.detail.from === this.contact.peerId) {
+ this.status = '';
+ }
+ }.bind(this), false);
+ app.p2p.addEventListener('file-declined', function(e) {
+ if (e.detail.from === this.contact.peerId) {
+ this.status = '';
+ }
+ }.bind(this), false);
+ app.p2p.addEventListener('upload-error', function(e) {
+ this.status = '';
+ }.bind(this), false);
+ }, 200);
}
});
</script>
diff --git a/app/elements/contact-item/anonymous-contact-behavior.html b/app/elements/contact-item/anonymous-contact-behavior.html
@@ -1,337 +0,0 @@
-<script>
-'use strict';
-window.Chat = window.Chat || {};
-var djb2Code = function(str) {
- var hash = 5381;
- for (var i = 0; i < str.length; i++) {
- var character = str.charCodeAt(i);
- hash = ((hash << 5) + hash) + character; /* hash * 33 + c */
- }
- return hash > 0 ? hash : -hash;
-};
-var animals = [
- 'Adelie',
- 'Penguin',
- 'Akita',
- 'Bulldog',
- 'Ant',
- 'Fox',
- 'Hare',
- 'Wolf',
- 'Terrier',
- 'Avocet',
- 'Baboon',
- 'Camel',
- 'Badger',
- 'Barb',
- 'Basenji',
- 'Basking',
- 'Bat',
- 'Beagle',
- 'Bear',
- 'Collie',
- 'Beaver',
- 'Beetle',
- 'Bichon',
- 'Bird',
- 'Birman',
- 'Bison',
- 'Bobcat',
- 'Bombay',
- 'Bongo',
- 'Bonobo',
- 'Booby',
- 'Boykin',
- 'Budgie',
- 'Buffalo',
- 'Burmese',
- 'Fish',
- 'Caiman',
- 'Lizard',
- 'Canaan',
- 'Caracal',
- 'Cat',
- 'Catfish',
- 'Cesky',
- 'Fousek',
- 'Chamois',
- 'Cheetah',
- 'Chicken',
- 'Chinook',
- 'Cichlid',
- 'Leopard',
- 'Clumber',
- 'Coati',
- 'Coral',
- 'Tamarin',
- 'Cougar',
- 'Cow',
- 'Coyote',
- 'Crab',
- 'Macaque',
- 'Crane',
- 'Cuscus',
- 'Frog',
- 'Deer',
- 'Bracke',
- 'Dhole',
- 'Dingo',
- 'Discus',
- 'Dodo',
- 'Dog',
- 'Dogo',
- 'Dolphin',
- 'Donkey',
- 'Drever',
- 'Duck',
- 'Dugong',
- 'Dunker',
- 'Dusky',
- 'Eagle',
- 'Earwig ',
- 'Gorilla',
- 'Echidna',
- 'Emu',
- 'Falcon',
- 'Fennec',
- 'Ferret',
- 'Spitz',
- 'Fly',
- 'Fossa',
- 'Gecko',
- 'Gerbil',
- 'Gharial',
- 'Gibbon',
- 'Giraffe',
- 'Goat',
- 'Oriole',
- 'Goose',
- 'Gopher',
- 'Grouse',
- 'Guppy',
- 'Shark',
- 'Hamster',
- 'Harrier',
- 'Heron',
- 'Horse',
- 'Human',
- 'Hyena',
- 'Ibis',
- 'Iguana',
- 'Impala',
- 'Indri',
- 'Insect',
- 'Setter',
- 'Jackal',
- 'Jaguar',
- 'Kakapo',
- 'Kiwi',
- 'Koala',
- 'Lemming',
- 'Lemur',
- 'Liger',
- 'Lion',
- 'Llama',
- 'Lobster',
- 'Owl',
- 'Lynx',
- 'Mayfly',
- 'Meerkat',
- 'Molly',
- 'Mongrel',
- 'Monkey',
- 'Moorhen',
- 'Moose',
- 'Mouse',
- 'Mule',
- 'Numbat',
- 'Ocelot',
- 'Octopus',
- 'Okapi',
- 'Opossum',
- 'Ostrich',
- 'Otter',
- 'Oyster',
- 'Panther',
- 'Parrot',
- 'Peacock',
- 'Pelican',
- 'Persian',
- 'Pig',
- 'Piranha',
- 'Pointer',
- 'Poodle',
- 'Possum',
- 'Prawn',
- 'Puffin',
- 'Pug',
- 'Puma',
- 'Pygmy',
- 'Quail',
- 'Quetzal',
- 'Quokka',
- 'Quoll',
- 'Rabbit',
- 'Raccoon',
- 'Ragdoll',
- 'Rat',
- 'Robin',
- 'Saola',
- 'Seal',
- 'Serval',
- 'Sheep',
- 'Shrimp',
- 'Siamese',
- 'Skunk',
- 'Sloth',
- 'Snail',
- 'Snake',
- 'Somali',
- 'Sparrow',
- 'Dogfish',
- 'Sponge',
- 'Squid',
- 'Stoat',
- 'Swan',
- 'Tang',
- 'Tapir',
- 'Tarsier',
- 'Termite',
- 'Tetra',
- 'Tiffany',
- 'Tiger',
- 'Toucan',
- 'Tuatara',
- 'Turkey',
- 'Uakari',
- 'Uguisu',
- 'Vulture',
- 'Wallaby',
- 'Walrus',
- 'Warthog',
- 'Wasp',
- 'Weasel',
- 'Whippet',
- 'Wombat',
- 'Wrasse',
- 'Yak',
- 'Yorkie',
- 'Zebra',
- 'Zebu',
- 'Zonkey',
- 'Zorse'
-];
-var bb = [
- 'Walter White',
- 'Skyler White',
- 'Jesse Pinkman',
- 'Hank Schrader',
- 'Marie Schrader',
- 'Walter White, Jr.',
- 'Saul Goodman',
- 'Gustavo Fring',
- 'Mike Ehrmantraut',
- 'Lydia Rodarte-Quayle',
- 'Todd Alquist',
- 'Steven Gomez',
- 'Detectives Kalanchoe & Munn',
- 'George Merkert',
- 'Sac Ramey',
- 'Tim Roberts',
- 'Maximino Arciniega',
- 'Gale Boetticher',
- 'Duane Chow',
- 'Ron Forenall',
- 'Barry Goodman',
- 'Tyrus Kitt',
- 'Chris Mara',
- 'Dennis Markowski',
- 'Victor',
- 'Dan Wachsberger',
- 'Don Eladio Vuente',
- 'Juan Bolsa',
- 'Hector Salamanca',
- 'Tuco Salamanca',
- 'Leonel Salamanca',
- 'Marco Salamanca',
- 'Gonzo',
- 'Emilio Koyama',
- 'Krazy-8 Molina',
- 'Jack Welker',
- 'Andrea Cantillo',
- 'Brock Cantillo',
- 'Jane Margolis',
- 'Brandon Mayhew',
- 'Combo Ortega',
- 'Skinny Pete',
- 'Adam Pinkman',
- 'Mrs. Pinkman',
- 'Jake Pinkman',
- 'Wendy',
- 'Huell Babineaux',
- 'Ed',
- 'Francesca',
- 'Patrick Kuby',
- 'Hugo Archuleta',
- 'Ted Beneke',
- 'Clovis',
- 'Louis Corbett',
- 'Dr. Delcavoli',
- 'Lawson',
- 'Donald Margolis',
- 'Carmen Molina',
- 'Old Joe',
- 'Pamela',
- 'Gretchen Schwartz',
- 'Elliott Schwartz',
- 'Drew Sharp',
- 'Spooge',
- 'Holly White',
- 'Bogdan Wolynetz'
-];
-Chat.AnonymousContactBehavior = {
- properties: {
- contact: {
- type: Object,
- notify: true
- },
- _displayName: {
- computed: '_computeDisplayName(contact)'
- }
- },
- _computeDisplayName: function(contact) {
- if (contact === undefined || contact === null) {
- return 'connecting...';
- }
- if (contact === 'error' || contact === 'invite') {
- return '';
- }
- if (!contact.name) {
- return this.anonymousAccount(contact).name;
- }
- return contact.name;
- },
- get names() {
- return bb;
- },
- anonymousAccount: function(contact) {
- if (contact && !contact.name) {
- var peer = contact.peer || contact;
- var hash = djb2Code(peer);
- var i = hash % this.names.length;
- var name = this.names[i];
- var marginTop = i % 2;
- var marginLeft = Math.floor(i / 2) % 5;
- return {
- name: name,
- peer: peer,
- avatar: {
- url: 'images/avatars.jpg',
- left: -14 + 80 * marginLeft,
- top: -19 + 95 * marginTop
- }
- };
- }
- }
-};
-</script>
diff --git a/app/elements/elements.html b/app/elements/elements.html
@@ -1,15 +1,15 @@
<link rel="import" href="../bower_components/platinum-sw/platinum-sw-cache.html">
<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">
-<!-- Configure your routes here -->
+<!-- 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="../styles/shared-styles.html">
<link rel="import" href="buddy-finder/buddy-finder.html">
-<link rel="import" href="p2p-network/p2p-network.html">
+<link rel="import" href="p2p-network/connection-wrapper.html">
<link rel="import" href="file-sharing/file-receiver.html">
diff --git a/app/elements/file-sharing/file-button-behavior.html b/app/elements/file-sharing/file-button-behavior.html
@@ -8,6 +8,7 @@ Chat.FileButtonBehaviorImpl = {
if (!fileInput) {
fileInput = document.createElement('input');
fileInput.type = 'file';
+ fileInput.multiple = 'true';
fileInput.className = 'fileInput';
fileInput.style.position = 'fixed';
fileInput.style.top = '-10000px';
diff --git a/app/elements/file-sharing/file-button.html b/app/elements/file-sharing/file-button.html
@@ -1,4 +1,4 @@
-<link rel="import" href="../../../bower_components/paper-icon-button/paper-icon-button.html">
+<link rel="import" href="../../bower_components/paper-icon-button/paper-icon-button.html">
<link rel="import" href="file-button-behavior.html">
<dom-module id="file-button">
<template>
diff --git a/app/elements/file-sharing/file-drop-behavior.html b/app/elements/file-sharing/file-drop-behavior.html
@@ -25,7 +25,7 @@ Chat.FileDropBehaviorImpl = {
dropZone.addEventListener('drop', function(event) {
event.stopPropagation();
event.preventDefault();
-
+
//call dragend
dragEnd();
@@ -36,5 +36,13 @@ Chat.FileDropBehaviorImpl = {
});
}
};
+document.body.addEventListener('dragover', function(e) {
+ e.stopPropagation();
+ e.preventDefault();
+}, false);
+document.body.addEventListener('drop', function(event) {
+ event.stopPropagation();
+ event.preventDefault();
+});
Chat.FileDropBehavior = [Chat.FileDropBehaviorImpl, Chat.FileSelectionBehavior];
</script>
diff --git a/app/elements/file-sharing/file-receiver.html b/app/elements/file-sharing/file-receiver.html
@@ -2,51 +2,94 @@
<link rel="import" href="../../bower_components/paper-button/paper-button.html">
<link rel="import" href="../../bower_components/neon-animation/animations/scale-up-animation.html">
<link rel="import" href="../../bower_components/neon-animation/animations/fade-out-animation.html">
+<link rel="import" href="../../bower_components/iron-pages/iron-pages.html">
+<link rel="import" href="../../bower_components/paper-spinner/paper-spinner.html">
<dom-module id="file-receiver">
<template>
<style>
:host {
display: block;
- position: fixed;
- z-index: 100;
+ }
+
+ #dialog,
+ #download {
+ width: 300px;
+ z-index: 101;
+ }
+
+ b {
+ word-break: break-word;
}
</style>
- <paper-dialog id="dialog" entry-animation="scale-up-animation" exit-animation="fade-out-animation" with-backdro>
+ <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>
+ <div class="buttons">
+ <paper-button dialog-dismiss on-tap="_decline">Discard</paper-button>
+ <paper-button dialog-confirm on-tap="_accept" autofocus>Download</paper-button>
+ </div>
+ </paper-dialog>
+ <paper-dialog id="download" entry-animation="scale-up-animation" exit-animation="fade-out-animation" with-backdrop modal>
<h2>File Received</h2>
- <p>You received file {{file.name}}</p>
+ <p>Right Click and "Save as"...</p>
<div class="buttons">
- <paper-button dialog-dismiss>Dismiss</paper-button>
- <paper-button dialog-confirm on-tap="_download">Download</paper-button>
+ <paper-button dialog-dismiss>Discard</paper-button>
+ <a href="{{dataUri}}" target="_blank">
+ <paper-button dialog-confirm autofocus>Download</paper-button>
+ </a>
</div>
</paper-dialog>
</template>
<script>
'use strict';
- Polymer({
- is: 'file-receiver',
- attached: function() {
- this.async(function() {
- app.p2p.addEventListener('file-received', function(e) {
- this.fileReceived(e.detail);
- }.bind(this), false);
- },200);
- },
- fileReceived: function(file) {
- this.set('file', file);
- this.$.dialog.open();
- },
- _download: function() {
- var link = document.createElement('a');
- link.download = this.file.name;
- // Construct the uri
- var uri = this.file.dataURI;
- link.href = uri;
- document.body.appendChild(link);
- link.click();
- // Cleanup the DOM
- document.body.removeChild(link);
- //delete link;
- }
- });
+ (function() {
+ Polymer({
+ is: 'file-receiver',
+ attached: function() {
+ this.async(function() {
+ app.p2p.addEventListener('file-offer', function(e) {
+ this.file = e.detail;
+ this.$.dialog.open();
+ }.bind(this), false);
+ app.p2p.addEventListener('file-received', function(e) {
+ this._fileReceived(e.detail);
+ }.bind(this), false);
+ app.p2p.addEventListener('file-declined', function(e) {
+ app.displayToast('User declined file ' + e.detail.name);
+ }.bind(this), false);
+ app.p2p.addEventListener('upload-complete', function(e) {
+ app.displayToast('User received file ' + e.detail.name);
+ }.bind(this), false);
+ app.p2p.addEventListener('upload-error', function(e) {
+ app.displayToast('The other device did not respond. Please try again.');
+ }.bind(this), false);
+ }, 200);
+ },
+ _fileReceived: function(file) {
+ this.downloadURI(file);
+ },
+ _decline: function() {
+ app.p2p.decline(this.file);
+ },
+ _accept: function() {
+ app.p2p.accept(this.file);
+ },
+ downloadURI: function(file) {
+ var link = document.createElement('a');
+ var uri = (window.URL || window.webkitURL).createObjectURL(file.blob);
+ if (typeof link.download !== 'undefined') {
+ //download attribute is supported
+ link.href = uri;
+ link.download = file.name || 'blank';
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ } else {
+ this.dataUri = uri;
+ this.$.download.open();
+ }
+ }
+ });
+ }());
</script>
</dom-module>
diff --git a/app/elements/file-sharing/file-saver.html b/app/elements/file-sharing/file-saver.html
@@ -0,0 +1,4 @@
+<script>
+ var saveAs=saveAs||function(view){"use strict";if(typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var doc=view.document,get_URL=function(){return view.URL||view.webkitURL||view},save_link=doc.createElementNS("http://www.w3.org/1999/xhtml","a"),can_use_save_link="download"in save_link,click=function(node){var event=new MouseEvent("click");node.dispatchEvent(event)},is_safari=/Version\/[\d\.]+.*Safari/.test(navigator.userAgent),webkit_req_fs=view.webkitRequestFileSystem,req_fs=view.requestFileSystem||webkit_req_fs||view.mozRequestFileSystem,throw_outside=function(ex){(view.setImmediate||view.setTimeout)(function(){throw ex},0)},force_saveable_type="application/octet-stream",fs_min_size=0,arbitrary_revoke_timeout=500,revoke=function(file){var revoker=function(){if(typeof file==="string"){get_URL().revokeObjectURL(file)}else{file.remove()}};if(view.chrome){revoker()}else{setTimeout(revoker,arbitrary_revoke_timeout)}},dispatch=function(filesaver,event_types,event){event_types=[].concat(event_types);var i=event_types.length;while(i--){var listener=filesaver["on"+event_types[i]];if(typeof listener==="function"){try{listener.call(filesaver,event||filesaver)}catch(ex){throw_outside(ex)}}}},auto_bom=function(blob){if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)){return new Blob(["\ufeff",blob],{type:blob.type})}return blob},FileSaver=function(blob,name,no_auto_bom){if(!no_auto_bom){blob=auto_bom(blob)}var filesaver=this,type=blob.type,blob_changed=false,object_url,target_view,dispatch_all=function(){dispatch(filesaver,"writestart progress write writeend".split(" "))},fs_error=function(){if(target_view&&is_safari&&typeof FileReader!=="undefined"){var reader=new FileReader;reader.onloadend=function(){var base64Data=reader.result;target_view.location.href="data:attachment/file"+base64Data.slice(base64Data.search(/[,;]/));filesaver.readyState=filesaver.DONE;dispatch_all()};reader.readAsDataURL(blob);filesaver.readyState=filesaver.INIT;return}if(blob_changed||!object_url){object_url=get_URL().createObjectURL(blob)}if(target_view){target_view.location.href=object_url}else{var new_tab=view.open(object_url,"_blank");if(new_tab==undefined&&is_safari){view.location.href=object_url}}filesaver.readyState=filesaver.DONE;dispatch_all();revoke(object_url)},abortable=function(func){return function(){if(filesaver.readyState!==filesaver.DONE){return func.apply(this,arguments)}}},create_if_not_found={create:true,exclusive:false},slice;filesaver.readyState=filesaver.INIT;if(!name){name="download"}if(can_use_save_link){object_url=get_URL().createObjectURL(blob);setTimeout(function(){save_link.href=object_url;save_link.download=name;click(save_link);dispatch_all();revoke(object_url);filesaver.readyState=filesaver.DONE});return}if(view.chrome&&type&&type!==force_saveable_type){slice=blob.slice||blob.webkitSlice;blob=slice.call(blob,0,blob.size,force_saveable_type);blob_changed=true}if(webkit_req_fs&&name!=="download"){name+=".download"}if(type===force_saveable_type||webkit_req_fs){target_view=view}if(!req_fs){fs_error();return}fs_min_size+=blob.size;req_fs(view.TEMPORARY,fs_min_size,abortable(function(fs){fs.root.getDirectory("saved",create_if_not_found,abortable(function(dir){var save=function(){dir.getFile(name,create_if_not_found,abortable(function(file){file.createWriter(abortable(function(writer){writer.onwriteend=function(event){target_view.location.href=file.toURL();filesaver.readyState=filesaver.DONE;dispatch(filesaver,"writeend",event);revoke(file)};writer.onerror=function(){var error=writer.error;if(error.code!==error.ABORT_ERR){fs_error()}};"writestart progress write abort".split(" ").forEach(function(event){writer["on"+event]=filesaver["on"+event]});writer.write(blob);filesaver.abort=function(){writer.abort();filesaver.readyState=filesaver.DONE};filesaver.readyState=filesaver.WRITING}),fs_error)}),fs_error)};dir.getFile(name,{create:false},abortable(function(file){file.remove();save()}),abortable(function(ex){if(ex.code===ex.NOT_FOUND_ERR){save()}else{fs_error()}}))}),fs_error)}),fs_error)},FS_proto=FileSaver.prototype,saveAs=function(blob,name,no_auto_bom){return new FileSaver(blob,name,no_auto_bom)};if(typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob){return function(blob,name,no_auto_bom){if(!no_auto_bom){blob=auto_bom(blob)}return navigator.msSaveOrOpenBlob(blob,name||"download")}}FS_proto.abort=function(){var filesaver=this;filesaver.readyState=filesaver.DONE;dispatch(filesaver,"abort")};FS_proto.readyState=FS_proto.INIT=0;FS_proto.WRITING=1;FS_proto.DONE=2;FS_proto.error=FS_proto.onwritestart=FS_proto.onprogress=FS_proto.onwrite=FS_proto.onabort=FS_proto.onerror=FS_proto.onwriteend=null;return saveAs}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined"&&module.exports){module.exports.saveAs=saveAs}else if(typeof define!=="undefined"&&define!==null&&define.amd!=null){define([],function(){return saveAs})}
+
+</script>
+\ No newline at end of file
diff --git a/app/elements/file-sharing/file-selection-behavior.html b/app/elements/file-sharing/file-selection-behavior.html
@@ -3,22 +3,16 @@
window.Chat = window.Chat || {};
Chat.FileSelectionBehavior = {
notifyFilesSelection: function(files) {
- if(!files){
+ if (!files) {
console.log('no files selected...');
return;
}
for (var i = 0; i < files.length; i++) {
var file = files[i];
- var reader = new FileReader();
- reader.onload = function(e2) {
- // finished reading file data.
- console.log('file dropped');
- this.fire('file-selected', {
- dataURI: e2.target.result,
- name: file.name
- });
- }.bind(this);
- reader.readAsDataURL(file); // start reading the file data.
+ this.fire('file-selected', {
+ file: file,
+ name: file.name
+ });
}
}
};
diff --git a/app/elements/p2p-network/binaryjs.html b/app/elements/p2p-network/binaryjs.html
@@ -0,0 +1,1572 @@
+<script>
+ /*! binary.js build:0.2.2, development. Copyright(c) 2012 Eric Zhang <eric@ericzhang.com> MIT Licensed */
+(function(exports){
+/*! binarypack.js build:0.0.9, production. Copyright(c) 2012 Eric Zhang <eric@ericzhang.com> MIT Licensed */(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+var BufferBuilder = require('./bufferbuilder').BufferBuilder;
+var binaryFeatures = require('./bufferbuilder').binaryFeatures;
+
+var BinaryPack = {
+ unpack: function(data){
+ var unpacker = new Unpacker(data);
+ return unpacker.unpack();
+ },
+ pack: function(data){
+ var packer = new Packer();
+ packer.pack(data);
+ var buffer = packer.getBuffer();
+ return buffer;
+ }
+};
+
+module.exports = BinaryPack;
+
+function Unpacker (data){
+ // Data is ArrayBuffer
+ this.index = 0;
+ this.dataBuffer = data;
+ this.dataView = new Uint8Array(this.dataBuffer);
+ this.length = this.dataBuffer.byteLength;
+}
+
+Unpacker.prototype.unpack = function(){
+ var type = this.unpack_uint8();
+ if (type < 0x80){
+ var positive_fixnum = type;
+ return positive_fixnum;
+ } else if ((type ^ 0xe0) < 0x20){
+ var negative_fixnum = (type ^ 0xe0) - 0x20;
+ return negative_fixnum;
+ }
+ var size;
+ if ((size = type ^ 0xa0) <= 0x0f){
+ return this.unpack_raw(size);
+ } else if ((size = type ^ 0xb0) <= 0x0f){
+ return this.unpack_string(size);
+ } else if ((size = type ^ 0x90) <= 0x0f){
+ return this.unpack_array(size);
+ } else if ((size = type ^ 0x80) <= 0x0f){
+ return this.unpack_map(size);
+ }
+ switch(type){
+ case 0xc0:
+ return null;
+ case 0xc1:
+ return undefined;
+ case 0xc2:
+ return false;
+ case 0xc3:
+ return true;
+ case 0xca:
+ return this.unpack_float();
+ case 0xcb:
+ return this.unpack_double();
+ case 0xcc:
+ return this.unpack_uint8();
+ case 0xcd:
+ return this.unpack_uint16();
+ case 0xce:
+ return this.unpack_uint32();
+ case 0xcf:
+ return this.unpack_uint64();
+ case 0xd0:
+ return this.unpack_int8();
+ case 0xd1:
+ return this.unpack_int16();
+ case 0xd2:
+ return this.unpack_int32();
+ case 0xd3:
+ return this.unpack_int64();
+ case 0xd4:
+ return undefined;
+ case 0xd5:
+ return undefined;
+ case 0xd6:
+ return undefined;
+ case 0xd7:
+ return undefined;
+ case 0xd8:
+ size = this.unpack_uint16();
+ return this.unpack_string(size);
+ case 0xd9:
+ size = this.unpack_uint32();
+ return this.unpack_string(size);
+ case 0xda:
+ size = this.unpack_uint16();
+ return this.unpack_raw(size);
+ case 0xdb:
+ size = this.unpack_uint32();
+ return this.unpack_raw(size);
+ case 0xdc:
+ size = this.unpack_uint16();
+ return this.unpack_array(size);
+ case 0xdd:
+ size = this.unpack_uint32();
+ return this.unpack_array(size);
+ case 0xde:
+ size = this.unpack_uint16();
+ return this.unpack_map(size);
+ case 0xdf:
+ size = this.unpack_uint32();
+ return this.unpack_map(size);
+ }
+}
+
+Unpacker.prototype.unpack_uint8 = function(){
+ var byte = this.dataView[this.index] & 0xff;
+ this.index++;
+ return byte;
+};
+
+Unpacker.prototype.unpack_uint16 = function(){
+ var bytes = this.read(2);
+ var uint16 =
+ ((bytes[0] & 0xff) * 256) + (bytes[1] & 0xff);
+ this.index += 2;
+ return uint16;
+}
+
+Unpacker.prototype.unpack_uint32 = function(){
+ var bytes = this.read(4);
+ var uint32 =
+ ((bytes[0] * 256 +
+ bytes[1]) * 256 +
+ bytes[2]) * 256 +
+ bytes[3];
+ this.index += 4;
+ return uint32;
+}
+
+Unpacker.prototype.unpack_uint64 = function(){
+ var bytes = this.read(8);
+ var uint64 =
+ ((((((bytes[0] * 256 +
+ bytes[1]) * 256 +
+ bytes[2]) * 256 +
+ bytes[3]) * 256 +
+ bytes[4]) * 256 +
+ bytes[5]) * 256 +
+ bytes[6]) * 256 +
+ bytes[7];
+ this.index += 8;
+ return uint64;
+}
+
+
+Unpacker.prototype.unpack_int8 = function(){
+ var uint8 = this.unpack_uint8();
+ return (uint8 < 0x80 ) ? uint8 : uint8 - (1 << 8);
+};
+
+Unpacker.prototype.unpack_int16 = function(){
+ var uint16 = this.unpack_uint16();
+ return (uint16 < 0x8000 ) ? uint16 : uint16 - (1 << 16);
+}
+
+Unpacker.prototype.unpack_int32 = function(){
+ var uint32 = this.unpack_uint32();
+ return (uint32 < Math.pow(2, 31) ) ? uint32 :
+ uint32 - Math.pow(2, 32);
+}
+
+Unpacker.prototype.unpack_int64 = function(){
+ var uint64 = this.unpack_uint64();
+ return (uint64 < Math.pow(2, 63) ) ? uint64 :
+ uint64 - Math.pow(2, 64);
+}
+
+Unpacker.prototype.unpack_raw = function(size){
+ if ( this.length < this.index + size){
+ throw new Error('BinaryPackFailure: index is out of range'
+ + ' ' + this.index + ' ' + size + ' ' + this.length);
+ }
+ var buf = this.dataBuffer.slice(this.index, this.index + size);
+ this.index += size;
+
+ //buf = util.bufferToString(buf);
+
+ return buf;
+}
+
+Unpacker.prototype.unpack_string = function(size){
+ var bytes = this.read(size);
+ var i = 0, str = '', c, code;
+ while(i < size){
+ c = bytes[i];
+ if ( c < 128){
+ str += String.fromCharCode(c);
+ i++;
+ } else if ((c ^ 0xc0) < 32){
+ code = ((c ^ 0xc0) << 6) | (bytes[i+1] & 63);
+ str += String.fromCharCode(code);
+ i += 2;
+ } else {
+ code = ((c & 15) << 12) | ((bytes[i+1] & 63) << 6) |
+ (bytes[i+2] & 63);
+ str += String.fromCharCode(code);
+ i += 3;
+ }
+ }
+ this.index += size;
+ return str;
+}
+
+Unpacker.prototype.unpack_array = function(size){
+ var objects = new Array(size);
+ for(var i = 0; i < size ; i++){
+ objects[i] = this.unpack();
+ }
+ return objects;
+}
+
+Unpacker.prototype.unpack_map = function(size){
+ var map = {};
+ for(var i = 0; i < size ; i++){
+ var key = this.unpack();
+ var value = this.unpack();
+ map[key] = value;
+ }
+ return map;
+}
+
+Unpacker.prototype.unpack_float = function(){
+ var uint32 = this.unpack_uint32();
+ var sign = uint32 >> 31;
+ var exp = ((uint32 >> 23) & 0xff) - 127;
+ var fraction = ( uint32 & 0x7fffff ) | 0x800000;
+ return (sign == 0 ? 1 : -1) *
+ fraction * Math.pow(2, exp - 23);
+}
+
+Unpacker.prototype.unpack_double = function(){
+ var h32 = this.unpack_uint32();
+ var l32 = this.unpack_uint32();
+ var sign = h32 >> 31;
+ var exp = ((h32 >> 20) & 0x7ff) - 1023;
+ var hfrac = ( h32 & 0xfffff ) | 0x100000;
+ var frac = hfrac * Math.pow(2, exp - 20) +
+ l32 * Math.pow(2, exp - 52);
+ return (sign == 0 ? 1 : -1) * frac;
+}
+
+Unpacker.prototype.read = function(length){
+ var j = this.index;
+ if (j + length <= this.length) {
+ return this.dataView.subarray(j, j + length);
+ } else {
+ throw new Error('BinaryPackFailure: read index out of range');
+ }
+}
+
+function Packer(){
+ this.bufferBuilder = new BufferBuilder();
+}
+
+Packer.prototype.getBuffer = function(){
+ return this.bufferBuilder.getBuffer();
+}
+
+Packer.prototype.pack = function(value){
+ var type = typeof(value);
+ if (type == 'string'){
+ this.pack_string(value);
+ } else if (type == 'number'){
+ if (Math.floor(value) === value){
+ this.pack_integer(value);
+ } else{
+ this.pack_double(value);
+ }
+ } else if (type == 'boolean'){
+ if (value === true){
+ this.bufferBuilder.append(0xc3);
+ } else if (value === false){
+ this.bufferBuilder.append(0xc2);
+ }
+ } else if (type == 'undefined'){
+ this.bufferBuilder.append(0xc0);
+ } else if (type == 'object'){
+ if (value === null){
+ this.bufferBuilder.append(0xc0);
+ } else {
+ var constructor = value.constructor;
+ if (constructor == Array){
+ this.pack_array(value);
+ } else if (constructor == Blob || constructor == File) {
+ this.pack_bin(value);
+ } else if (constructor == ArrayBuffer) {
+ if(binaryFeatures.useArrayBufferView) {
+ this.pack_bin(new Uint8Array(value));
+ } else {
+ this.pack_bin(value);
+ }
+ } else if ('BYTES_PER_ELEMENT' in value){
+ if(binaryFeatures.useArrayBufferView) {
+ this.pack_bin(new Uint8Array(value.buffer));
+ } else {
+ this.pack_bin(value.buffer);
+ }
+ } else if (constructor == Object){
+ this.pack_object(value);
+ } else if (constructor == Date){
+ this.pack_string(value.toString());
+ } else if (typeof value.toBinaryPack == 'function'){
+ this.bufferBuilder.append(value.toBinaryPack());
+ } else {
+ throw new Error('Type "' + constructor.toString() + '" not yet supported');
+ }
+ }
+ } else {
+ throw new Error('Type "' + type + '" not yet supported');
+ }
+ this.bufferBuilder.flush();
+}
+
+
+Packer.prototype.pack_bin = function(blob){
+ var length = blob.length || blob.byteLength || blob.size;
+ if (length <= 0x0f){
+ this.pack_uint8(0xa0 + length);
+ } else if (length <= 0xffff){
+ this.bufferBuilder.append(0xda) ;
+ this.pack_uint16(length);
+ } else if (length <= 0xffffffff){
+ this.bufferBuilder.append(0xdb);
+ this.pack_uint32(length);
+ } else{
+ throw new Error('Invalid length');
+ }
+ this.bufferBuilder.append(blob);
+}
+
+Packer.prototype.pack_string = function(str){
+ var length = utf8Length(str);
+
+ if (length <= 0x0f){
+ this.pack_uint8(0xb0 + length);
+ } else if (length <= 0xffff){
+ this.bufferBuilder.append(0xd8) ;
+ this.pack_uint16(length);
+ } else if (length <= 0xffffffff){
+ this.bufferBuilder.append(0xd9);
+ this.pack_uint32(length);
+ } else{
+ throw new Error('Invalid length');
+ }
+ this.bufferBuilder.append(str);
+}
+
+Packer.prototype.pack_array = function(ary){
+ var length = ary.length;
+ if (length <= 0x0f){
+ this.pack_uint8(0x90 + length);
+ } else if (length <= 0xffff){
+ this.bufferBuilder.append(0xdc)
+ this.pack_uint16(length);
+ } else if (length <= 0xffffffff){
+ this.bufferBuilder.append(0xdd);
+ this.pack_uint32(length);
+ } else{
+ throw new Error('Invalid length');
+ }
+ for(var i = 0; i < length ; i++){
+ this.pack(ary[i]);
+ }
+}
+
+Packer.prototype.pack_integer = function(num){
+ if ( -0x20 <= num && num <= 0x7f){
+ this.bufferBuilder.append(num & 0xff);
+ } else if (0x00 <= num && num <= 0xff){
+ this.bufferBuilder.append(0xcc);
+ this.pack_uint8(num);
+ } else if (-0x80 <= num && num <= 0x7f){
+ this.bufferBuilder.append(0xd0);
+ this.pack_int8(num);
+ } else if ( 0x0000 <= num && num <= 0xffff){
+ this.bufferBuilder.append(0xcd);
+ this.pack_uint16(num);
+ } else if (-0x8000 <= num && num <= 0x7fff){
+ this.bufferBuilder.append(0xd1);
+ this.pack_int16(num);
+ } else if ( 0x00000000 <= num && num <= 0xffffffff){
+ this.bufferBuilder.append(0xce);
+ this.pack_uint32(num);
+ } else if (-0x80000000 <= num && num <= 0x7fffffff){
+ this.bufferBuilder.append(0xd2);
+ this.pack_int32(num);
+ } else if (-0x8000000000000000 <= num && num <= 0x7FFFFFFFFFFFFFFF){
+ this.bufferBuilder.append(0xd3);
+ this.pack_int64(num);
+ } else if (0x0000000000000000 <= num && num <= 0xFFFFFFFFFFFFFFFF){
+ this.bufferBuilder.append(0xcf);
+ this.pack_uint64(num);
+ } else{
+ throw new Error('Invalid integer');
+ }
+}
+
+Packer.prototype.pack_double = function(num){
+ var sign = 0;
+ if (num < 0){
+ sign = 1;
+ num = -num;
+ }
+ var exp = Math.floor(Math.log(num) / Math.LN2);
+ var frac0 = num / Math.pow(2, exp) - 1;
+ var frac1 = Math.floor(frac0 * Math.pow(2, 52));
+ var b32 = Math.pow(2, 32);
+ var h32 = (sign << 31) | ((exp+1023) << 20) |
+ (frac1 / b32) & 0x0fffff;
+ var l32 = frac1 % b32;
+ this.bufferBuilder.append(0xcb);
+ this.pack_int32(h32);
+ this.pack_int32(l32);
+}
+
+Packer.prototype.pack_object = function(obj){
+ var keys = Object.keys(obj);
+ var length = keys.length;
+ if (length <= 0x0f){
+ this.pack_uint8(0x80 + length);
+ } else if (length <= 0xffff){
+ this.bufferBuilder.append(0xde);
+ this.pack_uint16(length);
+ } else if (length <= 0xffffffff){
+ this.bufferBuilder.append(0xdf);
+ this.pack_uint32(length);
+ } else{
+ throw new Error('Invalid length');
+ }
+ for(var prop in obj){
+ if (obj.hasOwnProperty(prop)){
+ this.pack(prop);
+ this.pack(obj[prop]);
+ }
+ }
+}
+
+Packer.prototype.pack_uint8 = function(num){
+ this.bufferBuilder.append(num);
+}
+
+Packer.prototype.pack_uint16 = function(num){
+ this.bufferBuilder.append(num >> 8);
+ this.bufferBuilder.append(num & 0xff);
+}
+
+Packer.prototype.pack_uint32 = function(num){
+ var n = num & 0xffffffff;
+ this.bufferBuilder.append((n & 0xff000000) >>> 24);
+ this.bufferBuilder.append((n & 0x00ff0000) >>> 16);
+ this.bufferBuilder.append((n & 0x0000ff00) >>> 8);
+ this.bufferBuilder.append((n & 0x000000ff));
+}
+
+Packer.prototype.pack_uint64 = function(num){
+ var high = num / Math.pow(2, 32);
+ var low = num % Math.pow(2, 32);
+ this.bufferBuilder.append((high & 0xff000000) >>> 24);
+ this.bufferBuilder.append((high & 0x00ff0000) >>> 16);
+ this.bufferBuilder.append((high & 0x0000ff00) >>> 8);
+ this.bufferBuilder.append((high & 0x000000ff));
+ this.bufferBuilder.append((low & 0xff000000) >>> 24);
+ this.bufferBuilder.append((low & 0x00ff0000) >>> 16);
+ this.bufferBuilder.append((low & 0x0000ff00) >>> 8);
+ this.bufferBuilder.append((low & 0x000000ff));
+}
+
+Packer.prototype.pack_int8 = function(num){
+ this.bufferBuilder.append(num & 0xff);
+}
+
+Packer.prototype.pack_int16 = function(num){
+ this.bufferBuilder.append((num & 0xff00) >> 8);
+ this.bufferBuilder.append(num & 0xff);
+}
+
+Packer.prototype.pack_int32 = function(num){
+ this.bufferBuilder.append((num >>> 24) & 0xff);
+ this.bufferBuilder.append((num & 0x00ff0000) >>> 16);
+ this.bufferBuilder.append((num & 0x0000ff00) >>> 8);
+ this.bufferBuilder.append((num & 0x000000ff));
+}
+
+Packer.prototype.pack_int64 = function(num){
+ var high = Math.floor(num / Math.pow(2, 32));
+ var low = num % Math.pow(2, 32);
+ this.bufferBuilder.append((high & 0xff000000) >>> 24);
+ this.bufferBuilder.append((high & 0x00ff0000) >>> 16);
+ this.bufferBuilder.append((high & 0x0000ff00) >>> 8);
+ this.bufferBuilder.append((high & 0x000000ff));
+ this.bufferBuilder.append((low & 0xff000000) >>> 24);
+ this.bufferBuilder.append((low & 0x00ff0000) >>> 16);
+ this.bufferBuilder.append((low & 0x0000ff00) >>> 8);
+ this.bufferBuilder.append((low & 0x000000ff));
+}
+
+function _utf8Replace(m){
+ var code = m.charCodeAt(0);
+
+ if(code <= 0x7ff) return '00';
+ if(code <= 0xffff) return '000';
+ if(code <= 0x1fffff) return '0000';
+ if(code <= 0x3ffffff) return '00000';
+ return '000000';
+}
+
+function utf8Length(str){
+ if (str.length > 600) {
+ // Blob method faster for large strings
+ return (new Blob([str])).size;
+ } else {
+ return str.replace(/[^\u0000-\u007F]/g, _utf8Replace).length;
+ }
+}
+
+},{"./bufferbuilder":2}],2:[function(require,module,exports){
+var binaryFeatures = {};
+binaryFeatures.useBlobBuilder = (function(){
+ try {
+ new Blob([]);
+ return false;
+ } catch (e) {
+ return true;
+ }
+})();
+
+binaryFeatures.useArrayBufferView = !binaryFeatures.useBlobBuilder && (function(){
+ try {
+ return (new Blob([new Uint8Array([])])).size === 0;
+ } catch (e) {
+ return true;
+ }
+})();
+
+module.exports.binaryFeatures = binaryFeatures;
+var BlobBuilder = module.exports.BlobBuilder;
+if (typeof window != 'undefined') {
+ BlobBuilder = module.exports.BlobBuilder = window.WebKitBlobBuilder ||
+ window.MozBlobBuilder || window.MSBlobBuilder || window.BlobBuilder;
+}
+
+function BufferBuilder(){
+ this._pieces = [];
+ this._parts = [];
+}
+
+BufferBuilder.prototype.append = function(data) {
+ if(typeof data === 'number') {
+ this._pieces.push(data);
+ } else {
+ this.flush();
+ this._parts.push(data);
+ }
+};
+
+BufferBuilder.prototype.flush = function() {
+ if (this._pieces.length > 0) {
+ var buf = new Uint8Array(this._pieces);
+ if(!binaryFeatures.useArrayBufferView) {
+ buf = buf.buffer;
+ }
+ this._parts.push(buf);
+ this._pieces = [];
+ }
+};
+
+BufferBuilder.prototype.getBuffer = function() {
+ this.flush();
+ if(binaryFeatures.useBlobBuilder) {
+ var builder = new BlobBuilder();
+ for(var i = 0, ii = this._parts.length; i < ii; i++) {
+ builder.append(this._parts[i]);
+ }
+ return builder.getBlob();
+ } else {
+ return new Blob(this._parts);
+ }
+};
+
+module.exports.BufferBuilder = BufferBuilder;
+
+},{}],3:[function(require,module,exports){
+var BufferBuilderExports = require('./bufferbuilder');
+
+window.BufferBuilder = BufferBuilderExports.BufferBuilder;
+window.binaryFeatures = BufferBuilderExports.binaryFeatures;
+window.BlobBuilder = BufferBuilderExports.BlobBuilder;
+window.BinaryPack = require('./binarypack');
+
+},{"./binarypack":1,"./bufferbuilder":2}]},{},[3]);
+/**
+ * Light EventEmitter. Ported from Node.js/events.js
+ * Eric Zhang
+ */
+
+/**
+ * EventEmitter class
+ * Creates an object with event registering and firing methods
+ */
+function EventEmitter() {
+ // Initialise required storage variables
+ this._events = {};
+}
+
+var isArray = Array.isArray;
+
+
+EventEmitter.prototype.addListener = function(type, listener, scope, once) {
+ if ('function' !== typeof listener) {
+ throw new Error('addListener only takes instances of Function');
+ }
+
+ // To avoid recursion in the case that type == "newListeners"! Before
+ // adding it to the listeners, first emit "newListeners".
+ this.emit('newListener', type, typeof listener.listener === 'function' ?
+ listener.listener : listener);
+
+ if (!this._events[type]) {
+ // Optimize the case of one listener. Don't need the extra array object.
+ this._events[type] = listener;
+ } else if (isArray(this._events[type])) {
+
+ // If we've already got an array, just append.
+ this._events[type].push(listener);
+
+ } else {
+ // Adding the second element, need to change to array.
+ this._events[type] = [this._events[type], listener];
+ }
+
+};
+
+EventEmitter.prototype.on = EventEmitter.prototype.addListener;
+
+EventEmitter.prototype.once = function(type, listener, scope) {
+ if ('function' !== typeof listener) {
+ throw new Error('.once only takes instances of Function');
+ }
+
+ var self = this;
+ function g() {
+ self.removeListener(type, g);
+ listener.apply(this, arguments);
+ };
+
+ g.listener = listener;
+ self.on(type, g);
+
+ return this;
+};
+
+EventEmitter.prototype.removeListener = function(type, listener, scope) {
+ if ('function' !== typeof listener) {
+ throw new Error('removeListener only takes instances of Function');
+ }
+
+ // does not use listeners(), so no side effect of creating _events[type]
+ if (!this._events[type]) return this;
+
+ var list = this._events[type];
+
+ if (isArray(list)) {
+ var position = -1;
+ for (var i = 0, length = list.length; i < length; i++) {
+ if (list[i] === listener ||
+ (list[i].listener && list[i].listener === listener))
+ {
+ position = i;
+ break;
+ }
+ }
+
+ if (position < 0) return this;
+ list.splice(position, 1);
+ if (list.length == 0)
+ delete this._events[type];
+ } else if (list === listener ||
+ (list.listener && list.listener === listener))
+ {
+ delete this._events[type];
+ }
+
+ return this;
+};
+
+
+EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
+
+
+EventEmitter.prototype.removeAllListeners = function(type) {
+ if (arguments.length === 0) {
+ this._events = {};
+ return this;
+ }
+
+ // does not use listeners(), so no side effect of creating _events[type]
+ if (type && this._events && this._events[type]) this._events[type] = null;
+ return this;
+};
+
+EventEmitter.prototype.listeners = function(type) {
+ if (!this._events[type]) this._events[type] = [];
+ if (!isArray(this._events[type])) {
+ this._events[type] = [this._events[type]];
+ }
+ return this._events[type];
+};
+
+EventEmitter.prototype.emit = function(type) {
+ var type = arguments[0];
+ var handler = this._events[type];
+ if (!handler) return false;
+
+ if (typeof handler == 'function') {
+ switch (arguments.length) {
+ // fast cases
+ case 1:
+ handler.call(this);
+ break;
+ case 2:
+ handler.call(this, arguments[1]);
+ break;
+ case 3:
+ handler.call(this, arguments[1], arguments[2]);
+ break;
+ // slower
+ default:
+ var l = arguments.length;
+ var args = new Array(l - 1);
+ for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
+ handler.apply(this, args);
+ }
+ return true;
+
+ } else if (isArray(handler)) {
+ var l = arguments.length;
+ var args = new Array(l - 1);
+ for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
+
+ var listeners = handler.slice();
+ for (var i = 0, l = listeners.length; i < l; i++) {
+ listeners[i].apply(this, args);
+ }
+ return true;
+ } else {
+ return false;
+ }
+};
+
+
+
+
+var util = {
+ inherits: function(ctor, superCtor) {
+ ctor.super_ = superCtor;
+ ctor.prototype = Object.create(superCtor.prototype, {
+ constructor: {
+ value: ctor,
+ enumerable: false,
+ writable: true,
+ configurable: true
+ }
+ });
+ },
+ extend: function(dest, source) {
+ for(var key in source) {
+ if(source.hasOwnProperty(key)) {
+ dest[key] = source[key];
+ }
+ }
+ return dest;
+ },
+ pack: BinaryPack.pack,
+ unpack: BinaryPack.unpack,
+ setZeroTimeout: (function(global) {
+ var timeouts = [];
+ var messageName = 'zero-timeout-message';
+
+ // Like setTimeout, but only takes a function argument. There's
+ // no time argument (always zero) and no arguments (you have to
+ // use a closure).
+ function setZeroTimeoutPostMessage(fn) {
+ timeouts.push(fn);
+ global.postMessage(messageName, '*');
+ }
+
+ function handleMessage(event) {
+ if (event.source == global && event.data == messageName) {
+ if (event.stopPropagation) {
+ event.stopPropagation();
+ }
+ if (timeouts.length) {
+ timeouts.shift()();
+ }
+ }
+ }
+ if (global.addEventListener) {
+ global.addEventListener('message', handleMessage, true);
+ } else if (global.attachEvent) {
+ global.attachEvent('onmessage', handleMessage);
+ }
+ return setZeroTimeoutPostMessage;
+ }(this))
+};
+
+exports.util = util;
+
+
+function Stream() {
+ EventEmitter.call(this);
+}
+
+util.inherits(Stream, EventEmitter);
+
+Stream.prototype.pipe = function(dest, options) {
+ var source = this;
+
+ function ondata(chunk) {
+ if (dest.writable) {
+ if (false === dest.write(chunk) && source.pause) {
+ source.pause();
+ }
+ }
+ }
+
+ source.on('data', ondata);
+
+ function ondrain() {
+ if (source.readable && source.resume) {
+ source.resume();
+ }
+ }
+
+ dest.on('drain', ondrain);
+
+ // If the 'end' option is not supplied, dest.end() will be called when
+ // source gets the 'end' or 'close' events. Only dest.end() once.
+ if (!dest._isStdio && (!options || options.end !== false)) {
+ source.on('end', onend);
+ source.on('close', onclose);
+ }
+
+ var didOnEnd = false;
+ function onend() {
+ if (didOnEnd) return;
+ didOnEnd = true;
+
+ dest.end();
+ }
+
+
+ function onclose() {
+ if (didOnEnd) return;
+ didOnEnd = true;
+
+ dest.destroy();
+ }
+
+ // don't leave dangling pipes when there are errors.
+ function onerror(er) {
+ cleanup();
+ if (this.listeners('error').length === 0) {
+ throw er; // Unhandled stream error in pipe.
+ }
+ }
+
+ source.on('error', onerror);
+ dest.on('error', onerror);
+
+ // remove all the event listeners that were added.
+ function cleanup() {
+ source.removeListener('data', ondata);
+ dest.removeListener('drain', ondrain);
+
+ source.removeListener('end', onend);
+ source.removeListener('close', onclose);
+
+ source.removeListener('error', onerror);
+ dest.removeListener('error', onerror);
+
+ source.removeListener('end', cleanup);
+ source.removeListener('close', cleanup);
+
+ dest.removeListener('end', cleanup);
+ dest.removeListener('close', cleanup);
+ }
+
+ source.on('end', cleanup);
+ source.on('close', cleanup);
+
+ dest.on('end', cleanup);
+ dest.on('close', cleanup);
+
+ dest.emit('pipe', source);
+
+ // Allow for unix-like usage: A.pipe(B).pipe(C)
+ return dest;
+};
+
+exports.Stream = Stream;
+function BlobReadStream(source, options){
+ Stream.call(this);
+
+ options = util.extend({
+ readDelay: 0,
+ paused: false
+ }, options);
+
+ this._source = source;
+ this._start = 0;
+ this._readChunkSize = options.chunkSize || source.size;
+ this._readDelay = options.readDelay;
+
+ this.readable = true;
+ this.paused = options.paused;
+
+ this._read();
+}
+
+util.inherits(BlobReadStream, Stream);
+
+
+BlobReadStream.prototype.pause = function(){
+ this.paused = true;
+};
+
+BlobReadStream.prototype.resume = function(){
+ this.paused = false;
+ this._read();
+};
+
+BlobReadStream.prototype.destroy = function(){
+ this.readable = false;
+ clearTimeout(this._timeoutId);
+};
+
+BlobReadStream.prototype._read = function(){
+
+ var self = this;
+
+ function emitReadChunk(){
+ self._emitReadChunk();
+ }
+
+ var readDelay = this._readDelay;
+ if (readDelay !== 0){
+ this._timeoutId = setTimeout(emitReadChunk, readDelay);
+ } else {
+ util.setZeroTimeout(emitReadChunk);
+ }
+
+};
+
+BlobReadStream.prototype._emitReadChunk = function(){
+
+ if(this.paused || !this.readable) return;
+
+ var chunkSize = Math.min(this._source.size - this._start, this._readChunkSize);
+
+ if(chunkSize === 0){
+ this.readable = false;
+ this.emit("end");
+ return;
+ }
+
+ var sourceEnd = this._start + chunkSize;
+ var chunk = (this._source.slice || this._source.webkitSlice || this._source.mozSlice).call(this._source, this._start, sourceEnd);
+
+ this._start = sourceEnd;
+ this._read();
+
+ this.emit("data", chunk);
+
+};
+
+/*
+
+
+
+
+function BlobWriteStream(options){
+
+ stream.Stream.call(this);
+
+ options = _.extend({
+ onFull: onFull,
+ onEnd: function(){},
+ minBlockAllocSize: 0,
+ drainDelay:0
+ }, options);
+
+ this._onFull = options.onFull;
+ this._onEnd = options.onEnd;
+ this._onWrite = options.onWrite;
+
+ this._minBlockAllocSize = options.minBlockAllocSize;
+ this._maxBlockAllocSize = options.maxBlockAllocSize;
+ this._drainDelay = options.drainDelay;
+
+ this._buffer = new Buffer(options.minBlockAllocSize);
+ this._destination = this._buffer;
+ this._destinationPos = 0;
+
+ this._writeQueue = [];
+ this._pendingOnFull = false;
+ this._pendingQueueDrain = false;
+
+ this.writable = true;
+ this.bytesWritten = 0;
+}
+
+util.inherits(BlobWriteStream, stream.Stream);
+
+BlobWriteStream.prototype.getBuffer = function(){
+ return this._buffer;
+};
+
+BlobWriteStream.prototype.write = function(data, encoding){
+
+ if(!this.writable){
+ throw new Error("stream is not writable");
+ }
+
+ if(!Buffer.isBuffer(data)){
+ data = new Buffer(data, encoding);
+ }
+
+ if(data.length){
+ this._writeQueue.push(data);
+ }
+
+ this._commit();
+
+ return this._writeQueue.length === 0;
+};
+
+BlobWriteStream.prototype._commit = function(){
+
+ var self = this;
+
+ var destination = this._destination;
+ var writeQueue = this._writeQueue;
+
+ var startDestinationPos = this._destinationPos;
+
+ while(writeQueue.length && destination.length){
+
+ var head = writeQueue[0];
+
+ var copySize = Math.min(destination.length, head.length);
+
+ head.copy(destination, 0, 0, copySize);
+
+ head = head.slice(copySize);
+ destination = destination.slice(copySize);
+
+ this.bytesWritten += copySize;
+ this._destinationPos += copySize;
+
+ if(head.length === 0){
+ writeQueue.shift();
+ }
+ else{
+ writeQueue[0] = head;
+ }
+ }
+
+ this._destination = destination;
+
+ bytesCommitted = this._destinationPos - startDestinationPos;
+ if(bytesCommitted){
+ if(this._onWrite){
+
+ if(writeQueue.length){
+ this._pendingQueueDrain = true;
+ }
+
+ // By locking destination the buffer is frozen and the onWrite
+ // callback cannot miss any write commits
+ this._destination = emptyBuffer;
+
+ var consumer = this._onWrite;
+ this._onWrite = null;
+
+ consumer.call(this, function(nextCallback){
+ util.setZeroTimeout(function(){
+ self._destination = destination;
+ self._onWrite = nextCallback;
+ self._commit();
+ });
+ }, consumer);
+
+ return;
+ }
+ }
+
+ if(writeQueue.length){
+
+ this._pendingQueueDrain = true;
+ this._growBuffer();
+ }
+ else if(this._pendingQueueDrain){
+
+ this._pendingQueueDrain = false;
+
+ if(this._drainDelay !== 0){
+ setTimeout(function(){
+ self.emit("drain");
+ }, this._drainDelay);
+ }
+ else{
+ util.setZeroTimeout(function(){
+ self.emit("drain");
+ });
+ }
+ }
+};
+
+BlobWriteStream.prototype._growBuffer = function(){
+
+ var self = this;
+ var writeQueue = this._writeQueue;
+
+ var requestSize = this._minBlockAllocSize;
+
+ var maxBlockAllocSize = this._maxBlockAllocSize;
+ var add = (maxBlockAllocSize === undefined ? function(a, b){return a + b;} : function(a, b){return Math.min(a + b, maxBlockAllocSize);});
+
+ for(var i = 0, queueLength = writeQueue.length; i < queueLength; i++){
+ requestSize = add(requestSize, writeQueue[i].length);
+ }
+
+ // Prevent concurrent onFull callbacks
+ if(this._pendingOnFull){
+ return;
+ }
+ this._pendingOnFull = true;
+
+ this._onFull(this._buffer, requestSize, function(buffer, destination){
+ util.setZeroTimeout(function(){
+
+ self._pendingOnFull = false;
+
+ if(!destination){
+ if(self.writable){
+ self.emit("error", new Error("buffer is full"));
+ }
+ self.destroy();
+ return;
+ }
+
+ self._buffer = buffer;
+ self._destination = destination;
+
+ self._commit();
+ });
+ });
+};
+
+BlobWriteStream.prototype.end = function(data, encoding){
+
+ var self = this;
+
+ function _end(){
+ self.writable = false;
+ self._onEnd();
+ }
+
+ if(data){
+ if(this.write(data, encoding)){
+ _end();
+ }else{
+ self.writable = false;
+ this.once("drain", _end);
+ }
+ }
+ else{
+ _end();
+ }
+};
+
+BlobWriteStream.prototype.destroy = function(){
+ this.writable = false;
+ this._pendingQueueDrain = false;
+ this._writeQueue = [];
+};
+
+BlobWriteStream.prototype.consume = function(consume){
+
+ this._buffer = this._buffer.slice(consume);
+ this._destinationPos -= consume;
+};
+
+BlobWriteStream.prototype.getCommittedSlice = function(){
+ return this._buffer.slice(0, this._destinationPos);
+};
+
+function onFull(buffer, extraSize, callback){
+ var newBuffer = new Buffer(buffer.length + extraSize);
+ buffer.copy(newBuffer);
+ callback(newBuffer, newBuffer.slice(buffer.length));
+}
+*/
+exports.BlobReadStream = BlobReadStream;
+
+function BinaryStream(socket, id, create, meta) {
+ if (!(this instanceof BinaryStream)) return new BinaryStream(options);
+
+ var self = this;
+
+ Stream.call(this);
+
+
+ this.id = id;
+ this._socket = socket;
+
+ this.writable = true;
+ this.readable = true;
+ this.paused = false;
+
+ this._closed = false;
+ this._ended = false;
+
+ if(create) {
+ // This is a stream we are creating
+ this._write(1, meta, this.id);
+ }
+}
+
+util.inherits(BinaryStream, Stream);
+
+
+BinaryStream.prototype._onDrain = function() {
+ if(!this.paused) {
+ this.emit('drain');
+ }
+};
+
+BinaryStream.prototype._onClose = function() {
+ // Emit close event
+ if (this._closed) {
+ return;
+ }
+ this.readable = false;
+ this.writable = false;
+ this._closed = true;
+ this.emit('close');
+};
+
+BinaryStream.prototype._onError = function(error){
+ this.readable = false;
+ this.writable = false;
+ this.emit('error', error);
+};
+
+// Write stream
+
+BinaryStream.prototype._onPause = function() {
+ // Emit pause event
+ this.paused = true;
+ this.emit('pause');
+};
+
+BinaryStream.prototype._onResume = function() {
+ // Emit resume event
+ this.paused = false;
+ this.emit('resume');
+ this.emit('drain');
+};
+
+BinaryStream.prototype._write = function(code, data, bonus) {
+ if (this._socket.readyState !== this._socket.constructor.OPEN) {
+ return false;
+ }
+ var message = util.pack([code, data, bonus]);
+ return this._socket.send(message) !== false;
+};
+
+BinaryStream.prototype.write = function(data) {
+ if(this.writable) {
+ var out = this._write(2, data, this.id);
+ return !this.paused && out;
+ } else {
+ this.emit('error', new Error('Stream is not writable'));
+ return false;
+ }
+};
+
+BinaryStream.prototype.end = function() {
+ this._ended = true;
+ this.readable = false;
+ this._write(5, null, this.id);
+};
+
+BinaryStream.prototype.destroy = BinaryStream.prototype.destroySoon = function() {
+ this._onClose();
+ this._write(6, null, this.id);
+};
+
+
+// Read stream
+
+BinaryStream.prototype._onEnd = function() {
+ if(this._ended) {
+ return;
+ }
+ this._ended = true;
+ this.readable = false;
+ this.emit('end');
+};
+
+BinaryStream.prototype._onData = function(data) {
+ // Dispatch
+ this.emit('data', data);
+};
+
+BinaryStream.prototype.pause = function() {
+ this._onPause();
+ this._write(3, null, this.id);
+};
+
+BinaryStream.prototype.resume = function() {
+ this._onResume();
+ this._write(4, null, this.id);
+};
+
+
+function BinaryClient(socket, options) {
+ if (!(this instanceof BinaryClient)) return new BinaryClient(socket, options);
+
+ EventEmitter.call(this);
+
+ var self = this;
+
+ this._options = util.extend({
+ chunkSize: 40960
+ }, options);
+
+ this.streams = {};
+
+ if(typeof socket === 'string') {
+ this._nextId = 0;
+ this._socket = new WebSocket(socket);
+ } else {
+ // Use odd numbered ids for server originated streams
+ this._nextId = 1;
+ this._socket = socket;
+ }
+
+ this._socket.binaryType = 'arraybuffer';
+
+ this._socket.addEventListener('open', function(){
+ self.emit('open');
+ });
+ this._socket.addEventListener('error', function(error){
+ var ids = Object.keys(self.streams);
+ for (var i = 0, ii = ids.length; i < ii; i++) {
+ self.streams[ids[i]]._onError(error);
+ }
+ self.emit('error', error);
+ });
+ this._socket.addEventListener('close', function(code, message){
+ var ids = Object.keys(self.streams);
+ for (var i = 0, ii = ids.length; i < ii; i++) {
+ self.streams[ids[i]]._onClose();
+ }
+ self.emit('close', code, message);
+ });
+ this._socket.addEventListener('message', function(data, flags){
+ util.setZeroTimeout(function(){
+
+ // Message format
+ // [type, payload, bonus ]
+ //
+ // Reserved
+ // [ 0 , X , X ]
+ //
+ //
+ // New stream
+ // [ 1 , Meta , new streamId ]
+ //
+ //
+ // Data
+ // [ 2 , Data , streamId ]
+ //
+ //
+ // Pause
+ // [ 3 , null , streamId ]
+ //
+ //
+ // Resume
+ // [ 4 , null , streamId ]
+ //
+ //
+ // End
+ // [ 5 , null , streamId ]
+ //
+ //
+ // Close
+ // [ 6 , null , streamId ]
+ //
+
+ data = data.data;
+
+ try {
+ data = util.unpack(data);
+ } catch (ex) {
+ return self.emit('error', new Error('Received unparsable message: ' + ex));
+ }
+ if (!(data instanceof Array))
+ return self.emit('error', new Error('Received non-array message'));
+ if (data.length != 3)
+ return self.emit('error', new Error('Received message with wrong part count: ' + data.length));
+ if ('number' != typeof data[0])
+ return self.emit('error', new Error('Received message with non-number type: ' + data[0]));
+
+ switch(data[0]) {
+ case 0:
+ // Reserved
+ break;
+ case 1:
+ var meta = data[1];
+ var streamId = data[2];
+ var binaryStream = self._receiveStream(streamId);
+ self.emit('stream', binaryStream, meta);
+ break;
+ case 2:
+ var payload = data[1];
+ var streamId = data[2];
+ var binaryStream = self.streams[streamId];
+ if(binaryStream) {
+ binaryStream._onData(payload);
+ } else {
+ self.emit('error', new Error('Received `data` message for unknown stream: ' + streamId));
+ }
+ break;
+ case 3:
+ var streamId = data[2];
+ var binaryStream = self.streams[streamId];
+ if(binaryStream) {
+ binaryStream._onPause();
+ } else {
+ self.emit('error', new Error('Received `pause` message for unknown stream: ' + streamId));
+ }
+ break;
+ case 4:
+ var streamId = data[2];
+ var binaryStream = self.streams[streamId];
+ if(binaryStream) {
+ binaryStream._onResume();
+ } else {
+ self.emit('error', new Error('Received `resume` message for unknown stream: ' + streamId));
+ }
+ break;
+ case 5:
+ var streamId = data[2];
+ var binaryStream = self.streams[streamId];
+ if(binaryStream) {
+ binaryStream._onEnd();
+ } else {
+ self.emit('error', new Error('Received `end` message for unknown stream: ' + streamId));
+ }
+ break;
+ case 6:
+ var streamId = data[2];
+ var binaryStream = self.streams[streamId];
+ if(binaryStream) {
+ binaryStream._onClose();
+ } else {
+ self.emit('error', new Error('Received `close` message for unknown stream: ' + streamId));
+ }
+ break;
+ default:
+ self.emit('error', new Error('Unrecognized message type received: ' + data[0]));
+ }
+ });
+ });
+}
+
+util.inherits(BinaryClient, EventEmitter);
+
+BinaryClient.prototype.send = function(data, meta){
+ var stream = this.createStream(meta);
+ if(data instanceof Stream) {
+ data.pipe(stream);
+ } else if (util.isNode === true) {
+ if(Buffer.isBuffer(data)) {
+ (new BufferReadStream(data, {chunkSize: this._options.chunkSize})).pipe(stream);
+ } else {
+ stream.write(data);
+ }
+ } else if (util.isNode !== true) {
+ if(data.constructor == Blob || data.constructor == File) {
+ (new BlobReadStream(data, {chunkSize: this._options.chunkSize})).pipe(stream);
+ } else if (data.constructor == ArrayBuffer) {
+ var blob;
+ if(binaryFeatures.useArrayBufferView) {
+ data = new Uint8Array(data);
+ }
+ if(binaryFeatures.useBlobBuilder) {
+ var builder = new BlobBuilder();
+ builder.append(data);
+ blob = builder.getBlob()
+ } else {
+ blob = new Blob([data]);
+ }
+ (new BlobReadStream(blob, {chunkSize: this._options.chunkSize})).pipe(stream);
+ } else if (typeof data === 'object' && 'BYTES_PER_ELEMENT' in data) {
+ var blob;
+ if(!binaryFeatures.useArrayBufferView) {
+ // Warn
+ data = data.buffer;
+ }
+ if(binaryFeatures.useBlobBuilder) {
+ var builder = new BlobBuilder();
+ builder.append(data);
+ blob = builder.getBlob()
+ } else {
+ blob = new Blob([data]);
+ }
+ (new BlobReadStream(blob, {chunkSize: this._options.chunkSize})).pipe(stream);
+ } else {
+ stream.write(data);
+ }
+ }
+ return stream;
+};
+
+BinaryClient.prototype._receiveStream = function(streamId){
+ var self = this;
+ var binaryStream = new BinaryStream(this._socket, streamId, false);
+ binaryStream.on('close', function(){
+ delete self.streams[streamId];
+ });
+ this.streams[streamId] = binaryStream;
+ return binaryStream;
+};
+
+BinaryClient.prototype.createStream = function(meta){
+ if(this._socket.readyState !== WebSocket.OPEN) {
+ throw new Error('Client is not yet connected or has closed');
+ return;
+ }
+ var self = this;
+ var streamId = this._nextId;
+ this._nextId += 2;
+ var binaryStream = new BinaryStream(this._socket, streamId, true, meta);
+ binaryStream.on('close', function(){
+ delete self.streams[streamId];
+ });
+ this.streams[streamId] = binaryStream;
+ return binaryStream;
+};
+
+BinaryClient.prototype.close = BinaryClient.prototype.destroy = function() {
+ this._socket.close();
+};
+
+exports.BinaryClient = BinaryClient;
+
+})(this);
+
+</script>
+\ No newline at end of file
diff --git a/app/elements/p2p-network/connection-wrapper.html b/app/elements/p2p-network/connection-wrapper.html
@@ -0,0 +1,59 @@
+<link rel="import" href="p2p-network.html">
+<link rel="import" href="web-socket.html">
+<dom-module id="connection-wrapper">
+ <template>
+ <p2p-network id="p2p" me="{{me}}"></p2p-network>
+ <web-socket id="ws" me="{{me}}"></web-socket>
+ </template>
+ <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;
+
+ function rtcConnectionSupported(peerId) {
+ return webRTCSupported && (peerId.indexOf('rtc_') === 0);
+ }
+ Polymer({
+ is: 'connection-wrapper',
+ properties: {
+ me: {
+ notify: true,
+ value: (webRTCSupported ? 'rtc_' : 'ws_') + guid()
+ }
+ },
+ behaviors: [Chat.FileTransferProtocol],
+ _sendFile: function(toPeer, file) {
+ if (!rtcConnectionSupported(toPeer)) {
+ this.$.ws._sendFile(toPeer, file);
+ } else {
+ this.$.p2p._sendFile(toPeer, file);
+ }
+ },
+ _sendSystemEvent: function(toPeer, event) {
+ console.log('system event', toPeer, event);
+ if (!rtcConnectionSupported(toPeer)) {
+ this.$.ws._sendSystemEvent(toPeer, event);
+ } else {
+ this.$.p2p._sendSystemEvent(toPeer, event);
+ }
+ },
+ connectToPeer: function(toPeer, callback) {
+ if (!rtcConnectionSupported(toPeer)) {
+ callback();
+ } else {
+ this.$.p2p.connectToPeer(toPeer,callback);
+ }
+ },
+ });
+ })();
+ </script>
+</dom-module>
diff --git a/app/elements/p2p-network/file-transfer-protocol.html b/app/elements/p2p-network/file-transfer-protocol.html
@@ -0,0 +1,137 @@
+<script>
+'use strict';
+window.Chat = window.Chat || {};
+Chat.FileTransferProtocol = {
+ properties: {
+ loading: {
+ type: Boolean,
+ notify: true,
+ value: false,
+ observer: '_loadingChanged'
+ },
+ buddies: {
+ notify: true
+ }
+ },
+ listeners: {
+ 'system-event': '_onSystemMsg',
+ 'file-received': '_onFileReceived',
+ },
+ _onSystemMsg: function(event) {
+ var msg = event.detail;
+ console.log('FTP received sysMsg:', msg);
+
+ switch (msg.type) {
+ case 'offer':
+ this._onOffered(msg);
+ break;
+ case 'decline':
+ this._onDeclined(msg);
+ break;
+ case 'accept':
+ this._onAccepted(msg);
+ break;
+ case 'transfer':
+ this._onTransfer(msg);
+ break;
+ case 'received':
+ this._onReceived(msg);
+ break;
+ case 'buddies':
+ this._onBuddies(msg);
+ break;
+ }
+ },
+ sendFile: function(peerId, file) {
+ this.set('loading', true);
+ this.fileToSend = file;
+ this.fire('file-offered', {
+ to: peerId
+ });
+ this.connectToPeer(peerId, function() {
+ this._offer(peerId, file);
+ }.bind(this));
+
+ //set 15sec timeout
+ this._timeoutTimer = this.async(function() {
+ this._onError();
+ }, 15000);
+ },
+ _offer: function(toPeer, file) {
+ console.log('FTP offer file:', file, 'To:', toPeer);
+
+ this._sendSystemEvent(toPeer, {
+ type: 'offer',
+ name: file.name
+ });
+ },
+ _onOffered: function(offer) {
+ console.log('FTP offered file:', offer.name, 'From:', offer.from);
+ this.fire('file-offer', {
+ from: offer.from,
+ name: offer.name
+ });
+ },
+ decline: function(offer) {
+ this._sendSystemEvent(offer.from, {
+ type: 'decline',
+ name: offer.name
+ });
+ },
+ _onDeclined: function(offer) {
+ this.cancelAsync(this._timeoutTimer);
+ delete this.fileToSend;
+ this.set('loading', false);
+ this.fire('file-declined', offer);
+ },
+ accept: function(offer) {
+ this._sendSystemEvent(offer.from, {
+ type: 'accept',
+ name: offer.name
+ });
+ this.fire('download-started', {
+ from: offer.from
+ });
+ },
+ _onAccepted: function(offer) {
+ this.cancelAsync(this._timeoutTimer);
+ this._sendSystemEvent(offer.from, {
+ type: 'transfer',
+ name: offer.name
+ });
+ this.fire('upload-started', {
+ to: offer.from
+ });
+ this._sendFile(offer.from, this.fileToSend);
+ },
+ _onTransfer: function() {
+ this.loading = true;
+ },
+ _onFileReceived: function(event) {
+ var file = event.detail;
+ this.loading = false;
+ this._sendSystemEvent(file.from, {
+ type: 'received',
+ name: file.name
+ });
+ this.fire('download-complete', {
+ from: file.from
+ });
+ console.log('FTP received:', file);
+ },
+ _onReceived: function(offer) {
+ this.loading = false;
+ this.fire('upload-complete', offer);
+ },
+ _onError: function() {
+ this.loading = false;
+ this.fire('upload-error');
+ },
+ _loadingChanged: function(loading) {
+ window.anim(loading);
+ },
+ _onBuddies: function(msg) {
+ this.set('buddies', msg.buddies);
+ }
+};
+</script>
diff --git a/app/elements/p2p-network/p2p-network.html b/app/elements/p2p-network/p2p-network.html
@@ -1,4 +1,5 @@
-<script src="../../../bower_components/peerjs/peer.min.js"></script>
+<script src="../../bower_components/peerjs/peer.min.js"></script>
+<link rel="import" href="file-transfer-protocol.html">
<dom-module id="p2p-network">
<template>
</template>
@@ -30,7 +31,7 @@
path: 'peerjs',
secure: true
};
- this._peer = new Peer(options);
+ this._peer = new Peer(this.me,options);
this._peer.on('open', function(id) {
console.log('My peer ID is: ' + id);
this.set('me', id);
@@ -65,12 +66,22 @@
if (c.label === 'file') {
c.on('data', function(data) {
- console.log('received!', data);
+ console.log(data);
+ var dataView = new Uint8Array(data.file);
+ var dataBlob = new Blob([dataView]);
this.fire('file-received', {
- peer: peer,
- dataURI: data.dataURI,
+ from: peer,
+ blob: dataBlob,
name: data.name,
});
+
+ }.bind(this));
+ }
+
+ if (c.label === 'system') {
+ c.on('data', function(data) {
+ data.from = peer;
+ this.fire('system-event', data);
}.bind(this));
}
},
@@ -78,15 +89,32 @@
function request(requestedPeer, callback) {
return function() {
+ //system messages channel
+ var s = this._peer.connect(requestedPeer, {
+ label: 'system'
+ });
+
+ s.on('open', function() {
+ this.connect(s);
+ if (callback) {
+ callback();
+ }
+ }.bind(this));
+ s.on('error', function(err) {
+ console.log(err);
+ if (err.message.indexOf('Connection is not open') > -1) {
+ console.err('Handle this error!!');
+ }
+ });
+
+ //files channel
var f = this._peer.connect(requestedPeer, {
label: 'file',
reliable: true
});
f.on('open', function() {
this.connect(f);
- if (callback) {
- callback();
- }
+
}.bind(this));
f.on('error', function(err) {
console.log(err);
@@ -98,7 +126,6 @@
callback();
return;
}
- this.set('loading', true);
if (this._peerOpen) {
request(requestedPeer, callback).bind(this)();
} else {
@@ -107,7 +134,7 @@
};
}()),
- sendFile: function(peerId, file) {
+ _sendFile: function(peerId, file) {
var conns = this._peer.connections[peerId];
if (conns) {
conns.forEach(function(conn) {
@@ -115,7 +142,17 @@
conn.send(file);
console.log('file send');
}
- });
+ }.bind(this));
+ }
+ },
+ _sendSystemEvent: function(peerId, msg) {
+ var conns = this._peer.connections[peerId];
+ if (conns) {
+ conns.forEach(function(conn) {
+ if (conn.label === 'system') {
+ conn.send(msg);
+ }
+ }.bind(this));
}
}
});
diff --git a/app/elements/p2p-network/web-socket.html b/app/elements/p2p-network/web-socket.html
@@ -0,0 +1,82 @@
+<link rel="import" href="binaryjs.html">
+<dom-module id="web-socket">
+ <template>
+ <style>
+ :host {
+ display: block;
+ }
+ </style>
+ </template>
+ <script>
+ 'use strict';
+ Polymer({
+ is: 'web-socket',
+ attached: function() {
+ this.init();
+ },
+ init: function() {
+ var websocketUrl = (window.location.protocol === 'https:' ? 'wss://' : 'ws://') + document.location.hostname + ':9001';
+ 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);
+ if (data.isSystemEvent) {
+ if (meta) {
+ data.from = meta.from;
+ }
+ this.fire('system-event', data);
+ } else {
+ parts.push(data);
+ }
+ }.bind(this));
+ // when finished, set it as the background image
+ stream.on('end', function() {
+ var blob = new Blob(parts, {
+ type: meta.type
+ });
+ console.log('file received', blob, meta);
+ this.fire('file-received', {
+ blob: blob,
+ name: meta.name,
+ from: meta.from
+ });
+ }.bind(this));
+ }.bind(this));
+ this.client.on('open', function(e) {
+ console.log(e);
+ this.client.send({}, {
+ handshake: this.me
+ });
+ }.bind(this));
+ this.client.on('error', function(e) {
+ console.log(e);
+ });
+ this.client.on('close', function(e) {
+ console.log(e);
+ //try to reconnect after 3s
+ this.async(this.init, 3000);
+ }.bind(this));
+ },
+ _sendFile: function(toPeer, file) {
+ console.log('send file!', file);
+ this.client.send(file.file, {
+ name: file.file.name,
+ type: file.file.type,
+ toPeer: toPeer
+ });
+ },
+ connectToPeer: function(peer, callback) {
+ callback();
+ },
+ _sendSystemEvent: function(toPeer, event) {
+ console.log('system event', toPeer, event);
+ event.isSystemEvent = true;
+ this.client.send(event, {
+ toPeer: toPeer
+ });
+ }
+ });
+ </script>
+</dom-module>
diff --git a/app/favicon.ico b/app/favicon.ico
Binary files differ.
diff --git a/app/images/touch/apple-touch-icon.png b/app/images/touch/apple-touch-icon.png
Binary files differ.
diff --git a/app/images/touch/chrome-splashscreen-icon-384x384.png b/app/images/touch/chrome-splashscreen-icon-384x384.png
Binary files differ.
diff --git a/app/images/touch/chrome-touch-icon-192x192.png b/app/images/touch/chrome-touch-icon-192x192.png
Binary files differ.
diff --git a/app/images/touch/icon-128x128.png b/app/images/touch/icon-128x128.png
Binary files differ.
diff --git a/app/images/touch/logo.png b/app/images/touch/logo.png
Binary files differ.
diff --git a/app/images/touch/ms-icon-144x144.png b/app/images/touch/ms-icon-144x144.png
Binary files differ.
diff --git a/app/images/touch/ms-touch-icon-144x144-precomposed.png b/app/images/touch/ms-touch-icon-144x144-precomposed.png
Binary files differ.
diff --git a/app/index.html b/app/index.html
@@ -4,12 +4,12 @@
<head>
<meta charset="utf-8">
<meta name="description" content="">
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <meta name="generator" content="Share With Me!">
- <title>Share With Me!</title>
+ <meta name="viewport" content="initial-scale=1,user-scalable=no,maximum-scale=1">
+ <meta name="generator" content="SnapDrop!">
+ <title>SnapDrop!</title>
<!-- Place favicon.ico in the `app/` directory -->
<!-- Chrome for Android theme color -->
- <meta name="theme-color" content="#2E3AA1">
+ <meta name="theme-color" content="#3367d6">
<!-- Web Application Manifest -->
<link rel="manifest" href="manifest.json">
<!-- Tile color for Win8 -->
@@ -21,7 +21,7 @@
<!-- 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="Share With Me!">
+ <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,23 +29,23 @@
<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"></script>
+ <script src="bower_components/webcomponentsjs/webcomponents-lite.js" async></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">
- <!-- For shared styles, shared-styles.html import in elements.html -->
- <style is="custom-style" include="shared-styles"></style>
+ <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 unresolved class="fullbleed layout vertical">
+<body class="fullbleed layout vertical" loading>
+ <script src="scripts/animated-bg.js" inline></script>
<span id="browser-sync-binding"></span>
<template is="dom-bind" id="app">
- <buddy-finder me="{{me}}"></buddy-finder>
- <p2p-network me="{{me}}"></p2p-network>
+ <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>
<file-receiver></file-receiver>
- <paper-toast id="toast">
- <span class="toast-hide-button" role="button" tabindex="0" onclick="app.$.toast.hide()">Ok</span>
+ <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.">
diff --git a/app/manifest.json b/app/manifest.json
@@ -1,28 +1,30 @@
{
- "name": "Share With Me",
- "short_name": "Share With Me",
- "icons": [{
+ "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": "#3E4EB8",
- "display": "standalone",
- "theme_color": "#2E3AA1"
+ }],
+ "background_color": "#3367d6",
+ "start_url": "index.html",
+ "display": "standalone",
+ "theme_color": "#3367d6",
+ "orientation": "portrait"
}
diff --git a/app/scripts/animated-bg.js b/app/scripts/animated-bg.js
@@ -0,0 +1,64 @@
+'use strict';
+(function() {
+ var requestAnimFrame = (function() {
+ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
+ function(callback) {
+ window.setTimeout(callback, 1000 / 60);
+ };
+ })();
+ var c = document.createElement('canvas');
+ document.body.appendChild(c);
+ var style = c.style;
+ style.width = '100%';
+ style.position = 'absolute';
+ var ctx = c.getContext('2d');
+ var x0, y0, w, h, dw;
+
+ function init() {
+ w = window.innerWidth;
+ h = window.innerHeight;
+ c.width = w;
+ c.height = h;
+ x0 = w / 2;
+ y0 = h - 103;
+ dw = Math.max(w, h, 1000) / 13;
+ drawCircles();
+ }
+ window.onresize = init;
+
+ function drawCicrle(radius) {
+ ctx.beginPath();
+ var color = Math.round(255 * (1- radius / Math.max(w, h)));
+ ctx.strokeStyle = 'rgba(' + color + ',' + color + ',' + color + ',0.1)';
+ ctx.arc(x0, y0, radius, 0, 2 * Math.PI);
+ ctx.stroke();
+ ctx.lineWidth = 2;
+ }
+
+ var step = 0;
+
+ function drawCircles() {
+ ctx.clearRect(0, 0, w, h);
+ for (var i = 0; i < 8; i++) {
+ drawCicrle(dw * i + step % dw);
+ }
+ step += 1;
+ }
+
+ var loading = true;
+
+ function animate() {
+ if (loading || step % dw < dw - 5) {
+ requestAnimFrame(function() {
+ drawCircles();
+ animate();
+ });
+ }
+ }
+ window.anim = function(l) {
+ loading = l;
+ animate();
+ };
+ init();
+ animate();
+}());
diff --git a/app/scripts/app.js b/app/scripts/app.js
@@ -21,17 +21,25 @@
}
};
+ app.displayToast = function(msg) {
+ var toast = Polymer.dom(document).querySelector('#toast');
+ toast.text = msg;
+ toast.show();
+ };
+
// Listen for template bound event to know when bindings
// have resolved and content has been stamped to the page
app.addEventListener('dom-change', function() {
console.log('Our app is ready to rock!');
+ app.p2p = document.querySelector('connection-wrapper');
});
// See https://github.com/Polymer/polymer/issues/1381
window.addEventListener('WebComponentsReady', function() {
// imports are loaded and elements have been registered
- app.p2p = document.querySelector('p2p-network');
});
+
+
})(document);
diff --git a/app/styles/app-theme.html b/app/styles/app-theme.html
@@ -1,214 +1,31 @@
-<!--
-@license
-Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
-This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
-The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
-The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
-Code distributed by Google as part of the polymer project is also
-subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
--->
-
<link rel="import" href="../bower_components/polymer/polymer.html">
-
<style is="custom-style">
-
- /*
- Polymer includes a shim for CSS Custom Properties that we can use for application theming.
- Below, you'll find the default palette for the Share With Me layout. Feel free to play
- with changing the colors used or generate your own palette of colours at MaterialPalette.com.
-
- See https://www.polymer-project.org/1.0/docs/devguide/styling.html#xscope-styling-details
- for further information on custom CSS properties.
- */
-
- /* Application theme */
-
- :root {
+:root {
--dark-primary-color: #303F9F;
--default-primary-color: #3F51B5;
--light-primary-color: #C5CAE9;
- --text-primary-color: #ffffff; /*text/icons*/
+ --text-primary-color: #ffffff;
+ /*text/icons*/
--accent-color: #FF4081;
--primary-background-color: #c5cae9;
--primary-text-color: #212121;
--secondary-text-color: #727272;
--disabled-text-color: #bdbdbd;
--divider-color: #B6B6B6;
-
/* Components */
-
/* paper-drawer-panel */
--drawer-menu-color: #ffffff;
--drawer-border-color: 1px solid #ccc;
--drawer-toolbar-border-color: 1px solid rgba(0, 0, 0, 0.22);
-
/* paper-menu */
--paper-menu-background-color: #fff;
--menu-link-color: #111111;
- }
-
- /* General styles */
-
- #drawerToolbar {
- color: var(--secondary-text-color);
- background-color: var(--drawer-menu-color);
- border-bottom: var(--drawer-toolbar-border-color);
- }
-
- paper-scroll-header-panel {
- height: 100%;
- }
-
- paper-material {
- border-radius: 2px;
- height: 100%;
- padding: 16px 0 16px 0;
- width: calc(98.66% - 16px);
- margin: 16px auto;
- background: white;
- }
-
- paper-menu iron-icon {
- margin-right: 33px;
- opacity: 0.54;
- }
-
- .paper-menu > .iron-selected {
- color: var(--default-primary-color);
- }
-
- paper-menu a {
- @apply(--layout-horizontal);
- @apply(--layout-center);
- text-decoration: none;
- color: var(--menu-link-color);
- font-family: 'Roboto', 'Noto', sans-serif;
- -webkit-font-smoothing: antialiased;
- text-rendering: optimizeLegibility;
- font-size: 14px;
- font-weight: 400;
- line-height: 24px;
- min-height: 48px;
- padding: 0 16px;
- }
-
- paper-toolbar.tall .app-name {
- font-size: 40px;
- font-weight: 300;
- /* Required for main area's paper-scroll-header-panel custom condensing transformation */
- -webkit-transform-origin: left center;
- transform-origin: left center;
- }
-
- #mainToolbar .middle-container {
- height: 100%;
- margin-left: 48px;
- }
-
- #mainToolbar:not(.tall) .middle {
- font-size: 18px;
- padding-bottom: 0;
- }
-
- #mainToolbar .bottom {
- margin-left: 48px;
- /* Required for main area's paper-scroll-header-panel custom condensing transformation */
- -webkit-transform-origin: left center;
- transform-origin: left center;
- }
-
- /* Height of the scroll area */
- .content {
- height: 900px;
- }
-
- #toast .toast-hide-button {
- color: #eeff41;
- margin: 10px;
- }
-
- /* Breakpoints */
-
- /* Small */
- @media (max-width: 600px) {
-
- paper-material {
- --menu-container-display: none;
- width: calc(97.33% - 32px);
- padding-left: 16px;
- padding-right: 16px;
- }
-
- paper-toolbar.tall .app-name {
- font-size: 24px;
- font-weight: 400;
- }
-
- #drawer .paper-toolbar {
- margin-left: 16px;
- }
-
- }
-
- /* Tablet+ */
- @media (min-width: 601px) {
-
- paper-material {
- width: calc(98% - 46px);
- margin-bottom: 32px;
- padding-left: 30px;
- padding-right: 30px;
- }
-
- #drawer.paper-drawer-panel > [drawer] {
- border-right: 1px solid rgba(0, 0, 0, 0.14);
- }
-
- iron-pages {
- padding: 48px 62px;
- }
-
- }
-
- /* Material Design Adaptive Breakpoints */
- /*
- Below you'll find CSS media queries based on the breakpoint guidance
- published by the Material Design team. You can choose to use, customise
- or remove these breakpoints based on your needs.
-
- http://www.google.com/design/spec/layout/adaptive-ui.html#adaptive-ui-breakpoints
- */
-
- /* mobile-small */
- @media all and (min-width: 0) and (max-width: 360px) and (orientation: portrait) { }
- /* mobile-large */
- @media all and (min-width: 361px) and (orientation: portrait) { }
- /* mobile-small-landscape */
- @media all and (min-width: 0) and (max-width: 480px) and (orientation: landscape) { }
- /* mobile-large-landscape */
- @media all and (min-width: 481px) and (orientation: landscape) { }
- /* tablet-small-landscape */
- @media all and (min-width: 600px) and (max-width: 960px) and (orientation: landscape) { }
- /* tablet-large-landscape */
- @media all and (min-width: 961px) and (orientation: landscape) { }
- /* tablet-small */
- @media all and (min-width: 600px) and (orientation: portrait) { }
- /* tablet-large */
- @media all and (min-width: 601px) and (max-width: 840px) and (orientation : portrait) { }
- /* desktop-x-small-landscape */
- @media all and (min-width: 0) and (max-width: 480px) and (orientation: landscape) { }
- /* desktop-x-small */
- @media all and (min-width: 0) and (max-width: 480px) and (max-aspect-ratio: 4/3) { }
- /* desktop-small-landscape */
- @media all and (min-width: 481px) and (max-width: 840px) and (orientation: landscape) { }
- /* desktop-small */
- @media all and (min-width: 481px) and (max-width: 840px) and (max-aspect-ratio: 4/3) { }
- /* desktop-medium-landscape */
- @media all and (min-width: 841px) and (max-width: 1280px) and (orientation: landscape) { }
- /* desktop-medium */
- @media all and (min-width: 841px) and (max-width: 1280px) and (max-aspect-ratio: 4/3) { }
- /* desktop-large */
- @media all and (min-width: 1281px) and (max-width: 1600px) { }
- /* desktop-xlarge */
- @media all and (min-width: 1601px) and (max-width: 1920px) { }
+}
+
+paper-progress {
+ width: 100%;
+ z-index: 1;
+ position: absolute;
+ top: 0;
+}
</style>
diff --git a/app/styles/icons.html b/app/styles/icons.html
@@ -0,0 +1,37 @@
+<link rel="import" href="../bower_components/iron-iconset-svg/iron-iconset-svg.html">
+<iron-iconset-svg name="chat" size="24">
+ <svg>
+ <defs>
+ <g id="notifications-off">
+ <path d="M11.5 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zM18 10.5c0-3.07-2.13-5.64-5-6.32V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5v.68c-.51.12-.99.32-1.45.56L18 14.18V10.5zm-.27 8.5l2 2L21 19.73 4.27 3 3 4.27l2.92 2.92C5.34 8.16 5 9.29 5 10.5V16l-2 2v1h14.73z" />
+ </g>
+ <g id="share">
+ <path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31 2.92-2.92s-1.31-2.92-2.92-2.92z" />
+ </g>
+ <g id="call">
+ <path d="M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z" />
+ </g>
+ <g id="wifi-tethering">
+ <path d="M12 11c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 2c0-3.31-2.69-6-6-6s-6 2.69-6 6c0 2.22 1.21 4.15 3 5.19l1-1.74c-1.19-.7-2-1.97-2-3.45 0-2.21 1.79-4 4-4s4 1.79 4 4c0 1.48-.81 2.75-2 3.45l1 1.74c1.79-1.04 3-2.97 3-5.19zM12 3C6.48 3 2 7.48 2 13c0 3.7 2.01 6.92 4.99 8.65l1-1.73C5.61 18.53 4 15.96 4 13c0-4.42 3.58-8 8-8s8 3.58 8 8c0 2.96-1.61 5.53-4 6.92l1 1.73c2.99-1.73 5-4.95 5-8.65 0-5.52-4.48-10-10-10z" />
+ </g>
+ <g id="attach-file">
+ <path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z" />
+ </g>
+ <g id="desktop-mac">
+ <path d="M21 2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h7l-2 3v1h8v-1l-2-3h7c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 12H3V4h18v10z" />
+ </g>
+ <g id="desktop-windows">
+ <path d="M21 2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h7v2H8v2h8v-2h-2v-2h7c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H3V4h18v12z" />
+ </g>
+ <g id="smartphone">
+ <path d="M17 1.01L7 1c-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-1.99-2-1.99zM17 19H7V5h10v14z" />
+ </g>
+ <g id="phone-iphone">
+ <path d="M15.5 1h-8C6.12 1 5 2.12 5 3.5v17C5 21.88 6.12 23 7.5 23h8c1.38 0 2.5-1.12 2.5-2.5v-17C18 2.12 16.88 1 15.5 1zm-4 21c-.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.5zm4.5-4H7V4h9v14z" />
+ </g>
+ <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>
+ </defs>
+ </svg>
+</iron-iconset-svg>
diff --git a/app/styles/main.css b/app/styles/main.css
@@ -1,14 +1,12 @@
-/*
-Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
-This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
-The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
-The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
-Code distributed by Google as part of the polymer project is also
-subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-*/
+html,
+body {
+ height: 100%;
+ width: 100%;
+}
body {
- background: #fafafa;
- font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial, sans-serif;
- color: #333;
+ background: #fafafa;
+ font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+ color: #333;
+ -webkit-font-smoothing: antialiased;
}
diff --git a/app/styles/shared-styles.html b/app/styles/shared-styles.html
@@ -1,23 +0,0 @@
-<link rel="import" href="../bower_components/polymer/polymer.html">
-<link rel="import" href="../bower_components/paper-styles/typography.html">
-
-<!-- shared styles for all elements and index.html -->
-<dom-module id="shared-styles">
- <template>
- <style>
- .page-title {
- @apply(--paper-font-display2);
- }
-
- paper-menu a > *, paper-menu paper-item > *, paper-menu paper-icon-item > * {
- pointer-events: none;
- }
-
- @media (max-width: 600px) {
- .page-title {
- font-size: 24px!important;
- }
- }
- </style>
- </template>
-</dom-module>
diff --git a/gulpfile.js b/gulpfile.js
@@ -1,5 +1,3 @@
-
-
'use strict';
// Include Gulp & tools we'll use
@@ -17,175 +15,185 @@ var historyApiFallback = require('connect-history-api-fallback');
var packageJson = require('./package.json');
var crypto = require('crypto');
var ensureFiles = require('./tasks/ensure-files.js');
+var inlinesource = require('gulp-inline-source');
// var ghPages = require('gulp-gh-pages');
var AUTOPREFIXER_BROWSERS = [
- 'ie >= 10',
- 'ie_mob >= 10',
- 'ff >= 30',
- 'chrome >= 34',
- 'safari >= 7',
- 'opera >= 23',
- 'ios >= 7',
- 'android >= 4.4',
- 'bb >= 10'
+ 'ie >= 10',
+ 'ie_mob >= 10',
+ 'ff >= 30',
+ 'chrome >= 34',
+ 'safari >= 7',
+ 'opera >= 23',
+ 'ios >= 7',
+ 'android >= 4.4',
+ 'bb >= 10'
];
var DIST = 'dist';
var dist = function(subpath) {
- return !subpath ? DIST : path.join(DIST, subpath);
+ return !subpath ? DIST : path.join(DIST, subpath);
};
var styleTask = function(stylesPath, srcs) {
- return gulp.src(srcs.map(function(src) {
- return path.join('app', stylesPath, src);
- }))
- .pipe($.changed(stylesPath, {extension: '.css'}))
- .pipe($.autoprefixer(AUTOPREFIXER_BROWSERS))
- .pipe(gulp.dest('.tmp/' + stylesPath))
- .pipe($.minifyCss())
- .pipe(gulp.dest(dist(stylesPath)))
- .pipe($.size({title: stylesPath}));
+ return gulp.src(srcs.map(function(src) {
+ return path.join('app', stylesPath, src);
+ }))
+ .pipe($.changed(stylesPath, {
+ extension: '.css'
+ }))
+ .pipe($.autoprefixer(AUTOPREFIXER_BROWSERS))
+ .pipe(gulp.dest('.tmp/' + stylesPath))
+ .pipe($.minifyCss())
+ .pipe(gulp.dest(dist(stylesPath)))
+ .pipe($.size({
+ title: stylesPath
+ }));
};
var imageOptimizeTask = function(src, dest) {
- return gulp.src(src)
- .pipe($.imagemin({
- progressive: true,
- interlaced: true
- }))
- .pipe(gulp.dest(dest))
- .pipe($.size({title: 'images'}));
+ return gulp.src(src)
+ .pipe($.imagemin({
+ progressive: true,
+ interlaced: true
+ }))
+ .pipe(gulp.dest(dest))
+ .pipe($.size({
+ title: 'images'
+ }));
};
var optimizeHtmlTask = function(src, dest) {
- var assets = $.useref.assets({
- searchPath: ['.tmp', 'app']
- });
-
- return gulp.src(src)
- .pipe(assets)
- // Concatenate and minify JavaScript
- .pipe($.if('*.js', $.uglify({
- preserveComments: 'some'
- })))
- // Concatenate and minify styles
- // In case you are still using useref build blocks
- .pipe($.if('*.css', $.minifyCss()))
- .pipe(assets.restore())
- .pipe($.useref())
- // Minify any HTML
- .pipe($.if('*.html', $.minifyHtml({
- quotes: true,
- empty: true,
- spare: true
- })))
- // Output files
- .pipe(gulp.dest(dest))
- .pipe($.size({
- title: 'html'
- }));
+ var assets = $.useref.assets({
+ searchPath: ['.tmp', 'app']
+ });
+
+ return gulp.src(src)
+ .pipe(assets)
+ // Concatenate and minify JavaScript
+ .pipe($.if('*.js', $.uglify({
+ preserveComments: 'some'
+ })))
+ // Concatenate and minify styles
+ // In case you are still using useref build blocks
+ .pipe($.if('*.css', $.minifyCss()))
+ .pipe(assets.restore())
+ .pipe($.useref())
+ // Minify any HTML
+ .pipe($.if('*.html', $.minifyHtml({
+ quotes: true,
+ empty: true,
+ spare: true
+ })))
+ .pipe($.if('*.html', inlinesource()))
+ // Output files
+ .pipe(gulp.dest(dest))
+ .pipe($.size({
+ title: 'html'
+ }));
};
// Compile and automatically prefix stylesheets
gulp.task('styles', function() {
- return styleTask('styles', ['**/*.css']);
+ return styleTask('styles', ['**/*.css']);
});
gulp.task('elements', function() {
- return styleTask('elements', ['**/*.css']);
+ return styleTask('elements', ['**/*.css']);
});
// Ensure that we are not missing required files for the project
// "dot" files are specifically tricky due to them being hidden on
// some systems.
gulp.task('ensureFiles', function(cb) {
- var requiredFiles = ['.jscsrc', '.jshintrc', '.bowerrc'];
+ var requiredFiles = ['.jscsrc', '.jshintrc', '.bowerrc'];
- ensureFiles(requiredFiles.map(function(p) {
- return path.join(__dirname, p);
- }), cb);
+ ensureFiles(requiredFiles.map(function(p) {
+ return path.join(__dirname, p);
+ }), cb);
});
// Lint JavaScript
gulp.task('lint', ['ensureFiles'], function() {
- return gulp.src([
- 'app/scripts/**/*.js',
- 'app/elements/**/*.js',
- 'app/elements/**/*.html',
- 'gulpfile.js'
- ])
- .pipe(reload({
- stream: true,
- once: true
- }))
-
- // JSCS has not yet a extract option
- .pipe($.if('*.html', $.htmlExtract()))
- .pipe($.jshint())
- .pipe($.jscs())
- .pipe($.jscsStylish.combineWithHintResults())
- .pipe($.jshint.reporter('jshint-stylish'))
- .pipe($.if(!browserSync.active, $.jshint.reporter('fail')));
+ return gulp.src([
+ 'app/scripts/**/*.js',
+ 'app/elements/**/*.js',
+ 'app/elements/**/*.html',
+ 'gulpfile.js'
+ ])
+ .pipe(reload({
+ stream: true,
+ once: true
+ }))
+
+ // JSCS has not yet a extract option
+ .pipe($.if('*.html', $.htmlExtract()))
+ .pipe($.jshint())
+ .pipe($.jscs())
+ .pipe($.jscsStylish.combineWithHintResults())
+ .pipe($.jshint.reporter('jshint-stylish'))
+ .pipe($.if(!browserSync.active, $.jshint.reporter('fail')));
});
// Optimize images
gulp.task('images', function() {
- return imageOptimizeTask('app/images/**/*', dist('images'));
+ return imageOptimizeTask('app/images/**/*', dist('images'));
});
// Copy all files at the root level (app)
gulp.task('copy', function() {
- var app = gulp.src([
- 'app/*',
- '!app/test',
- '!app/elements',
- '!app/bower_components',
- '!app/cache-config.json'
- ], {
- dot: true
- }).pipe(gulp.dest(dist()));
-
- // Copy over only the bower_components we need
- // These are things which cannot be vulcanized
- var bower = gulp.src([
- 'app/bower_components/{webcomponentsjs,platinum-sw,sw-toolbox,promise-polyfill}/**/*'
- ]).pipe(gulp.dest(dist('bower_components')));
-
- return merge(app, bower)
- .pipe($.size({
- title: 'copy'
- }));
+ var app = gulp.src([
+ 'app/*',
+ '!app/test',
+ '!app/elements',
+ '!app/bower_components',
+ '!app/cache-config.json'
+ ], {
+ dot: true
+ }).pipe(gulp.dest(dist()));
+
+ // Copy over only the bower_components we need
+ // These are things which cannot be vulcanized
+ var bower = gulp.src([
+ 'app/bower_components/{webcomponentsjs,platinum-sw,sw-toolbox,promise-polyfill}/**/*'
+ ]).pipe(gulp.dest(dist('bower_components')));
+
+ return merge(app, bower)
+ .pipe($.size({
+ title: 'copy'
+ }));
});
// Copy web fonts to dist
gulp.task('fonts', function() {
- return gulp.src(['app/fonts/**'])
- .pipe(gulp.dest(dist('fonts')))
- .pipe($.size({
- title: 'fonts'
- }));
+ return gulp.src(['app/fonts/**'])
+ .pipe(gulp.dest(dist('fonts')))
+ .pipe($.size({
+ title: 'fonts'
+ }));
});
// Scan your HTML for assets & optimize them
gulp.task('html', function() {
- return optimizeHtmlTask(
- ['app/**/*.html', '!app/{elements,test,bower_components}/**/*.html'],
- dist());
+ return optimizeHtmlTask(
+ ['app/**/*.html', '!app/{elements,test,bower_components}/**/*.html'],
+ dist());
});
// Vulcanize granular configuration
gulp.task('vulcanize', function() {
- return gulp.src('app/elements/elements.html')
- .pipe($.vulcanize({
- stripComments: true,
- inlineCss: true,
- inlineScripts: true
- }))
- .pipe(gulp.dest(dist('elements')))
- .pipe($.size({title: 'vulcanize'}));
+ return gulp.src('app/elements/elements.html')
+ .pipe($.vulcanize({
+ stripComments: true,
+ inlineCss: true,
+ inlineScripts: true
+ }))
+ .pipe(gulp.dest(dist('elements')))
+ .pipe($.size({
+ title: 'vulcanize'
+ }));
});
// Generate config data for the <sw-precache-cache> element.
@@ -196,121 +204,123 @@ gulp.task('vulcanize', function() {
// See https://github.com/PolymerElements/polymer-starter-kit#enable-service-worker-support
// for more context.
gulp.task('cache-config', function(callback) {
- var dir = dist();
- var config = {
- cacheId: packageJson.name || path.basename(__dirname),
- disabled: false
- };
-
- glob([
- 'index.html',
- './',
- 'bower_components/webcomponentsjs/webcomponents-lite.min.js',
- '{elements,scripts,styles}/**/*.*'],
- {cwd: dir}, function(error, files) {
- if (error) {
- callback(error);
- } else {
- config.precache = files;
-
- var md5 = crypto.createHash('md5');
- md5.update(JSON.stringify(config.precache));
- config.precacheFingerprint = md5.digest('hex');
-
- var configPath = path.join(dir, 'cache-config.json');
- fs.writeFile(configPath, JSON.stringify(config), callback);
- }
- });
+ var dir = dist();
+ var config = {
+ cacheId: packageJson.name || path.basename(__dirname),
+ disabled: false
+ };
+
+ glob([
+ 'index.html',
+ './',
+ 'bower_components/webcomponentsjs/webcomponents-lite.min.js',
+ '{elements,scripts,styles}/**/*.*'
+ ], {
+ cwd: dir
+ }, function(error, files) {
+ if (error) {
+ callback(error);
+ } else {
+ config.precache = files;
+
+ var md5 = crypto.createHash('md5');
+ md5.update(JSON.stringify(config.precache));
+ config.precacheFingerprint = md5.digest('hex');
+
+ var configPath = path.join(dir, 'cache-config.json');
+ fs.writeFile(configPath, JSON.stringify(config), callback);
+ }
+ });
});
// Clean output directory
gulp.task('clean', function() {
- return del(['.tmp', dist()]);
+ return del(['.tmp', dist()]);
});
// Watch files for changes & reload
-gulp.task('serve', [ 'styles', 'elements', 'images'], function() {
- browserSync({
- port: 5000,
- notify: false,
- logPrefix: 'PSK',
- snippetOptions: {
- rule: {
- match: '<span id="browser-sync-binding"></span>',
- fn: function(snippet) {
- return snippet;
+gulp.task('serve', ['styles', 'elements', 'images'], function() {
+ browserSync({
+ port: 5000,
+ notify: false,
+ logPrefix: 'PSK',
+ ghostMode: false,
+ snippetOptions: {
+ rule: {
+ match: '<span id="browser-sync-binding"></span>',
+ fn: function(snippet) {
+ return snippet;
+ }
+ }
+ },
+ // Run as an https by uncommenting 'https: true'
+ // Note: this uses an unsigned certificate which on first access
+ // will present a certificate warning in the browser.
+ // https: true,
+ server: {
+ baseDir: ['.tmp', 'app'],
+ middleware: [historyApiFallback()]
}
- }
- },
- // Run as an https by uncommenting 'https: true'
- // Note: this uses an unsigned certificate which on first access
- // will present a certificate warning in the browser.
- // https: true,
- server: {
- baseDir: ['.tmp', 'app'],
- middleware: [historyApiFallback()]
- }
- });
-
- gulp.watch(['app/**/*.html'], reload);
- gulp.watch(['app/styles/**/*.css'], ['styles', reload]);
- gulp.watch(['app/elements/**/*.css'], ['elements', reload]);
- gulp.watch(['app/{scripts,elements}/**/{*.js,*.html}'], ['lint']);
- gulp.watch(['app/images/**/*'], reload);
+ });
+
+ gulp.watch(['app/**/*.html'], reload);
+ gulp.watch(['app/styles/**/*.css'], ['styles', reload]);
+ gulp.watch(['app/elements/**/*.css'], ['elements', reload]);
+ gulp.watch(['app/{scripts,elements}/**/{*.js,*.html}'], ['lint']);
+ gulp.watch(['app/images/**/*'], reload);
});
// Build and serve the output from the dist build
gulp.task('serve:dist', ['default'], function() {
- browserSync({
- port: 5001,
- notify: false,
- logPrefix: 'PSK',
- snippetOptions: {
- rule: {
- match: '<span id="browser-sync-binding"></span>',
- fn: function(snippet) {
- return snippet;
- }
- }
- },
- // Run as an https by uncommenting 'https: true'
- // Note: this uses an unsigned certificate which on first access
- // will present a certificate warning in the browser.
- // https: true,
- server: dist(),
- middleware: [historyApiFallback()]
- });
+ browserSync({
+ port: 5001,
+ notify: false,
+ logPrefix: 'PSK',
+ snippetOptions: {
+ rule: {
+ match: '<span id="browser-sync-binding"></span>',
+ fn: function(snippet) {
+ return snippet;
+ }
+ }
+ },
+ // Run as an https by uncommenting 'https: true'
+ // Note: this uses an unsigned certificate which on first access
+ // will present a certificate warning in the browser.
+ // https: true,
+ server: dist(),
+ middleware: [historyApiFallback()]
+ });
});
// Build production files, the default task
gulp.task('default', ['clean'], function(cb) {
- // Uncomment 'cache-config' if you are going to use service workers.
- runSequence(
- ['copy', 'styles'],
- 'elements',
- ['lint', 'images', 'fonts', 'html'],
- 'vulcanize', // 'cache-config',
- cb);
+ // Uncomment 'cache-config' if you are going to use service workers.
+ runSequence(
+ ['copy', 'styles'],
+ 'elements', ['images', 'fonts', 'html'], //'lint',
+ 'vulcanize', 'cache-config',
+ cb);
});
// Build then deploy to GitHub pages gh-pages branch
gulp.task('build-deploy-gh-pages', function(cb) {
- runSequence(
- 'default',
- 'deploy-gh-pages',
- cb);
+ runSequence(
+ 'default',
+ 'deploy-gh-pages',
+ cb);
});
// Deploy to GitHub pages gh-pages branch
gulp.task('deploy-gh-pages', function() {
- return gulp.src(dist('**/*'))
- // Check if running task from Travis CI, if so run using GH_TOKEN
- // otherwise run using ghPages defaults.
- .pipe($.if(process.env.TRAVIS === 'true', $.ghPages({
- remoteUrl: 'https://$GH_TOKEN@github.com/polymerelements/polymer-starter-kit.git',
- silent: true,
- branch: 'gh-pages'
- }), $.ghPages()));
+ return gulp.src(dist('**/*'))
+ // Check if running task from Travis CI, if so run using GH_TOKEN
+ // otherwise run using ghPages defaults.
+ .pipe($.if(process.env.TRAVIS === 'true', $.ghPages({
+ remoteUrl: 'https://$GH_TOKEN@github.com/polymerelements/polymer-starter-kit.git',
+ silent: true,
+ branch: 'gh-pages'
+ }), $.ghPages()));
});
// Load tasks for web-component-tester
@@ -319,5 +329,5 @@ require('web-component-tester').gulp.init(gulp);
// Load custom tasks from the `tasks` directory
try {
- require('require-dir')('tasks');
+ require('require-dir')('tasks');
} catch (err) {}
diff --git a/package.json b/package.json
@@ -13,6 +13,7 @@
"gulp-html-extract": "^0.0.3",
"gulp-if": "^2.0.0",
"gulp-imagemin": "^2.2.1",
+ "gulp-inline-source": "^2.1.0",
"gulp-jscs": "^3.0.0",
"gulp-jscs-stylish": "^1.1.2",
"gulp-jshint": "^1.6.3",
@@ -39,5 +40,11 @@
},
"engines": {
"node": ">=0.10.0"
+ },
+ "dependencies": {
+ "binaryjs": "^0.2.1",
+ "express": "^4.13.3",
+ "ua-parser-js": "^0.7.10",
+ "ws": "^0.8.1"
}
}
diff --git a/server/ws-server.js b/server/ws-server.js
@@ -0,0 +1,106 @@
+'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
+});
+
+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.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;
+ }
+ meta.from = client.uuid;
+
+ // 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 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 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
+ });
+ }
+ });
+ var msg = {
+ buddies: buddies,
+ isSystemEvent: true,
+ type: 'buddies'
+ };
+ client1.send(msg);
+ });
+}
+setInterval(notifyBuddies, 4000);
+
+server.listen(9001);
+console.log('HTTP and BinaryJS server started on port 9001');