class Logger { static debug(message, ...optionalParams) { if (window.debugMode) { console.debug("DEBUG:", message, ...optionalParams); } } static log(message, ...optionalParams) { console.log("LOG:", message, ...optionalParams); } static warn(message, ...optionalParams) { console.warn("WARN:", message, ...optionalParams); } static error(message, ...optionalParams) { console.error("ERROR:", message, ...optionalParams); } } class PairDrop { constructor() { this.$headerNotificationBtn = $('notification'); this.$headerEditPairedDevicesBtn = $('edit-paired-devices'); this.$footerPairedDevicesBadge = $$('.discovery-wrapper .badge-room-secret'); this.$headerInstallBtn = $('install'); this.deferredStyles = [ "styles/styles-deferred.css" ]; this.deferredScripts = [ "scripts/browser-tabs-connector.js", "scripts/util.js", "scripts/network.js", "scripts/ui.js", "scripts/qr-code.min.js", "scripts/zip.min.js", "scripts/no-sleep.min.js", "scripts/heic2any.min.js" ]; this.registerServiceWorker(); Events.on('beforeinstallprompt', e => this.onPwaInstallable(e)); this.persistentStorage = new PersistentStorage(); this.localization = new Localization(); this.themeUI = new ThemeUI(); this.backgroundCanvas = new BackgroundCanvas(); this.headerUI = new HeaderUI(); this.centerUI = new CenterUI(); this.footerUI = new FooterUI(); this.initialize() .then(_ => { Logger.log("Initialization completed."); }); } async initialize() { // Translate page before fading in await this.localization.setInitialTranslation() Logger.log("Initial translation successful."); // Show "Loading..." until connected to WsServer await this.footerUI.showLoading(); // Evaluate css shifting UI elements and fade in UI elements await this.evaluatePermissionsAndRoomSecrets(); await this.headerUI.evaluateOverflowing(); await this.headerUI.fadeIn(); await this.footerUI._evaluateFooterBadges(); await this.footerUI.fadeIn(); await this.centerUI.fadeIn(); await this.backgroundCanvas.fadeIn(); // Load deferred assets Logger.log("Load deferred assets..."); await this.loadDeferredAssets(); Logger.log("Loading of deferred assets completed."); Logger.log("Hydrate UI..."); await this.hydrate(); Logger.log("UI hydrated."); // Evaluate url params as soon as ws is connected Logger.log("Evaluate URL params as soon as websocket connection is established."); Events.on('ws-connected', _ => this.evaluateUrlParams(), {once: true}); } registerServiceWorker() { if ('serviceWorker' in navigator) { navigator.serviceWorker .register('service-worker.js') .then(serviceWorker => { Logger.log('Service Worker registered'); window.serviceWorker = serviceWorker }); } } onPwaInstallable(e) { if (!window.matchMedia('(display-mode: standalone)').matches) { // only display install btn when not installed this.$headerInstallBtn.removeAttribute('hidden'); this.$headerInstallBtn.addEventListener('click', () => { this.$headerInstallBtn.setAttribute('hidden', true); e.prompt(); }); } return e.preventDefault(); } async evaluatePermissionsAndRoomSecrets() { // Check whether notification permissions have already been granted if ('Notification' in window && Notification.permission !== 'granted') { this.$headerNotificationBtn.removeAttribute('hidden'); } let roomSecrets = await PersistentStorage.getAllRoomSecrets(); if (roomSecrets.length > 0) { this.$headerEditPairedDevicesBtn.removeAttribute('hidden'); this.$footerPairedDevicesBadge.removeAttribute('hidden'); } } 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) { return new Promise((resolve, reject) => { let stylesheet = document.createElement('link'); stylesheet.rel = 'preload'; stylesheet.as = 'style'; stylesheet.defer = true; stylesheet.href = url; stylesheet.onload = _ => { stylesheet.onload = null; stylesheet.rel = 'stylesheet'; resolve(); }; stylesheet.onerror = reject; document.head.appendChild(stylesheet); }); } loadAndApplyStylesheet(url) { return new Promise( async (resolve) => { try { await this.loadStyleSheet(url); Logger.log(`Stylesheet loaded successfully: ${url}`); resolve(); } catch (error) { Logger.error('Error loading stylesheet:', error); } }); } loadScript(url) { return new Promise((resolve, reject) => { let script = document.createElement("script"); script.src = url; script.onload = resolve; script.onerror = reject; document.body.appendChild(script); }); } loadAndApplyScript(url) { return new Promise( async (resolve) => { try { await this.loadScript(url); Logger.log(`Script loaded successfully: ${url}`); resolve(); } catch (error) { Logger.error('Error loading script:', error); } }); } async hydrate() { this.aboutUI = new AboutUI(); this.peersUI = new PeersUI(); this.languageSelectDialog = new LanguageSelectDialog(); this.receiveFileDialog = new ReceiveFileDialog(); this.receiveRequestDialog = new ReceiveRequestDialog(); this.sendTextDialog = new SendTextDialog(); this.receiveTextDialog = new ReceiveTextDialog(); this.pairDeviceDialog = new PairDeviceDialog(); this.clearDevicesDialog = new EditPairedDevicesDialog(); this.publicRoomDialog = new PublicRoomDialog(); this.base64Dialog = new Base64Dialog(); this.shareTextDialog = new ShareTextDialog(); this.toast = new Toast(); this.notifications = new Notifications(); this.networkStatusUI = new NetworkStatusUI(); this.webShareTargetUI = new WebShareTargetUI(); this.webFileHandlersUI = new WebFileHandlersUI(); this.noSleepUI = new NoSleepUI(); this.broadCast = new BrowserTabsConnector(); this.server = new ServerConnection(); this.peers = new PeersManager(this.server); } async evaluateUrlParams() { // get url params const urlParams = new URLSearchParams(window.location.search); const hash = window.location.hash.substring(1); // evaluate url params if (urlParams.has('pair_key')) { const pairKey = urlParams.get('pair_key'); this.pairDeviceDialog._pairDeviceJoin(pairKey); } else if (urlParams.has('room_id')) { const roomId = urlParams.get('room_id'); this.publicRoomDialog._joinPublicRoom(roomId); } else if (urlParams.has('base64text')) { const base64Text = urlParams.get('base64text'); await this.base64Dialog.evaluateBase64Text(base64Text, hash); } else if (urlParams.has('base64zip')) { const base64Zip = urlParams.get('base64zip'); await this.base64Dialog.evaluateBase64Zip(base64Zip, hash); } else if (urlParams.has("share_target")) { const shareTargetType = urlParams.get("share_target"); const title = urlParams.get('title') || ''; const text = urlParams.get('text') || ''; const url = urlParams.get('url') || ''; await this.webShareTargetUI.evaluateShareTarget(shareTargetType, title, text, url); } else if (urlParams.has("file_handler")) { await this.webFileHandlersUI.evaluateLaunchQueue(); } else if (urlParams.has("init")) { const init = urlParams.get("init"); if (init === "pair") { this.pairDeviceDialog._pairDeviceInitiate(); } else if (init === "public_room") { this.publicRoomDialog._createPublicRoom(); } } else if (urlParams.has("debug") && urlParams.get("debug") === "true") { window.debugMode = true; } if (!window.debugMode) { // remove url params from url const urlWithoutParams = getUrlWithoutArguments(); window.history.replaceState({}, "Rewrite URL", urlWithoutParams); } Logger.log("URL params evaluated."); } } const pairDrop = new PairDrop();