Procházet zdrojové kódy

fix GDrive integration in Firefox (fix #882)

Gildas před 3 roky
rodič
revize
16c06098e0

+ 3 - 4
build-extension.sh

@@ -6,12 +6,13 @@ zip -r singlefile-extension-source.zip manifest.json package.json _locales src r
 rm singlefile-extension-firefox.zip singlefile-extension-chromium.zip singlefile-extension-edge.zip
 cp manifest.json manifest.copy.json
 cp src/extension/core/bg/downloads.js downloads.copy.js
-sed -i 's/207618107333-3pj2pmelhnl4sf3rpctghs9cean3q8nj/207618107333-7tjs1im1pighftpoepea2kvkubnfjj44/g' src/extension/core/bg/downloads.js
+sed -i 's/207618107333-3pj2pmelhnl4sf3rpctghs9cean3q8nj/207618107333-h1220p1oasj3050kr5r416661adm091a/g' src/extension/core/bg/downloads.js
+sed -i 's/000000000000000000000000/VQJ8Gq8Vxx72QyxPyeLtWvUt/g' src/extension/core/bg/downloads.js
+sed -i 's/207618107333-3pj2pmelhnl4sf3rpctghs9cean3q8nj/207618107333-7tjs1im1pighftpoepea2kvkubnfjj44/g' manifest.json
 
 cp src/extension/core/bg/config.js config.copy.js
 cp src/extension/core/bg/companion.js companion.copy.js
 jq "del(.options_page,.background.persistent,.optional_permissions[0],.permissions[3],.oauth2)" manifest.copy.json > manifest.json
-sed -i 's/207618107333-3pj2pmelhnl4sf3rpctghs9cean3q8nj/207618107333-7tjs1im1pighftpoepea2kvkubnfjj44/g' manifest.json
 sed -i 's/forceWebAuthFlow: false/forceWebAuthFlow: true/g' src/extension/core/bg/config.js
 sed -i 's/enabled: true/enabled: false/g' src/extension/core/bg/companion.js
 zip -r singlefile-extension-firefox.zip manifest.json lib _locales src/extension
@@ -19,12 +20,10 @@ mv config.copy.js src/extension/core/bg/config.js
 mv companion.copy.js src/extension/core/bg/companion.js
 
 jq "del(.browser_specific_settings,.permissions[0],.permissions[1],.options_ui.browser_style)" manifest.copy.json > manifest.json
-sed -i 's/207618107333-3pj2pmelhnl4sf3rpctghs9cean3q8nj/207618107333-7tjs1im1pighftpoepea2kvkubnfjj44/g' manifest.json
 zip -r singlefile-extension-chromium.zip manifest.json lib _locales src/extension
 
 cp src/extension/core/bg/config.js config.copy.js
 jq "del(.browser_specific_settings,.permissions[0],.permissions[1],.options_ui.browser_style)" manifest.copy.json > manifest.json
-sed -i 's/207618107333-3pj2pmelhnl4sf3rpctghs9cean3q8nj/207618107333-7tjs1im1pighftpoepea2kvkubnfjj44/g' manifest.json
 sed -i 's/forceWebAuthFlow: false/forceWebAuthFlow: true/g' src/extension/core/bg/config.js
 mkdir _locales.copy
 cp -R _locales/* _locales.copy

+ 0 - 1
src/extension/core/bg/config.js

@@ -95,7 +95,6 @@ const DEFAULT_CONFIG = {
 	githubBranch: "main",
 	saveWithCompanion: false,
 	forceWebAuthFlow: false,
-	extractAuthCode: true,
 	resolveFragmentIdentifierURLs: false,
 	userScriptEnabled: false,
 	openEditor: false,

+ 6 - 8
src/extension/core/bg/downloads.js

@@ -28,7 +28,7 @@ import * as bookmarks from "./bookmarks.js";
 import * as companion from "./companion.js";
 import * as business from "./business.js";
 import * as editor from "./editor.js";
-import { launchWebAuthFlow, extractAuthCode, promptValue } from "./tabs-util.js";
+import { launchWebAuthFlow, extractAuthCode } from "./tabs-util.js";
 import * as ui from "./../../ui/bg/index.js";
 import * as woleet from "./../../lib/woleet/woleet.js";
 import { GDrive } from "./../../lib/gdrive/gdrive.js";
@@ -37,7 +37,8 @@ import { download } from "./download-util.js";
 
 const partialContents = new Map();
 const MIMETYPE_HTML = "text/html";
-const CLIENT_ID = "207618107333-3pj2pmelhnl4sf3rpctghs9cean3q8nj.apps.googleusercontent.com";
+const GDRIVE_CLIENT_ID = "207618107333-3pj2pmelhnl4sf3rpctghs9cean3q8nj.apps.googleusercontent.com";
+const GDRIVE_CLIENT_KEY = "000000000000000000000000";
 const SCOPES = ["https://www.googleapis.com/auth/drive.file"];
 const CONFLICT_ACTION_SKIP = "skip";
 const CONFLICT_ACTION_UNIQUIFY = "uniquify";
@@ -45,7 +46,7 @@ const REGEXP_ESCAPE = /([{}()^$&.*?/+|[\\\\]|\]|-)/g;
 
 const manifest = browser.runtime.getManifest();
 const requestPermissionIdentity = manifest.optional_permissions && manifest.optional_permissions.includes("identity");
-const gDrive = new GDrive(CLIENT_ID, SCOPES);
+const gDrive = new GDrive(GDRIVE_CLIENT_ID, GDRIVE_CLIENT_KEY, SCOPES);
 export {
 	onMessage,
 	downloadPage,
@@ -127,8 +128,7 @@ async function downloadContent(contents, tab, incognito, message) {
 	try {
 		if (message.saveToGDrive) {
 			await (await saveToGDrive(message.taskId, message.filename, new Blob(contents, { type: MIMETYPE_HTML }), {
-				forceWebAuthFlow: message.forceWebAuthFlow,
-				extractAuthCode: message.extractAuthCode
+				forceWebAuthFlow: message.forceWebAuthFlow
 			}, {
 				onProgress: (offset, size) => ui.onUploadProgress(tab.id, offset, size)
 			})).uploadPromise;
@@ -178,12 +178,10 @@ async function getAuthInfo(authOptions, force) {
 	let authInfo = await config.getAuthInfo();
 	const options = {
 		interactive: true,
-		auto: authOptions.extractAuthCode,
 		forceWebAuthFlow: authOptions.forceWebAuthFlow,
 		requestPermissionIdentity,
 		launchWebAuthFlow: options => launchWebAuthFlow(options),
-		extractAuthCode: authURL => extractAuthCode(authURL),
-		promptAuthCode: () => promptValue("Please enter the access code for Google Drive")
+		extractAuthCode: authURL => extractAuthCode(authURL)
 	};
 	gDrive.setAuthInfo(authInfo, options);
 	if (!authInfo || !authInfo.accessToken || force) {

+ 9 - 47
src/extension/core/bg/tabs-util.js

@@ -21,71 +21,33 @@
  *   Source.
  */
 
-/* global browser */
-
-const pendingPrompts = new Map();
+/* global browser, URLSearchParams, URL */
 
 export {
-	onPromptValueResponse,
 	queryTabs,
-	promptValue,
 	extractAuthCode,
 	launchWebAuthFlow
 };
 
-async function onPromptValueResponse(message, sender) {
-	const promptPromise = pendingPrompts.get(sender.tab.id);
-	if (promptPromise) {
-		promptPromise.resolve(message.value);
-		pendingPrompts.delete(sender.tab.id);
-	}
-}
-
 async function queryTabs(options) {
 	const tabs = await browser.tabs.query(options);
 	return tabs.sort((tab1, tab2) => tab1.index - tab2.index);
 }
 
-async function promptValue(promptMessage) {
-	const tabs = await browser.tabs.query({ currentWindow: true, active: true });
-	return new Promise((resolve, reject) => {
-		const selectedTabId = tabs[0].id;
-		browser.tabs.onRemoved.addListener(onTabRemoved);
-		pendingPrompts.set(selectedTabId, { resolve, reject });
-		browser.tabs.sendMessage(selectedTabId, { method: "common.promptValueRequest", promptMessage });
-
-		function onTabRemoved(tabId) {
-			if (tabId == selectedTabId) {
-				pendingPrompts.delete(tabId);
-				browser.tabs.onUpdated.removeListener(onTabRemoved);
-				reject();
-			}
-		}
-	});
-}
-
 function extractAuthCode(authURL) {
 	return new Promise((resolve, reject) => {
-		let authTabId;
 		browser.tabs.onUpdated.addListener(onTabUpdated);
-		browser.tabs.onRemoved.addListener(onTabRemoved);
 
 		function onTabUpdated(tabId, changeInfo) {
-			if (changeInfo && changeInfo.url == authURL) {
-				authTabId = tabId;
-			}
-			if (authTabId == tabId && changeInfo && changeInfo.title && changeInfo.title.startsWith("Success code=")) {
-				browser.tabs.onUpdated.removeListener(onTabUpdated);
-				browser.tabs.onUpdated.removeListener(onTabRemoved);
-				resolve(changeInfo.title.substring(13, changeInfo.title.length - 49));
-			}
-		}
-
-		function onTabRemoved(tabId) {
-			if (tabId == authTabId) {
+			if (changeInfo && changeInfo.url.startsWith(authURL)) {
 				browser.tabs.onUpdated.removeListener(onTabUpdated);
-				browser.tabs.onUpdated.removeListener(onTabRemoved);
-				reject();
+				const code = new URLSearchParams(new URL(changeInfo.url).search).get("code");
+				if (code) {
+					browser.tabs.remove(tabId);
+					resolve(code);
+				} else {
+					reject();
+				}
 			}
 		}
 	});

+ 0 - 4
src/extension/core/bg/tabs.js

@@ -29,7 +29,6 @@ import * as business from "./business.js";
 import * as editor from "./editor.js";
 import * as tabsData from "./tabs-data.js";
 import * as ui from "./../../ui/bg/index.js";
-import { onPromptValueResponse } from "./tabs-util.js";
 
 const DELAY_MAYBE_INIT = 1500;
 
@@ -49,9 +48,6 @@ async function onMessage(message, sender) {
 		business.onInit(sender.tab);
 		autosave.onInit(sender.tab);
 	}
-	if (message.method.endsWith(".promptValueResponse")) {
-		onPromptValueResponse(message, sender);
-	}
 	if (message.method.endsWith(".getOptions")) {
 		return config.getOptions(message.url);
 	}

+ 0 - 1
src/extension/core/common/download.js

@@ -53,7 +53,6 @@ async function downloadPage(pageData, options) {
 				githubBranch: options.githubBranch,
 				saveWithCompanion: options.saveWithCompanion,
 				forceWebAuthFlow: options.forceWebAuthFlow,
-				extractAuthCode: options.extractAuthCode,
 				filenameReplacementCharacter: options.filenameReplacementCharacter,
 				openEditor: options.openEditor,
 				openSavedPage: options.openSavedPage,

+ 2 - 7
src/extension/core/content/content-bootstrap.js

@@ -21,7 +21,7 @@
  *   Source.
  */
 
-/* global browser, globalThis, window, document, location, setTimeout, prompt, Node */
+/* global browser, globalThis, window, document, location, setTimeout, Node */
 
 const singlefile = globalThis.singlefileBootstrap;
 
@@ -52,8 +52,7 @@ browser.runtime.onMessage.addListener(message => {
 		message.method == "content.maybeInit" ||
 		message.method == "content.init" ||
 		message.method == "content.openEditor" ||
-		message.method == "devtools.resourceCommitted" ||
-		message.method == "common.promptValueRequest") {
+		message.method == "devtools.resourceCommitted") {
 		return onMessage(message);
 	}
 });
@@ -86,10 +85,6 @@ async function onMessage(message) {
 		singlefile.pageInfo.updatedResources[message.url] = { content: message.content, type: message.type, encoding: message.encoding };
 		return {};
 	}
-	if (message.method == "common.promptValueRequest") {
-		browser.runtime.sendMessage({ method: "tabs.promptValueResponse", value: prompt("SingleFile: " + message.promptMessage) });
-		return {};
-	}
 }
 
 function init() {

+ 18 - 25
src/extension/lib/gdrive/gdrive.js

@@ -21,7 +21,7 @@
  *   Source.
  */
 
-/* global browser, fetch, setInterval */
+/* global browser, fetch, setInterval, URLSearchParams, URL */
 
 const TOKEN_URL = "https://oauth2.googleapis.com/token";
 const AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
@@ -32,13 +32,14 @@ const GDRIVE_UPLOAD_URL = "https://www.googleapis.com/upload/drive/v3/files";
 let requestPermissionIdentityNeeded = true;
 
 class GDrive {
-	constructor(clientId, scopes) {
+	constructor(clientId, clientKey, scopes) {
 		this.clientId = clientId;
+		this.clientKey = clientKey;
 		this.scopes = scopes;
 		this.folderIds = new Map();
 		setInterval(() => this.folderIds.clear(), 60 * 1000);
 	}
-	async auth(options = { interactive: true, auto: true }) {
+	async auth(options = { interactive: true }) {
 		if (options.requestPermissionIdentity && requestPermissionIdentityNeeded) {
 			try {
 				await browser.permissions.request({ permissions: ["identity"] });
@@ -52,7 +53,12 @@ class GDrive {
 			this.accessToken = await browser.identity.getAuthToken({ interactive: options.interactive });
 			return { revokableAccessToken: this.accessToken };
 		} else {
-			getAuthURL(this, options);
+			this.authURL = AUTH_URL +
+				"?client_id=" + this.clientId +
+				"&response_type=code" +
+				"&access_type=offline" +
+				"&redirect_uri=" + browser.identity.getRedirectURL() +
+				"&scope=" + this.scopes.join(" ");
 			return options.code ? authFromCode(this, options) : initAuth(this, options);
 		}
 	}
@@ -192,9 +198,10 @@ async function authFromCode(gdrive, options) {
 		method: "POST",
 		headers: { "Content-Type": "application/x-www-form-urlencoded" },
 		body: "client_id=" + gdrive.clientId +
+			"&client_secret=" + gdrive.clientKey +
 			"&grant_type=authorization_code" +
 			"&code=" + options.code +
-			"&redirect_uri=" + gdrive.redirectURI
+			"&redirect_uri=" + browser.identity.getRedirectURL()
 	});
 	const response = await getJSON(httpResponse);
 	gdrive.accessToken = response.access_token;
@@ -205,18 +212,18 @@ async function authFromCode(gdrive, options) {
 
 async function initAuth(gdrive, options) {
 	let code;
-	if (options.extractAuthCode) {
-		options.extractAuthCode(getAuthURL(gdrive, options))
-			.then(authCode => code = authCode)
-			.catch(() => { /* ignored */ });
-	}
 	try {
 		if (browser.identity && browser.identity.launchWebAuthFlow && !options.forceWebAuthFlow) {
-			return await browser.identity.launchWebAuthFlow({
+			const authURL = await browser.identity.launchWebAuthFlow({
 				interactive: options.interactive,
 				url: gdrive.authURL
 			});
+			options.code = new URLSearchParams(new URL(authURL).search).get("code");
+			return await authFromCode(gdrive, options);
 		} else if (options.launchWebAuthFlow) {
+			options.extractAuthCode(browser.identity.getRedirectURL())
+				.then(authCode => code = authCode)
+				.catch(() => { /* ignored */ });
 			return await options.launchWebAuthFlow({ url: gdrive.authURL });
 		} else {
 			throw new Error("auth_not_supported");
@@ -224,9 +231,6 @@ async function initAuth(gdrive, options) {
 	}
 	catch (error) {
 		if (error.message && (error.message == "code_required" || error.message.includes("access"))) {
-			if (!options.auto && !code && options.promptAuthCode) {
-				code = await options.promptAuthCode();
-			}
 			if (code) {
 				options.code = code;
 				return await authFromCode(gdrive, options);
@@ -239,17 +243,6 @@ async function initAuth(gdrive, options) {
 	}
 }
 
-function getAuthURL(gdrive, options = {}) {
-	gdrive.redirectURI = encodeURIComponent("urn:ietf:wg:oauth:2.0:oob" + (options.auto ? ":auto" : ""));
-	gdrive.authURL = AUTH_URL +
-		"?client_id=" + gdrive.clientId +
-		"&response_type=code" +
-		"&access_type=offline" +
-		"&redirect_uri=" + gdrive.redirectURI +
-		"&scope=" + gdrive.scopes.join(" ");
-	return gdrive.authURL;
-}
-
 function nativeAuth(options = {}) {
 	return Boolean(browser.identity && browser.identity.getAuthToken) && !options.forceWebAuthFlow;
 }

+ 3 - 0
src/extension/lib/single-file/browser-polyfill/chrome-browser-polyfill.js

@@ -193,6 +193,9 @@ if (typeof globalThis == "undefined") {
 				getMessage: (messageName, substitutions) => nativeAPI.i18n.getMessage(messageName, substitutions)
 			},
 			identity: {
+				getRedirectURL() {
+					return nativeAPI.identity.getRedirectURL();
+				},
 				get getAuthToken() {
 					return nativeAPI.identity && nativeAPI.identity.getAuthToken && (details => new Promise((resolve, reject) =>
 						nativeAPI.identity.getAuthToken(details, token => {

+ 1 - 5
src/extension/ui/bg/ui-editor.js

@@ -21,7 +21,7 @@
  *   Source.
  */
 
-/* global browser, document, prompt, matchMedia, addEventListener */
+/* global browser, document, matchMedia, addEventListener */
 
 import * as download from "../../core/common/download.js";
 import { onError } from "./../common/content-error.js";
@@ -318,10 +318,6 @@ browser.runtime.onMessage.addListener(message => {
 		browser.runtime.sendMessage({ method: "ui.processInit" });
 		return Promise.resolve({});
 	}
-	if (message.method == "common.promptValueRequest") {
-		browser.runtime.sendMessage({ method: "tabs.promptValueResponse", value: prompt(message.promptMessage) });
-		return Promise.resolve({});
-	}
 	if (message.method == "editor.setTabData") {
 		if (message.truncated) {
 			tabDataContents.push(message.content);