From d8f95320397cacdf402ea3b9d9291cfb79c0851e Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Wed, 13 Dec 2023 19:20:57 +0100 Subject: [PATCH 1/5] Parallelize asset loading --- public/scripts/main.js | 52 ++++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/public/scripts/main.js b/public/scripts/main.js index e6ff2598..1b05c79f 100644 --- a/public/scripts/main.js +++ b/public/scripts/main.js @@ -56,13 +56,16 @@ class PairDrop { await this.backgroundCanvas.fadeIn(); // Load deferred assets + console.log("Load deferred assets..."); await this.loadDeferredAssets(); console.log("Loading of deferred assets completed."); + console.log("Hydrate UI..."); await this.hydrate(); console.log("UI hydrated."); // Evaluate url params as soon as ws is connected + console.log("Evaluate URL params when websocket connection is established."); Events.on('ws-connected', _ => this.evaluateUrlParams(), {once: true}); } @@ -102,14 +105,11 @@ class PairDrop { } } - async loadDeferredAssets() { - console.log("Load deferred assets"); - for (const url of this.deferredStyles) { - await this.loadAndApplyStylesheet(url); - } - for (const url of this.deferredScripts) { - await this.loadAndApplyScript(url); - } + loadDeferredAssets() { + const stylePromises = this.deferredStyles.map(url => this.loadAndApplyStylesheet(url)); + const scriptPromises = this.deferredScripts.map(url => this.loadAndApplyScript(url)); + + return Promise.all([...stylePromises, ...scriptPromises]); } loadStyleSheet(url) { @@ -125,13 +125,16 @@ class PairDrop { }); } - async loadAndApplyStylesheet(url) { - try { - await this.loadStyleSheet(url); - console.log(`Stylesheet loaded successfully: ${url}`); - } catch (error) { - console.error('Error loading stylesheet:', error); - } + loadAndApplyStylesheet(url) { + return new Promise( async (resolve) => { + try { + await this.loadStyleSheet(url); + console.log(`Stylesheet loaded successfully: ${url}`); + resolve(); + } catch (error) { + console.error('Error loading stylesheet:', error); + } + }); } loadScript(url) { @@ -145,13 +148,16 @@ class PairDrop { }); } - async loadAndApplyScript(url) { - try { - await this.loadScript(url); - console.log(`Script loaded successfully: ${url}`); - } catch (error) { - console.error('Error loading script:', error); - } + loadAndApplyScript(url) { + return new Promise( async (resolve) => { + try { + await this.loadScript(url); + console.log(`Script loaded successfully: ${url}`); + resolve(); + } catch (error) { + console.error('Error loading script:', error); + } + }); } async hydrate() { @@ -223,6 +229,8 @@ class PairDrop { // remove url params from url const urlWithoutParams = getUrlWithoutArguments(); window.history.replaceState({}, "Rewrite URL", urlWithoutParams); + + console.log("URL params evaluated."); } } From d3a623d352fc0f82f0aab32ce68f40c4d5f421d6 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Fri, 15 Dec 2023 21:19:56 +0100 Subject: [PATCH 2/5] Refactor for loops to specify imagesOnly to Array.prototype.every() --- public/scripts/ui.js | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/public/scripts/ui.js b/public/scripts/ui.js index 52fecbce..ae63a0e7 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -2420,14 +2420,9 @@ class Notifications { _downloadNotification(files) { if (document.visibilityState !== 'visible') { - let imagesOnly = true; - for(let i=0; i file.type.split('/')[0] === 'image'); let title; + if (files.length === 1) { title = `${files[0].name}`; } @@ -2452,15 +2447,8 @@ class Notifications { _requestNotification(request, peerId) { if (document.visibilityState !== 'visible') { - let imagesOnly = true; - for(let i=0; i header.mime.split('/')[0] === 'image'); + let displayName = $(peerId).querySelector('.name').textContent; let descriptor; if (request.header.length === 1) { From c08b324d6a51e336d7f5b28c5d9360574c2b8ed1 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Fri, 15 Dec 2023 21:20:26 +0100 Subject: [PATCH 3/5] Refactor localization.js --- public/scripts/localization.js | 142 +++++++++++++++++++++------------ public/scripts/ui-main.js | 2 +- 2 files changed, 92 insertions(+), 52 deletions(-) diff --git a/public/scripts/localization.js b/public/scripts/localization.js index b696ef9a..d81d3e37 100644 --- a/public/scripts/localization.js +++ b/public/scripts/localization.js @@ -1,40 +1,49 @@ class Localization { constructor() { + Localization.$htmlRoot = document.querySelector('html'); + Localization.defaultLocale = "en"; Localization.supportedLocales = ["ar", "ca", "de", "en", "es", "fr", "id", "it", "ja", "nb", "nl", "pt-BR", "ro", "ru", "tr", "zh-CN"]; Localization.supportedLocalesRtl = ["ar"]; Localization.translations = {}; - Localization.defaultTranslations = {}; + Localization.translationsDefaultLocale = {}; - Localization.systemLocale = Localization.getSupportedOrDefault(navigator.languages); + Localization.systemLocale = Localization.getSupportedOrDefaultLocales(navigator.languages); let storedLanguageCode = localStorage.getItem('language_code'); - Localization.initialLocale = storedLanguageCode && Localization.isSupported(storedLanguageCode) + Localization.initialLocale = storedLanguageCode && Localization.localeIsSupported(storedLanguageCode) ? storedLanguageCode : Localization.systemLocale; } - static isSupported(locale) { + static localeIsSupported(locale) { return Localization.supportedLocales.indexOf(locale) > -1; } - static isRtlLanguage(locale) { + static localeIsRtl(locale) { return Localization.supportedLocalesRtl.indexOf(locale) > -1; } - static isCurrentLocaleRtl() { - return Localization.isRtlLanguage(Localization.locale); + static currentLocaleIsRtl() { + return Localization.localeIsRtl(Localization.locale); + } + + static currentLocaleIsDefault() { + return Localization.locale === Localization.defaultLocale } - static getSupportedOrDefault(locales) { + static getSupportedOrDefaultLocales(locales) { + // get generic locales not included in locales + // ["en-us", "de-CH", "fr"] --> ["en", "de"] let localesGeneric = locales .map(locale => locale.split("-")[0]) .filter(locale => locales.indexOf(locale) === -1); - return locales.find(Localization.isSupported) - || localesGeneric.find(Localization.isSupported) + // If there is no perfect match for browser locales, try generic locales first before resorting to the default locale + return locales.find(Localization.localeIsSupported) + || localesGeneric.find(Localization.localeIsSupported) || Localization.defaultLocale; } @@ -48,16 +57,14 @@ class Localization { await Localization.setLocale(locale) await Localization.translatePage(); - const htmlRootNode = document.querySelector('html'); - - if (Localization.isRtlLanguage(locale)) { - htmlRootNode.setAttribute('dir', 'rtl'); + if (Localization.localeIsRtl(locale)) { + Localization.$htmlRoot.setAttribute('dir', 'rtl'); } else { - htmlRootNode.removeAttribute('dir'); + Localization.$htmlRoot.removeAttribute('dir'); } - htmlRootNode.setAttribute('lang', locale); + Localization.$htmlRoot.setAttribute('lang', locale); console.log("Page successfully translated", @@ -111,75 +118,108 @@ class Localization { const key = element.getAttribute("data-i18n-key"); const attrs = element.getAttribute("data-i18n-attrs").split(" "); - for (let i in attrs) { - let attr = attrs[i]; + attrs.forEach(attr => { if (attr === "text") { element.innerText = Localization.getTranslation(key); } else { element.setAttribute(attr, Localization.getTranslation(key, attr)); } - } + }) } - static getTranslation(key, attr = null, data = {}, useDefault = false) { - const keys = key.split("."); - - let translationCandidates = useDefault - ? Localization.defaultTranslations - : Localization.translations; - + static getTranslationFromTranslationsObj(translationObj, key, attr) { let translation; - try { + const keys = key.split("."); + for (let i = 0; i < keys.length - 1; i++) { - translationCandidates = translationCandidates[keys[i]] + // iterate into translation object until last layer + translationObj = translationObj[keys[i]] } let lastKey = keys[keys.length - 1]; if (attr) lastKey += "_" + attr; - translation = translationCandidates[lastKey]; - - for (let j in data) { - if (translation.includes(`{{${j}}}`)) { - translation = translation.replace(`{{${j}}}`, data[j]); - } else { - console.warn(`Translation for your language ${Localization.locale.toUpperCase()} misses at least one data placeholder:`, key, attr, data); - Localization.logHelpCallKey(key); - Localization.logHelpCall(); - translation = ""; - break; - } - } + translation = translationObj[lastKey]; + } catch (e) { console.error(e); - translation = ""; } if (!translation) { - if (!useDefault) { - console.warn(`Missing translation entry for your language ${Localization.locale.toUpperCase()}. Using ${Localization.defaultLocale.toUpperCase()} instead.`, key, attr); - Localization.logHelpCallKey(key); - Localization.logHelpCall(); - translation = this.getTranslation(key, attr, data, true); + throw new Error(`Translation misses entry. Key: ${key} Attribute: ${attr}`); + } + + return translation; + } + + static addDataToTranslation(translation, data) { + for (let j in data) { + if (!translation.includes(`{{${j}}}`)) { + throw new Error(`Translation misses data placeholder: ${j}`); + } + // Add data to translation + translation = translation.replace(`{{${j}}}`, data[j]); + } + return translation; + } + + static getTranslation(key, attr = null, data = {}, useDefault = false) { + let translationObj = useDefault + ? Localization.translationsDefaultLocale + : Localization.translations; + + let translation; + + try { + translation = Localization.getTranslationFromTranslationsObj(translationObj, key, attr); + translation = Localization.addDataToTranslation(translation, data); + } + catch (e) { + // Log warnings and help calls + console.warn(e); + Localization.logTranslationMissingOrBroken(key, attr, data, useDefault); + Localization.logHelpCallKey(key, attr); + Localization.logHelpCall(); + + if (useDefault || Localization.currentLocaleIsDefault()) { + // Is default locale already + // Use empty string as translation + translation = "" } else { - console.warn("Missing translation in default language:", key, attr); - Localization.logHelpCall(); + // Is not default locale yet + // Get translation for default language with same arguments + console.log(`Using default language ${Localization.defaultLocale.toUpperCase()} instead.`); + translation = this.getTranslation(key, attr, data, true); } } return Localization.escapeHTML(translation); } + static logTranslationMissingOrBroken(key, attr, data, useDefault) { + let usedLocale = useDefault + ? Localization.defaultLocale.toUpperCase() + : Localization.locale.toUpperCase(); + + console.warn(`Missing or broken translation for language ${usedLocale}.\n`, 'key:', key, 'attr:', attr, 'data:', data); + } + static logHelpCall() { console.log("Help translating PairDrop: https://hosted.weblate.org/engage/pairdrop/"); } - static logHelpCallKey(key) { - console.warn(`Translate this string here: https://hosted.weblate.org/browse/pairdrop/pairdrop-spa/${Localization.locale.toLowerCase()}/?q=${key}`); + static logHelpCallKey(key, attr) { + let locale = Localization.locale.toLowerCase(); + + let keyComplete = !attr || attr === "text" + ? key + : `${key}_${attr}`; + + console.warn(`Translate this string here: https://hosted.weblate.org/browse/pairdrop/pairdrop-spa/${locale}/?q=${keyComplete}`); } static escapeHTML(unsafeText) { diff --git a/public/scripts/ui-main.js b/public/scripts/ui-main.js index 409a29c9..c7a27e5d 100644 --- a/public/scripts/ui-main.js +++ b/public/scripts/ui-main.js @@ -132,7 +132,7 @@ class HeaderUI { this.$header.classList.remove('overflow-expanded'); - const rtlLocale = Localization.isCurrentLocaleRtl(); + const rtlLocale = Localization.currentLocaleIsRtl(); let icon; const $headerIconsShown = document.querySelectorAll('body > header:first-of-type > *:not([hidden])'); From c2ee459231672a3f457cc3b3188dad89b85ace33 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Fri, 15 Dec 2023 21:22:33 +0100 Subject: [PATCH 4/5] Fix error if env var `RTC_CONFIG=false` (as in default docker-compose.yml and docs) --- server/index.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/server/index.js b/server/index.js index 2134c672..9f5d7b8b 100644 --- a/server/index.js +++ b/server/index.js @@ -32,10 +32,14 @@ process.on('unhandledRejection', (reason, promise) => { // Evaluate arguments for deployment with Docker and Node.js let conf = {}; + conf.debugMode = process.env.DEBUG_MODE === "true"; + conf.port = process.env.PORT || 3000; + conf.wsFallback = process.argv.includes('--include-ws-fallback') || process.env.WS_FALLBACK === "true"; -conf.rtcConfig = process.env.RTC_CONFIG + +conf.rtcConfig = process.env.RTC_CONFIG && process.env.RTC_CONFIG !== "false" ? JSON.parse(fs.readFileSync(process.env.RTC_CONFIG, 'utf8')) : { "sdpSemantics": "unified-plan", @@ -47,7 +51,10 @@ conf.rtcConfig = process.env.RTC_CONFIG }; -conf.signalingServer = process.env.SIGNALING_SERVER || false; +conf.signalingServer = process.env.SIGNALING_SERVER && process.env.SIGNALING_SERVER !== "false" + ? process.env.SIGNALING_SERVER + : false; + conf.ipv6Localize = parseInt(process.env.IPV6_LOCALIZE) || false; let rateLimit = false; @@ -61,6 +68,7 @@ else { } } conf.rateLimit = rateLimit; + conf.buttons = { "donation_button": { "active": process.env.DONATION_BUTTON_ACTIVE, @@ -96,8 +104,10 @@ conf.buttons = { // Evaluate arguments for deployment with Node.js only conf.autoStart = process.argv.includes('--auto-restart'); + conf.localhostOnly = process.argv.includes('--localhost-only'); + // Validate configuration if (conf.ipv6Localize) { if (!(0 < conf.ipv6Localize && conf.ipv6Localize < 8)) { From 6737dcacf77d04483678990a14daaa6b7ea46e66 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Fri, 15 Dec 2023 23:40:30 +0100 Subject: [PATCH 5/5] Defer scripts and prevent deferred stylesheets from being render blocking --- public/index.html | 8 ++++---- public/scripts/main.js | 12 ++++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/public/index.html b/public/index.html index 3c3c03a9..0fa0e94a 100644 --- a/public/index.html +++ b/public/index.html @@ -734,10 +734,10 @@

PairDrop

- - - - + + + +