Compare commits

...

8 Commits

Author SHA1 Message Date
schlagmichdoch
1bb8a63eed increase version to v1.5.1 2023-03-27 02:31:56 +02:00
schlagmichdoch
7d581ca858 Merge pull request #92 from schlagmichdoch/optimize_animation
Optimize background animation to drastically reduce CPU/GPU resources
2023-03-27 02:19:19 +02:00
schlagmichdoch
dcc4e8b747 Optimize background animation drastically by using offscreen canvases to reuse frames. Rewrite animate function to prevent it from being called multiple times 2023-03-27 02:17:36 +02:00
schlagmichdoch
ce549adf22 Merge pull request #82 from kgncloud/patch-1
Add Healthcheck to Dockerfile
2023-03-25 04:09:46 +01:00
schlagmichdoch
bdb39a1d2c add docker-swarm-usage.md reference to host-your-own.md and tidy up docker-swarm-usage.md 2023-03-25 04:08:11 +01:00
schlagmichdoch
195dfd0bb3 Add https requirement for PWAs to faq.md 2023-03-25 03:37:15 +01:00
Kaindl Network
680ed81bd7 Create 2023-03-18 23:52:33 +00:00
Kaindl Network
b7781e2bab Add Healthcheck to Dockerfile 2023-03-13 20:38:36 +01:00
10 changed files with 205 additions and 76 deletions

View File

@@ -9,3 +9,6 @@ RUN npm ci
COPY . .
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD wget --quiet --tries=1 --spider http://localhost:3000 || exit 1

View File

@@ -0,0 +1,43 @@
# Docker Swarm Usage
## Healthcheck
The [Docker Image](../Dockerfile) includes a Healthcheck with the following options:
```
--interval=30s
```
> Specifies the time interval at which the health check should be performed. In this case, the health check will be performed every 30 seconds.
<br>
```
--timeout=10s
```
> Specifies the amount of time to wait for a response from the health check command. If the response does not arrive within 10 seconds, the health check will be considered a failure.
<br>
```
--start-period=5s
```
> Specifies the amount of time to wait before starting the health check process. In this case, the health check process will begin 5 seconds after the container is started.
<br>
```
--retries=3
```
> Specifies the number of times Docker should retry the health check before considering the container to be unhealthy.
<br>
The CMD instruction is used to define the command that will be run as part of the health check.
In this case, the command is `wget --quiet --tries=1 --spider http://localhost:3000/ || exit 1`. This command will attempt to connect to `http://localhost:3000/`
and if it fails it will exit with a status code of `1`. If this command returns a status code other than `0`, the health check will be considered a failure.
Overall, this HEALTHCHECK instruction is defining a health check process that will run every 30 seconds, wait up to 10 seconds for a response,
begin 5 seconds after the container is started, and retry up to 3 times.
The health check will consist of attempting to connect to http://localhost:3000/ and will consider the container to be unhealthy if it is unable to connect.

View File

