diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index f4540344..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -custom: ['https://www.buymeacoffee.com/AkashHamirwasia'] diff --git a/README.md b/README.md index a7e116ce..d7efddb4 100644 --- a/README.md +++ b/README.md @@ -140,8 +140,8 @@ Blaze app can now be accessed at port `8080` :tada: ## Privacy and Analytics - Blaze server does not track or record the files that are being shared both by WebSockets and WebTorrent. - Any user related data like nickname, room names are always stored on device, and are only shared with the server when the user joins a room for file sharing. -- Blaze client uses Google Analytics to record the following: - - [Basic visit data](https://developers.google.com/analytics/devguides/collection/analyticsjs#what_data_does_the_google_analytics_tag_capture) as recorded by [Google Analytics](https://support.google.com/analytics/answer/6004245?ref_topic=2919631) +- Blaze client uses Google Analytics 4 to record the following: + - Part of [Basic visit data](https://support.google.com/analytics/answer/9234069?hl=en) - page views, scrolls and outbound clicks, rest are disabled. - If Blaze PWA is installed on the device, and whether files are shared using share targets. ## Contributing diff --git a/client/package-lock.json b/client/package-lock.json index e1610719..ed979358 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -13,7 +13,6 @@ "classnames": "^2.3.2", "copy-to-clipboard": "^3.3.3", "date-fns": "^2.29.3", - "downloadjs": "^1.4.7", "nanoid": "^3.3.4", "preact": "^10.3.2", "preact-feather": "^4.2.1", @@ -6373,11 +6372,6 @@ "node": ">=12" } }, - "node_modules/downloadjs": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/downloadjs/-/downloadjs-1.4.7.tgz", - "integrity": "sha1-9p+W+UDg0FU9rCkROYZaPNAQHjw=" - }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", diff --git a/client/package.json b/client/package.json index e7c9b843..dffc8e21 100644 --- a/client/package.json +++ b/client/package.json @@ -23,7 +23,6 @@ "classnames": "^2.3.2", "copy-to-clipboard": "^3.3.3", "date-fns": "^2.29.3", - "downloadjs": "^1.4.7", "nanoid": "^3.3.4", "preact": "^10.3.2", "preact-feather": "^4.2.1", diff --git a/client/src/routes/App/FileTransfer/FileTransfer.js b/client/src/routes/App/FileTransfer/FileTransfer.js index 727aee96..b3006296 100644 --- a/client/src/routes/App/FileTransfer/FileTransfer.js +++ b/client/src/routes/App/FileTransfer/FileTransfer.js @@ -1,5 +1,4 @@ import { h, createRef } from 'preact'; -import download from 'downloadjs'; import { route } from 'preact-router'; import { PureComponent, forwardRef, memo } from 'preact/compat'; import { ArrowLeft, CheckCircle, Home, Plus, Image, Film, Box, Music, File, Zap, Share2, Send } from 'preact-feather'; @@ -17,6 +16,7 @@ import Visualizer from '../../../utils/visualizer'; import formatSize from '../../../utils/formatSize'; import pluralize from '../../../utils/pluralize'; import urls from '../../../utils/urls'; +import { multiDownload } from '../../../utils/download'; import constants from '../../../../../common/constants'; import roomsDispatch from '../../../reducers/rooms'; @@ -292,17 +292,10 @@ class FileTransfer extends PureComponent { isSelectorEnabled: false, }); }, - onDone: (file, meta) => { - if (file !== undefined) { - if (Array.isArray(file)) { - file.forEach(file => { - file.getBlob((err, blob) => download(blob, file.name)); - }); - } - else { - download(file, meta.name, meta.type); - } - } + onDone: (files) => { + if (files === undefined) return; + + multiDownload(files); this.resetState(); }, }); diff --git a/client/src/routes/App/layouts/AppLanding/AppLanding.js b/client/src/routes/App/layouts/AppLanding/AppLanding.js index 9d8e7f08..63638a69 100644 --- a/client/src/routes/App/layouts/AppLanding/AppLanding.js +++ b/client/src/routes/App/layouts/AppLanding/AppLanding.js @@ -1,6 +1,6 @@ import { h } from 'preact'; import { memo } from 'preact/compat'; -import { ArrowDownCircle, Gift, Grid, PieChart, Settings } from 'preact-feather'; +import { ArrowDownCircle, Gift, Grid, Settings } from 'preact-feather'; import { Link } from 'preact-router/match'; import { useContext } from 'preact/hooks'; import { PWAInstall } from '../../contexts/PWAInstall'; @@ -36,9 +36,7 @@ function AppLanding({ children, title, subtitle }) { deferredPrompt.prompt(); const { outcome } = await deferredPrompt.userChoice; - window.ga('send', 'event', { - eventCategory: 'pwa-install', - eventAction: 'promo-shown', + window.gtag('event', 'pwa-install-prompt', { outcome, }); }; diff --git a/client/src/template.html b/client/src/template.html index 77dbe194..d1c0ff8e 100644 --- a/client/src/template.html +++ b/client/src/template.html @@ -35,13 +35,13 @@ + - <% preact.headEnd %> diff --git a/client/src/utils/download.js b/client/src/utils/download.js new file mode 100644 index 00000000..b02c3651 --- /dev/null +++ b/client/src/utils/download.js @@ -0,0 +1,41 @@ +// Adapted from https://github.com/sindresorhus/multi-download/blob/v4.0.0/index.js +// to take File as input https://developer.mozilla.org/en-US/docs/Web/API/File + +/** + * Creates a promise that resolves after the specified number of milliseconds + */ +const delay = milliseconds => new Promise(resolve => { + setTimeout(resolve, milliseconds); +}); + +/** + * Downloads a single file + * @param file - An instance of the File type representing the file to download + */ +const download = async (file) => { + const a = document.createElement('a'); + const url = URL.createObjectURL(file); + a.download = file.name; + a.href = url; + a.style.display = 'none'; + document.body.append(a); + a.click(); + await delay(100); // for Chrome + a.remove(); + URL.revokeObjectURL(url); +}; + +/** + * Initiates multiple file downloads with a constant delay between each one + * @param files - An array of instances of the File type representing the files to download + */ +export const multiDownload = async (files) => { + if (!files) { + throw new Error('`files` required'); + }; + + for (const file of files) { + download(file); + await delay(1000); + } +}; diff --git a/client/src/utils/fileShare.js b/client/src/utils/fileShare.js index 056a1971..acc5a90a 100644 --- a/client/src/utils/fileShare.js +++ b/client/src/utils/fileShare.js @@ -50,7 +50,9 @@ class FileShare { this.socket.listen(constants.FILE_INIT, (data) => { if (data.end) { if (fileParts.length) { - onDone(new Blob(fileParts), metaData.meta[0]); + onDone([ + new File(fileParts, metaData.meta[0].name, {type: metaData.meta[0].type}) + ]); fileParts = []; size = 0; statProg = 0.25; @@ -106,8 +108,20 @@ class FileShare { torrent.on('upload', update); torrent.on('download', update); - torrent.on('done', () => { - onDone(torrent.files); + torrent.on('done', async () => { + const files = await Promise.all( + torrent.files.map( + (file) => + new Promise((resolve, reject) => { + // make regular File from webtorrent File https://github.com/webtorrent/webtorrent/blob/v1.9.7/lib/file.js#L13 + file.getBlob((err, blob) => { + if (err) reject(err); + resolve(new File([blob], file.name, { type: file.type })); + }); + }) + ) + ); + onDone(files); }); } @@ -227,4 +241,4 @@ class FileShare { } -export default FileShare; \ No newline at end of file +export default FileShare; diff --git a/docker-compose.yml b/docker-compose.yml index 3700fa27..409efca1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ services: context: . dockerfile: ./server/Dockerfile environment: - TRUST_PROXY: true + TRUST_PROXY: 1 ports: - 3030:3030