diff --git a/.bowerrc b/.bowerrc deleted file mode 100644 index 5773025..0000000 --- a/.bowerrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "directory": "app/bower_components" -} diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index c2cdfb8..0000000 --- a/.editorconfig +++ /dev/null @@ -1,21 +0,0 @@ -# EditorConfig helps developers define and maintain consistent -# coding styles between different editors and IDEs -# editorconfig.org - -root = true - - -[*] - -# Change these settings to your own preference -indent_style = space -indent_size = 2 - -# We recommend you to keep these unchanged -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.md] -trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 2125666..0000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -* text=auto \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3a1b68d..28f1ba7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ node_modules -bower_components -.tmp -.publish/ +.DS_Store \ No newline at end of file diff --git a/.jscsrc b/.jscsrc deleted file mode 100644 index ac1d229..0000000 --- a/.jscsrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "preset": "google", - "disallowSpacesInAnonymousFunctionExpression": null, - "disallowTrailingWhitespace": null, - "validateIndentation": null, - "maximumLineLength": 100, - "excludeFiles": ["node_modules/**"] -} diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index ff72d26..0000000 --- a/.jshintrc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "node": true, - "browser": true, - "bitwise": true, - "camelcase": true, - "curly": true, - "eqeqeq": true, - "immed": true, - "indent": 2, - "latedef": true, - "noarg": true, - "quotmark": "single", - "undef": true, - "unused": true, - "newcap": false, - "globals": { - "wrap": true, - "unwrap": true, - "Polymer": true, - "Platform": true, - "page": true, - "app": true, - "Chat": true - } -} diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index e33fcf6..0000000 --- a/LICENSE.md +++ /dev/null @@ -1,19 +0,0 @@ -# License - -Everything in this repo is BSD style license unless otherwise specified. - -Copyright (c) 2015 Robin Linus. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. -* Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/app/cache-config.json b/app/cache-config.json deleted file mode 100644 index c6a8cef..0000000 --- a/app/cache-config.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "README": "This is the cache config for the dev server. The service worker cache is disabled, and it is recommended that you leave this as-is. In the dist environment, this file will be auto-generated based on the contents of your dist/ directory.", - "disabled": true -} diff --git a/app/elements/buddy-finder/buddy-avatar.html b/app/elements/buddy-finder/buddy-avatar.html deleted file mode 100644 index c08b87b..0000000 --- a/app/elements/buddy-finder/buddy-avatar.html +++ /dev/null @@ -1,168 +0,0 @@ - - - - - - - diff --git a/app/elements/buddy-finder/buddy-finder.html b/app/elements/buddy-finder/buddy-finder.html deleted file mode 100644 index 455f533..0000000 --- a/app/elements/buddy-finder/buddy-finder.html +++ /dev/null @@ -1,186 +0,0 @@ - - - - - - - - diff --git a/app/elements/buddy-finder/device-name-dialog.html b/app/elements/buddy-finder/device-name-dialog.html deleted file mode 100644 index b65d8dd..0000000 --- a/app/elements/buddy-finder/device-name-dialog.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - diff --git a/app/elements/buddy-finder/device-name.html b/app/elements/buddy-finder/device-name.html deleted file mode 100644 index 5c40589..0000000 --- a/app/elements/buddy-finder/device-name.html +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - diff --git a/app/elements/buddy-finder/personal-avatar.html b/app/elements/buddy-finder/personal-avatar.html deleted file mode 100644 index c3fa17f..0000000 --- a/app/elements/buddy-finder/personal-avatar.html +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - diff --git a/app/elements/donate-dialog/donate-dialog.html b/app/elements/donate-dialog/donate-dialog.html deleted file mode 100644 index 1ee36ef..0000000 --- a/app/elements/donate-dialog/donate-dialog.html +++ /dev/null @@ -1,95 +0,0 @@ - - - diff --git a/app/elements/elements.html b/app/elements/elements.html deleted file mode 100644 index 1a74c44..0000000 --- a/app/elements/elements.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/elements/file-sharing/file-button-behavior.html b/app/elements/file-sharing/file-button-behavior.html deleted file mode 100644 index 4482007..0000000 --- a/app/elements/file-sharing/file-button-behavior.html +++ /dev/null @@ -1,37 +0,0 @@ - - diff --git a/app/elements/file-sharing/file-button.html b/app/elements/file-sharing/file-button.html deleted file mode 100644 index 1c11655..0000000 --- a/app/elements/file-sharing/file-button.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - diff --git a/app/elements/file-sharing/file-drop-behavior.html b/app/elements/file-sharing/file-drop-behavior.html deleted file mode 100644 index 6e1c20d..0000000 --- a/app/elements/file-sharing/file-drop-behavior.html +++ /dev/null @@ -1,48 +0,0 @@ - - diff --git a/app/elements/file-sharing/file-input-behavior.html b/app/elements/file-sharing/file-input-behavior.html deleted file mode 100644 index 772632d..0000000 --- a/app/elements/file-sharing/file-input-behavior.html +++ /dev/null @@ -1,6 +0,0 @@ - - - \ No newline at end of file diff --git a/app/elements/file-sharing/file-receiver.html b/app/elements/file-sharing/file-receiver.html deleted file mode 100644 index 0a05253..0000000 --- a/app/elements/file-sharing/file-receiver.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - - - - diff --git a/app/elements/file-sharing/file-selection-behavior.html b/app/elements/file-sharing/file-selection-behavior.html deleted file mode 100644 index 02548dc..0000000 --- a/app/elements/file-sharing/file-selection-behavior.html +++ /dev/null @@ -1,21 +0,0 @@ - diff --git a/app/elements/invitation-link/invitation-link-behavior.html b/app/elements/invitation-link/invitation-link-behavior.html deleted file mode 100644 index 288ecf4..0000000 --- a/app/elements/invitation-link/invitation-link-behavior.html +++ /dev/null @@ -1,45 +0,0 @@ - diff --git a/app/elements/invitation-link/invitation-link.html b/app/elements/invitation-link/invitation-link.html deleted file mode 100644 index 87a99ba..0000000 --- a/app/elements/invitation-link/invitation-link.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - diff --git a/app/elements/p2p-network/binaryjs.html b/app/elements/p2p-network/binaryjs.html deleted file mode 100644 index 7d9160f..0000000 --- a/app/elements/p2p-network/binaryjs.html +++ /dev/null @@ -1,1345 +0,0 @@ - \ No newline at end of file diff --git a/app/elements/p2p-network/connection-wrapper.html b/app/elements/p2p-network/connection-wrapper.html deleted file mode 100644 index 8d62b16..0000000 --- a/app/elements/p2p-network/connection-wrapper.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - diff --git a/app/elements/p2p-network/file-transfer-protocol.html b/app/elements/p2p-network/file-transfer-protocol.html deleted file mode 100644 index b75ead8..0000000 --- a/app/elements/p2p-network/file-transfer-protocol.html +++ /dev/null @@ -1,159 +0,0 @@ - diff --git a/app/elements/p2p-network/p2p-network.html b/app/elements/p2p-network/p2p-network.html deleted file mode 100644 index 0cfea0f..0000000 --- a/app/elements/p2p-network/p2p-network.html +++ /dev/null @@ -1,171 +0,0 @@ - - - - - - diff --git a/app/elements/p2p-network/tab-active.html b/app/elements/p2p-network/tab-active.html deleted file mode 100644 index 912230a..0000000 --- a/app/elements/p2p-network/tab-active.html +++ /dev/null @@ -1,25 +0,0 @@ - diff --git a/app/elements/p2p-network/web-socket.html b/app/elements/p2p-network/web-socket.html deleted file mode 100644 index 5e76e16..0000000 --- a/app/elements/p2p-network/web-socket.html +++ /dev/null @@ -1,89 +0,0 @@ - - - - - diff --git a/app/elements/sound-notification/sound-notification-behavior.html b/app/elements/sound-notification/sound-notification-behavior.html deleted file mode 100644 index 807ef21..0000000 --- a/app/elements/sound-notification/sound-notification-behavior.html +++ /dev/null @@ -1,24 +0,0 @@ - - diff --git a/app/elements/sound-notification/sound-notification.html b/app/elements/sound-notification/sound-notification.html deleted file mode 100644 index 310b738..0000000 --- a/app/elements/sound-notification/sound-notification.html +++ /dev/null @@ -1,56 +0,0 @@ - - - - diff --git a/app/elements/text-sharing/clipboard-behavior.html b/app/elements/text-sharing/clipboard-behavior.html deleted file mode 100644 index 04bf7bc..0000000 --- a/app/elements/text-sharing/clipboard-behavior.html +++ /dev/null @@ -1,36 +0,0 @@ - diff --git a/app/elements/text-sharing/linkify.html b/app/elements/text-sharing/linkify.html deleted file mode 100644 index 1c5b68f..0000000 --- a/app/elements/text-sharing/linkify.html +++ /dev/null @@ -1,5 +0,0 @@ - \ No newline at end of file diff --git a/app/elements/text-sharing/text-input-behavior.html b/app/elements/text-sharing/text-input-behavior.html deleted file mode 100644 index cf010f2..0000000 --- a/app/elements/text-sharing/text-input-behavior.html +++ /dev/null @@ -1,48 +0,0 @@ - - diff --git a/app/elements/text-sharing/text-input-dialog.html b/app/elements/text-sharing/text-input-dialog.html deleted file mode 100644 index 17fa6e7..0000000 --- a/app/elements/text-sharing/text-input-dialog.html +++ /dev/null @@ -1,210 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/elements/x-cards/about-page.html b/app/elements/x-cards/about-page.html deleted file mode 100644 index 6e9aa96..0000000 --- a/app/elements/x-cards/about-page.html +++ /dev/null @@ -1,229 +0,0 @@ - - - - - - - - - diff --git a/app/elements/x-cards/settings-page.html b/app/elements/x-cards/settings-page.html deleted file mode 100644 index 6998358..0000000 --- a/app/elements/x-cards/settings-page.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - diff --git a/app/elements/x-cards/x-cards.html b/app/elements/x-cards/x-cards.html deleted file mode 100644 index 2e400b6..0000000 --- a/app/elements/x-cards/x-cards.html +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - - - - diff --git a/app/favicon.ico b/app/favicon.ico deleted file mode 100644 index ef96003..0000000 Binary files a/app/favicon.ico and /dev/null differ diff --git a/app/images/touch/apple-touch-icon.png b/app/images/touch/apple-touch-icon.png deleted file mode 100644 index d403fe7..0000000 Binary files a/app/images/touch/apple-touch-icon.png and /dev/null differ diff --git a/app/images/touch/chrome-splashscreen-icon-384x384.png b/app/images/touch/chrome-splashscreen-icon-384x384.png deleted file mode 100644 index 1a88f60..0000000 Binary files a/app/images/touch/chrome-splashscreen-icon-384x384.png and /dev/null differ diff --git a/app/images/touch/chrome-touch-icon-192x192.png b/app/images/touch/chrome-touch-icon-192x192.png deleted file mode 100644 index 3c9b566..0000000 Binary files a/app/images/touch/chrome-touch-icon-192x192.png and /dev/null differ diff --git a/app/images/touch/icon-128x128.png b/app/images/touch/icon-128x128.png deleted file mode 100644 index 61e0a85..0000000 Binary files a/app/images/touch/icon-128x128.png and /dev/null differ diff --git a/app/images/touch/logo.png b/app/images/touch/logo.png deleted file mode 100644 index 22cd7a6..0000000 Binary files a/app/images/touch/logo.png and /dev/null differ diff --git a/app/images/touch/ms-icon-144x144-precomposed.png b/app/images/touch/ms-icon-144x144-precomposed.png deleted file mode 100644 index a746f3d..0000000 Binary files a/app/images/touch/ms-icon-144x144-precomposed.png and /dev/null differ diff --git a/app/images/touch/ms-icon-144x144.png b/app/images/touch/ms-icon-144x144.png deleted file mode 100644 index a746f3d..0000000 Binary files a/app/images/touch/ms-icon-144x144.png and /dev/null differ diff --git a/app/images/touch/snapdrop-icon.png b/app/images/touch/snapdrop-icon.png deleted file mode 100644 index 2094dd0..0000000 Binary files a/app/images/touch/snapdrop-icon.png and /dev/null differ diff --git a/app/index.html b/app/index.html deleted file mode 100644 index 59b6dc1..0000000 --- a/app/index.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - Snapdrop - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/robots.txt b/app/robots.txt deleted file mode 100644 index 5a2d9d7..0000000 --- a/app/robots.txt +++ /dev/null @@ -1,4 +0,0 @@ -# www.robotstxt.org - -User-agent: * -Disallow: diff --git a/app/scripts/animated-bg.js b/app/scripts/animated-bg.js deleted file mode 100644 index afa8491..0000000 --- a/app/scripts/animated-bg.js +++ /dev/null @@ -1,65 +0,0 @@ -'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; - var offset = h > 380 ? 100 : 65; - x0 = w / 2; - y0 = h - offset; - 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 deleted file mode 100644 index 0abc572..0000000 --- a/app/scripts/app.js +++ /dev/null @@ -1,47 +0,0 @@ -(function(document) { - 'use strict'; - - var app = document.querySelector('#app'); - - // Sets app default base URL - app.baseUrl = '/'; - - - // don't display the install prompt if the user has *already* installed - window.addEventListener('beforeinstallprompt', function(event) { - if (window.matchMedia('(display-mode: standalone)').matches) { - return event.preventDefault(); - } - }); - - - app.displayInstalledToast = function() { - // Check to make sure caching is actually enabled—it won't be in the dev environment. - if (!Polymer.dom(document).querySelector('platinum-sw-cache').disabled) { - Polymer.dom(document).querySelector('#caching-complete').show(); - } - }; - - 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.conn = 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._showAbout=function(){ - document.querySelector('#pages').select(0); - }; -})(document); diff --git a/app/styles/app-theme.html b/app/styles/app-theme.html deleted file mode 100644 index 8f6c9bd..0000000 --- a/app/styles/app-theme.html +++ /dev/null @@ -1,35 +0,0 @@ - - - diff --git a/app/styles/icons.html b/app/styles/icons.html deleted file mode 100644 index 2eb2867..0000000 --- a/app/styles/icons.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/styles/main.css b/app/styles/main.css deleted file mode 100644 index fffab6b..0000000 --- a/app/styles/main.css +++ /dev/null @@ -1,16 +0,0 @@ -html, -body { - height: 100%; - width: 100%; - padding: 0; - margin: 0; -} - -body { - background: #fafafa; - font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial, sans-serif; - color: #333; - -webkit-font-smoothing: antialiased; - overflow-x: hidden; -} - diff --git a/app/sw-import.js b/app/sw-import.js deleted file mode 100644 index f3a1a00..0000000 --- a/app/sw-import.js +++ /dev/null @@ -1 +0,0 @@ -importScripts('bower_components/platinum-sw/service-worker.js'); diff --git a/bower.json b/bower.json deleted file mode 100644 index 9f4767f..0000000 --- a/bower.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "sharewithme", - "private": true, - "dependencies": { - "iron-elements": "PolymerElements/iron-elements#^1.0.0", - "neon-elements": "PolymerElements/neon-elements#^1.0.0", - "page": "visionmedia/page.js#~1.6.4", - "paper-elements": "PolymerElements/paper-elements#^1.0.1", - "platinum-elements": "PolymerElements/platinum-elements#^1.1.0", - "polymer": "Polymer/polymer#^1.2.0", - "paper-menu": "PolymerElements/paper-menu#4fecb43601", - "peerjs": "~0.3.14", - "clipboard": "~1.5.5" - }, - "devDependencies": { - "web-component-tester": "*" - }, - "ignore": [], - "resolutions": { - "paper-menu": "4fecb43601" - } -} diff --git a/client/images/android-chrome-192x192.png b/client/images/android-chrome-192x192.png new file mode 100644 index 0000000..1833d41 Binary files /dev/null and b/client/images/android-chrome-192x192.png differ diff --git a/client/images/android-chrome-512x512.png b/client/images/android-chrome-512x512.png new file mode 100644 index 0000000..f92b0d1 Binary files /dev/null and b/client/images/android-chrome-512x512.png differ diff --git a/client/images/apple-touch-icon.png b/client/images/apple-touch-icon.png new file mode 100644 index 0000000..d357860 Binary files /dev/null and b/client/images/apple-touch-icon.png differ diff --git a/client/images/favicon-96x96.png b/client/images/favicon-96x96.png new file mode 100644 index 0000000..7efaf8c Binary files /dev/null and b/client/images/favicon-96x96.png differ diff --git a/client/images/logo_blue_512x512.png b/client/images/logo_blue_512x512.png new file mode 100644 index 0000000..aacd1b6 Binary files /dev/null and b/client/images/logo_blue_512x512.png differ diff --git a/client/images/logo_transparent_128x128.png b/client/images/logo_transparent_128x128.png new file mode 100644 index 0000000..abaf087 Binary files /dev/null and b/client/images/logo_transparent_128x128.png differ diff --git a/client/images/logo_transparent_512x512.png b/client/images/logo_transparent_512x512.png new file mode 100644 index 0000000..2e53bf9 Binary files /dev/null and b/client/images/logo_transparent_512x512.png differ diff --git a/client/images/logo_transparent_white_512x512.png b/client/images/logo_transparent_white_512x512.png new file mode 100644 index 0000000..d7151ae Binary files /dev/null and b/client/images/logo_transparent_white_512x512.png differ diff --git a/client/images/logo_white_512x512.png b/client/images/logo_white_512x512.png new file mode 100644 index 0000000..b9eab01 Binary files /dev/null and b/client/images/logo_white_512x512.png differ diff --git a/client/images/mstile-150x150.png b/client/images/mstile-150x150.png new file mode 100644 index 0000000..0ff94dc Binary files /dev/null and b/client/images/mstile-150x150.png differ diff --git a/client/images/safari-pinned-tab.svg b/client/images/safari-pinned-tab.svg new file mode 100644 index 0000000..263ee4e --- /dev/null +++ b/client/images/safari-pinned-tab.svg @@ -0,0 +1,251 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/images/snapdrop-graphics.sketch b/client/images/snapdrop-graphics.sketch new file mode 100755 index 0000000..b8b756a Binary files /dev/null and b/client/images/snapdrop-graphics.sketch differ diff --git a/client/images/twitter-stream.jpg b/client/images/twitter-stream.jpg new file mode 100644 index 0000000..61ceb24 Binary files /dev/null and b/client/images/twitter-stream.jpg differ diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..629f84b --- /dev/null +++ b/client/index.html @@ -0,0 +1,200 @@ + + + + + + + + Snapdrop + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Open Snapdrop on other devices to send files.

