diff --git a/background.js b/background.js
deleted file mode 100644
index 2b0086b..0000000
--- a/background.js
+++ /dev/null
@@ -1,76 +0,0 @@
-browser.runtime.onMessage.addListener(async (msg) => {
- switch (msg.action) {
- case 'track':
- await track(...msg.args);
- return;
- case 'addTask':
- await addTask(...msg.args);
- return;
- default:
- throw new Error(`unknown action: ${msg.action}`);
- }
-});
-
-async function track(cardId) {
- const task = await addTask(cardId);
- const card = await trektor.trelloGateway.getCard(cardId);
- const cardName = stripStoryPointsAndTaskToken(card.name);
- const response = await trektor.togglGateway.startTimeEntry(task.id, cardName);
- return response.data;
-}
-
-async function addTask(cardId) {
- const card = await trektor.trelloGateway.getCard(cardId);
-
- const taskPrefixes = card.labels
- .map((label) => label.name.match(/(?<=#)[a-z0-9]+$/)?.[0])
- .filter((prefix) => prefix !== undefined);
-
- if (taskPrefixes.length === 0) {
- throw new Error('Card has no valid project labels.');
- }
- if (taskPrefixes.length > 1) {
- throw new Error('Card has multiple project labels.');
- }
- const taskPrefix = taskPrefixes[0];
-
- let taskName = card.name.match(/(?<=#)[A-Za-z0-9_-]+/)?.[0];
-
- if (taskName === undefined) {
- taskName = `${taskPrefix}_${card.idShort}`;
-
- await trektor.trelloGateway.updateCard(card.id, {
- name: `${card.name} #${taskName}`,
- });
- }
-
- const workspaces = await trektor.togglGateway.getWorkspaces();
-
- if (workspaces.length === 0) {
- throw new Error('Could not find any toggl workspaces.');
- }
- if (workspaces.length > 1) {
- throw new Error('Found multiple toggl workspaces. Not sure how to deal with that...');
- }
- const allProjects = await trektor.togglGateway.getProjects(workspaces[0].id);
- const projects = allProjects.filter((project) => project.name.endsWith(`(${taskPrefix})`));
-
- if (projects.length === 0) {
- throw new Error('Could not find any matching toggl project.');
- }
- if (projects.length > 1) {
- throw new Error('Found multiple matching toggl projects. Not sure how to deal with that...');
- }
- const tasks = await trektor.togglGateway.getTasks(projects[0].id);
- const task = tasks.find((task) => task.name === taskName);
- if (task !== undefined) return task;
-
- const response = await trektor.togglGateway.createTask(projects[0].id, taskName)
- return response.data;
-}
-
-function stripStoryPointsAndTaskToken(cardName) {
- return cardName
- .replace(/^(\s*\(\d+\))?\s*/, '') // story points, e.g. (3)
- .replace(/\s*#[a-z0-9_]+\s*$/, ''); // task token, e.g. #orga_5417
-}
diff --git a/manifest.json b/manifest.json
index a04da4a..402a2d0 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,13 +1,8 @@
{
- "manifest_version": 2,
+ "manifest_version": 3,
"name": "Trektor",
"description": "Browser-Extension zum automatischen Anlegen von Toggl tracking tasks",
"version": "0.0.11",
- "browser_specific_settings": {
- "gecko": {
- "id": "trektor@aboutsource.net"
- }
- },
"icons": {
"64": "icons/64.png"
},
@@ -17,8 +12,8 @@
"https://trello.com/*"
],
"js": [
- "vendor/browser-polyfill.js",
- "content_script.js"
+ "scripts/content_script.js",
+ "scripts/trektor.js"
],
"css": [
"content_style.css"
@@ -26,17 +21,20 @@
}
],
"background": {
+ "service_worker": "scripts/chromium.js",
"scripts": [
- "vendor/browser-polyfill.js",
- "trektor.js",
- "background.js"
+ "scripts/trektor.js",
+ "scripts/background.js",
+ "scripts/firefox.js"
]
},
"permissions": [
- "https://api.trello.com/*",
- "https://api.track.toggl.com/*",
"storage"
],
+ "host_permissions": [
+ "https://api.trello.com/*",
+ "https://api.track.toggl.com/*"
+ ],
"options_ui": {
"page": "options/index.html"
}
diff --git a/options/index.html b/options/index.html
index 3a45c93..a07ce6a 100644
--- a/options/index.html
+++ b/options/index.html
@@ -2,34 +2,25 @@
+
Trektor settings
-
+ Willkommen bei Trektor!
+ ✔ Alle Berechtigungen vorhanden
+ ✘ Berechtigung fehlt!
+
- Trello Token
+ Trello Token ↗
-
-
+
- Toggl Token
+ Toggl Token ↗
+
-
-
-
diff --git a/options/script.js b/options/script.js
index fd1c05c..32ed71e 100644
--- a/options/script.js
+++ b/options/script.js
@@ -1,9 +1,42 @@
+if (typeof browser == "undefined") {
+ globalThis.browser = chrome
+}
+
+const permissions = {
+ origins: [
+ "https://api.trello.com/*",
+ "https://api.track.toggl.com/*",
+ "https://trello.com/*"
+ ]
+}
+
document.querySelectorAll("input").forEach((field) => {
field.addEventListener("input", (e) => {
browser.storage.local.set({ [e.target.name]: e.target.value });
});
browser.storage.local.get(field.name).then(({ [field.name]: value }) => {
- field.value = (value === undefined) ? '' : value;
+ field.value = (value === undefined) ? "" : value;
});
});
+
+checkPermissions()
+
+document.getElementById("request").addEventListener("click", () => { requestPermissions() })
+
+function requestPermissions() {
+ console.log("hallo")
+ browser.permissions.request(permissions, () => { checkPermissions() })
+}
+
+function checkPermissions() {
+ browser.permissions.contains(permissions, (contains) => {
+ if (contains) {
+ document.getElementById("permissions-granted").style.display = "block";
+ document.getElementById("permissions-not-granted").style.display = "none";
+ } else {
+ document.getElementById("permissions-granted").style.display = "none";
+ document.getElementById("permissions-not-granted").style.display = "block";
+ }
+ })
+}
diff --git a/options/style.css b/options/style.css
new file mode 100644
index 0000000..cea7222
--- /dev/null
+++ b/options/style.css
@@ -0,0 +1,30 @@
+a {
+ display: block;
+ color: blue;
+ margin: 10px;
+}
+input {
+ width: 100%;
+ box-sizing: border-box;
+ margin-bottom: 10px;
+}
+body {
+ font-family: sans-serif;
+ width: 90%;
+ max-width: 500px;
+ margin: 1.5rem auto;
+}
+
+#permissions-granted {
+ background-color: rgba(0, 255, 0, 0.5);
+ border: 2px green solid;
+}
+#permissions-not-granted {
+ background-color: rgba(255, 20, 0, 0.5);
+ border: 2px red solid;
+}
+.notification {
+ text-align: center;
+ padding: 10px;
+ border-radius: 10px;
+}
diff --git a/scripts/background.js b/scripts/background.js
new file mode 100644
index 0000000..4e27767
--- /dev/null
+++ b/scripts/background.js
@@ -0,0 +1,90 @@
+class BackgroundWorker {
+
+ constructor() {
+ }
+
+ run() {
+ trektor.browser.runtime.onMessage.addListener(async (msg) => {
+ switch (msg.action) {
+ case "track":
+ await this.track(...msg.args);
+ return;
+ case "addTask":
+ await this.addTask(...msg.args);
+ return;
+ case "options":
+ trektor.browser.runtime.openOptionsPage();
+ default:
+ throw new Error(`unknown action: ${msg.action}`);
+ }
+ });
+
+ trektor.browser.runtime.onInstalled.addListener((details) => {
+ if (details.reason == "install") trektor.browser.runtime.openOptionsPage();
+ })
+ }
+
+ async track(cardId) {
+ const task = await this.addTask(cardId);
+ const card = await trektor.trelloGateway.getCard(cardId);
+ const cardName = this.stripStoryPointsAndTaskToken(card.name);
+ const response = await trektor.togglGateway.startTimeEntry(task.id, cardName);
+ return response.data;
+ }
+
+ async addTask(cardId) {
+ const card = await trektor.trelloGateway.getCard(cardId);
+
+ const taskPrefixes = card.labels
+ .map((label) => label.name.match(/(?<=#)[a-z0-9]+$/)?.[0])
+ .filter((prefix) => prefix !== undefined);
+
+ if (taskPrefixes.length === 0) {
+ throw new Error("Card has no valid project labels.");
+ }
+ if (taskPrefixes.length > 1) {
+ throw new Error("Card has multiple project labels.");
+ }
+ const taskPrefix = taskPrefixes[0];
+
+ let taskName = card.name.match(/(?<=#)[A-Za-z0-9_-]+/)?.[0];
+
+ if (taskName === undefined) {
+ taskName = `${taskPrefix}_${card.idShort}`;
+
+ await trektor.trelloGateway.updateCard(card.id, {
+ name: `${card.name} #${taskName}`,
+ });
+ }
+
+ const workspaces = await trektor.togglGateway.getWorkspaces();
+
+ if (workspaces.length === 0) {
+ throw new Error("Could not find any toggl workspaces.");
+ }
+ if (workspaces.length > 1) {
+ throw new Error("Found multiple toggl workspaces. Not sure how to deal with that...");
+ }
+ const allProjects = await trektor.togglGateway.getProjects(workspaces[0].id);
+ const projects = allProjects.filter((project) => project.name.endsWith(`(${taskPrefix})`));
+
+ if (projects.length === 0) {
+ throw new Error("Could not find any matching toggl project.");
+ }
+ if (projects.length > 1) {
+ throw new Error("Found multiple matching toggl projects. Not sure how to deal with that...");
+ }
+ const tasks = await trektor.togglGateway.getTasks(projects[0].id);
+ const task = tasks.find((task) => task.name === taskName);
+ if (task !== undefined) return task;
+
+ const response = await trektor.togglGateway.createTask(projects[0].id, taskName)
+ return response.data;
+ }
+
+ stripStoryPointsAndTaskToken(cardName) {
+ return cardName
+ .replace(/^(\s*\(\d+\))?\s*/, "") // story points, e.g. (3)
+ .replace(/\s*#[a-z0-9_]+\s*$/, ""); // task token, e.g. #orga_5417
+ }
+}
diff --git a/scripts/chromium.js b/scripts/chromium.js
new file mode 100644
index 0000000..d8bb7a8
--- /dev/null
+++ b/scripts/chromium.js
@@ -0,0 +1,3 @@
+importScripts('background.js', 'trektor.js');
+
+new BackgroundWorker().run()
diff --git a/content_script.js b/scripts/content_script.js
similarity index 86%
rename from content_script.js
rename to scripts/content_script.js
index 1fcd717..346d19a 100644
--- a/content_script.js
+++ b/scripts/content_script.js
@@ -34,14 +34,14 @@ async function addButton() {
trackButtonIcon.classList.add("trektor-state-loading");
try {
- await browser.runtime.sendMessage({
+ await trektor.browser.runtime.sendMessage({
action: "track",
args: [window.location.pathname.split("/", 3)[2]],
});
trackButtonIcon.classList.replace("icon-clock", "icon-check-circle");
window.setTimeout(() => trackButtonIcon.classList.replace("icon-check-circle", "icon-clock"), 2000);
} catch (err) {
- window.alert(err);
+ if (window.confirm(err + "\n\n Open Trektor options page now?")) trektor.browser.runtime.sendMessage({ action: "options" });
} finally {
trackButtonIcon.classList.remove("trektor-state-loading");
}
@@ -49,12 +49,12 @@ async function addButton() {
addButton.addEventListener("click", async () => {
try {
- await browser.runtime.sendMessage({
+ await trektor.browser.runtime.sendMessage({
action: "addTask",
args: [window.location.pathname.split("/", 3)[2]],
});
} catch (err) {
- window.alert(err);
+ if (window.confirm(err + "\n\n Open Trektor options page now?")) trektor.browser.runtime.sendMessage({ action: "options" });
}
});
}
@@ -71,7 +71,7 @@ function awaitSelector(selector, timeout) {
resolve(element);
window.clearInterval(interval);
} else if (timeout < 0) {
- reject(new Error('timeout'));
+ reject(new Error("timeout"));
window.clearInterval(interval);
}
timeout -= 100;
@@ -82,7 +82,3 @@ function awaitSelector(selector, timeout) {
window.addEventListener("pushstate", () => {
if (window.location.pathname.startsWith("/c/")) addButton();
});
-
-window.addEventListener('load', () => {
- if (window.location.pathname.startsWith("/c/")) addButton();
-});
diff --git a/scripts/firefox.js b/scripts/firefox.js
new file mode 100644
index 0000000..9660239
--- /dev/null
+++ b/scripts/firefox.js
@@ -0,0 +1 @@
+new BackgroundWorker().run()
diff --git a/trektor.js b/scripts/trektor.js
similarity index 89%
rename from trektor.js
rename to scripts/trektor.js
index cc5d72c..b77fd79 100644
--- a/trektor.js
+++ b/scripts/trektor.js
@@ -1,3 +1,7 @@
+if (typeof browser == "undefined") {
+ globalThis.browser = chrome
+}
+
class TrelloGateway {
static ENDPOINT = "https://api.trello.com/1";
static API_KEY = "2379d540412e417f6f0696c1397f38a6";
@@ -30,7 +34,7 @@ class TrelloGateway {
});
if (response.status === 401) {
- throw new Error('Invalid or expired trello token.');
+ throw new Error('Invalid or expired trello token. \n Get a new one in the settings.');
} else {
return response.json();
}
@@ -88,14 +92,16 @@ class TogglGateway {
});
if (response.status === 403) {
- throw new Error('Invalid toggl token.');
+ throw new Error('Invalid toggl token. \n Get a valid one from the settings.');
} else {
return response.json();
}
}
}
-window.trektor = {
+
+const trektor = {
trelloGateway: new TrelloGateway(browser.storage.local),
togglGateway: new TogglGateway(browser.storage.local),
-};
+ browser: browser,
+}
\ No newline at end of file
diff --git a/vendor/browser-polyfill.js b/vendor/browser-polyfill.js
deleted file mode 100644
index 230b763..0000000
--- a/vendor/browser-polyfill.js
+++ /dev/null
@@ -1,1277 +0,0 @@
-(function (global, factory) {
- if (typeof define === "function" && define.amd) {
- define("webextension-polyfill", ["module"], factory);
- } else if (typeof exports !== "undefined") {
- factory(module);
- } else {
- var mod = {
- exports: {}
- };
- factory(mod);
- global.browser = mod.exports;
- }
-})(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : this, function (module) {
- /* webextension-polyfill - v0.8.0 - Tue Apr 20 2021 11:27:38 */
-
- /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
-
- /* vim: set sts=2 sw=2 et tw=80: */
-
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- "use strict";
-
- if (typeof browser === "undefined" || Object.getPrototypeOf(browser) !== Object.prototype) {
- const CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE = "The message port closed before a response was received.";
- const SEND_RESPONSE_DEPRECATION_WARNING = "Returning a Promise is the preferred way to send a reply from an onMessage/onMessageExternal listener, as the sendResponse will be removed from the specs (See https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage)"; // Wrapping the bulk of this polyfill in a one-time-use function is a minor
- // optimization for Firefox. Since Spidermonkey does not fully parse the
- // contents of a function until the first time it's called, and since it will
- // never actually need to be called, this allows the polyfill to be included
- // in Firefox nearly for free.
-
- const wrapAPIs = extensionAPIs => {
- // NOTE: apiMetadata is associated to the content of the api-metadata.json file
- // at build time by replacing the following "include" with the content of the
- // JSON file.
- const apiMetadata = {
- "alarms": {
- "clear": {
- "minArgs": 0,
- "maxArgs": 1
- },
- "clearAll": {
- "minArgs": 0,
- "maxArgs": 0
- },
- "get": {
- "minArgs": 0,
- "maxArgs": 1
- },
- "getAll": {
- "minArgs": 0,
- "maxArgs": 0
- }
- },
- "bookmarks": {
- "create": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "get": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "getChildren": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "getRecent": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "getSubTree": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "getTree": {
- "minArgs": 0,
- "maxArgs": 0
- },
- "move": {
- "minArgs": 2,
- "maxArgs": 2
- },
- "remove": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "removeTree": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "search": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "update": {
- "minArgs": 2,
- "maxArgs": 2
- }
- },
- "browserAction": {
- "disable": {
- "minArgs": 0,
- "maxArgs": 1,
- "fallbackToNoCallback": true
- },
- "enable": {
- "minArgs": 0,
- "maxArgs": 1,
- "fallbackToNoCallback": true
- },
- "getBadgeBackgroundColor": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "getBadgeText": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "getPopup": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "getTitle": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "openPopup": {
- "minArgs": 0,
- "maxArgs": 0
- },
- "setBadgeBackgroundColor": {
- "minArgs": 1,
- "maxArgs": 1,
- "fallbackToNoCallback": true
- },
- "setBadgeText": {
- "minArgs": 1,
- "maxArgs": 1,
- "fallbackToNoCallback": true
- },
- "setIcon": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "setPopup": {
- "minArgs": 1,
- "maxArgs": 1,
- "fallbackToNoCallback": true
- },
- "setTitle": {
- "minArgs": 1,
- "maxArgs": 1,
- "fallbackToNoCallback": true
- }
- },
- "browsingData": {
- "remove": {
- "minArgs": 2,
- "maxArgs": 2
- },
- "removeCache": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "removeCookies": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "removeDownloads": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "removeFormData": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "removeHistory": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "removeLocalStorage": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "removePasswords": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "removePluginData": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "settings": {
- "minArgs": 0,
- "maxArgs": 0
- }
- },
- "commands": {
- "getAll": {
- "minArgs": 0,
- "maxArgs": 0
- }
- },
- "contextMenus": {
- "remove": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "removeAll": {
- "minArgs": 0,
- "maxArgs": 0
- },
- "update": {
- "minArgs": 2,
- "maxArgs": 2
- }
- },
- "cookies": {
- "get": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "getAll": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "getAllCookieStores": {
- "minArgs": 0,
- "maxArgs": 0
- },
- "remove": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "set": {
- "minArgs": 1,
- "maxArgs": 1
- }
- },
- "devtools": {
- "inspectedWindow": {
- "eval": {
- "minArgs": 1,
- "maxArgs": 2,
- "singleCallbackArg": false
- }
- },
- "panels": {
- "create": {
- "minArgs": 3,
- "maxArgs": 3,
- "singleCallbackArg": true
- },
- "elements": {
- "createSidebarPane": {
- "minArgs": 1,
- "maxArgs": 1
- }
- }
- }
- },
- "downloads": {
- "cancel": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "download": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "erase": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "getFileIcon": {
- "minArgs": 1,
- "maxArgs": 2
- },
- "open": {
- "minArgs": 1,
- "maxArgs": 1,
- "fallbackToNoCallback": true
- },
- "pause": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "removeFile": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "resume": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "search": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "show": {
- "minArgs": 1,
- "maxArgs": 1,
- "fallbackToNoCallback": true
- }
- },
- "extension": {
- "isAllowedFileSchemeAccess": {
- "minArgs": 0,
- "maxArgs": 0
- },
- "isAllowedIncognitoAccess": {
- "minArgs": 0,
- "maxArgs": 0
- }
- },
- "history": {
- "addUrl": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "deleteAll": {
- "minArgs": 0,
- "maxArgs": 0
- },
- "deleteRange": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "deleteUrl": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "getVisits": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "search": {
- "minArgs": 1,
- "maxArgs": 1
- }
- },
- "i18n": {
- "detectLanguage": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "getAcceptLanguages": {
- "minArgs": 0,
- "maxArgs": 0
- }
- },
- "identity": {
- "launchWebAuthFlow": {
- "minArgs": 1,
- "maxArgs": 1
- }
- },
- "idle": {
- "queryState": {
- "minArgs": 1,
- "maxArgs": 1
- }
- },
- "management": {
- "get": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "getAll": {
- "minArgs": 0,
- "maxArgs": 0
- },
- "getSelf": {
- "minArgs": 0,
- "maxArgs": 0
- },
- "setEnabled": {
- "minArgs": 2,
- "maxArgs": 2
- },
- "uninstallSelf": {
- "minArgs": 0,
- "maxArgs": 1
- }
- },
- "notifications": {
- "clear": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "create": {
- "minArgs": 1,
- "maxArgs": 2
- },
- "getAll": {
- "minArgs": 0,
- "maxArgs": 0
- },
- "getPermissionLevel": {
- "minArgs": 0,
- "maxArgs": 0
- },
- "update": {
- "minArgs": 2,
- "maxArgs": 2
- }
- },
- "pageAction": {
- "getPopup": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "getTitle": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "hide": {
- "minArgs": 1,
- "maxArgs": 1,
- "fallbackToNoCallback": true
- },
- "setIcon": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "setPopup": {
- "minArgs": 1,
- "maxArgs": 1,
- "fallbackToNoCallback": true
- },
- "setTitle": {
- "minArgs": 1,
- "maxArgs": 1,
- "fallbackToNoCallback": true
- },
- "show": {
- "minArgs": 1,
- "maxArgs": 1,
- "fallbackToNoCallback": true
- }
- },
- "permissions": {
- "contains": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "getAll": {
- "minArgs": 0,
- "maxArgs": 0
- },
- "remove": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "request": {
- "minArgs": 1,
- "maxArgs": 1
- }
- },
- "runtime": {
- "getBackgroundPage": {
- "minArgs": 0,
- "maxArgs": 0
- },
- "getPlatformInfo": {
- "minArgs": 0,
- "maxArgs": 0
- },
- "openOptionsPage": {
- "minArgs": 0,
- "maxArgs": 0
- },
- "requestUpdateCheck": {
- "minArgs": 0,
- "maxArgs": 0
- },
- "sendMessage": {
- "minArgs": 1,
- "maxArgs": 3
- },
- "sendNativeMessage": {
- "minArgs": 2,
- "maxArgs": 2
- },
- "setUninstallURL": {
- "minArgs": 1,
- "maxArgs": 1
- }
- },
- "sessions": {
- "getDevices": {
- "minArgs": 0,
- "maxArgs": 1
- },
- "getRecentlyClosed": {
- "minArgs": 0,
- "maxArgs": 1
- },
- "restore": {
- "minArgs": 0,
- "maxArgs": 1
- }
- },
- "storage": {
- "local": {
- "clear": {
- "minArgs": 0,
- "maxArgs": 0
- },
- "get": {
- "minArgs": 0,
- "maxArgs": 1
- },
- "getBytesInUse": {
- "minArgs": 0,
- "maxArgs": 1
- },
- "remove": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "set": {
- "minArgs": 1,
- "maxArgs": 1
- }
- },
- "managed": {
- "get": {
- "minArgs": 0,
- "maxArgs": 1
- },
- "getBytesInUse": {
- "minArgs": 0,
- "maxArgs": 1
- }
- },
- "sync": {
- "clear": {
- "minArgs": 0,
- "maxArgs": 0
- },
- "get": {
- "minArgs": 0,
- "maxArgs": 1
- },
- "getBytesInUse": {
- "minArgs": 0,
- "maxArgs": 1
- },
- "remove": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "set": {
- "minArgs": 1,
- "maxArgs": 1
- }
- }
- },
- "tabs": {
- "captureVisibleTab": {
- "minArgs": 0,
- "maxArgs": 2
- },
- "create": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "detectLanguage": {
- "minArgs": 0,
- "maxArgs": 1
- },
- "discard": {
- "minArgs": 0,
- "maxArgs": 1
- },
- "duplicate": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "executeScript": {
- "minArgs": 1,
- "maxArgs": 2
- },
- "get": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "getCurrent": {
- "minArgs": 0,
- "maxArgs": 0
- },
- "getZoom": {
- "minArgs": 0,
- "maxArgs": 1
- },
- "getZoomSettings": {
- "minArgs": 0,
- "maxArgs": 1
- },
- "goBack": {
- "minArgs": 0,
- "maxArgs": 1
- },
- "goForward": {
- "minArgs": 0,
- "maxArgs": 1
- },
- "highlight": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "insertCSS": {
- "minArgs": 1,
- "maxArgs": 2
- },
- "move": {
- "minArgs": 2,
- "maxArgs": 2
- },
- "query": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "reload": {
- "minArgs": 0,
- "maxArgs": 2
- },
- "remove": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "removeCSS": {
- "minArgs": 1,
- "maxArgs": 2
- },
- "sendMessage": {
- "minArgs": 2,
- "maxArgs": 3
- },
- "setZoom": {
- "minArgs": 1,
- "maxArgs": 2
- },
- "setZoomSettings": {
- "minArgs": 1,
- "maxArgs": 2
- },
- "update": {
- "minArgs": 1,
- "maxArgs": 2
- }
- },
- "topSites": {
- "get": {
- "minArgs": 0,
- "maxArgs": 0
- }
- },
- "webNavigation": {
- "getAllFrames": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "getFrame": {
- "minArgs": 1,
- "maxArgs": 1
- }
- },
- "webRequest": {
- "handlerBehaviorChanged": {
- "minArgs": 0,
- "maxArgs": 0
- }
- },
- "windows": {
- "create": {
- "minArgs": 0,
- "maxArgs": 1
- },
- "get": {
- "minArgs": 1,
- "maxArgs": 2
- },
- "getAll": {
- "minArgs": 0,
- "maxArgs": 1
- },
- "getCurrent": {
- "minArgs": 0,
- "maxArgs": 1
- },
- "getLastFocused": {
- "minArgs": 0,
- "maxArgs": 1
- },
- "remove": {
- "minArgs": 1,
- "maxArgs": 1
- },
- "update": {
- "minArgs": 2,
- "maxArgs": 2
- }
- }
- };
-
- if (Object.keys(apiMetadata).length === 0) {
- throw new Error("api-metadata.json has not been included in browser-polyfill");
- }
- /**
- * A WeakMap subclass which creates and stores a value for any key which does
- * not exist when accessed, but behaves exactly as an ordinary WeakMap
- * otherwise.
- *
- * @param {function} createItem
- * A function which will be called in order to create the value for any
- * key which does not exist, the first time it is accessed. The
- * function receives, as its only argument, the key being created.
- */
-
-
- class DefaultWeakMap extends WeakMap {
- constructor(createItem, items = undefined) {
- super(items);
- this.createItem = createItem;
- }
-
- get(key) {
- if (!this.has(key)) {
- this.set(key, this.createItem(key));
- }
-
- return super.get(key);
- }
-
- }
- /**
- * Returns true if the given object is an object with a `then` method, and can
- * therefore be assumed to behave as a Promise.
- *
- * @param {*} value The value to test.
- * @returns {boolean} True if the value is thenable.
- */
-
-
- const isThenable = value => {
- return value && typeof value === "object" && typeof value.then === "function";
- };
- /**
- * Creates and returns a function which, when called, will resolve or reject
- * the given promise based on how it is called:
- *
- * - If, when called, `chrome.runtime.lastError` contains a non-null object,
- * the promise is rejected with that value.
- * - If the function is called with exactly one argument, the promise is
- * resolved to that value.
- * - Otherwise, the promise is resolved to an array containing all of the
- * function's arguments.
- *
- * @param {object} promise
- * An object containing the resolution and rejection functions of a
- * promise.
- * @param {function} promise.resolve
- * The promise's resolution function.
- * @param {function} promise.reject
- * The promise's rejection function.
- * @param {object} metadata
- * Metadata about the wrapped method which has created the callback.
- * @param {boolean} metadata.singleCallbackArg
- * Whether or not the promise is resolved with only the first
- * argument of the callback, alternatively an array of all the
- * callback arguments is resolved. By default, if the callback
- * function is invoked with only a single argument, that will be
- * resolved to the promise, while all arguments will be resolved as
- * an array if multiple are given.
- *
- * @returns {function}
- * The generated callback function.
- */
-
-
- const makeCallback = (promise, metadata) => {
- return (...callbackArgs) => {
- if (extensionAPIs.runtime.lastError) {
- promise.reject(new Error(extensionAPIs.runtime.lastError.message));
- } else if (metadata.singleCallbackArg || callbackArgs.length <= 1 && metadata.singleCallbackArg !== false) {
- promise.resolve(callbackArgs[0]);
- } else {
- promise.resolve(callbackArgs);
- }
- };
- };
-
- const pluralizeArguments = numArgs => numArgs == 1 ? "argument" : "arguments";
- /**
- * Creates a wrapper function for a method with the given name and metadata.
- *
- * @param {string} name
- * The name of the method which is being wrapped.
- * @param {object} metadata
- * Metadata about the method being wrapped.
- * @param {integer} metadata.minArgs
- * The minimum number of arguments which must be passed to the
- * function. If called with fewer than this number of arguments, the
- * wrapper will raise an exception.
- * @param {integer} metadata.maxArgs
- * The maximum number of arguments which may be passed to the
- * function. If called with more than this number of arguments, the
- * wrapper will raise an exception.
- * @param {boolean} metadata.singleCallbackArg
- * Whether or not the promise is resolved with only the first
- * argument of the callback, alternatively an array of all the
- * callback arguments is resolved. By default, if the callback
- * function is invoked with only a single argument, that will be
- * resolved to the promise, while all arguments will be resolved as
- * an array if multiple are given.
- *
- * @returns {function(object, ...*)}
- * The generated wrapper function.
- */
-
-
- const wrapAsyncFunction = (name, metadata) => {
- return function asyncFunctionWrapper(target, ...args) {
- if (args.length < metadata.minArgs) {
- throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`);
- }
-
- if (args.length > metadata.maxArgs) {
- throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`);
- }
-
- return new Promise((resolve, reject) => {
- if (metadata.fallbackToNoCallback) {
- // This API method has currently no callback on Chrome, but it return a promise on Firefox,
- // and so the polyfill will try to call it with a callback first, and it will fallback
- // to not passing the callback if the first call fails.
- try {
- target[name](...args, makeCallback({
- resolve,
- reject
- }, metadata));
- } catch (cbError) {
- console.warn(`${name} API method doesn't seem to support the callback parameter, ` + "falling back to call it without a callback: ", cbError);
- target[name](...args); // Update the API method metadata, so that the next API calls will not try to
- // use the unsupported callback anymore.
-
- metadata.fallbackToNoCallback = false;
- metadata.noCallback = true;
- resolve();
- }
- } else if (metadata.noCallback) {
- target[name](...args);
- resolve();
- } else {
- target[name](...args, makeCallback({
- resolve,
- reject
- }, metadata));
- }
- });
- };
- };
- /**
- * Wraps an existing method of the target object, so that calls to it are
- * intercepted by the given wrapper function. The wrapper function receives,
- * as its first argument, the original `target` object, followed by each of
- * the arguments passed to the original method.
- *
- * @param {object} target
- * The original target object that the wrapped method belongs to.
- * @param {function} method
- * The method being wrapped. This is used as the target of the Proxy
- * object which is created to wrap the method.
- * @param {function} wrapper
- * The wrapper function which is called in place of a direct invocation
- * of the wrapped method.
- *
- * @returns {Proxy}
- * A Proxy object for the given method, which invokes the given wrapper
- * method in its place.
- */
-
-
- const wrapMethod = (target, method, wrapper) => {
- return new Proxy(method, {
- apply(targetMethod, thisObj, args) {
- return wrapper.call(thisObj, target, ...args);
- }
-
- });
- };
-
- let hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty);
- /**
- * Wraps an object in a Proxy which intercepts and wraps certain methods
- * based on the given `wrappers` and `metadata` objects.
- *
- * @param {object} target
- * The target object to wrap.
- *
- * @param {object} [wrappers = {}]
- * An object tree containing wrapper functions for special cases. Any
- * function present in this object tree is called in place of the
- * method in the same location in the `target` object tree. These
- * wrapper methods are invoked as described in {@see wrapMethod}.
- *
- * @param {object} [metadata = {}]
- * An object tree containing metadata used to automatically generate
- * Promise-based wrapper functions for asynchronous. Any function in
- * the `target` object tree which has a corresponding metadata object
- * in the same location in the `metadata` tree is replaced with an
- * automatically-generated wrapper function, as described in
- * {@see wrapAsyncFunction}
- *
- * @returns {Proxy