Gildas 4 роки тому
батько
коміт
16e9155729

+ 1 - 1
extension/core/bg/business.js

@@ -201,7 +201,7 @@ async function runTask(taskInfo) {
 	} catch (error) {
 		if (error && (!error.message || !isIgnoredError(error))) {
 			console.log(error.message ? error.message : error); // eslint-disable-line no-console
-			ui.onError(taskInfo.tab.id);
+			ui.onError(taskInfo.tab.id, error.message);
 			taskInfo.done();
 		}
 	}

+ 15 - 23
extension/core/bg/companion.js

@@ -23,8 +23,6 @@
 
 /* global browser */
 
-import * as ui from "./../../ui/bg/index.js";
-
 let enabled = true;
 
 export {
@@ -41,27 +39,21 @@ async function onMessage(message) {
 }
 
 async function externalSave(options) {
-	try {
-		options.autoSaveExternalSave = false;
-		const port = browser.runtime.connectNative("singlefile_companion");
-		port.postMessage({
-			method: "externalSave",
-			pageData: options
-		});
-		await new Promise((resolve, reject) => {
-			port.onDisconnect.addListener(() => {
-				if (port.error) {
-					reject(port.error.message);
-				} else if (!browser.runtime.lastError || browser.runtime.lastError.message.includes("Native host has exited")) {
-					resolve();
-				}
-			});
+	options.autoSaveExternalSave = false;
+	const port = browser.runtime.connectNative("singlefile_companion");
+	port.postMessage({
+		method: "externalSave",
+		pageData: options
+	});
+	await new Promise((resolve, reject) => {
+		port.onDisconnect.addListener(() => {
+			if (port.error) {
+				reject(new Error(port.error.message + " (Companion)"));
+			} else if (!browser.runtime.lastError || browser.runtime.lastError.message.includes("Native host has exited")) {
+				resolve();
+			}
 		});
-		ui.onEnd(options.tabId, options.autoSave);
-	} catch (error) {
-		console.error(error); // eslint-disable-line no-console			
-		ui.onError(options.tabId);
-	}
+	});
 }
 
 async function save(pageData) {
@@ -73,7 +65,7 @@ async function save(pageData) {
 	await new Promise((resolve, reject) => {
 		port.onDisconnect.addListener(() => {
 			if (port.error) {
-				reject(port.error.message);
+				reject(new Error(port.error.message + " (Companion)"));
 			} else if (!browser.runtime.lastError || browser.runtime.lastError.message.includes("Native host has exited")) {
 				resolve();
 			}

+ 17 - 8
extension/core/bg/downloads.js

@@ -74,7 +74,11 @@ async function onMessage(message, sender) {
 	}
 	if (message.method.endsWith(".end")) {
 		if (message.hash) {
-			await woleet.anchor(message.hash);
+			try {
+				await woleet.anchor(message.hash);
+			} catch (error) {
+				ui.onError(sender.tab.id, error.message + " (Woleet)");
+			}
 		}
 		business.onSaveEnd(message.taskId);
 		return {};
@@ -166,7 +170,7 @@ async function downloadContent(contents, tab, incognito, message) {
 	} catch (error) {
 		if (!error.message || error.message != "upload_cancelled") {
 			console.error(error); // eslint-disable-line no-console
-			ui.onError(tab.id);
+			ui.onError(tab.id, error.message);
 		}
 	} finally {
 		if (message.url) {
@@ -202,12 +206,17 @@ async function getAuthInfo(authOptions, force) {
 	return authInfo;
 }
 
-function saveToGitHub(taskId, filename, content, githubToken, githubUser, githubRepository, githubBranch) {
+async function saveToGitHub(taskId, filename, content, githubToken, githubUser, githubRepository, githubBranch) {
 	const taskInfo = business.getTaskInfo(taskId);
-	if (taskInfo && !taskInfo.cancelled) {
+	if (!taskInfo || !taskInfo.cancelled) {
 		const pushInfo = pushGitHub(githubToken, githubUser, githubRepository, githubBranch, filename, content);
 		business.setCancelCallback(taskId, pushInfo.cancelPush);
-		return pushInfo;
+		try {
+			await (await pushInfo).pushPromise;
+			return pushInfo;
+		} catch (error) {
+			throw new Error(error.message + " (GitHub)");
+		}
 	}
 }
 
@@ -215,7 +224,7 @@ async function saveToGDrive(taskId, filename, blob, authOptions, uploadOptions)
 	try {
 		await getAuthInfo(authOptions);
 		const taskInfo = business.getTaskInfo(taskId);
-		if (taskInfo && !taskInfo.cancelled) {
+		if (!taskInfo || !taskInfo.cancelled) {
 			const uploadInfo = await gDrive.upload(filename, blob, uploadOptions);
 			business.setCancelCallback(taskId, uploadInfo.cancelUpload);
 			return uploadInfo;
@@ -230,7 +239,7 @@ async function saveToGDrive(taskId, filename, blob, authOptions, uploadOptions)
 				if (error.message == "unknown_token") {
 					authInfo = await getAuthInfo(authOptions, true);
 				} else {
-					throw error;
+					throw new Error(error.message + " (Google Drive)");
 				}
 			}
 			if (authInfo) {
@@ -240,7 +249,7 @@ async function saveToGDrive(taskId, filename, blob, authOptions, uploadOptions)
 			}
 			await saveToGDrive(taskId, filename, blob, authOptions, uploadOptions);
 		} else {
-			throw error;
+			throw new Error(error.message + " (Google Drive)");
 		}
 	}
 }

+ 5 - 1
extension/core/content/content.js

@@ -26,6 +26,7 @@
 import * as download from "./../common/download.js";
 import { fetch, frameFetch } from "./../../lib/single-file/fetch/content/content-fetch.js";
 import * as ui from "./../../ui/content/content-ui.js";
+import { onError } from "./../../ui/common/content-error.js";
 
 const singlefile = globalThis.singlefile;
 const bootstrap = globalThis.singlefileBootstrap;
@@ -36,7 +37,7 @@ let processor, processing;
 
 singlefile.init({ fetch, frameFetch });
 browser.runtime.onMessage.addListener(message => {
-	if (message.method == "content.save" || message.method == "content.cancelSave" || message.method == "content.getSelectedLinks") {
+	if (message.method == "content.save" || message.method == "content.cancelSave" || message.method == "content.getSelectedLinks" || message.method == "content.error") {
 		return onMessage(message);
 	}
 });
@@ -63,6 +64,9 @@ async function onMessage(message) {
 				urls: ui.getSelectedLinks()
 			};
 		}
+		if (message.method == "content.error") {
+			onError(message.error);
+		}
 	}
 }
 

+ 6 - 3
extension/lib/woleet/woleet.js

@@ -27,7 +27,7 @@ export {
 	anchor
 };
 async function anchor(hash) {
-	return (await fetch(urlService, {
+	const response = await fetch(urlService, {
 		method: "POST",
 		headers: {
 			"Accept": "application/json",
@@ -39,6 +39,9 @@ async function anchor(hash) {
 			"hash": hash,
 			"public": true
 		})
-	}))
-		.json();
+	});
+	if (response.status >= 400) {
+		throw new Error((response.statusText || ("Error " + response.status)));
+	}
+	return response.json();
 }

+ 5 - 1
extension/ui/bg/index.js

@@ -23,6 +23,7 @@
 
 import * as button from "./ui-button.js";
 import * as menus from "./ui-menus.js";
+import * as tabs from "./../../core/bg/tabs.js";
 
 export {
 	onMessage,
@@ -59,8 +60,11 @@ function onStart(tabId, step, autoSave) {
 	button.onStart(tabId, step, autoSave);
 }
 
-function onError(tabId) {
+async function onError(tabId, message) {
 	button.onError(tabId);
+	if (message) {
+		await tabs.sendMessage(tabId, { method: "content.error", error: message.toString() });
+	}
 }
 
 function onEdit(tabId) {

+ 4 - 0
extension/ui/bg/ui-editor.js

@@ -24,6 +24,7 @@
 /* global browser, document, prompt, matchMedia, addEventListener, innerHeight, innerWidth */
 
 import * as download from "../../core/common/download.js";
+import { onError } from "./../common/content-error.js";
 
 const editorElement = document.querySelector(".editor");
 const toolbarElement = document.querySelector(".toolbar");
@@ -331,6 +332,9 @@ browser.runtime.onMessage.addListener(message => {
 	if (message.method == "options.refresh") {
 		return refreshOptions(message.profileName);
 	}
+	if (message.method == "content.error") {
+		onError(message.error);
+	}
 });
 
 addEventListener("load", () => {

+ 118 - 0
extension/ui/common/content-error.js

@@ -0,0 +1,118 @@
+/*
+ * Copyright 2010-2020 Gildas Lormeau
+ * contact : gildas.lormeau <at> gmail.com
+ * 
+ * This file is part of SingleFile.
+ *
+ *   The code in this file is free software: you can redistribute it and/or 
+ *   modify it under the terms of the GNU Affero General Public License 
+ *   (GNU AGPL) as published by the Free Software Foundation, either version 3
+ *   of the License, or (at your option) any later version.
+ * 
+ *   The code in this file is distributed in the hope that it will be useful, 
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero 
+ *   General Public License for more details.
+ *
+ *   As additional permission under GNU AGPL version 3 section 7, you may 
+ *   distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU 
+ *   AGPL normally required by section 4, provided you include this license 
+ *   notice and a URL through which recipients can access the Corresponding 
+ *   Source.
+ */
+
+/* global document, globalThis, getComputedStyle */
+
+const singlefile = globalThis.singlefile;
+
+const CLOSE_ICON = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABhmlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AYht+mSlUqHewg4hChOogFURFHqWIRLJS2QqsOJpf+CE0akhQXR8G14ODPYtXBxVlXB1dBEPwBcXNzUnSREr9LCi1ivOO4h/e+9+XuO0Col5lqdowDqmYZqXhMzOZWxMAruhGiOYohiZl6Ir2Qgef4uoeP73dRnuVd9+foVfImA3wi8SzTDYt4nXh609I57xOHWUlSiM+Jxwy6IPEj12WX3zgXHRZ4ZtjIpOaIw8RisY3lNmYlQyWeIo4oqkb5QtZlhfMWZ7VcZc178hcG89pymuu0BhHHIhJIQoSMKjZQhoUo7RopJlJ0HvPwDzj+JLlkcm2AkWMeFaiQHD/4H/zurVmYnHCTgjGg88W2P4aBwC7QqNn297FtN04A/zNwpbX8lTow80l6raVFjoDQNnBx3dLkPeByB+h/0iVDciQ/LaFQAN7P6JtyQN8t0LPq9q15jtMHIEO9WroBDg6BkSJlr3m8u6u9b//WNPv3A6mTcr3f/E/sAAAABmJLR0QAigCKAIrj2uckAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH5QkPDysvCdPVuwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAELSURBVHja7ZpLFsIwDAPj3v/OsGHDe1BIa8tKO7Mnlkw+dpoxAAAAAGCfx4ur6Yx/B337UUS4mp/VuWUEcjSfOgO+BXCZCWe0hSqQo/npBLglIUNLdAV2MH84Ad1JyIwdLkK6YoabIHWscBWmihHuAqvHtv+XqmdXOK9TxdKy3axUm2vZkXXGgPJksTuz1bVFeeU2Y6ijsLIpXbtKa1kDs2ews69o7+A+ihJ2lvI+/lcS1G21zUVG18XKNm4OS4BNkGOQQohSmGaIdpgLESvzyiRwKepsXjE2H0ZWMF8Zi4+jK5mviM0DiRXNZ2rhkdTK5jO0xermz2o8dCnq+FS2XNNVH0sDAAAA3JYnre9cH8BZmhEAAAAASUVORK5CYII=";
+
+const SINGLE_FILE_UI_ELEMENT_CLASS = singlefile.helper.SINGLE_FILE_UI_ELEMENT_CLASS;
+const ERROR_BAR_TAGNAME = "singlefile-error-bar";
+
+const CSS_PROPERTIES = new Set(Array.from(getComputedStyle(document.body)));
+
+let errorBarElement;
+
+export {
+	onError
+};
+
+function onError(message) {
+	try {
+		console.error("SingleFile", message); // eslint-disable-line no-console
+		errorBarElement = document.querySelector(ERROR_BAR_TAGNAME);
+		if (!errorBarElement) {
+			errorBarElement = createElement(ERROR_BAR_TAGNAME);
+			const shadowRoot = errorBarElement.attachShadow({ mode: "open" });
+			const styleElement = document.createElement("style");
+			styleElement.textContent = `
+				.container {
+					background-color: #ff6c00;
+					color: white;
+					display: flex;
+					position: fixed;
+					top: 0px;
+					left: 0px;
+					right: 0px;
+					height: auto;
+					width: auto;
+					min-height: 24px;
+					min-width: 24px;					
+					z-index: 2147483647;
+					margin: 0;
+					padding: 2px;
+					font-family: Arial;
+				}
+				.text {
+					flex: 1;
+					padding-top: 4px;
+					padding-bottom: 4px;
+					padding-left: 8px;					
+				}
+				.close-button {
+					opacity: .7;
+					padding-top: 4px;
+					padding-left: 8px;
+					padding-right: 8px;
+					cursor: pointer;
+					transition: opacity 250ms;
+					height: 16px;
+				}
+				.close-button:hover {
+					opacity: 1;
+				}
+			`;
+			shadowRoot.appendChild(styleElement);
+			const containerElement = document.createElement("div");
+			containerElement.className = "container";
+			const errorTextElement = document.createElement("span");
+			errorTextElement.classList.add("text");
+			errorTextElement.textContent = "SingleFile error: " + message;
+			containerElement.appendChild(errorTextElement);
+			const closeElement = document.createElement("img");
+			closeElement.classList.add("close-button");
+			containerElement.appendChild(closeElement);
+			shadowRoot.appendChild(containerElement);
+			closeElement.src = CLOSE_ICON;
+			closeElement.onclick = event => {
+				if (event.button === 0) {
+					errorBarElement.remove();
+				}
+			};
+			document.body.appendChild(errorBarElement);
+		}
+	} catch (error) {
+		// iignored
+	}
+}
+
+function createElement(tagName, parentElement) {
+	const element = document.createElement(tagName);
+	element.className = SINGLE_FILE_UI_ELEMENT_CLASS;
+	if (parentElement) {
+		parentElement.appendChild(element);
+	}
+	CSS_PROPERTIES.forEach(property => element.style.setProperty(property, "initial", "important"));
+	return element;
+}