Centralize evaluation of URL parameters to clean up code and remove redundancies, and streamline Base64Dialog class

This commit is contained in:
schlagmichdoch 2023-12-05 18:57:31 +01:00
parent cb86ce0e39
commit 10b658e2e9
6 changed files with 195 additions and 179 deletions

View file

@ -59,6 +59,9 @@ class PairDrop {
await this.hydrate();
console.log("UI hydrated.");
// Evaluate url params as soon as ws is connected
Events.on('ws-connected', _ => this.evaluateUrlParams(), {once: true});
}
registerServiceWorker() {
@ -171,6 +174,44 @@ class PairDrop {
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();
}
// remove url params from url
const urlWithoutParams = getUrlWithoutArguments();
window.history.replaceState({}, "Rewrite URL", urlWithoutParams);
}
}
const pairDrop = new PairDrop();

View file

@ -1321,8 +1321,6 @@ class PairDeviceDialog extends Dialog {
this.$el.addEventListener('paste', e => this._onPaste(e));
this.$qrCode.addEventListener('click', _ => this._copyPairUrl());
this.evaluateUrlAttributes();
this.pairPeer = {};
}
@ -1344,15 +1342,6 @@ class PairDeviceDialog extends Dialog {
this.inputKeyContainer._onPaste(pastedKey);
}
evaluateUrlAttributes() {
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('pair_key')) {
this._pairDeviceJoin(urlParams.get('pair_key'));
const url = getUrlWithoutArguments();
window.history.replaceState({}, "Rewrite URL", url); //remove pair_key from url
}
}
_pairDeviceInitiate() {
Events.fire('pair-device-initiate');
}
@ -1700,8 +1689,6 @@ class PublicRoomDialog extends Dialog {
this.$el.addEventListener('paste', e => this._onPaste(e));
this.$qrCode.addEventListener('click', _ => this._copyShareRoomUrl());
this.evaluateUrlAttributes();
Events.on('ws-connected', _ => this._onWsConnected());
Events.on('translation-loaded', _ => this.setFooterBadge());
}
@ -1791,15 +1778,6 @@ class PublicRoomDialog extends Dialog {
})
}
evaluateUrlAttributes() {
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('room_id')) {
this._joinPublicRoom(urlParams.get('room_id'));
const url = getUrlWithoutArguments();
window.history.replaceState({}, "Rewrite URL", url); //remove pair_key from url
}
}
_onWsConnected() {
let roomId = sessionStorage.getItem('public_room_id');
@ -2147,61 +2125,47 @@ class Base64Dialog extends Dialog {
constructor() {
super('base64-paste-dialog');
const urlParams = new URL(window.location).searchParams;
const base64Text = urlParams.get('base64text');
const base64Zip = urlParams.get('base64zip');
const base64Hash = window.location.hash.substring(1);
this.$title = this.$el.querySelector('.dialog-title');
this.$pasteBtn = this.$el.querySelector('#base64-paste-btn');
this.$fallbackTextarea = this.$el.querySelector('.textarea');
}
if (base64Text) {
async evaluateBase64Text(base64Text, hash) {
this.$title.innerText = Localization.getTranslation('dialogs.base64-title-text');
if (base64Text === 'paste') {
// ?base64text=paste
// base64 encoded string is ready to be pasted from clipboard
this.preparePasting('text');
this.show();
if (base64Text === 'paste') {
// ?base64text=paste
// base64 encoded string is ready to be pasted from clipboard
this.preparePasting('text');
}
else if (base64Text === 'hash') {
// ?base64text=hash#BASE64ENCODED
// base64 encoded string is url hash which is never sent to server and faster (recommended)
this.processBase64Text(base64Hash)
.catch(_ => {
Events.fire('notify-user', Localization.getTranslation("notifications.text-content-incorrect"));
console.log("Text content incorrect.");
}).finally(() => {
this.hide();
});
}
else {
// ?base64text=BASE64ENCODED
// base64 encoded string was part of url param (not recommended)
this.processBase64Text(base64Text)
.catch(_ => {
Events.fire('notify-user', Localization.getTranslation("notifications.text-content-incorrect"));
console.log("Text content incorrect.");
}).finally(() => {
this.hide();
});
}
}
else if (base64Zip) {
else if (base64Text === 'hash') {
// ?base64text=hash#BASE64ENCODED
// base64 encoded text is url hash which cannot be seen by the server and is faster (recommended)
this.show();
if (base64Zip === "hash") {
// ?base64zip=hash#BASE64ENCODED
// base64 encoded zip file is url hash which is never sent to the server
this.processBase64Zip(base64Hash)
.catch(_ => {
Events.fire('notify-user', Localization.getTranslation("notifications.file-content-incorrect"));
console.log("File content incorrect.");
}).finally(() => {
this.hide();
});
}
else {
// ?base64zip=paste || ?base64zip=true
this.preparePasting('files');
}
await this.processBase64Text(hash)
}
else {
// ?base64text=BASE64ENCODED
// base64 encoded text is part of the url param. Seen by server and slow (not recommended)
this.show();
await this.processBase64Text(base64Text)
}
}
async evaluateBase64Zip(base64Zip, hash) {
this.$title.innerText = Localization.getTranslation('dialogs.base64-title-files');
if (base64Zip === 'paste') {
// ?base64zip=paste || ?base64zip=true
this.preparePasting('files');
this.show();
}
else if (base64Zip === 'hash') {
// ?base64zip=hash#BASE64ENCODED
// base64 encoded zip file is url hash which cannot be seen by the server
await this.processBase64Zip(hash)
}
}
@ -2234,28 +2198,15 @@ class Base64Dialog extends Dialog {
async processInput(type) {
const base64 = this.$fallbackTextarea.textContent;
this.$fallbackTextarea.textContent = '';
await this.processBase64(type, base64);
await this.processPastedBase64(type, base64);
}
async processClipboard(type) {
const base64 = await navigator.clipboard.readText();
await this.processBase64(type, base64);
await this.processPastedBase64(type, base64);
}
isValidBase64(base64) {
try {
// check if input is base64 encoded
window.atob(base64);
return true;
} catch (e) {
// input is not base64 string.
return false;
}
}
async processBase64(type, base64) {
if (!base64 || !this.isValidBase64(base64)) return;
this._setPasteBtnToProcessing();
async processPastedBase64(type, base64) {
try {
if (type === 'text') {
await this.processBase64Text(base64);
@ -2263,51 +2214,50 @@ class Base64Dialog extends Dialog {
else {
await this.processBase64Zip(base64);
}
} catch(_) {
}
catch(e) {
Events.fire('notify-user', Localization.getTranslation("notifications.clipboard-content-incorrect"));
console.log("Clipboard content is incorrect.")
}
this.hide();
}
processBase64Text(base64Text){
return new Promise((resolve) => {
this._setPasteBtnToProcessing();
let decodedText = decodeURIComponent(escape(window.atob(base64Text)));
async processBase64Text(base64){
this._setPasteBtnToProcessing();
try {
const decodedText = await decodeBase64Text(base64);
if (ShareTextDialog.isApproveShareTextSet()) {
Events.fire('share-text-dialog', decodedText);
} else {
}
else {
Events.fire('activate-share-mode', {text: decodedText});
}
resolve();
});
}
catch (e) {
Events.fire('notify-user', Localization.getTranslation("notifications.text-content-incorrect"));
console.log("Text content incorrect.");
}
this.hide();
}
async processBase64Zip(base64zip) {
async processBase64Zip(base64) {
this._setPasteBtnToProcessing();
let bstr = atob(base64zip), n = bstr.length, u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
try {
const decodedFiles = await decodeBase64Files(base64);
Events.fire('activate-share-mode', {files: decodedFiles});
}
catch (e) {
Events.fire('notify-user', Localization.getTranslation("notifications.file-content-incorrect"));
console.log("File content incorrect.");
}
const zipBlob = new File([u8arr], 'archive.zip');
let files = [];
const zipEntries = await zipper.getEntries(zipBlob);
for (let i = 0; i < zipEntries.length; i++) {
let fileBlob = await zipper.getData(zipEntries[i]);
files.push(new File([fileBlob], zipEntries[i].filename));
}
Events.fire('activate-share-mode', {files: files});
}
clearBrowserHistory() {
const url = getUrlWithoutArguments();
window.history.replaceState({}, "Rewrite URL", url);
this.hide();
}
hide() {
this.clearBrowserHistory();
this.$pasteBtn.removeEventListener('click', _ => this._clickCallback());
this.$fallbackTextarea.removeEventListener('input', _ => this._inputCallback());
super.hide();
@ -2529,80 +2479,72 @@ class NetworkStatusUI {
}
class WebShareTargetUI {
constructor() {
const urlParams = new URL(window.location).searchParams;
const share_target_type = urlParams.get("share-target")
if (share_target_type) {
if (share_target_type === "text") {
const title = urlParams.get('title') || '';
const text = urlParams.get('text') || '';
const url = urlParams.get('url') || '';
let shareTargetText;
if (url) {
shareTargetText = url; // we share only the link - no text.
}
else if (title && text) {
shareTargetText = title + '\r\n' + text;
}
else {
shareTargetText = title + text;
}
if (ShareTextDialog.isApproveShareTextSet()) {
Events.fire('share-text-dialog', shareTargetText);
} else {
Events.fire('activate-share-mode', {text: shareTargetText});
}
async evaluateShareTarget(shareTargetType, title, text, url) {
if (shareTargetType === "text") {
let shareTargetText;
if (url) {
shareTargetText = url; // we share only the link - no text.
}
else if (title && text) {
shareTargetText = title + '\r\n' + text;
}
else {
shareTargetText = title + text;
}
else if (share_target_type === "files") {
let openRequest = window.indexedDB.open('pairdrop_store')
openRequest.onsuccess = e => {
const db = e.target.result;
const tx = db.transaction('share_target_files', 'readwrite');
const store = tx.objectStore('share_target_files');
const request = store.getAll();
request.onsuccess = _ => {
const fileObjects = request.result;
let filesReceived = [];
for (let i=0; i<fileObjects.length; i++) {
filesReceived.push(new File([fileObjects[i].buffer], fileObjects[i].name));
}
const clearRequest = store.clear()
clearRequest.onsuccess = _ => db.close();
Events.fire('activate-share-mode', {files: filesReceived})
if (ShareTextDialog.isApproveShareTextSet()) {
Events.fire('share-text-dialog', shareTargetText);
}
else {
Events.fire('activate-share-mode', {text: shareTargetText});
}
}
else if (shareTargetType === "files") {
let openRequest = window.indexedDB.open('pairdrop_store')
openRequest.onsuccess = e => {
const db = e.target.result;
const tx = db.transaction('share_target_files', 'readwrite');
const store = tx.objectStore('share_target_files');
const request = store.getAll();
request.onsuccess = _ => {
const fileObjects = request.result;
let filesReceived = [];
for (let i = 0; i < fileObjects.length; i++) {
filesReceived.push(new File([fileObjects[i].buffer], fileObjects[i].name));
}
const clearRequest = store.clear()
clearRequest.onsuccess = _ => db.close();
Events.fire('activate-share-mode', {files: filesReceived})
}
}
const url = getUrlWithoutArguments();
window.history.replaceState({}, "Rewrite URL", url);
}
}
}
class WebFileHandlersUI {
constructor() {
const urlParams = new URL(window.location).searchParams;
if (urlParams.has("file_handler") && "launchQueue" in window) {
launchQueue.setConsumer(async launchParams => {
console.log("Launched with: ", launchParams);
if (!launchParams.files.length)
return;
let files = [];
async evaluateLaunchQueue() {
if (!"launchQueue" in window) return;
for (let i=0; i<launchParams.files.length; i++) {
if (i !== 0 && await launchParams.files[i].isSameEntry(launchParams.files[i-1])) continue;
const fileHandle = launchParams.files[i];
const file = await fileHandle.getFile();
files.push(file);
}
Events.fire('activate-share-mode', {files: files})
launchParams = null;
});
const url = getUrlWithoutArguments();
window.history.replaceState({}, "Rewrite URL", url);
}
launchQueue.setConsumer(async launchParams => {
console.log("Launched with: ", launchParams);
if (!launchParams.files.length) return;
let files = [];
for (let i = 0; i < launchParams.files.length; i++) {
if (i !== 0 && await launchParams.files[i].isSameEntry(launchParams.files[i-1])) continue;
const file = await launchParams.files[i].getFile();
files.push(file);
}
Events.fire('activate-share-mode', {files: files})
});
}
}

View file

@ -504,4 +504,29 @@ function getResizedImageDataUrl(file, width = undefined, height = undefined, qua
}
image.onerror = _ => reject(`Could not create an image thumbnail from type ${file.type}`);
})
}
async function decodeBase64Files(base64) {
if (!base64) throw new Error('Base64 is empty');
let bstr = atob(base64), n = bstr.length, u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
const zipBlob = new File([u8arr], 'archive.zip');
let files = [];
const zipEntries = await zipper.getEntries(zipBlob);
for (let i = 0; i < zipEntries.length; i++) {
let fileBlob = await zipper.getData(zipEntries[i]);
files.push(new File([fileBlob], zipEntries[i].filename));
}
return files
}
async function decodeBase64Text(base64) {
if (!base64) throw new Error('Base64 is empty');
return decodeURIComponent(escape(window.atob(base64)))
}