+
Short link: yg.gl
+
+ + + + + + + +

File Received

+
Filename
+
+
+ Download + +
+
+
+
+ + +
+ + +

Send a Message

+ +
+ + Cancel +
+
+
+
+
+ + + + +

Message Received

+
+
+ + +
+
+
+
+ +
+ File Transfer Completed +
+ +
+ + + + + + +

Snapdrop

+
The easiest way to transfer files across devices.
+
+ + + + + + + + + + + + + + + +
+
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/manifest.json b/client/manifest.json similarity index 53% rename from app/manifest.json rename to client/manifest.json index 05ab83b..6dc46ec 100644 --- a/app/manifest.json +++ b/client/manifest.json @@ -2,28 +2,28 @@ "name": "Snapdrop", "short_name": "Snapdrop", "icons": [{ - "src": "images/touch/icon-128x128.png", - "sizes": "128x128", + "src": "images/favicon-96x96.png", + "sizes": "96x96", "type": "image/png" }, { - "src": "images/touch/apple-touch-icon.png", + "src": "images/apple-touch-icon.png", "sizes": "152x152", "type": "image/png" }, { - "src": "images/touch/ms-touch-icon-144x144-precomposed.png", + "src": "images/mstile-150x150.png", "sizes": "144x144", "type": "image/png" }, { - "src": "images/touch/chrome-touch-icon-192x192.png", + "src": "images/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, { - "src": "images/touch/chrome-splashscreen-icon-384x384.png", - "sizes": "384x384", + "src": "logo_transparent_white_512x512.png", + "sizes": "512x512", "type": "image/png" }], "background_color": "#3367d6", - "start_url": "index.html", + "start_url": "/", "display": "standalone", "theme_color": "#3367d6" } diff --git a/client/scripts/network-v2.js b/client/scripts/network-v2.js new file mode 100644 index 0000000..7a15cbd --- /dev/null +++ b/client/scripts/network-v2.js @@ -0,0 +1,34 @@ +class ServerConnection { + +} + +class Connection { + +} + +class WSConnection extends Connection { + +} + +class RTCConnection extends Connection { + +} + +class Peer { + + constructor(serverConnection) { + this._ws = new WSConnection(serverConnection); + this._rtc = new RTCConnection(serverConnection); + this._fileReceiver = new FileReceiver(this); + this._fileSender = new FileSender(this); + } + + send(message) { + + } + +} + +class Peers { + +} \ No newline at end of file diff --git a/client/scripts/network.js b/client/scripts/network.js new file mode 100644 index 0000000..e7ebd87 --- /dev/null +++ b/client/scripts/network.js @@ -0,0 +1,475 @@ +class ServerConnection { + + constructor() { + this._connect(); + Events.on('beforeunload', e => this._disconnect(), false); + } + + _connect() { + const ws = new WebSocket(this._endpoint()); + ws.binaryType = 'arraybuffer'; + ws.onopen = e => console.log('WS: server connection opened'); + ws.onmessage = e => this._onMessage(e.data); + ws.onclose = e => this._onDisconnect(); + ws.onerror = e => console.error(e); + this._socket = ws; + clearTimeout(this._reconnectTimer); + } + + _onMessage(msg) { + msg = JSON.parse(msg); + console.log('WS:', msg); + switch (msg.type) { + case 'peers': + Events.fire('peers', msg.peers); + break; + case 'peer-joined': + Events.fire('peer-joined', msg.peer); + break; + case 'peer-left': + Events.fire('peer-left', msg.peerId); + break; + case 'signal': + Events.fire('signal', msg); + break; + case 'ping': + this.send({ type: 'pong' }); + break; + default: + console.error('WS: unkown message type', msg) + } + } + + send(message) { + if (this._socket.readyState !== this._socket.OPEN) return; + this._socket.send(JSON.stringify(message)); + } + + _endpoint() { + // hack to detect if deployment or development environment + const protocol = location.protocol.startsWith('https') ? 'wss' : 'ws'; + const host = location.hostname.startsWith('localhost') ? 'localhost:3000' : (location.host + '/server'); + const webrtc = window.isRtcSupported ? '/webrtc' : '/fallback'; + const url = protocol + '://' + host + webrtc; + return url; + } + + _disconnect() { + this.send({ type: 'disconnect' }); + this._socket.close(); + } + + _onDisconnect() { + console.log('WS: server disconnected'); + Events.fire('notify-user', 'Connection lost. Retry in 5 seconds...'); + clearTimeout(this._reconnectTimer); + this._reconnectTimer = setTimeout(_ => this._connect(), 5000); + } +} + +class Peer { + + constructor(serverConnection, peerId) { + this._server = serverConnection; + this._peerId = peerId; + this._filesQueue = []; + this._busy = false; + } + + sendJSON(message) { + this._send(JSON.stringify(message)); + } + + sendFiles(files) { + for (let i = 0; i < files.length; i++) { + this._filesQueue.push(files[i]); + } + if (this._busy) return; + this._dequeueFile(); + } + + _dequeueFile() { + if (!this._filesQueue.length) return; + this._busy = true; + const file = this._filesQueue.shift(); + this._sendFile(file); + } + + _sendFile(file) { + this.sendJSON({ + type: 'header', + name: file.name, + mime: file.type, + size: file.size, + }); + this._chunker = new FileChunker(file, + chunk => this._send(chunk), + offset => this._onPartitionEnd(offset)); + this._chunker.nextPartition(); + } + + _onPartitionEnd(offset) { + this.sendJSON({ type: 'partition', offset: offset }); + } + + _onReceivedPartitionEnd(offset) { + this.sendJSON({ type: 'partition_received', offset: offset }); + } + + _sendNextPartition() { + if (!this._chunker || this._chunker.isFileEnd()) return; + this._chunker.nextPartition(); + } + + _sendProgress(progress) { + this.sendJSON({ type: 'progress', progress: progress }); + } + + _onMessage(message) { + if (typeof message !== 'string') { + this._onChunkReceived(message); + return; + } + message = JSON.parse(message); + console.log('RTC:', message); + switch (message.type) { + case 'header': + this._onFileHeader(message); + break; + case 'partition': + this._onReceivedPartitionEnd(message); + break; + case 'partition_received': + this._sendNextPartition(); + break; + case 'progress': + this._onDownloadProgress(message.progress); + break; + case 'transfer-complete': + this._onTransferCompleted(); + break; + case 'text': + this._onTextReceived(message); + break; + } + } + + _onFileHeader(header) { + this._lastProgress = 0; + this._digester = new FileDigester({ + name: header.name, + mime: header.mime, + size: header.size + }, file => this._onFileReceived(file)); + } + + _onChunkReceived(chunk) { + this._digester.unchunk(chunk); + const progress = this._digester.progress; + this._onDownloadProgress(progress); + + // occasionally notify sender about our progress + if (progress - this._lastProgress < 0.01) return; + this._lastProgress = progress; + this._sendProgress(progress); + } + + _onDownloadProgress(progress) { + Events.fire('file-progress', { + sender: this._peerId, + progress: progress + }); + } + + _onFileReceived(proxyFile) { + Events.fire('file-received', proxyFile); + this.sendJSON({ type: 'transfer-complete' }); + // this._digester = null; + } + + _onTransferCompleted() { + this._onDownloadProgress(1); + this._reader = null; + this._busy = false; + this._dequeueFile(); + Events.fire('notify-user', 'File transfer completed.'); + } + + sendText(text) { + this.sendJSON({ + type: 'text', + text: btoa(unescape(encodeURIComponent(text))) + }); + } + + _onTextReceived(message) { + Events.fire('text-received', { + text: decodeURIComponent(escape(atob(message.text))), + sender: this._peerId + }); + } +} + +class RTCPeer extends Peer { + + constructor(serverConnection, peerId) { + super(serverConnection, peerId); + if (!peerId) return; // we will listen for a caller + this._start(peerId, true); + } + + _start(peerId, isCaller) { + if (!this._peer) { + this._isCaller = isCaller; + this._peerId = peerId; + this._peer = new RTCPeerConnection(RTCPeer.config); + this._peer.onicecandidate = e => this._onIceCandidate(e); + this._peer.onconnectionstatechange = e => console.log('RTC: state changed:', this._peer.connectionState); + } + + if (isCaller) { + this._createChannel(); + } else { + this._peer.ondatachannel = e => this._onChannelOpened(e); + } + } + + _createChannel() { + const channel = this._peer.createDataChannel('data-channel', { reliable: true }); + channel.binaryType = 'arraybuffer'; + channel.onopen = e => this._onChannelOpened(e) + this._peer.createOffer(d => this._onDescription(d), e => this._onError(e)); + } + + _onDescription(description) { + // description.sdp = description.sdp.replace('b=AS:30', 'b=AS:1638400'); + this._peer.setLocalDescription(description, + _ => this._sendSignal({ sdp: description }), + e => this._onError(e)); + } + + _onIceCandidate(event) { + if (!event.candidate) return; + this._sendSignal({ ice: event.candidate }); + } + + _sendSignal(signal) { + signal.type = 'signal'; + signal.to = this._peerId; + this._server.send(signal); + } + + onServerMessage(message) { + if (!this._peer) this._start(message.sender, false); + const conn = this._peer; + + if (message.sdp) { + this._peer.setRemoteDescription(new RTCSessionDescription(message.sdp), () => { + if (message.sdp.type !== 'offer') return; + this._peer.createAnswer(d => this._onDescription(d), e => this._onError(e)); + }, e => this._onError(e)); + } else if (message.ice) { + this._peer.addIceCandidate(new RTCIceCandidate(message.ice)); + } + } + + _onChannelOpened(event) { + console.log('RTC: channel opened with', this._peerId); + const channel = event.channel || event.target; + channel.onmessage = e => this._onMessage(e.data); + channel.onclose = e => this._onChannelClosed(); + this._channel = channel; + } + + _onChannelClosed() { + console.log('RTC: channel closed ', this._peerId); + if (!this.isCaller) return; + this._start(this._peerId, true); // reopen the channel + } + + _send(message) { + this._channel.send(message); + } + + _onError(error) { + console.error(error); + } + + refresh() { + // check if channel open. otherwise create one + if (this._peer && this._channel && this._channel.readyState !== 'open') return; + this._createChannel(this._peerId, this._isCaller); + } +} + +class PeersManager { + + constructor(serverConnection) { + this.peers = {}; + this._server = serverConnection; + Events.on('signal', e => this._onMessage(e.detail)); + Events.on('peers', e => this._onPeers(e.detail)); + Events.on('files-selected', e => this._onFilesSelected(e.detail)); + Events.on('send-text', e => this._onSendText(e.detail)); + Events.on('peer-left', e => this._onPeerLeft(e.detail)); + } + + _onMessage(message) { + if (!this.peers[message.sender]) { + this.peers[message.sender] = new RTCPeer(this._server); + } + this.peers[message.sender].onServerMessage(message); + } + + _onPeers(peers) { + peers.forEach(peer => { + if (this.peers[peer.id]) { + this.peers[peer.id].refresh(); + return; + } + if (window.isRtcSupported && peer.rtcSupported) { + this.peers[peer.id] = new RTCPeer(this._server, peer.id); + } else { + this.peers[peer.id] = new WSPeer(this._server, peer.id); + } + }) + } + + sendTo(peerId, message) { + this.peers[peerId].send(message); + } + + _onFilesSelected(message) { + this.peers[message.to].sendFiles(message.files); + } + + _onSendText(message) { + this.peers[message.to].sendText(message.text); + } + + _onPeerLeft(peerId) { + const peer = this.peers[peerId]; + delete this.peers[peerId]; + if (!peer || !peer._peer) return; + peer._peer.close(); + } + +} + +class WSPeer { + _send(message) { + message.to = this._peerId; + this._server.send(message); + } +} + +class FileChunker { + + constructor(file, onChunk, onPartitionEnd) { + this._chunkSize = 64000; + this._maxPartitionSize = 1e6; + this._offset = 0; + this._partitionSize = 0; + this._file = file; + this._onChunk = onChunk; + this._onPartitionEnd = onPartitionEnd; + this._reader = new FileReader(); + this._reader.addEventListener('load', e => this._onChunkRead(e.target.result)); + } + + nextPartition() { + this._partitionSize = 0; + this._readChunk(); + } + + _readChunk() { + const chunk = this._file.slice(this._offset, this._offset + this._chunkSize); + this._reader.readAsArrayBuffer(chunk); + } + + _onChunkRead(chunk) { + this._offset += chunk.byteLength; + this._partitionSize += chunk.byteLength; + this._onChunk(chunk); + if (this._isPartitionEnd() || this.isFileEnd()) { + this._onPartitionEnd(this._partitionSize); + return; + } + this._readChunk(); + } + + repeatPartition() { + this._offset -= this._partitionSize; + this._nextPartition(); + } + + _isPartitionEnd() { + return this._partitionSize >= this._maxPartitionSize; + } + + isFileEnd() { + return this._offset > this._file.size; + } + + get progress() { + return this._offset / this._file.size; + } +} + +class FileDigester { + constructor(meta, callback) { + this._buffer = []; + this._bytesReceived = 0; + this._size = meta.size; + this._mime = meta.mime || 'application/octet-stream'; + this._name = meta.name; + this._callback = callback; + } + + unchunk(chunk) { + this._buffer.push(chunk); + this._bytesReceived += chunk.byteLength || chunk.size; + const totalChunks = this._buffer.length; + this.progress = this._bytesReceived / this._size; + if (this._bytesReceived < this._size) return; + + let received = new Blob(this._buffer, { type: this._mime }); // pass a useful mime type here + let url = URL.createObjectURL(received); + this._callback({ + name: this._name, + mime: this._mime, + size: this._size, + url: url + }); + this._callback = null; + } +} + +class Events { + static fire(type, detail) { + window.dispatchEvent(new CustomEvent(type, { detail: detail })); + } + + static on(type, callback) { + return window.addEventListener(type, callback, false); + } +} + + +window.isRtcSupported = !!(window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection); + +RTCPeer.config = { + 'iceServers': [{ + urls: 'stun:stun.stunprotocol.org:3478' + }, { + urls: 'stun:stun.l.google.com:19302' + }, { + urls: 'turn:turn.bistri.com:80', + credential: 'homeo', + username: 'homeo' + }, { + urls: 'turn:turn.anyfirewall.com:443?transport=tcp', + credential: 'webrtc', + username: 'webrtc' + }] +} \ No newline at end of file diff --git a/client/scripts/ui.js b/client/scripts/ui.js new file mode 100644 index 0000000..45c9483 --- /dev/null +++ b/client/scripts/ui.js @@ -0,0 +1,521 @@ +const $ = query => document.getElementById(query); +const $$ = query => document.body.querySelector(query); +const isURL = text => /^((https?:\/\/|www)[^\s]+)/g.test(text.toLowerCase()); +const isDownloadSupported = (typeof document.createElement('a').download !== 'undefined'); +const isProductionEnvironment = !window.location.host.startsWith('localhost'); + +class PeersUI { + + constructor() { + Events.on('peer-joined', e => this._onPeerJoined(e.detail)); + Events.on('peer-left', e => this._onPeerLeft(e.detail)); + Events.on('peers', e => this._onPeers(e.detail)); + Events.on('file-progress', e => this._onFileProgress(e.detail)); + } + + _onPeerJoined(peer) { + if (document.getElementById(peer.id)) return; + const peerUI = new PeerUI(peer); + $$('x-peers').appendChild(peerUI.$el); + } + + _onPeers(peers) { + this._clearPeers(); + peers.forEach(peer => this._onPeerJoined(peer)); + } + + _onPeerLeft(peerId) { + const peer = $(peerId); + if (!peer) return; + peer.remove(); + } + + _onFileProgress(progress) { + const peerId = progress.sender || progress.recipient; + const peer = $(peerId); + if (!peer) return; + peer.ui.setProgress(progress.progress); + } + + _clearPeers() { + const $peers = $$('x-peers').innerHTML = ''; + } +} + +class PeerUI { + + html() { + return ` + ` + } + + constructor(peer) { + this._peer = peer; + this._initDom(); + this._bindListeners(this.$el); + } + + _initDom() { + const el = document.createElement('x-peer'); + el.id = this._peer.id; + el.innerHTML = this.html(); + el.ui = this; + el.querySelector('svg use').setAttribute('xlink:href', this._icon()); + el.querySelector('.name').textContent = this._name(); + this.$el = el; + this.$progress = el.querySelector('.progress'); + } + + _bindListeners(el) { + el.querySelector('input').addEventListener('change', e => this._onFilesSelected(e)); + el.addEventListener('drop', e => this._onDrop(e)); + el.addEventListener('dragend', e => this._onDragEnd(e)); + el.addEventListener('dragleave', e => this._onDragEnd(e)); + el.addEventListener('dragover', e => this._onDragOver(e)); + el.addEventListener('contextmenu', e => this._onRightClick(e)); + el.addEventListener('touchstart', e => this._onTouchStart(e)); + el.addEventListener('touchend', e => this._onTouchEnd(e)); + // prevent browser's default file drop behavior + Events.on('dragover', e => e.preventDefault()); + Events.on('drop', e => e.preventDefault()); + } + + _name() { + if (this._peer.name.model) { + return this._peer.name.os + ' ' + this._peer.name.model; + } + this._peer.name.os = this._peer.name.os.replace('Mac OS', 'Mac'); + return this._peer.name.os + ' ' + this._peer.name.browser; + } + + _icon() { + const device = this._peer.name.device || this._peer.name; + if (device.type === 'mobile') { + return '#phone-iphone'; + } + if (device.type === 'tablet') { + return '#tablet-mac'; + } + return '#desktop-mac'; + } + + _onFilesSelected(e) { + const $input = e.target; + const files = $input.files; + Events.fire('files-selected', { + files: files, + to: this._peer.id + }); + $input.value = null; // reset input + this.setProgress(0.01); + } + + setProgress(progress) { + if (progress > 0) { + this.$el.setAttribute('transfer', '1'); + } + if (progress > 0.5) { + this.$progress.classList.add('over50'); + } else { + this.$progress.classList.remove('over50'); + } + const degrees = `rotate(${360 * progress}deg)`; + this.$progress.style.setProperty('--progress', degrees); + if (progress >= 1) { + this.setProgress(0); + this.$el.removeAttribute('transfer'); + } + } + + _onDrop(e) { + e.preventDefault(); + const files = e.dataTransfer.files; + Events.fire('files-selected', { + files: files, + to: this._peer.id + }); + this._onDragEnd(); + } + + _onDragOver() { + this.$el.setAttribute('drop', 1); + } + + _onDragEnd() { + this.$el.removeAttribute('drop'); + } + + _onRightClick(e) { + e.preventDefault(); + Events.fire('text-recipient', this._peer.id); + } + + _onTouchStart(e) { + this._touchStart = Date.now(); + this._touchTimer = setTimeout(_ => this._onTouchEnd(), 610); + } + + _onTouchEnd(e) { + if (Date.now() - this._touchStart < 500) { + clearTimeout(this._touchTimer); + } else { // this was a long tap + if (e) e.preventDefault(); + Events.fire('text-recipient', this._peer.id); + } + } +} + + +class Dialog { + constructor(id) { + this.$el = $(id); + this.$el.querySelectorAll('[close]').forEach(el => el.addEventListener('click', e => this.hide())) + this.$autoFocus = this.$el.querySelector('[autofocus]'); + } + + show() { + this.$el.setAttribute('show', 1); + if (this.$autoFocus) this.$autoFocus.focus(); + } + + hide() { + this.$el.removeAttribute('show'); + document.activeElement.blur(); + window.blur(); + } +} + +class ReceiveDialog extends Dialog { + + constructor() { + super('receiveDialog'); + Events.on('file-received', e => { + this._nextFile(e.detail); + window.blop.play(); + }); + this._filesQueue = []; + } + + _nextFile(nextFile) { + if (nextFile) this._filesQueue.push(nextFile); + if (this._busy) return; + this._busy = true; + const file = this._filesQueue.shift(); + this._displayFile(file); + } + + _dequeueFile() { + if (!this._filesQueue.length) { // nothing to do + this._busy = false; + return; + } + // dequeue next file + setTimeout(_ => { + this._busy = false; + this._nextFile(); + }, 300); + } + + _displayFile(file) { + const $a = this.$el.querySelector('#download'); + $a.href = file.url; + $a.download = file.name; + + this.$el.querySelector('#fileName').textContent = file.name; + this.$el.querySelector('#fileSize').textContent = this._formatFileSize(file.size); + this.show(); + + if (!isDownloadSupported) return; + // $a.target = "_blank"; // fallback + $a.target = "_system"; // fallback + $a.href = 'external:' + $a.href; + } + + _formatFileSize(bytes) { + if (bytes >= 1e9) { + return (Math.round(bytes / 1e8) / 10) + ' GB'; + } else if (bytes >= 1e6) { + return (Math.round(bytes / 1e5) / 10) + ' MB'; + } else if (bytes > 1000) { + return Math.round(bytes / 1000) + ' KB'; + } else { + return bytes + ' Bytes'; + } + } + + hide() { + super.hide(); + this._dequeueFile(); + } +} + + +class SendTextDialog extends Dialog { + constructor() { + super('sendTextDialog'); + Events.on('text-recipient', e => this._onRecipient(e.detail)) + this.$text = this.$el.querySelector('#textInput'); + const button = this.$el.querySelector('form'); + button.addEventListener('submit', e => this._send(e)); + } + + _onRecipient(recipient) { + this._recipient = recipient; + this.show(); + this.$text.setSelectionRange(0, this.$text.value.length) + } + + _send(e) { + e.preventDefault(); + Events.fire('send-text', { + to: this._recipient, + text: this.$text.value + }); + } +} + +class ReceiveTextDialog extends Dialog { + constructor() { + super('receiveTextDialog'); + Events.on('text-received', e => this._onText(e.detail)) + this.$text = this.$el.querySelector('#text'); + const $copy = this.$el.querySelector('#copy'); + copy.addEventListener('click', _ => this._onCopy()); + } + + _onText(e) { + this.$text.innerHTML = ''; + const text = e.text; + if (isURL(text)) { + const $a = document.createElement('a'); + $a.href = text; + $a.target = '_blank'; + $a.textContent = text; + this.$text.appendChild($a); + } else { + this.$text.textContent = text; + } + this.show(); + window.blop.play(); + } + + _onCopy() { + if (!document.copy(this.$text.textContent)) return; + Events.fire('notify-user', 'Copied to clipboard'); + } +} + +class Toast extends Dialog { + constructor() { + super('toast'); + Events.on('notify-user', e => this._onNotfiy(e.detail)); + } + + _onNotfiy(message) { + this.$el.textContent = message; + this.show(); + setTimeout(_ => this.hide(), 3000); + } +} + + +class Notifications { + + constructor() { + // Check if the browser supports notifications + if (!('Notification' in window)) return; + // Check whether notification permissions have already been granted + + if (Notification.permission !== 'granted') { + this.$button = $('notification'); + this.$button.removeAttribute('hidden'); + this.$button.addEventListener('click', e => this._requestPermission()); + } + Events.on('text-received', e => this._messageNotification(e.detail.text)); + Events.on('file-received', e => this._downloadNotification(e.detail.name)); + } + + _requestPermission() { + Notification.requestPermission(permission => { + if (permission !== 'granted') { + Events.fire('notify-user', Notifications.PERMISSION_ERROR || 'Error'); + return; + } + this._notify('Even more snappy sharing!'); + this.$button.setAttribute('hidden', 1); + }); + } + + _notify(message, body) { + var img = '/images/logo_transparent_128x128.png'; + return new Notification(message, { + body: body, + icon: img, + vibrate: [200, 100, 200, 100, 200, 100, 400], + }); + } + + _messageNotification(message) { + if (isURL(message)) { + const notification = this._notify(message, 'Click to open link'); + notification.onclick = e => window.open(message, '_blank', null, true); + } else { + const notification = this._notify(message, 'Click to copy text'); + notification.onclick = e => document.copy(message); + } + } + + _downloadNotification(message) { + const notification = this._notify(message, 'Click to download'); + if (window.isDownloadSupported) return; + notification.onclick = e => { + document.querySelector('x-dialog [download]').click(); + }; + } + +} + +class Snapdrop { + constructor() { + const server = new ServerConnection(); + const peers = new PeersManager(server); + const peersUI = new PeersUI(); + Events.on('load', e => { + const receiveDialog = new ReceiveDialog(); + const sendTextDialog = new SendTextDialog(); + const receiveTextDialog = new ReceiveTextDialog(); + const toast = new Toast(); + const notifications = new Notifications(); + }) + } +} + +const snapdrop = new Snapdrop(); + +document.copy = text => { + // A contains the text to copy + const span = document.createElement('span'); + span.textContent = text; + span.style.whiteSpace = 'pre'; // Preserve consecutive spaces and newlines + + // Paint the span outside the viewport + span.style.position = 'absolute'; + span.style.left = '-9999px'; + span.style.top = '-9999px'; + + const win = window; + const selection = win.getSelection(); + win.document.body.appendChild(span); + + const range = win.document.createRange(); + selection.removeAllRanges(); + range.selectNode(span); + selection.addRange(range); + + let success = false; + try { + success = win.document.execCommand('copy'); + } catch (err) {} + + selection.removeAllRanges(); + span.remove(); + + return success; +} + +if ('serviceWorker' in navigator && isProductionEnvironment) { + navigator.serviceWorker + .register('/service-worker.js') + .then(e => console.log("Service Worker Registered")); +} + +// Background Animation +Events.on('load', () => { + 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'; + style.zIndex = -1; + 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; + var offset = h > 380 ? 100 : 65; + x0 = w / 2; + y0 = h - offset; + 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.animateBackground = function(l) { + loading = l; + animate(); + }; + init(); + animate(); + setTimeout(e => window.animateBackground(false), 3000); +}); + +Notifications.PERMISSION_ERROR = ` +Notifications permission has been blocked +as the user has dismissed the permission prompt several times. +This can be reset in Page Info +which can be accessed by clicking the lock icon next to the URL.`; + +document.body.onclick = e => { // safari hack to fix audio + document.body.onclick = null; + if (!(/.*Version.*Safari.*/.test(navigator.userAgent))) return; + blop.play(); +} \ No newline at end of file diff --git a/test.txt b/client/service-worker.js similarity index 100% rename from test.txt rename to client/service-worker.js diff --git a/app/sounds/blop.mp3 b/client/sounds/blop.mp3 old mode 100755 new mode 100644 similarity index 100% rename from app/sounds/blop.mp3 rename to client/sounds/blop.mp3 diff --git a/app/sounds/blop.ogg b/client/sounds/blop.ogg similarity index 100% rename from app/sounds/blop.ogg rename to client/sounds/blop.ogg diff --git a/client/styles.css b/client/styles.css new file mode 100644 index 0000000..517e727 --- /dev/null +++ b/client/styles.css @@ -0,0 +1,640 @@ +/* Constants */ + +:root { + --icon-size: 24px; + --primary-color: #4285f4; + --peer-width: 120px; +} + +/* Layout */ + +html { + height: 100%; +} + +html, +body { + margin: 0; + display: flex; + flex-direction: column; + width: 100%; + overflow-x: hidden; +} + +body { + flex-grow: 1; + align-items: center; + justify-content: center; + overflow-y: hidden; +} + +.row-reverse { + display: flex; + flex-direction: row-reverse; +} + +.row { + display: flex; + flex-direction: row; +} + +.column { + display: flex; + flex-direction: column; +} + +.center { + display: flex; + align-items: center; + justify-content: center; +} + +.full { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +[hidden] { + display: none !important; +} + +/* Typography */ + +body { + background: #fafafa; + font-family: -apple-system, BlinkMacSystemFont, Roboto, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + color: #333; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +h1 { + font-size: 34px; + font-weight: 400; + letter-spacing: -.01em; + line-height: 40px; + margin: 8px 0 0; +} + +h2 { + font-size: 24px; + font-weight: 400; + letter-spacing: -.012em; + line-height: 32px; +} + +h3 { + font-size: 20px; + font-weight: 500; + margin: 16px 0; +} + +.font-subheading { + font-size: 16px; + font-weight: 400; + line-height: 24px; +} + +.font-body1, +body { + font-size: 14px; + font-weight: 400; + line-height: 20px; +} + +.font-body2 { + font-size: 12px; + line-height: 18px; +} + +a { + text-decoration: none; + color: var(--primary-color); + cursor: pointer; +} + + + +/* Icons */ + +.icon { + width: var(--icon-size); + height: var(--icon-size); + fill: currentColor; +} + + + +/* Shadows */ + +[shadow="1"] { + box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.14), + 0 1px 8px 0 rgba(0, 0, 0, 0.12), + 0 3px 3px -2px rgba(0, 0, 0, 0.4); +} + +[shadow="2"] { + box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), + 0 1px 10px 0 rgba(0, 0, 0, 0.12), + 0 2px 4px -1px rgba(0, 0, 0, 0.4); +} + + + + +/* Animations */ + +@keyframes fade-in { + 0% { + opacity: 0; + } +} + + + +/* Peers List */ + +x-peers { + width: 100%; + overflow: hidden; + flex-flow: row wrap; + z-index: 2; +} + + + +/* Empty Peers List */ + +x-no-peers { + padding: 8px; + text-align: center; + /* prevent flickering on load */ + animation: fade-in 300ms; + animation-delay: 500ms; + animation-fill-mode: backwards; +} + +x-no-peers h2 { + color: var(--primary-color); +} + +x-peers:not(:empty)+x-no-peers { + display: none; +} + + + +/* Peer */ + +x-peer { + -webkit-user-select: none; + user-select: none; +} + +x-peer label { + width: var(--peer-width); + padding: 8px; + cursor: pointer; + touch-action: manipulation; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + position: relative; +} + +x-peer .name { + width: var(--peer-width); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-align: center; +} + +input[type="file"] { + visibility: hidden; + position: absolute; +} + +x-peer x-icon { + --icon-size: 40px; + width: var(--icon-size); + padding: 12px; + border-radius: 50%; + background: var(--primary-color); + color: white; + display: flex; + margin-bottom: 8px; + transition: transform 150ms; + will-change: transform; +} + +x-peer:not([transfer]):hover x-icon, +x-peer:not([transfer]):focus x-icon { + transform: scale(1.05); +} + +x-peer[transfer] x-icon { + box-shadow: none; + opacity: 0.8; + transform: scale(1); +} + +.status { + height: 18px; + opacity: 0.7; +} + +x-peer[transfer] .status:before { + content: 'Transfering...'; +} + +x-peer x-icon { + animation: pop 600ms ease-out 1; +} + +@keyframes pop { + 0% { + transform: scale(0.7); + } + 40% { + transform: scale(1.2); + } +} + +x-peer[drop] x-icon { + transform: scale(1.1); +} + +/* Footer */ + +footer { + position: absolute; + bottom: 0; + left: 0; + right: 0; + align-items: center; + padding: 0 0 16px 0; +} + +footer .logo { + --icon-size: 80px; + margin-bottom: 8px; + color: var(--primary-color); +} + +footer .font-body2 { + color: var(--primary-color); +} + +@media (min-height: 800px) { + footer { + margin-bottom: 16px; + } +} + + + +/* Dialog */ + +x-dialog x-background { + background: rgba(0, 0, 0, 0.61); + z-index: 10; + transition: opacity 300ms; + padding: 16px; +} + +x-dialog x-paper { + z-index: 3; + background: white; + border-radius: 8px; + padding: 16px 24px; + width: 100%; + max-width: 400px; + box-sizing: border-box; + transition: transform 300ms; +} + +x-dialog:not([show]) { + pointer-events: none; +} + +x-dialog:not([show]) x-paper { + transform: scale(0.1); +} + +x-dialog:not([show]) x-background { + opacity: 0; +} + +x-dialog .row-reverse>.button { + margin-top: 16px; + margin-left: 8px; +} + + + +/* Receive Dialog */ + +#receiveTextDialog #text { + width: 100%; + word-break: break-all; + max-height: 300px; + overflow-x: hidden; + overflow-y: auto; + -webkit-user-select: all; + -moz-user-select: all; + user-select: all; +} + + + + +/* Button */ + +.button { + padding: 0 16px; + box-sizing: border-box; + min-height: 36px; + border: none; + outline: none; + min-width: 100px; + font-size: 14px; + line-height: 24px; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; + white-space: nowrap; + cursor: pointer; + user-select: none; + background: inherit; + color: var(--primary-color); +} + +.button, +.icon-button { + position: relative; + display: flex; + align-items: center; + justify-content: center; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + touch-action: manipulation; +} + +.button:before, +.icon-button:before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: currentColor; + opacity: 0; + transition: opacity 300ms; +} + +.button:hover:before, +.icon-button:hover:before { + opacity: 0.1; +} + +.button:before { + border-radius: 8px; +} + +.button:focus:before { + opacity: 0.2; +} + +button::-moz-focus-inner { + border: 0; +} + + + +/* Icon Button */ + +.icon-button { + width: 40px; + height: 40px; +} + +.icon-button:before { + border-radius: 50%; +} + + + +/* Text Input */ + +input { + width: 100%; + box-sizing: border-box; + border: none; + outline: none; + padding: 16px 24px; + background: #f1f3f4; + border-radius: 50px; + margin: 8px 0; + line-height: 16px; + font-size: 14px; +} + + + +/* Info Animation */ + +#info { + text-align: center; + color: white; + transition: opacity 300ms; + will-change: opacity; + z-index: 11; + transition-delay: 300ms; +} + +#info:not(:target) { + opacity: 0; + pointer-events: none; + transition-delay: 0; +} + +#info .logo { + --icon-size: 96px; +} + +#info .close { + position: absolute; + top: 12px; + right: 12px; + color: white; + border-radius: 50%; +} + +.info-background { + position: relative; +} + +.info-background:before { + content: ''; + position: absolute; + width: 40px; + height: 40px; + top: -20px; + left: -32px; + border-radius: 50%; + background: var(--primary-color); + transform: scale(0); + transition: transform 800ms cubic-bezier(0.77, 0, 0.175, 1); + will-change: transform; +} + +#info:target+a>.info-background:before { + transform: scale(100); +} + +a[href="#info"] { + position: absolute; + top: 12px; + right: 12px; + color: #333; + z-index: 10; +} + +#info .row a { + color: currentColor; + margin: 8px 8px -16px; +} + + + + +/* Loading Indicator */ + +.progress { + width: 80px; + height: 80px; + position: absolute; + top: 0px; + clip: rect(0px, 80px, 80px, 40px); + --progress: rotate(0deg); + transition: transform 200ms; +} + +.circle { + width: 72px; + height: 72px; + border: 4px solid var(--primary-color); + border-radius: 40px; + position: absolute; + clip: rect(0px, 40px, 80px, 0px); + will-change: transform; + transform: var(--progress); +} + +.over50 { + clip: rect(auto, auto, auto, auto); +} + +.over50 .circle.right { + transform: rotate(180deg); +} + + + +/* Toast */ + +.toast-container { + padding: 0 8px 24px; + overflow: hidden; + pointer-events: none; +} + +x-toast { + position: absolute; + min-height: 48px; + bottom: 24px; + width: 100%; + max-width: 344px; + border-radius: 8px; + background-color: #323232; + color: rgba(255, 255, 255, 0.95); + align-items: center; + box-sizing: border-box; + padding: 8px 24px; + z-index: 20; + transition: opacity 200ms, transform 300ms ease-out; + cursor: default; + line-height: 24px; + border-radius: 6px; + pointer-events: all; +} + +x-toast:not([show]):not(:hover) { + opacity: 0; + transform: translateY(100px); +} + +#notification { + position: absolute; + right: 56px; + top: 12px; + color: #333; +} + +/* Instructions */ + +x-instructions { + position: absolute; + top: 120px; + opacity: 0.5; + transition: opacity 300ms; + z-index: -1; +} + +x-instructions:before { + content: attr(mobile); +} + +x-peers:empty~x-instructions { + opacity: 0; +} + +/* Responsive Styles */ + +@media (min-height: 800px) { + x-toast { + right: 24px; + } + footer { + margin-bottom: 16px; + } +} + +@media screen and (min-height: 800px), +screen and (min-width: 1100px) { + x-instructions:before { + content: attr(desktop); + } +} + +@media (max-height: 420px) { + x-instructions { + top: 24px; + } + footer .logo { + --icon-size: 40px; + } +} + +@supports (-webkit-overflow-scrolling: touch) { + /* CSS specific to iOS devices */ + html { + position: fixed; + } + + x-instructions:before { + content: attr(mobile); + } +} \ No newline at end of file diff --git a/dist/index.js b/dist/index.js deleted file mode 100644 index 11da079..0000000 --- a/dist/index.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; -var express = require('express'); -var compression = require('compression'); -var app = express(); -var http = require('http'); -var ExpressPeerServer = require('peer').ExpressPeerServer; -var wsServer = require('./server/ws-server.js'); - -var server = http.createServer(app); - -// Serve up content from public directory -app.use(compression()); -app.use(express.static(__dirname + '/public')); - -var port = process.env.PORT || 3002; -server.listen(port); -wsServer.create(server); -app.use('/peerjs', ExpressPeerServer(server, { - debug: true -})); - - -console.log('listening on port ' + port); diff --git a/dist/package.json b/dist/package.json deleted file mode 100644 index f6c84c6..0000000 --- a/dist/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "private": true, - "engines": { - "node": ">=0.10.0" - }, - "dependencies": { - "binaryjs": "^0.2.1", - "compression": "^1.6.0", - "express": "^4.13.3", - "peer": "^0.2.8", - "ua-parser-js": "^0.7.10", - "ws": "^1.1.1" - } -} diff --git a/dist/public/elements/elements.html b/dist/public/elements/elements.html deleted file mode 100644 index f521d85..0000000 --- a/dist/public/elements/elements.html +++ /dev/null @@ -1,22671 +0,0 @@ - - - - - - diff --git a/dist/public/index.html b/dist/public/index.html deleted file mode 100644 index 3fc7401..0000000 --- a/dist/public/index.html +++ /dev/null @@ -1,18 +0,0 @@ - -Snapdrop - diff --git a/dist/public/manifest.json b/dist/public/manifest.json deleted file mode 100644 index 05ab83b..0000000 --- a/dist/public/manifest.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "Snapdrop", - "short_name": "Snapdrop", - "icons": [{ - "src": "images/touch/icon-128x128.png", - "sizes": "128x128", - "type": "image/png" - }, { - "src": "images/touch/apple-touch-icon.png", - "sizes": "152x152", - "type": "image/png" - }, { - "src": "images/touch/ms-touch-icon-144x144-precomposed.png", - "sizes": "144x144", - "type": "image/png" - }, { - "src": "images/touch/chrome-touch-icon-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, { - "src": "images/touch/chrome-splashscreen-icon-384x384.png", - "sizes": "384x384", - "type": "image/png" - }], - "background_color": "#3367d6", - "start_url": "index.html", - "display": "standalone", - "theme_color": "#3367d6" -} diff --git a/dist/public/scripts/app.js b/dist/public/scripts/app.js deleted file mode 100644 index 27ba397..0000000 --- a/dist/public/scripts/app.js +++ /dev/null @@ -1 +0,0 @@ -!function(e){"use strict";var o=e.querySelector("#app");o.baseUrl="/",""===window.location.port,o.displayInstalledToast=function(){Polymer.dom(e).querySelector("platinum-sw-cache").disabled||Polymer.dom(e).querySelector("#caching-complete").show()},o.displayToast=function(o){var t=Polymer.dom(e).querySelector("#toast");t.text=o,t.show()},o.addEventListener("dom-change",function(){console.log("Our app is ready to rock!"),o.conn=e.querySelector("connection-wrapper")}),window.addEventListener("WebComponentsReady",function(){}),o._showAbout=function(){e.querySelector("#pages").select(1)},o._showAbout=function(){e.querySelector("#pages").select(0)}}(document); \ No newline at end of file diff --git a/dist/public/sounds/blop.mp3 b/dist/public/sounds/blop.mp3 deleted file mode 100755 index 28a6244..0000000 Binary files a/dist/public/sounds/blop.mp3 and /dev/null differ diff --git a/dist/public/sounds/blop.ogg b/dist/public/sounds/blop.ogg deleted file mode 100644 index d1ce0c2..0000000 Binary files a/dist/public/sounds/blop.ogg and /dev/null differ diff --git a/dist/public/styles/main.css b/dist/public/styles/main.css deleted file mode 100644 index 6e71009..0000000 --- a/dist/public/styles/main.css +++ /dev/null @@ -1 +0,0 @@ -body,html{height:100%;width:100%;padding:0;margin:0}body{background:#fafafa;font-family:Roboto,'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333;-webkit-font-smoothing:antialiased;overflow-x:hidden}#ads,#ads2{display:none}@media screen and (min-width:520px){#ads{display:block;position:absolute;top:8px;left:50%;margin-left:-150px}}@media screen and (min-width:720px){#ads{display:none}#ads2{display:block;position:absolute;bottom:4px;left:4px}} \ No newline at end of file diff --git a/dist/readme.md b/dist/readme.md deleted file mode 100644 index 6c01c5f..0000000 --- a/dist/readme.md +++ /dev/null @@ -1,5 +0,0 @@ -# Run a Snapdrop Server -- `npm install` -- `node index.js` -- TODO: SSL connection (i.e nginx) - - ( Please do a PR if you've build an alternative index.js with a self-signed cert ) \ No newline at end of file diff --git a/dist/server/ws-server.js b/dist/server/ws-server.js deleted file mode 100644 index c067053..0000000 --- a/dist/server/ws-server.js +++ /dev/null @@ -1,154 +0,0 @@ -'use strict'; -var parser = require('ua-parser-js'); - -// Start Binary.js server -var BinaryServer = require('binaryjs').BinaryServer; - -exports.create = function(server) { - - // link it to express - var bs = BinaryServer({ - server: server, - path: '/binary' - }); - - function guid() { - function s4() { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); - } - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + - s4() + '-' + s4() + s4() + s4(); - } - - function getDeviceName(req) { - var ua = parser(req.headers['user-agent']); - return { - model: ua.device.model, - os: ua.os.name, - browser: ua.browser.name, - type: ua.device.type - }; - } - - function hash(text) { - // A string hashing function based on Daniel J. Bernstein's popular 'times 33' hash algorithm. - var h = 5381, - index = text.length; - while (index) { - h = (h * 33) ^ text.charCodeAt(--index); - } - return h >>> 0; - } - - function getIP(socket) { - return socket.upgradeReq.headers['x-forwarded-for'] || socket.upgradeReq.connection.remoteAddress; - } - // Wait for new user connections - bs.on('connection', function(client) { - - client.uuidRaw = guid(); - //ip is hashed to prevent injections by spoofing the 'x-forwarded-for' header - // client.hashedIp = 1; //use this to test locally - client.hashedIp = hash(getIP(client._socket)); - - client.deviceName = getDeviceName(client._socket.upgradeReq); - - // Incoming stream from browsers - client.on('stream', function(stream, meta) { - if (meta && meta.serverMsg === 'rtc-support') { - client.uuid = (meta.rtc ? 'rtc_' : '') + client.uuidRaw; - client.send({ - isSystemEvent: true, - type: 'handshake', - name: client.deviceName, - uuid: client.uuid - }); - return; - } - if (meta && meta.serverMsg === 'device-name') { - //max name length = 40 - if (meta.name && meta.name.length > 40) { - return; - } - client.name = meta.name; - return; - } - - meta.from = client.uuid; - - // broadcast to the other client - for (var id in bs.clients) { - if (bs.clients.hasOwnProperty(id)) { - var otherClient = bs.clients[id]; - if (otherClient !== client && meta.toPeer === otherClient.uuid) { - var send = otherClient.createStream(meta); - stream.pipe(send, meta); - } - } - } - }); - }); - - function forEachClient(fn) { - for (var id in bs.clients) { - if (bs.clients.hasOwnProperty(id)) { - var client = bs.clients[id]; - fn(client); - } - } - } - - - - - - function notifyBuddies() { - var locations = {}; - //group all clients by location (by public ip address) - forEachClient(function(client) { - var ip = client.hashedIp; - locations[ip] = locations[ip] || []; - locations[ip].push({ - socket: client, - contact: { - peerId: client.uuid, - name: client.name || client.deviceName, - device: client.name ? client.deviceName : undefined - } - }); - }); - //notify every location - Object.keys(locations).forEach(function(locationKey) { - //notify every client of all other clients in this location - var location = locations[locationKey]; - location.forEach(function(client) { - //all other clients - var buddies = location.reduce(function(result, otherClient) { - if (otherClient !== client) { - result.push(otherClient.contact); - } - return result; - }, []); - var currState = hash(JSON.stringify(buddies)); - console.log(currState); - var socket = client.socket; - //protocol - var msg = { - buddies: buddies, - isSystemEvent: true, - type: 'buddies' - }; - //send only if state changed - if (currState !== socket.lastState) { - socket.send(msg); - socket.lastState = currState; - return; - } - }); - }); - } - - setInterval(notifyBuddies, 3000); -}; diff --git a/gulpfile.js b/gulpfile.js deleted file mode 100644 index 74cf0fc..0000000 --- a/gulpfile.js +++ /dev/null @@ -1,359 +0,0 @@ -'use strict'; - -// Include Gulp & tools we'll use -var autoprefixer = require('gulp-autoprefixer'); -var useref = require('gulp-useref'); -var vulcanize = require('vulcanize'); -var size = require('gulp-size'); -var gulp = require('gulp'); -var ghPages = require('gulp-gh-pages'); -var gulpIf = require('gulp-if'); -var jscs = require('gulp-jscs'); -var jscsStylish = require('gulp-jscs-stylish'); -var htmlExtract = require('gulp-html-extract'); -var imagemin = require('gulp-imagemin'); -var cleanCSS = require('gulp-clean-css'); -var changed = require('gulp-changed'); -var del = require('del'); -var uglify = require('gulp-uglify'); -var jshint = require('gulp-jshint'); -var runSequence = require('run-sequence'); -var browserSync = require('browser-sync'); -var reload = browserSync.reload; -var merge = require('merge-stream'); -var path = require('path'); -var fs = require('fs'); -var glob = require('glob-all'); -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 proxy = require('proxy-middleware'); -var url = require('url'); -var minifyHTML = require('gulp-htmlmin'); -var replace = require('gulp-replace'); - -// 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' -]; - -var DIST = 'dist'; - -var dist = function(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(cleanCSS()) - .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' - })); -}; - -var optimizeHtmlTask = function(src, dest) { - var assets = useref.assets({ - searchPath: ['.tmp', 'app'] - }); - - return gulp.src(src) - .pipe(assets) - // Concatenate and minify JavaScript - .pipe(gulpIf('*.js', uglify({ - preserveComments: 'some' - }))) - // Concatenate and minify styles - // In case you are still using useref build blocks - .pipe(gulpIf('*.css', cleanCSS())) - .pipe(assets.restore()) - .pipe(useref()) - // Minify any HTML - .pipe(gulpIf('*.html', minifyHTML({ - quotes: true, - empty: true, - spare: true - }))) - .pipe(gulpIf('*.html', inlinesource())) - .pipe(replace('window.debug = true;', '')) - // Output files - .pipe(gulp.dest(dest)) - .pipe(size({ - title: 'html' - })); -}; - -// Compile and automatically prefix stylesheets -gulp.task('styles', function() { - return styleTask('styles', ['**/*.css']); -}); - -gulp.task('elements', function() { - 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']; - - 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(gulpIf('*.html', htmlExtract())) - .pipe(jshint()) - .pipe(jscs()) - .pipe(jscsStylish.combineWithHintResults()) - .pipe(jshint.reporter('jshint-stylish')) - .pipe(gulpIf(!browserSync.active, jshint.reporter('fail'))); -}); - -// Optimize images -gulp.task('images', function() { - 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' - })); -}); - -// Copy web fonts to dist -gulp.task('fonts', function() { - 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()); -}); - -// Vulcanize granular configuration -gulp.task('vulcanize', function() { - return gulp.src('app/elements/elements.html') - .pipe(vulcanize({ - stripComments: true, - stripExclude:['app/bower_components/font-roboto/roboto.html'], - inlineCss: true, - inlineScripts: true - })) - .pipe(minifyHTML({ - empty: true - })) - .pipe(gulp.dest(dist('elements'))) - .pipe(size({ - title: 'vulcanize' - })); -}); - -// Generate config data for the element. -// This include a list of files that should be precached, as well as a (hopefully unique) cache -// id that ensure that multiple PSK projects don't share the same Cache Storage. -// This task does not run by default, but if you are interested in using service worker caching -// in your project, please enable it within the 'default' task. -// 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); - } - }); -}); - -// Clean output directory -gulp.task('clean', function() { - return del(['.tmp', dist()]); -}); - -// Watch files for changes & reload -gulp.task('serve', ['styles', 'elements', 'images'], function() { - var peerjsProxy = url.parse('http://localhost:3002/peerjs'); - peerjsProxy.route = '/peerjs'; - var websocketProxy = url.parse('http://localhost:3002/binary'); - websocketProxy.route = '/binary'; - browserSync({ - port: 5000, - notify: false, - logPrefix: 'PSK', - ghostMode: false, - snippetOptions: { - rule: { - match: '', - 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: [proxy(peerjsProxy), proxy(websocketProxy), 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); -}); - -// 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: '', - 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', ['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); -}); - -// 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(gulpIf(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 -// Adds tasks for `gulp test:local` and `gulp test:remote` -require('web-component-tester').gulp.init(gulp); - -// Load custom tasks from the `tasks` directory -try { - require('require-dir')('tasks'); -} catch (err) {} diff --git a/index.js b/index.js deleted file mode 100644 index 11da079..0000000 --- a/index.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; -var express = require('express'); -var compression = require('compression'); -var app = express(); -var http = require('http'); -var ExpressPeerServer = require('peer').ExpressPeerServer; -var wsServer = require('./server/ws-server.js'); - -var server = http.createServer(app); - -// Serve up content from public directory -app.use(compression()); -app.use(express.static(__dirname + '/public')); - -var port = process.env.PORT || 3002; -server.listen(port); -wsServer.create(server); -app.use('/peerjs', ExpressPeerServer(server, { - debug: true -})); - - -console.log('listening on port ' + port); diff --git a/nginx.conf.example b/nginx.conf.example new file mode 100644 index 0000000..5318725 --- /dev/null +++ b/nginx.conf.example @@ -0,0 +1,60 @@ +# This is an configuration example. Please adjust to fit your environment (especially root location and server_name). +# The nginx user requires read permissions to the root location. + +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log; +pid /run/nginx.pid; + +# Load dynamic modules. See /usr/share/nginx/README.dynamic. +include /usr/share/nginx/modules/*.conf; + +events { + worker_connections 1024; +} + +http { + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Load modular configuration files from the /etc/nginx/conf.d directory. + # See http://nginx.org/en/docs/ngx_core_module.html#include + # for more information. + include /etc/nginx/conf.d/*.conf; + + server { + server_name your.domain; + root /path/to/secret-snapdrop/client; + + # Load configuration files for the default server block. + include /etc/nginx/default.d/*.conf; + + location /server { + proxy_pass http://localhost:3000/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header X-Forwarded-For $remote_addr; + } + + location / { + proxy_http_version 1.1; + } + + + listen [::]:80 ; + listen 80 ; +} +} diff --git a/package.json b/package.json deleted file mode 100644 index 2c00b34..0000000 --- a/package.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "private": true, - "devDependencies": { - "browser-sync": "^2.10.1", - "connect-history-api-fallback": "^1.1.0", - "del": "^2.0.2", - "glob-all": "^3.0.1", - "gulp": "^3.9.0", - "gulp-autoprefixer": "^3.1.0", - "gulp-cache": "^0.4.0", - "gulp-changed": "^1.0.0", - "gulp-clean-css": "^2.0.13", - "gulp-gh-pages": "^0.5.4", - "gulp-html-extract": "^0.0.3", - "gulp-htmlmin": "^3.0.0", - "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", - "gulp-load-plugins": "^1.1.0", - "gulp-rename": "^1.2.0", - "gulp-replace": "^0.5.4", - "gulp-size": "^2.0.0", - "gulp-uglify": "^1.2.0", - "gulp-useref": "^2.1.0", - "gulp-vulcanize": "^6.0.0", - "jshint-stylish": "^2.0.0", - "merge-stream": "^1.0.0", - "proxy-middleware": "^0.15.0", - "require-dir": "^0.3.0", - "run-sequence": "^1.0.2", - "url": "^0.11.0", - "vulcanize": ">= 1.4.2", - "web-component-tester": "^4.0.0" - }, - "scripts": { - "test": "gulp test:local", - "start": "gulp serve", - "lint": "gulp lint" - }, - "engines": { - "node": ">=0.10.0" - }, - "dependencies": { - "binaryjs": "^0.2.1", - "compression": "^1.6.0", - "express": "^4.13.3", - "peer": "^0.2.8", - "ua-parser-js": "^0.7.10", - "ws": "^1.1.1" - } -} diff --git a/server/index.js b/server/index.js new file mode 100644 index 0000000..2d8bbfd --- /dev/null +++ b/server/index.js @@ -0,0 +1,217 @@ +const parser = require('ua-parser-js'); + +class SnapdropServer { + + constructor(port) { + const WebSocket = require('ws'); + this._wss = new WebSocket.Server({ + port: port + }); + this._wss.on('connection', (socket, request) => this._onConnection(new Peer(socket, request))); + this._wss.on('headers', (headers, response) => this._onHeaders(headers, response)); + + this._rooms = {}; + this._timerID = 0; + + console.log('Snapdrop is running on port', port); + } + + _onConnection(peer) { + this._joinRoom(peer); + peer.socket.on('message', message => this._onMessage(peer, message)); + this._keepAlive(peer); + } + + _onHeaders(headers, response) { + if (response.headers.cookie && response.headers.cookie.indexOf('peerid=') > -1) return; + response.peerId = Peer.uuid(); + headers.push('Set-Cookie: peerid=' + response.peerId); + } + + _onMessage(sender, message) { + message = JSON.parse(message); + + switch (message.type) { + case 'disconnect': + this._leaveRoom(sender); + case 'pong': + sender.lastBeat = Date.now(); + } + + // relay message to recipient + if (message.to) { + const recipientId = message.to; // TODO: sanitize + const recipient = this._rooms[sender.ip][recipientId]; + delete message.to; + // add sender id + message.sender = sender.id; + this._send(recipient, message); + return; + } + } + + _joinRoom(peer) { + // if room doesn't exist, create it + if (!this._rooms[peer.ip]) { + this._rooms[peer.ip] = {}; + } + + // console.log(peer.id, ' joined the room', peer.ip); + // notify all other peers + for (const otherPeerId in this._rooms[peer.ip]) { + const otherPeer = this._rooms[peer.ip][otherPeerId]; + this._send(otherPeer, { + type: 'peer-joined', + peer: peer.getInfo() + }); + } + + // notify peer about the other peers + const otherPeers = []; + for (const otherPeerId in this._rooms[peer.ip]) { + otherPeers.push(this._rooms[peer.ip][otherPeerId].getInfo()); + } + + this._send(peer, { + type: 'peers', + peers: otherPeers + }); + + // add peer to room + this._rooms[peer.ip][peer.id] = peer; + } + + _leaveRoom(peer) { + // delete the peer + this._cancelKeepAlive(peer); + if (!this._rooms[peer.ip]) return; + + delete this._rooms[peer.ip][peer.id]; + + peer.socket.terminate(); + //if room is empty, delete the room + if (!Object.keys(this._rooms[peer.ip]).length) { + delete this._rooms[peer.ip]; + } else { + // notify all other peers + for (const otherPeerId in this._rooms[peer.ip]) { + const otherPeer = this._rooms[peer.ip][otherPeerId]; + this._send(otherPeer, { + type: 'peer-left', + peerId: peer.id + }); + } + } + } + + _send(peer, message) { + if (!peer) return console.error('undefined peer'); + message = JSON.stringify(message); + peer.socket.send(message, error => { + if (error) this._leaveRoom(peer); + }); + } + + _keepAlive(peer) { + var timeout = 10000; + // console.log(Date.now() - peer.lastBeat); + if (Date.now() - peer.lastBeat > 2 * timeout) { + this._leaveRoom(peer); + return; + } + + if (this._wss.readyState == this._wss.OPEN) { + this._send(peer, { + type: 'ping' + }); + } + peer.timerId = setTimeout(() => this._keepAlive(peer), timeout); + } + + _cancelKeepAlive(peer) { + if (peer.timerId) { + clearTimeout(peer.timerId); + } + } +} + + + +class Peer { + + constructor(socket, request) { + // set socket + this.socket = socket; + + + // set remote ip + if (request.headers['x-forwarded-for']) + this.ip = request.headers['x-forwarded-for'].split(/\s*,\s*/)[0]; + else + this.ip = request.connection.remoteAddress; + + if (request.peerId) { + this.id = request.peerId; + } else { + this.id = request.headers.cookie.replace('peerid=', ''); + } + // set peer id + // is WebRTC supported ? + this.rtcSupported = request.url.indexOf('webrtc') > -1; + // set name + this.setName(request); + // for keepalive + this.timerId = 0; + this.lastBeat = Date.now(); + } + + // return uuid of form xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx + static uuid() { + let uuid = '', + ii; + for (ii = 0; ii < 32; ii += 1) { + switch (ii) { + case 8: + case 20: + uuid += '-'; + uuid += (Math.random() * 16 | 0).toString(16); + break; + case 12: + uuid += '-'; + uuid += '4'; + break; + case 16: + uuid += '-'; + uuid += (Math.random() * 4 | 8).toString(16); + break; + default: + uuid += (Math.random() * 16 | 0).toString(16); + } + } + return uuid; + }; + + toString() { + return `` + } + + setName(req) { + var ua = parser(req.headers['user-agent']); + this.name = { + model: ua.device.model, + os: ua.os.name, + browser: ua.browser.name, + type: ua.device.type + }; + } + + getInfo() { + return { + id: this.id, + name: this.name, + rtcSupported: this.rtcSupported + } + } +} + +const server = new SnapdropServer(process.env.PORT || 3000); \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json new file mode 100644 index 0000000..836eaf0 --- /dev/null +++ b/server/package-lock.json @@ -0,0 +1,26 @@ +{ + "name": "snapdrop", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + }, + "ua-parser-js": { + "version": "0.7.18", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.18.tgz", + "integrity": "sha512-LtzwHlVHwFGTptfNSgezHp7WUlwiqb0gA9AALRbKaERfxwJoiX0A73QbTToxteIAuIaFshhgIZfqK8s7clqgnA==" + }, + "ws": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.0.0.tgz", + "integrity": "sha512-c2UlYcAZp1VS8AORtpq6y4RJIkJ9dQz18W32SpR/qXGfLDZ2jU4y4wKvvZwqbi7U6gxFQTeE+urMbXU/tsDy4w==", + "requires": { + "async-limiter": "~1.0.0" + } + } + } +} diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..dac023d --- /dev/null +++ b/server/package.json @@ -0,0 +1,15 @@ +{ + "name": "snapdrop", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "ua-parser-js": "^0.7.18", + "ws": "^6.0.0" + } +} diff --git a/server/ws-server.js b/server/ws-server.js deleted file mode 100644 index c067053..0000000 --- a/server/ws-server.js +++ /dev/null @@ -1,154 +0,0 @@ -'use strict'; -var parser = require('ua-parser-js'); - -// Start Binary.js server -var BinaryServer = require('binaryjs').BinaryServer; - -exports.create = function(server) { - - // link it to express - var bs = BinaryServer({ - server: server, - path: '/binary' - }); - - function guid() { - function s4() { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); - } - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + - s4() + '-' + s4() + s4() + s4(); - } - - function getDeviceName(req) { - var ua = parser(req.headers['user-agent']); - return { - model: ua.device.model, - os: ua.os.name, - browser: ua.browser.name, - type: ua.device.type - }; - } - - function hash(text) { - // A string hashing function based on Daniel J. Bernstein's popular 'times 33' hash algorithm. - var h = 5381, - index = text.length; - while (index) { - h = (h * 33) ^ text.charCodeAt(--index); - } - return h >>> 0; - } - - function getIP(socket) { - return socket.upgradeReq.headers['x-forwarded-for'] || socket.upgradeReq.connection.remoteAddress; - } - // Wait for new user connections - bs.on('connection', function(client) { - - client.uuidRaw = guid(); - //ip is hashed to prevent injections by spoofing the 'x-forwarded-for' header - // client.hashedIp = 1; //use this to test locally - client.hashedIp = hash(getIP(client._socket)); - - client.deviceName = getDeviceName(client._socket.upgradeReq); - - // Incoming stream from browsers - client.on('stream', function(stream, meta) { - if (meta && meta.serverMsg === 'rtc-support') { - client.uuid = (meta.rtc ? 'rtc_' : '') + client.uuidRaw; - client.send({ - isSystemEvent: true, - type: 'handshake', - name: client.deviceName, - uuid: client.uuid - }); - return; - } - if (meta && meta.serverMsg === 'device-name') { - //max name length = 40 - if (meta.name && meta.name.length > 40) { - return; - } - client.name = meta.name; - return; - } - - meta.from = client.uuid; - - // broadcast to the other client - for (var id in bs.clients) { - if (bs.clients.hasOwnProperty(id)) { - var otherClient = bs.clients[id]; - if (otherClient !== client && meta.toPeer === otherClient.uuid) { - var send = otherClient.createStream(meta); - stream.pipe(send, meta); - } - } - } - }); - }); - - function forEachClient(fn) { - for (var id in bs.clients) { - if (bs.clients.hasOwnProperty(id)) { - var client = bs.clients[id]; - fn(client); - } - } - } - - - - - - function notifyBuddies() { - var locations = {}; - //group all clients by location (by public ip address) - forEachClient(function(client) { - var ip = client.hashedIp; - locations[ip] = locations[ip] || []; - locations[ip].push({ - socket: client, - contact: { - peerId: client.uuid, - name: client.name || client.deviceName, - device: client.name ? client.deviceName : undefined - } - }); - }); - //notify every location - Object.keys(locations).forEach(function(locationKey) { - //notify every client of all other clients in this location - var location = locations[locationKey]; - location.forEach(function(client) { - //all other clients - var buddies = location.reduce(function(result, otherClient) { - if (otherClient !== client) { - result.push(otherClient.contact); - } - return result; - }, []); - var currState = hash(JSON.stringify(buddies)); - console.log(currState); - var socket = client.socket; - //protocol - var msg = { - buddies: buddies, - isSystemEvent: true, - type: 'buddies' - }; - //send only if state changed - if (currState !== socket.lastState) { - socket.send(msg); - socket.lastState = currState; - return; - } - }); - }); - } - - setInterval(notifyBuddies, 3000); -}; diff --git a/tasks/ensure-files.js b/tasks/ensure-files.js deleted file mode 100644 index 48fa4c0..0000000 --- a/tasks/ensure-files.js +++ /dev/null @@ -1,34 +0,0 @@ -var fs = require('fs'); - -/** - * @param {Array} files - * @param {Function} cb - */ - -function ensureFiles(files, cb) { - var missingFiles = files.reduce(function(prev, filePath) { - var fileFound = false; - - try { - fileFound = fs.statSync(filePath).isFile(); - } catch (e) { } - - if (!fileFound) { - prev.push(filePath + ' Not Found'); - } - - return prev; - }, []); - - if (missingFiles.length) { - var err = new Error('Missing Required Files\n' + missingFiles.join('\n')); - } - - if (cb) { - cb(err); - } else if (err) { - throw err; - } -} - -module.exports = ensureFiles; diff --git a/wct.conf.js b/wct.conf.js deleted file mode 100644 index 2ea6e1e..0000000 --- a/wct.conf.js +++ /dev/null @@ -1,18 +0,0 @@ -var path = require('path'); - -var ret = { - 'suites': ['app/test'], - 'webserver': { - 'pathMappings': [] - } -}; - -var mapping = {}; -var rootPath = (__dirname).split(path.sep).slice(-1)[0]; - -mapping['/components/' + rootPath + -'/app/bower_components'] = 'bower_components'; - -ret.webserver.pathMappings.push(mapping); - -module.exports = ret;