Compare commits
2 Commits
2ad1ceecac
...
8169c84bf9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8169c84bf9 | ||
|
|
58f57a6027 |
5
.idea/.gitignore
generated
vendored
Normal file
5
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
12
.idea/drop.a-hxin.cn.iml
generated
Normal file
12
.idea/drop.a-hxin.cn.iml
generated
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/drop.a-hxin.cn.iml" filepath="$PROJECT_DIR$/.idea/drop.a-hxin.cn.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
16
client/404.html
Executable file
16
client/404.html
Executable file
@ -0,0 +1,16 @@
|
||||
<html>
|
||||
<style>
|
||||
.btlink {
|
||||
color: #20a53a;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
<meta charset="UTF-8">
|
||||
<html>
|
||||
<head><title>404 Not Found</title></head>
|
||||
<body>
|
||||
<center><h1>404 Not Found</h1></center>
|
||||
<hr>
|
||||
<div style="text-align: center;font-size: 15px" >Power by <a class="btlink" href="https://www.bt.cn/?from=404" target="_blank">堡塔 (免费,高效和安全的托管控制面板)</a></div>
|
||||
</body>
|
||||
</html>
|
||||
233
client/index.html
Executable file
233
client/index.html
Executable file
@ -0,0 +1,233 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<!-- Web App Config -->
|
||||
<title>Snapdrop</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<meta name="theme-color" content="#3367d6">
|
||||
<meta name="color-scheme" content="dark light">
|
||||
<meta name="apple-mobile-web-app-capable" content="no">
|
||||
<meta name="apple-mobile-web-app-title" content="Snapdrop">
|
||||
<!-- Descriptions -->
|
||||
<meta name="description" content="Instantly share images, videos, PDFs, and links with people nearby. Peer2Peer and Open Source. No Setup, No Signup.">
|
||||
<meta name="keywords" content="File, Transfer, Share, Peer2Peer">
|
||||
<meta name="author" content="RobinLinus">
|
||||
<meta property="og:title" content="Snapdrop">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="og:url" content="https://snapdrop.net/">
|
||||
<meta property="og:author" content="https://facebook.com/RobinLinus">
|
||||
<meta name="twitter:author" content="@RobinLinus">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:description" content="Instantly share images, videos, PDFs, and links with people nearby. Peer2Peer and Open Source. No Setup, No Signup.">
|
||||
<meta name="og:description" content="Instantly share images, videos, PDFs, and links with people nearby. Peer2Peer and Open Source. No Setup, No Signup.">
|
||||
<!-- Icons -->
|
||||
<link rel="icon" sizes="96x96" href="images/favicon-96x96.png">
|
||||
<link rel="shortcut icon" href="images/favicon-96x96.png">
|
||||
<link rel="apple-touch-icon" href="images/apple-touch-icon.png">
|
||||
<meta name="msapplication-TileImage" content="images/mstile-150x150.png">
|
||||
<link rel="fluid-icon" type="image/png" href="images/android-chrome-192x192.png">
|
||||
<meta name="twitter:image" content="https://snapdrop.net/images/twitter-stream.jpg">
|
||||
<meta property="og:image" content="https://snapdrop.net/images/twitter-stream.jpg">
|
||||
<!-- Resources -->
|
||||
<link rel="stylesheet" type="text/css" href="styles.css">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
</head>
|
||||
|
||||
<body translate="no">
|
||||
<header class="row-reverse">
|
||||
<a href="#about" class="icon-button" title="About Snapdrop">
|
||||
<svg class="icon">
|
||||
<use xlink:href="#info-outline" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="#" id="notification" class="icon-button" title="Enable Notifications" hidden>
|
||||
<svg class="icon">
|
||||
<use xlink:href="#notifications" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="#" id="install" class="icon-button" title="Install Snapdrop" hidden>
|
||||
<svg class="icon">
|
||||
<use xlink:href="#homescreen" />
|
||||
</svg>
|
||||
</a>
|
||||
</header>
|
||||
<!-- Peers -->
|
||||
<x-peers class="center"></x-peers>
|
||||
<x-no-peers>
|
||||
<h2>在其他设备上打开Snapdrop以发送文件</h2>
|
||||
</x-no-peers>
|
||||
<x-instructions desktop="点击发送文件或右键发送消息" mobile="轻触发送文件或长按发送消息"></x-instructions>
|
||||
<!-- Footer -->
|
||||
<footer class="column">
|
||||
<svg class="icon logo">
|
||||
<use xlink:href="#wifi-tethering" />
|
||||
</svg>
|
||||
<div id="displayName" placeholder="在设备间传输文件的最简单方法"></div>
|
||||
<div class="font-body2">您可以被此网络上的每个人发现</div>
|
||||
</footer>
|
||||
<!-- Receive Dialog -->
|
||||
<x-dialog id="receiveDialog">
|
||||
<x-background class="full center">
|
||||
<x-paper shadow="2">
|
||||
<h3>文件已接收</h3>
|
||||
<div class="font-subheading" id="fileName">文件名</div>
|
||||
<div class="font-body2" id="fileSize"></div>
|
||||
<div class='preview' style="visibility: hidden;">
|
||||
<img id='img-preview' src="">
|
||||
</div>
|
||||
<div class="row">
|
||||
<label for="autoDownload" class="grow">全选所有</label>
|
||||
<input type="checkbox" id="autoDownload" checked="">
|
||||
</div>
|
||||
<div class="row-reverse">
|
||||
<a class="button" close id="download" title="Download File" autofocus>保存</a>
|
||||
<button class="button" close>忽视</button>
|
||||
</div>
|
||||
</x-paper>
|
||||
</x-background>
|
||||
</x-dialog>
|
||||
<!-- Send Text Dialog -->
|
||||
<x-dialog id="sendTextDialog">
|
||||
<form action="#">
|
||||
<x-background class="full center">
|
||||
<x-paper shadow="2">
|
||||
<h3>发送信息</h3>
|
||||
<div id="textInput" class="textarea" role="textbox" placeholder="发送消息" autocomplete="off" autofocus contenteditable></div>
|
||||
<div class="row-reverse">
|
||||
<button class="button" type="submit" close>发送</button>
|
||||
<a class="button" close>取消</a>
|
||||
</div>
|
||||
</x-paper>
|
||||
</x-background>
|
||||
</form>
|
||||
</x-dialog>
|
||||
<!-- Receive Text Dialog -->
|
||||
<x-dialog id="receiveTextDialog">
|
||||
<x-background class="full center">
|
||||
<x-paper shadow="2">
|
||||
<h3>已收到消息</h3>
|
||||
<div class="font-subheading" id="text"></div>
|
||||
<div class="row-reverse">
|
||||
<button class="button" id="copy" close autofocus>复制</button>
|
||||
<button class="button" close>关闭</button>
|
||||
</div>
|
||||
</x-paper>
|
||||
</x-background>
|
||||
</x-dialog>
|
||||
<!-- Toast -->
|
||||
<div class="toast-container full center">
|
||||
<x-toast class="row" shadow="1" id="toast">文件传输完成!</x-toast>
|
||||
</div>
|
||||
<!-- About Page -->
|
||||
<x-about id="about" class="full center column">
|
||||
<section class="center column fade-in">
|
||||
<header class="row-reverse">
|
||||
<a href="#" class="close icon-button">
|
||||
<svg class="icon">
|
||||
<use xlink:href="#close" />
|
||||
</svg>
|
||||
</a>
|
||||
</header>
|
||||
<svg class="icon logo">
|
||||
<use xlink:href="#wifi-tethering" />
|
||||
</svg>
|
||||
<h1>Snapdrop</h1>
|
||||
<div class="font-subheading">在设备间传输文件的最简单方法</div>
|
||||
<div class="row">
|
||||
<a class="icon-button" target="_blank" href="https://github.com/RobinLinus/snapdrop" title="Snapdrop on Github" rel="noreferrer">
|
||||
<svg class="icon">
|
||||
<use xlink:href="#github" />
|
||||
</svg>
|
||||
</a>
|
||||
<!--<a class="icon-button" target="_blank" href="https://www.paypal.com/donate?hosted_button_id=FTP9DXUR7LA7Q" title="Help cover the server costs!" rel="noreferrer">-->
|
||||
<!-- <svg class="icon">-->
|
||||
<!-- <use xlink:href="#monetarization" />-->
|
||||
<!-- </svg>-->
|
||||
<!--</a>-->
|
||||
<!--<a class="icon-button" target="_blank" href="https://twitter.com/intent/tweet?text=https://snapdrop.net%20by%20@robin_linus%20&" title="Tweet about Snapdrop" rel="noreferrer">-->
|
||||
<!-- <svg class="icon">-->
|
||||
<!-- <use xlink:href="#twitter" />-->
|
||||
<!-- </svg>-->
|
||||
<!--</a>-->
|
||||
<!--<a class="icon-button" target="_blank" href="https://github.com/RobinLinus/snapdrop/blob/master/docs/faq.md" title="Frequently asked questions" rel="noreferrer">-->
|
||||
<!-- <svg class="icon">-->
|
||||
<!-- <use xlink:href="#help-outline" />-->
|
||||
<!-- </svg>-->
|
||||
<!--</a>-->
|
||||
</div>
|
||||
</section>
|
||||
<x-background></x-background>
|
||||
</x-about>
|
||||
<!-- SVG Icon Library -->
|
||||
<svg style="display: none;">
|
||||
<symbol id=wifi-tethering viewBox="0 0 24 24">
|
||||
<path d="M12 11c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 2c0-3.31-2.69-6-6-6s-6 2.69-6 6c0 2.22 1.21 4.15 3 5.19l1-1.74c-1.19-.7-2-1.97-2-3.45 0-2.21 1.79-4 4-4s4 1.79 4 4c0 1.48-.81 2.75-2 3.45l1 1.74c1.79-1.04 3-2.97 3-5.19zM12 3C6.48 3 2 7.48 2 13c0 3.7 2.01 6.92 4.99 8.65l1-1.73C5.61 18.53 4 15.96 4 13c0-4.42 3.58-8 8-8s8 3.58 8 8c0 2.96-1.61 5.53-4 6.92l1 1.73c2.99-1.73 5-4.95 5-8.65 0-5.52-4.48-10-10-10z"></path>
|
||||
</symbol>
|
||||
<symbol id=desktop-mac viewBox="0 0 24 24">
|
||||
<path d="M21 2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h7l-2 3v1h8v-1l-2-3h7c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 12H3V4h18v10z"></path>
|
||||
</symbol>
|
||||
<symbol id=phone-iphone viewBox="0 0 24 24">
|
||||
<path d="M15.5 1h-8C6.12 1 5 2.12 5 3.5v17C5 21.88 6.12 23 7.5 23h8c1.38 0 2.5-1.12 2.5-2.5v-17C18 2.12 16.88 1 15.5 1zm-4 21c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm4.5-4H7V4h9v14z"></path>
|
||||
</symbol>
|
||||
<symbol id=tablet-mac viewBox="0 0 24 24">
|
||||
<path d="M18.5 0h-14C3.12 0 2 1.12 2 2.5v19C2 22.88 3.12 24 4.5 24h14c1.38 0 2.5-1.12 2.5-2.5v-19C21 1.12 19.88 0 18.5 0zm-7 23c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm7.5-4H4V3h15v16z"></path>
|
||||
</symbol>
|
||||
<symbol id=info-outline viewBox="0 0 24 24">
|
||||
<path d="M11 17h2v-6h-2v6zm1-15C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zM11 9h2V7h-2v2z"></path>
|
||||
</symbol>
|
||||
<symbol id=close viewBox="0 0 24 24">
|
||||
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path>
|
||||
</symbol>
|
||||
<symbol id=help-outline viewBox="0 0 24 24">
|
||||
<path d="M11 18h2v-2h-2v2zm1-16C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-14c-2.21 0-4 1.79-4 4h2c0-1.1.9-2 2-2s2 .9 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5 0-2.21-1.79-4-4-4z"></path>
|
||||
</symbol>
|
||||
<symbol id="twitter">
|
||||
<path d="M23.954 4.569c-.885.389-1.83.654-2.825.775 1.014-.611 1.794-1.574 2.163-2.723-.951.555-2.005.959-3.127 1.184-.896-.959-2.173-1.559-3.591-1.559-2.717 0-4.92 2.203-4.92 4.917 0 .39.045.765.127 1.124C7.691 8.094 4.066 6.13 1.64 3.161c-.427.722-.666 1.561-.666 2.475 0 1.71.87 3.213 2.188 4.096-.807-.026-1.566-.248-2.228-.616v.061c0 2.385 1.693 4.374 3.946 4.827-.413.111-.849.171-1.296.171-.314 0-.615-.03-.916-.086.631 1.953 2.445 3.377 4.604 3.417-1.68 1.319-3.809 2.105-6.102 2.105-.39 0-.779-.023-1.17-.067 2.189 1.394 4.768 2.209 7.557 2.209 9.054 0 13.999-7.496 13.999-13.986 0-.209 0-.42-.015-.63.961-.689 1.8-1.56 2.46-2.548l-.047-.02z" />
|
||||
</symbol>
|
||||
<symbol id="github">
|
||||
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
|
||||
</symbol>
|
||||
<g id="notifications">
|
||||
<path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z" />
|
||||
</g>
|
||||
<symbol id="homescreen">
|
||||
<path fill="none" d="M0 0h24v24H0V0z" />
|
||||
<path d="M18 1.01L8 1c-1.1 0-2 .9-2 2v3h2V5h10v14H8v-1H6v3c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-1.99-2-1.99zM10 15h2V8H5v2h3.59L3 15.59 4.41 17 10 11.41z" />
|
||||
<path fill="none" d="M0 0h24v24H0V0z" />
|
||||
</symbol>
|
||||
<symbol id="monetarization">
|
||||
<path d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1.41 16.09V20h-2.67v-1.93c-1.71-.36-3.16-1.46-3.27-3.4h1.96c.1 1.05.82 1.87 2.65 1.87 1.96 0 2.4-.98 2.4-1.59 0-.83-.44-1.61-2.67-2.14-2.48-.6-4.18-1.62-4.18-3.67 0-1.72 1.39-2.84 3.11-3.21V4h2.67v1.95c1.86.45 2.79 1.86 2.85 3.39H14.3c-.05-1.11-.64-1.87-2.22-1.87-1.5 0-2.4.68-2.4 1.64 0 .84.65 1.39 2.67 1.91s4.18 1.39 4.18 3.91c-.01 1.83-1.38 2.83-3.12 3.16z" />
|
||||
</symbol>
|
||||
</svg>
|
||||
<!-- Scripts -->
|
||||
<script src="scripts/network.js"></script>
|
||||
<script src="scripts/ui.js"></script>
|
||||
<script src="scripts/clipboard.js" async></script>
|
||||
<!-- Sounds -->
|
||||
<audio id="blop" autobuffer="true">
|
||||
<source src="sounds/blop.mp3" type="audio/mpeg">
|
||||
<source src="sounds/blop.ogg" type="audio/ogg">
|
||||
</audio>
|
||||
<!-- no script -->
|
||||
<noscript>
|
||||
<x-noscript class="full center column">
|
||||
<h1>启用 JS</h1>
|
||||
<h3>Snardrep 只能使用 JavaScript</h3>
|
||||
</x-noscript>
|
||||
<style>
|
||||
x-noscript {
|
||||
background: #599cfc;
|
||||
color: white;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
a[href="#info"] {
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
</noscript>
|
||||
</body>
|
||||
39
client/manifest.json
Executable file
39
client/manifest.json
Executable file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "Snapdrop",
|
||||
"short_name": "Snapdrop",
|
||||
"icons": [{
|
||||
"src": "images/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},{
|
||||
"src": "images/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
},{
|
||||
"src": "images/android-chrome-192x192-maskable.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},{
|
||||
"src": "images/android-chrome-512x512-maskable.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},{
|
||||
"src": "images/favicon-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png"
|
||||
}],
|
||||
"background_color": "#efefef",
|
||||
"display": "minimal-ui",
|
||||
"theme_color": "#3367d6",
|
||||
"share_target": {
|
||||
"method":"GET",
|
||||
"action": "/?share_target",
|
||||
"params": {
|
||||
"title": "title",
|
||||
"text": "text",
|
||||
"url": "url"
|
||||
}
|
||||
}
|
||||
}
|
||||
56
client/service-worker.js
Executable file
56
client/service-worker.js
Executable file
@ -0,0 +1,56 @@
|
||||
var CACHE_NAME = 'snapdrop-cache-v2';
|
||||
var urlsToCache = [
|
||||
'index.html',
|
||||
'./',
|
||||
'styles.css',
|
||||
'scripts/network.js',
|
||||
'scripts/ui.js',
|
||||
'scripts/clipboard.js',
|
||||
'sounds/blop.mp3',
|
||||
'images/favicon-96x96.png'
|
||||
];
|
||||
|
||||
self.addEventListener('install', function(event) {
|
||||
// Perform install steps
|
||||
event.waitUntil(
|
||||
caches.open(CACHE_NAME)
|
||||
.then(function(cache) {
|
||||
console.log('Opened cache');
|
||||
return cache.addAll(urlsToCache);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
self.addEventListener('fetch', function(event) {
|
||||
event.respondWith(
|
||||
caches.match(event.request)
|
||||
.then(function(response) {
|
||||
// Cache hit - return response
|
||||
if (response) {
|
||||
return response;
|
||||
}
|
||||
return fetch(event.request);
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
self.addEventListener('activate', function(event) {
|
||||
console.log('Updating Service Worker...')
|
||||
event.waitUntil(
|
||||
caches.keys().then(function(cacheNames) {
|
||||
return Promise.all(
|
||||
cacheNames.filter(function(cacheName) {
|
||||
// Return true if you want to remove this cache,
|
||||
// but remember that caches are shared across
|
||||
// the whole origin
|
||||
return true
|
||||
}).map(function(cacheName) {
|
||||
return caches.delete(cacheName);
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
733
client/styles.css
Executable file
733
client/styles.css
Executable file
@ -0,0 +1,733 @@
|
||||
/* Constants */
|
||||
|
||||
:root {
|
||||
--icon-size: 24px;
|
||||
--primary-color: #4285f4;
|
||||
--peer-width: 120px;
|
||||
--text-color: #333;
|
||||
--bg-color: #fafafa;
|
||||
--bg-color-secondary: #f1f3f4;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
.grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.full {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
header {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 56px;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
/* Typography */
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, Roboto, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
-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;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.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: currentColor;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/* Main Header */
|
||||
|
||||
body>header a {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
/* 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,
|
||||
x-no-peers a {
|
||||
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,
|
||||
.device-name {
|
||||
height: 18px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
x-peer[transfer] .status:before {
|
||||
content: 'Transferring...';
|
||||
}
|
||||
|
||||
x-peer:not([transfer]) .status,
|
||||
x-peer[transfer] .device-name {
|
||||
display: none;
|
||||
}
|
||||
|
||||
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;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
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;
|
||||
will-change: opacity;
|
||||
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;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
x-dialog a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* Receive Dialog */
|
||||
#receiveDialog .row {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* Receive Text 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;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
#receiveTextDialog #text a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#receiveTextDialog #text a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#receiveTextDialog h3 {
|
||||
/* Select the received text when double-clicking the dialog */
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Button */
|
||||
|
||||
.button {
|
||||
padding: 0 16px;
|
||||
box-sizing: border-box;
|
||||
min-height: 36px;
|
||||
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;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.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,
|
||||
.icon-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 */
|
||||
|
||||
.textarea {
|
||||
box-sizing: border-box;
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 16px 24px;
|
||||
border-radius: 16px;
|
||||
margin: 8px 0;
|
||||
font-size: 14px;
|
||||
font-family: inherit;
|
||||
background: #f1f3f4;
|
||||
display: block;
|
||||
overflow: auto;
|
||||
resize: none;
|
||||
min-height: 40px;
|
||||
line-height: 16px;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
|
||||
/* Info Animation */
|
||||
|
||||
#about {
|
||||
color: white;
|
||||
z-index: 11;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#about .fade-in {
|
||||
transition: opacity 300ms;
|
||||
will-change: opacity;
|
||||
transition-delay: 300ms;
|
||||
z-index: 11;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
#about:not(:target) .fade-in {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition-delay: 0;
|
||||
}
|
||||
|
||||
#about .logo {
|
||||
--icon-size: 96px;
|
||||
}
|
||||
|
||||
#about x-background {
|
||||
position: absolute;
|
||||
top: calc(32px - 200px);
|
||||
right: calc(32px - 200px);
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
border-radius: 50%;
|
||||
background: var(--primary-color);
|
||||
transform: scale(0);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* Hack such that initial scale(0) isn't animated */
|
||||
#about x-background {
|
||||
will-change: transform;
|
||||
transition: transform 800ms cubic-bezier(0.77, 0, 0.175, 1);
|
||||
}
|
||||
|
||||
#about:target x-background {
|
||||
transform: scale(12);
|
||||
}
|
||||
|
||||
#about .row a {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
/* Generic placeholder */
|
||||
[placeholder]:empty:before {
|
||||
content: attr(placeholder);
|
||||
}
|
||||
|
||||
/* 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;
|
||||
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: 8px;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
x-toast:not([show]):not(:hover) {
|
||||
opacity: 0;
|
||||
transform: translateY(100px);
|
||||
}
|
||||
|
||||
|
||||
/* Instructions */
|
||||
|
||||
x-instructions {
|
||||
position: absolute;
|
||||
top: 120px;
|
||||
opacity: 0.5;
|
||||
transition: opacity 300ms;
|
||||
z-index: -1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
x-instructions:before {
|
||||
content: attr(mobile);
|
||||
}
|
||||
|
||||
x-peers:empty~x-instructions {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
/* Responsive Styles */
|
||||
|
||||
@media (min-height: 800px) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
iOS specific styles
|
||||
*/
|
||||
@supports (-webkit-overflow-scrolling: touch) {
|
||||
|
||||
|
||||
html {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
x-instructions:before {
|
||||
content: attr(mobile);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Color Themes
|
||||
*/
|
||||
|
||||
/* Default colors */
|
||||
body {
|
||||
--text-color: #333;
|
||||
--bg-color: #fafafa;
|
||||
--bg-color-secondary: #f1f3f4;
|
||||
}
|
||||
|
||||
/* Colored Elements */
|
||||
body {
|
||||
color: var(--text-color);
|
||||
background-color: var(--bg-color);
|
||||
transition: background-color 0.5s ease;
|
||||
}
|
||||
|
||||
x-dialog x-paper {
|
||||
background-color: var(--bg-color);
|
||||
}
|
||||
|
||||
.textarea {
|
||||
color: var(--text-color);
|
||||
background-color: var(--bg-color-secondary);
|
||||
}
|
||||
/* Image Preview */
|
||||
#img-preview{
|
||||
max-width: 100%;
|
||||
max-height: 50vh;
|
||||
margin: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
Edge specific styles
|
||||
*/
|
||||
@supports (-ms-ime-align: auto) {
|
||||
|
||||
html,
|
||||
body {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
25
docker-compose.yml
Executable file
25
docker-compose.yml
Executable file
@ -0,0 +1,25 @@
|
||||
version: "3"
|
||||
services:
|
||||
node:
|
||||
image: "node:lts-alpine"
|
||||
user: "node"
|
||||
working_dir: /home/node/app
|
||||
volumes:
|
||||
- ./server/:/home/node/app
|
||||
command: ash -c "npm i && node index.js"
|
||||
nginx:
|
||||
build:
|
||||
context: ./docker/
|
||||
dockerfile: nginx-with-openssl.Dockerfile
|
||||
image: "nginx-with-openssl"
|
||||
volumes:
|
||||
- ./client:/usr/share/nginx/html
|
||||
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
|
||||
- ./docker/certs:/etc/ssl/certs
|
||||
- ./docker/openssl:/mnt/openssl
|
||||
ports:
|
||||
- "8080:80"
|
||||
- "443:443"
|
||||
env_file: ./docker/fqdn.env
|
||||
entrypoint: /mnt/openssl/create.sh
|
||||
command: ["nginx", "-g", "daemon off;"]
|
||||
1
docker/fqdn.env
Executable file
1
docker/fqdn.env
Executable file
@ -0,0 +1 @@
|
||||
FQDN=localhost
|
||||
3
docker/nginx-with-openssl.Dockerfile
Executable file
3
docker/nginx-with-openssl.Dockerfile
Executable file
@ -0,0 +1,3 @@
|
||||
FROM nginx:alpine
|
||||
|
||||
RUN apk add --no-cache openssl
|
||||
75
docker/nginx/default.conf
Executable file
75
docker/nginx/default.conf
Executable file
@ -0,0 +1,75 @@
|
||||
server {
|
||||
listen 80;
|
||||
#server_name your.domain;
|
||||
|
||||
#charset koi8-r;
|
||||
#access_log /var/log/nginx/host.access.log main;
|
||||
|
||||
expires epoch;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
location /server {
|
||||
proxy_connect_timeout 300;
|
||||
proxy_pass http://node:3000;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header X-Forwarded-for $remote_addr;
|
||||
}
|
||||
|
||||
location /ca.crt {
|
||||
alias /etc/ssl/certs/snapdropCA.crt;
|
||||
}
|
||||
|
||||
#error_page 404 /404.html;
|
||||
|
||||
# redirect server error pages to the static page /50x.html
|
||||
#
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
ssl_certificate /etc/ssl/certs/snapdrop-dev.crt;
|
||||
ssl_certificate_key /etc/ssl/certs/snapdrop-dev.key;
|
||||
|
||||
#server_name ;
|
||||
|
||||
#charset koi8-r;
|
||||
#access_log /var/log/nginx/host.access.log main;
|
||||
|
||||
expires epoch;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
location /server {
|
||||
proxy_connect_timeout 300;
|
||||
proxy_pass http://node:3000;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header X-Forwarded-for $remote_addr;
|
||||
}
|
||||
|
||||
location /ca.crt {
|
||||
alias /etc/ssl/certs/snapdropCA.crt;
|
||||
}
|
||||
|
||||
#error_page 404 /404.html;
|
||||
|
||||
# redirect server error pages to the static page /50x.html
|
||||
#
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
||||
|
||||
9
docker/openssl/create.sh
Executable file
9
docker/openssl/create.sh
Executable file
@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
cnf_dir='/mnt/openssl/'
|
||||
certs_dir='/etc/ssl/certs/'
|
||||
openssl req -config ${cnf_dir}snapdropCA.cnf -new -x509 -days 1 -keyout ${certs_dir}snapdropCA.key -out ${certs_dir}snapdropCA.crt
|
||||
openssl req -config ${cnf_dir}snapdropCert.cnf -new -out /tmp/snapdrop-dev.csr -keyout ${certs_dir}snapdrop-dev.key
|
||||
openssl x509 -req -in /tmp/snapdrop-dev.csr -CA ${certs_dir}snapdropCA.crt -CAkey ${certs_dir}snapdropCA.key -CAcreateserial -extensions req_ext -extfile ${cnf_dir}snapdropCert.cnf -sha512 -days 1 -out ${certs_dir}snapdrop-dev.crt
|
||||
|
||||
exec "$@"
|
||||
26
docker/openssl/snapdropCA.cnf
Executable file
26
docker/openssl/snapdropCA.cnf
Executable file
@ -0,0 +1,26 @@
|
||||
[ req ]
|
||||
default_bits = 2048
|
||||
default_md = sha256
|
||||
default_days = 1
|
||||
encrypt_key = no
|
||||
distinguished_name = subject
|
||||
x509_extensions = x509_ext
|
||||
string_mask = utf8only
|
||||
prompt = no
|
||||
|
||||
[ subject ]
|
||||
organizationName = Snapdrop
|
||||
OU = CA
|
||||
commonName = snapdrop-CA
|
||||
|
||||
[ x509_ext ]
|
||||
subjectKeyIdentifier = hash
|
||||
authorityKeyIdentifier = keyid:always,issuer
|
||||
|
||||
# You only need digitalSignature below. *If* you don't allow
|
||||
# RSA Key transport (i.e., you use ephemeral cipher suites), then
|
||||
# omit keyEncipherment because that's key transport.
|
||||
|
||||
basicConstraints = critical, CA:TRUE, pathlen:0
|
||||
keyUsage = critical, digitalSignature, keyEncipherment, cRLSign, keyCertSign
|
||||
|
||||
29
docker/openssl/snapdropCert.cnf
Executable file
29
docker/openssl/snapdropCert.cnf
Executable file
@ -0,0 +1,29 @@
|
||||
[ req ]
|
||||
default_bits = 2048
|
||||
default_md = sha256
|
||||
default_days = 1
|
||||
encrypt_key = no
|
||||
distinguished_name = subject
|
||||
req_extensions = req_ext
|
||||
string_mask = utf8only
|
||||
prompt = no
|
||||
|
||||
[ subject ]
|
||||
organizationName = Snapdrop
|
||||
OU = Development
|
||||
|
||||
# Use a friendly name here because it's presented to the user. The server's DNS
|
||||
# names are placed in Subject Alternate Names. Plus, DNS names here is deprecated
|
||||
# by both IETF and CA/Browser Forums. If you place a DNS name here, then you
|
||||
# must include the DNS name in the SAN too (otherwise, Chrome and others that
|
||||
# strictly follow the CA/Browser Baseline Requirements will fail).
|
||||
|
||||
commonName = ${ENV::FQDN}
|
||||
|
||||
[ req_ext ]
|
||||
subjectKeyIdentifier = hash
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = digitalSignature, keyEncipherment
|
||||
subjectAltName = DNS:${ENV::FQDN}
|
||||
nsComment = "OpenSSL Generated Certificate"
|
||||
extendedKeyUsage = serverAuth
|
||||
76
docs/faq.md
Executable file
76
docs/faq.md
Executable file
@ -0,0 +1,76 @@
|
||||
# Frequently Asked Questions
|
||||
|
||||
### Instructions / Discussions
|
||||
* [Video Instructions](https://www.youtube.com/watch?v=4XN02GkcHUM) (Big thanks to [TheiTeckHq](https://www.youtube.com/channel/UC_DUzWMb8gZZnAbISQjmAfQ))
|
||||
* [idownloadblog](http://www.idownloadblog.com/2015/12/29/snapdrop/)
|
||||
* [thenextweb](http://thenextweb.com/insider/2015/12/27/snapdrop-is-a-handy-web-based-replacement-for-apples-fiddly-airdrop-file-transfer-tool/)
|
||||
* [winboard](http://www.winboard.org/artikel-ratgeber/6253-dateien-vom-desktop-pc-mit-anderen-plattformen-teilen-mit-snapdrop.html)
|
||||
* [免費資源網路社群](https://free.com.tw/snapdrop/)
|
||||
* [Hackernews](https://news.ycombinator.com/front?day=2020-12-24)
|
||||
* [Reddit](https://www.reddit.com/r/Android/comments/et4qny/snapdrop_is_a_free_open_source_cross_platform/)
|
||||
* [Producthunt](https://www.producthunt.com/posts/snapdrop)
|
||||
|
||||
### Help! I can't install the PWA!
|
||||
if you are using a Chromium-based browser (Chrome, Edge, Brave, etc.), you can easily install Snapdrop PWA on your desktop by clicking the install button in the top-right corner while on [snapdrop.net](https://snapdrop.net) (see below).
|
||||
<img src="pwa-install.png">
|
||||
|
||||
### What about the connection? Is it a P2P-connection directly from device to device or is there any third-party-server?
|
||||
It uses a P2P connection if WebRTC is supported by the browser. WebRTC needs a Signaling Server, but it is only used to establish a connection and is not involved in the file transfer.
|
||||
|
||||
### What about privacy? Will files be saved on third-party-servers?
|
||||
None of your files are ever sent to any server. Files are sent only between peers. Snapdrop doesn't even use a database. If you are curious have a look [at the Server](https://github.com/RobinLinus/snapdrop/blob/master/server/). Even if Snapdrop was able to view the files being transfered, WebRTC encrypts the files on transit, so the server would be unable to read them.
|
||||
|
||||
### What about security? Are my files encrypted while being sent between the computers?
|
||||
Yes. Your files are sent using WebRTC, which encrypts them on transit.
|
||||
|
||||
### Why don't you implement feature xyz?
|
||||
Snapdrop is a study in radical simplicity. The user interface is insanely simple. Features are chosen very carefully because complexity grows quadratically since every feature potentially interferes with each other feature. We focus very narrowly on a single use case: instant file transfer.
|
||||
We are not trying to optimize for some edge-cases. We are optimizing the user flow of the average users. Don't be sad if we decline your feature request for the sake of simplicity.
|
||||
|
||||
If you want to learn more about simplicity you can read [Insanely Simple: The Obsession that Drives Apple's Success](https://www.amazon.com/Insanely-Simple-Ken-Segall-audiobook/dp/B007Z9686O) or [Thinking, Fast and Slow](https://www.amazon.com/Thinking-Fast-Slow-Daniel-Kahneman/dp/0374533555).
|
||||
|
||||
|
||||
### Snapdrop is awesome! How can I support it?
|
||||
* [Donate via PayPal to help cover the server costs](https://www.paypal.com/donate/?hosted_button_id=FTP9DXUR7LA7Q)
|
||||
* [File bugs, give feedback, submit suggestions](https://github.com/RobinLinus/snapdrop/issues)
|
||||
* Share Snapdrop on your social media.
|
||||
* Fix bugs and make a pull request.
|
||||
* Do security analysis and suggestions
|
||||
|
||||
|
||||
## "Inofficial" Instances
|
||||
Here's a list of other people hosting inofficial instances of Snapdrop:
|
||||
- https://pairdrop.net/
|
||||
- https://snapdrop.k26.ch/
|
||||
- https://snapdrop.9pfs.repl.co/
|
||||
- https://filedrop.codext.de/
|
||||
- https://s.hoothin.com/
|
||||
- https://www.wulingate.com/
|
||||
- https://snapdrop.fairysoft.net/
|
||||
- https://airtransferer.web.app/
|
||||
- https://drop.wuyuan.dev
|
||||
- https://share.jck.cx
|
||||
|
||||
DISCLAIMER: WE ARE NOT IN ANY WAY AFFILIATED WITH THE PEOPLE WHO RUN THESE INSTANCES. WE DO NOT KNOW THEM. WE CANNOT VERIFY THE CODE THEY ARE RUNNING!
|
||||
|
||||
|
||||
## Third-Party Apps
|
||||
Here's a list of some third-party Snapdrop apps:
|
||||
|
||||
1. [Snapdrop Desktop App](https://github.com/alextwothousand/snapdrop-desktop) built on top of Electron. (Thanks to [alextwothousand!](https://github.com/alextwothousand/)).
|
||||
|
||||
1. [Snapdrop Android App](https://github.com/fm-sys/snapdrop-android) allows you to also send files directly from other apps via the share action.
|
||||
|
||||
1. [Snapdrop Flutter App](https://github.com/congnguyendinh0/snapdrop_flutter)
|
||||
|
||||
1. [Snapdrop iOS App](https://github.com/CDsigma/Snapdrop-iOS-App)
|
||||
|
||||
1. [Snapdrop Node App (with completely Node server)](https://github.com/Bellisario/node-snapdrop)
|
||||
|
||||
1. [SnapDrop VSCode Extension](https://github.com/Yash-Garg/snapdrop-vsc)
|
||||
|
||||
1. Feel free to make one :)
|
||||
|
||||
|
||||
|
||||
[< Back](/README.md)
|
||||
61
docs/local-dev.md
Executable file
61
docs/local-dev.md
Executable file
@ -0,0 +1,61 @@
|
||||
# Local Development
|
||||
## Install
|
||||
|
||||
First, [Install docker with docker-compose.](https://docs.docker.com/compose/install/)
|
||||
|
||||
Then, clone the repository:
|
||||
```
|
||||
git clone https://github.com/RobinLinus/snapdrop.git
|
||||
cd snapdrop
|
||||
docker-compose up -d
|
||||
```
|
||||
Now point your browser to `http://localhost:8080`.
|
||||
|
||||
- To restart the containers run `docker-compose restart`.
|
||||
- To stop the containers run `docker-compose stop`.
|
||||
- To debug the NodeJS server run `docker logs snapdrop_node_1`.
|
||||
|
||||
|
||||
## Run locally by pulling image from Docker Hub
|
||||
|
||||
Have docker installed, then use the command:
|
||||
```
|
||||
docker pull linuxserver/snapdrop
|
||||
```
|
||||
|
||||
To run the image, type (if port 8080 is occupied by host use another random port <random port>:80):
|
||||
```
|
||||
docker run -d -p 8080:80 linuxserver/snapdrop
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Testing PWA related features
|
||||
PWAs require that the app is served under a correctly set up and trusted TLS endpoint.
|
||||
|
||||
The nginx container creates a CA certificate and a website certificate for you. To correctly set the common name of the certificate, you need to change the FQDN environment variable in `docker/fqdn.env` to the fully qualified domain name of your workstation.
|
||||
|
||||
If you want to test PWA features, you need to trust the CA of the certificate for your local deployment. For your convenience, you can download the crt file from `http://<Your FQDN>:8080/ca.crt`. Install that certificate to the trust store of your operating system.
|
||||
- On Windows, make sure to install it to the `Trusted Root Certification Authorities` store.
|
||||
- On MacOS, double click the installed CA certificate in `Keychain Access`, expand `Trust`, and select `Always Trust` for SSL.
|
||||
- Firefox uses its own trust store. To install the CA, point Firefox at `http://<Your FQDN>:8080/ca.crt`. When prompted, select `Trust this CA to identify websites` and click OK.
|
||||
- When using Chrome, you need to restart Chrome so it reloads the trust store (`chrome://restart`). Additionally, after installing a new cert, you need to clear the Storage (DevTools -> Application -> Clear storage -> Clear site data).
|
||||
|
||||
Please note that the certificates (CA and webserver cert) expire after a day.
|
||||
Also, whenever you restart the nginx docker, container new certificates are created.
|
||||
|
||||
The site is served on `https://<Your FQDN>:443`.
|
||||
|
||||
## Deployment Notes
|
||||
The client expects the server at http(s)://your.domain/server.
|
||||
|
||||
When serving the node server behind a proxy, the `X-Forwarded-For` header has to be set by the proxy. Otherwise, all clients that are served by the proxy will be mutually visible.
|
||||
|
||||
By default, the server listens on port 3000.
|
||||
|
||||
For an nginx configuration example, see `docker/nginx/default.conf`.
|
||||
|
||||
[< Back](/README.md)
|
||||
BIN
docs/pwa-install.png
Executable file
BIN
docs/pwa-install.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
16
server/package.json
Executable file
16
server/package.json
Executable file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"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.24",
|
||||
"unique-names-generator": "^4.3.0",
|
||||
"ws": "^7.4.6"
|
||||
}
|
||||
}
|
||||
41
server/pnpm-lock.yaml
generated
Normal file
41
server/pnpm-lock.yaml
generated
Normal file
@ -0,0 +1,41 @@
|
||||
lockfileVersion: '6.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
dependencies:
|
||||
ua-parser-js:
|
||||
specifier: ^0.7.24
|
||||
version: 0.7.40
|
||||
unique-names-generator:
|
||||
specifier: ^4.3.0
|
||||
version: 4.7.1
|
||||
ws:
|
||||
specifier: ^7.4.6
|
||||
version: 7.5.10
|
||||
|
||||
packages:
|
||||
|
||||
/ua-parser-js@0.7.40:
|
||||
resolution: {integrity: sha512-us1E3K+3jJppDBa3Tl0L3MOJiGhe1C6P0+nIvQAFYbxlMAx0h81eOwLmU57xgqToduDDPx3y5QsdjPfDu+FgOQ==}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/unique-names-generator@4.7.1:
|
||||
resolution: {integrity: sha512-lMx9dX+KRmG8sq6gulYYpKWZc9RlGsgBR6aoO8Qsm3qvkSJ+3rAymr+TnV8EDMrIrwuFJ4kruzMWM/OpYzPoow==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/ws@7.5.10:
|
||||
resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==}
|
||||
engines: {node: '>=8.3.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
utf-8-validate: ^5.0.2
|
||||
peerDependenciesMeta:
|
||||
bufferutil:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
dev: false
|
||||
Loading…
x
Reference in New Issue
Block a user