@@ -11,6 +11,16 @@ by clicking the install-button in the top-right corner while on [pairdrop.net](h
<img width="400" src="pwa-install.png" alt="Example on how to install a pwa with Edge">
On Firefox, PWAs are installable via [this browser extensions](https://addons.mozilla.org/de/firefox/addon/pwas-for-firefox/)
<br>
<b>Self-Hosted Instance?</b>
To be able to install the PWA from a self-hosted instance, the connection needs to be [established through HTTPS](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Installable_PWAs).
See [this host your own section](https://github.com/schlagmichdoch/PairDrop/blob/master/docs/host-your-own.md#testing-pwa-related-features) for more information.
<br>
</details>
<details>
@@ -23,6 +33,9 @@ Shortcuts!
- Close all send and pair dialogs by pressing `Escape`.
- Copy a received message to clipboard with `CTRL/⌘ + C`.
- Accept file transfer request with `Enter` and decline with `Escape`.
<br>
</details>
<details>
@@ -37,6 +50,9 @@ iOS Shortcuts to the win:
I created a simple iOS shortcut that takes your photos and saves them to your gallery:
https://routinehub.co/shortcut/13988/
<br>
</details>
<details>
@@ -49,6 +65,9 @@ Yes, it finally is!
* [Send directly from share menu on iOS](/docs/how-to.md#send-directly-from-share-menu-on-ios)
* [Send directly from share menu on Android](/docs/how-to.md#send-directly-from-share-menu-on-android)
<br>
</details>
<details>
@@ -60,6 +79,9 @@ Yes, it is!
* [Send directly from command-line interface](/docs/how-to.md#send-directly-via-command-line-interface)
<br>
</details>
<details>
@@ -72,6 +94,9 @@ Here's a list of some third-party apps compatible with PairDrop:
1. [Snapdrop Android App](https://github.com/fm-sys/snapdrop-android)
2. [Snapdrop for Firefox (Addon)](https://github.com/ueen/SnapdropFirefoxAddon)
3. Feel free to make one :)
<br>
</details>
<details>
@@ -83,6 +108,9 @@ It uses a P2P connection if WebRTC is supported by the browser. WebRTC needs a S
If your devices are paired and behind a NAT, the public TURN Server from [Open Relay](https://www.metered.ca/tools/openrelay/) is used to route your files and messages.
<br>
</details>
<details>
@@ -95,6 +123,9 @@ WebRTC encrypts the files on transit.
If your devices are paired and behind a NAT, the public TURN Server from [Open Relay](https://www.metered.ca/tools/openrelay/) is used to route your files and messages.
<br>
</details>
<details>
@@ -104,6 +135,9 @@ If your devices are paired and behind a NAT, the public TURN Server from [Open R
Yes. Your files are sent using WebRTC, which encrypts them on transit. To ensure the connection is secure and there is no MITM, compare the security number shown under the device name on both devices. The security number is different for every connection.
<br>
</details>
<details>
@@ -124,6 +158,9 @@ Alternatively, you can open a hotspot on one of your devices to bridge the conne
You can also use mobile hotspots on phones to do that.
Then, all data should be sent directly between devices and your data plan should not be charged.
<br>
</details>
<details>
@@ -136,6 +173,9 @@ We are not trying to optimize for some edge-cases. We are optimizing the user fl
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).
<br>
</details>
<details>
@@ -150,6 +190,9 @@ If you want to learn more about simplicity you can read [Insanely Simple: The Ob
* Do security analysis and suggestions
* To support the original Snapdrop and its creator go to [his GitHub page](https://github.com/RobinLinus/snapdrop)
<br>
</details>
<details>
@@ -158,6 +201,9 @@ If you want to learn more about simplicity you can read [Insanely Simple: The Ob
</summary>
[See here for Information about the Technical Implementation](/docs/technical-documentation.md)
<br>
</details>
[< Back](/README.md)

View File

@@ -82,6 +82,8 @@ docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 gh
>
> To specify options replace `npm run start:prod` according to [the documentation below.](#options--flags-1)
> The Docker Image includes a Healthcheck. To learn more see [Docker Swarm Usage](./docker-swarm-usage.md#docker-swarm-usage)
### Docker Image self-built
#### Build the image
```bash
@@ -101,6 +103,8 @@ docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 -i
>
> To specify options replace `npm run start:prod` according to [the documentation below.](#options--flags-1)
> The Docker Image includes a Healthcheck. To learn more see [Docker Swarm Usage](./docker-swarm-usage.md#docker-swarm-usage)
<br>
## Deployment with Docker Compose

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "pairdrop",
"version": "1.5.0",
"version": "1.5.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "pairdrop",
"version": "1.5.0",
"version": "1.5.1",
"license": "ISC",
"dependencies": {
"express": "^4.18.2",

View File

@@ -1,6 +1,6 @@
{
"name": "pairdrop",
"version": "1.5.0",
"version": "1.5.1",
"description": "",
"main": "index.js",
"scripts": {

View File

@@ -1943,68 +1943,84 @@ Events.on('load', () => {
style.zIndex = -1;
style.top = 0;
style.left = 0;
let ctx = c.getContext('2d');
let cCtx = c.getContext('2d');
let x0, y0, w, h, dw, offset;
let offscreenCanvases = [];
function init() {
let oldW = w;
let oldH = h;
let oldOffset = offset
w = document.documentElement.clientWidth;
h = document.documentElement.clientHeight;
c.width = w;
c.height = h;
offset = $$('footer').offsetHeight - 32;
if (h > 800) offset += 16;
if (oldW === w && oldH === h && oldOffset === offset) return; // nothing has changed
c.width = w;
c.height = h;
x0 = w / 2;
y0 = h - offset;
dw = Math.max(w, h, 1000) / 13;
drawCircles();
dw = Math.round(Math.max(w, h, 1000) / 13);
drawCircles(cCtx, 0);
// enforce redrawing of frames
offscreenCanvases = [];
}
Events.on('bg-resize', _ => init());
window.onresize = _ => Events.fire('bg-resize');
function drawCircle(radius) {
function drawCircle(ctx, radius) {
ctx.beginPath();
let color = Math.round(255 * (1 - radius / Math.max(w, h)));
ctx.strokeStyle = 'rgba(' + color + ',' + color + ',' + color + ',0.1)';
ctx.lineWidth = 2;
let opacity = 0.2 * (1 - 1.2 * radius / Math.max(w, h));
ctx.strokeStyle = `rgb(128, 128, 128, ${opacity})`;
ctx.arc(x0, y0, radius, 0, 2 * Math.PI);
ctx.stroke();
ctx.lineWidth = 2;
}
let step = 0;
function drawCircles() {
ctx.clearRect(0, 0, w, h);
for (let i = 0; i < 8; i++) {
drawCircle(dw * i + step % dw);
}
step += 1;
}
let loading = true;
function animate() {
if (loading || !finished()) {
requestAnimationFrame(function() {
drawCircles();
animate();
});
function drawCircles(ctx, frame) {
for (let i = 0; i < 13; i++) {
drawCircle(ctx, dw * i + frame);
}
}
function finished() {
return step % dw >= dw - 5;
function createOffscreenCanvas(frame) {
let canvas = document.createElement("canvas");
canvas.width = c.width;
canvas.height = c.height;
offscreenCanvases[frame] = canvas;
let ctx = canvas.getContext('2d');
drawCircles(ctx, frame);
}
function drawFrame(frame) {
cCtx.clearRect(0, 0, w, h);
if (!offscreenCanvases[frame]) {
createOffscreenCanvas(frame);
}
cCtx.drawImage(offscreenCanvases[frame], 0, 0);
}
let animate = true;
let currentFrame = 0;
function animateBg() {
if (currentFrame + 1 < dw || animate) {
currentFrame = (currentFrame + 1) % dw;
drawFrame(currentFrame);
}
setTimeout(_ => animateBg(), 3000 / dw);
}
window.animateBackground = function(l) {
if (!l) {
loading = false;
} else if (!loading) {
loading = true;
if (finished()) animate();
}
animate = l;
};
init();
animate();
animateBg();
});
document.changeFavicon = function (src) {

View File

@@ -1,4 +1,4 @@
const cacheVersion = 'v1.5.0';
const cacheVersion = 'v1.5.1';
const cacheTitle = `pairdrop-cache-${cacheVersion}`;
const urlsToCache = [
'index.html',

View File

@@ -1944,67 +1944,84 @@ Events.on('load', () => {
style.zIndex = -1;
style.top = 0;
style.left = 0;
let ctx = c.getContext('2d');
let cCtx = c.getContext('2d');
let x0, y0, w, h, dw, offset;
let offscreenCanvases = [];
function init() {
let oldW = w;
let oldH = h;
let oldOffset = offset
w = document.documentElement.clientWidth;
h = document.documentElement.clientHeight;
offset = $$('footer').offsetHeight - 32;
if (h > 800) offset += 16;
if (oldW === w && oldH === h && oldOffset === offset) return; // nothing has changed
c.width = w;
c.height = h;
offset = $$('footer').offsetHeight - 32;
x0 = w / 2;
y0 = h - offset;
dw = Math.max(w, h, 1000) / 13;
drawCircles();
dw = Math.round(Math.max(w, h, 1000) / 13);
drawCircles(cCtx, 0);
// enforce redrawing of frames
offscreenCanvases = [];
}
Events.on('bg-resize', _ => init());
window.onresize = _ => Events.fire('bg-resize');
function drawCircle(radius) {
function drawCircle(ctx, radius) {
ctx.beginPath();
let color = Math.round(255 * (1 - radius / Math.max(w, h)));
ctx.strokeStyle = 'rgba(' + color + ',' + color + ',' + color + ',0.1)';
ctx.lineWidth = 2;
let opacity = 0.2 * (1 - 1.2 * radius / Math.max(w, h));
ctx.strokeStyle = `rgb(128, 128, 128, ${opacity})`;
ctx.arc(x0, y0, radius, 0, 2 * Math.PI);
ctx.stroke();
ctx.lineWidth = 2;
}
let step = 0;
function drawCircles() {
ctx.clearRect(0, 0, w, h);
for (let i = 0; i < 8; i++) {
drawCircle(dw * i + step % dw);
}
step += 1;
}
let loading = true;
function animate() {
if (loading || !finished()) {
requestAnimationFrame(function() {
drawCircles();
animate();
});
function drawCircles(ctx, frame) {
for (let i = 0; i < 13; i++) {
drawCircle(ctx, dw * i + frame);
}
}
function finished() {
return step % dw >= dw - 5;
function createOffscreenCanvas(frame) {
let canvas = document.createElement("canvas");
canvas.width = c.width;
canvas.height = c.height;
offscreenCanvases[frame] = canvas;
let ctx = canvas.getContext('2d');
drawCircles(ctx, frame);
}
function drawFrame(frame) {
cCtx.clearRect(0, 0, w, h);
if (!offscreenCanvases[frame]) {
createOffscreenCanvas(frame);
}
cCtx.drawImage(offscreenCanvases[frame], 0, 0);
}
let animate = true;
let currentFrame = 0;
function animateBg() {
if (currentFrame + 1 < dw || animate) {
currentFrame = (currentFrame + 1) % dw;
drawFrame(currentFrame);
}
setTimeout(_ => animateBg(), 3000 / dw);
}
window.animateBackground = function(l) {
if (!l) {
loading = false;
} else if (!loading) {
loading = true;
if (finished()) animate();
}
animate = l;
};
init();
animate();
animateBg();
});
document.changeFavicon = function (src) {

View File

@@ -1,4 +1,4 @@
const cacheVersion = 'v1.5.0';
const cacheVersion = 'v1.5.1';
const cacheTitle = `pairdrop-included-ws-fallback-cache-${cacheVersion}`;
const urlsToCache = [
'index.html',