diff --git a/Chromium Web Store.crx b/Chromium Web Store.crx
index 6c7311c..63e317f 100644
Binary files a/Chromium Web Store.crx and b/Chromium Web Store.crx differ
diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json
index 7868c33..c8ebaaa 100644
--- a/src/_locales/en/messages.json
+++ b/src/_locales/en/messages.json
@@ -69,6 +69,14 @@
"message": "Always download CRX files",
"description": ""
},
+ "options_webstoreIntegrationTooltip": {
+ "message": "Enable Install/Uninstall buttons on the Chrome Web Store. (Could expose information about which extensions you have installed to Google.)",
+ "description": ""
+ },
+ "options_webstoreIntegration": {
+ "message": "Enable Chrome Web Store Integration",
+ "description": ""
+ },
"options_importExportInstallAllButton": {
"message": "Install All",
"description": ""
diff --git a/src/manifest.json b/src/manifest.json
index ff7f171..3a39054 100644
--- a/src/manifest.json
+++ b/src/manifest.json
@@ -4,7 +4,7 @@
"name": "__MSG_extension_Name__",
"description": "__MSG_extension_Description__",
"default_locale": "en",
- "version": "1.5.4",
+ "version": "1.5.4.1",
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqF/d41Q7agjkUzYq8ZGbQr8XW8mmEIMXOnR1uCTnYLL+Dm9Z+LO50xZukOISNy6zFxpI8ts/OGLsm+I2x9+UprUU4/EVdmxuwegFE6NBoEhHoRNYY0gbXZkaU8YY/XwzjVY/k18DDhl5NYPEnF6uq4Oyidg+xtd3W4+iGYczuOLER1Tp5y614zOTphcvFYhvUkCijQ6HT1TtRq/34SlFoRQqo4SFiLriK451xWIcfwiMLIekWrdoQa1v8dqIlMA3r6CKc0QykJpSYbiyormWiZ0hl2HLpkZ85mD9V0eDQ5RCtb6vkybK7INcq4yKQV4YkXhr9NpX9U4re4dlFQjEJQIDAQAB",
"permissions": [
"management",
@@ -53,5 +53,14 @@
},
"storage": {
"managed_schema": "managed_storage.json"
+ },
+ "web_accessible_resources": [
+ {
+ "resources": ["scripts/chromeApi.js"],
+ "matches": ["https://chromewebstore.google.com/*"]
+ }
+ ],
+ "externally_connectable": {
+ "matches": ["https://chromewebstore.google.com/*"]
}
}
diff --git a/src/options.html b/src/options.html
index 1df7121..7ff13cf 100644
--- a/src/options.html
+++ b/src/options.html
@@ -6,37 +6,75 @@
diff --git a/src/scripts/background.js b/src/scripts/background.js
index c3822ec..9777ba6 100644
--- a/src/scripts/background.js
+++ b/src/scripts/background.js
@@ -202,3 +202,112 @@ chrome.downloads.onChanged.addListener((d) => {
}
});
chrome.contextMenus.onClicked.addListener(handleContextClick);
+
+// below functions are for the new chrome web store:
+// port is used for functions with callbacks:
+chrome.runtime.onConnectExternal.addListener((port) => {
+ var port_disconnected = false;
+ port.onDisconnect.addListener(() => {
+ port_disconnected = true;
+ });
+ port.onMessage.addListener(function (msg, port) {
+ // console.log("recieved msg through port:", msg);
+ switch (msg.func) {
+ case "onInstalled":
+ var [callback, ..._] = msg.args;
+ chrome.management.onInstalled.addListener(function cb() {
+ if (port_disconnected) {
+ chrome.management.onInstalled.removeListener(cb);
+ return;
+ }
+ port.postMessage({
+ args: arguments,
+ callbackIndex: callback,
+ err: chrome.runtime.lastError,
+ });
+ });
+ break;
+ case "onUninstalled":
+ var [callback, ..._] = msg.args;
+ chrome.management.onUninstalled.addListener(function cb() {
+ if (port_disconnected) {
+ chrome.management.onUninstalled.removeListener(cb);
+ return;
+ }
+ port.postMessage({
+ args: arguments,
+ callbackIndex: callback,
+ err: chrome.runtime.lastError,
+ });
+ });
+ break;
+ case "uninstall":
+ var [ext_id, options, callback, ..._] = msg.args;
+ chrome.management.uninstall(ext_id, options, function () {
+ port.postMessage({
+ args: arguments,
+ callbackIndex: callback,
+ err: chrome.runtime.lastError,
+ });
+ });
+ break;
+ case "setEnabled":
+ var [ext_id, enabled, callback, ..._] = msg.args;
+ chrome.management.setEnabled(ext_id, enabled, function () {
+ port.postMessage({
+ args: arguments,
+ callbackIndex: callback,
+ err: chrome.runtime.lastError,
+ });
+ });
+ break;
+ }
+ });
+});
+chrome.runtime.onMessageExternal.addListener(function (
+ request,
+ sender,
+ sendResponse,
+) {
+ // console.log("recieved msg through runtime:", request);
+ switch (request.func) {
+ case "getExtensionStatus":
+ var [id, ext, ..._] = request.args;
+ chrome.management.getAll((exts) => {
+ const this_ext = exts.filter((extInfo) => extInfo.id === id);
+ if (!this_ext.length) {
+ sendResponse({ args: ["installable"] });
+ return;
+ }
+ sendResponse({
+ args: [this_ext[0].enabled ? "enabled" : "disabled"],
+ });
+ });
+ break;
+ case "getAll":
+ var [id, ..._] = request.args;
+ chrome.management.getAll((exts) => {
+ // instead of exposing entire extension list, filter to only the relevant extension.
+ sendResponse({
+ args: [exts.filter((extInfo) => extInfo.id === id)],
+ });
+ });
+ break;
+ case "beginInstallWithManifest3":
+ var [extInfo, href, ..._] = request.args;
+
+ promptInstall(
+ buildExtensionUrl(href),
+ true,
+ WEBSTORE.chrome,
+ msgHandler,
+ );
+ sendResponse({
+ // because a "cancel" in the context of chromium-web-store won't be detected,
+ // we must throw user_cancelled here to ensure the button doesn't get stuck on loading spinner.
+ // The behaviour of this is similar to sending success ("") so this should be fine.
+ args: ["user_cancelled"],
+ });
+ break;
+ }
+});
diff --git a/src/scripts/chromeApi.js b/src/scripts/chromeApi.js
new file mode 100644
index 0000000..3d834a0
--- /dev/null
+++ b/src/scripts/chromeApi.js
@@ -0,0 +1,97 @@
+// https://source.chromium.org/chromium/chromium/src/+/main:chrome/common/extensions/api/webstore_private.json
+const ncws_re = /.*detail(?:\/[^\/]+)?\/([a-z]{32})/i; // copied from util.js since it's out of context
+const THIS_EXT_ID = ncws_re.exec(window.location.href)[1];
+const EXT_ID = "ocaahdebbfolfmndjeplogmgcagdmblk"; // need to hardcode this so it's available immediately. (chrome.runtime.id will be undefined in this context)
+
+const port = chrome.runtime.connect(EXT_ID, { name: "windowchromeport" });
+const CALLBACKS = [];
+port.onMessage.addListener((msg, port) => {
+ if (msg.callbackIndex !== undefined) {
+ if (msg.err) {
+ // "throw" error
+ chrome.runtime.lastError = msg.err;
+ }
+ CALLBACKS[msg.callbackIndex].apply(...Array.from(msg.args));
+ }
+});
+window.chrome.webstorePrivate = {
+ getExtensionStatus: function (id, manifest, cb) {
+ chrome.runtime.sendMessage(
+ EXT_ID,
+ { func: "getExtensionStatus", args: [id, manifest] },
+ (resp) => {
+ resp.args && cb(...resp.args);
+ },
+ );
+ },
+ beginInstallWithManifest3: function (extinfo, cb) {
+ chrome.runtime.sendMessage(
+ EXT_ID,
+ {
+ func: "beginInstallWithManifest3",
+ args: [extinfo, window.location.href],
+ },
+ (resp) => {
+ resp.args && cb(...resp.args);
+ },
+ );
+ },
+ isInIncognitoMode: (cb) => cb(false), // just return false since it's likely this extension isn't running in incognito
+ getReferrerChain: (cb) => cb("EgIIAA=="),
+ completeInstall: function (id, cb) {
+ // will never be called since we cancel all install attempts with "user_cancelled"
+ // instead we rely on the onInstalled listener to continue the flow correctly (behaviour might be slightly different,
+ // for example an extension installed tooltip will not show.)
+ cb(true);
+ },
+};
+window.chrome.management = {
+ setEnabled: function (extId, enabled, callback) {
+ port.postMessage({
+ func: "setEnabled",
+ args: [extId, enabled, CALLBACKS.length],
+ });
+ CALLBACKS.push(callback);
+ },
+ install: function () {
+ console.log(
+ "chrome.management.install not implemented, but called with args:",
+ arguments,
+ );
+ },
+ uninstall: function (extId, options, callback) {
+ port.postMessage({
+ func: "uninstall",
+ args: [extId, options, CALLBACKS.length],
+ });
+ CALLBACKS.push(callback);
+ },
+ getAll: function (cb) {
+ chrome.runtime.sendMessage(
+ EXT_ID,
+ { func: "getAll", args: [THIS_EXT_ID] },
+ (resp) => {
+ resp.args && cb(...resp.args);
+ },
+ );
+ },
+ onInstalled: {
+ addListener: function (callback) {
+ port.postMessage({
+ func: "onInstalled",
+ args: [CALLBACKS.length],
+ });
+ CALLBACKS.push(callback);
+ },
+ },
+ onUninstalled: {
+ addListener: function (callback) {
+ port.postMessage({
+ func: "onUninstalled",
+ args: [CALLBACKS.length],
+ });
+ CALLBACKS.push(callback);
+ },
+ },
+};
+// window.chrome.runtime.getManifest = () => true; // this is referenced in the CWS source but omitting it seems to have no effect
diff --git a/src/scripts/inject.js b/src/scripts/inject.js
index 8c701fc..b4c3796 100644
--- a/src/scripts/inject.js
+++ b/src/scripts/inject.js
@@ -133,29 +133,6 @@ attachMainObserver = new MutationObserver(function (mutations, observer) {
});
observer.disconnect();
});
-var modifyButtonObserverNew = new MutationObserver(function (
- mutations,
- observer,
-) {
- mutations.forEach(function (mutation) {
- const btn = mutation.target.querySelector(
- "button.UywwFc-LgbsSe-OWXEXe-dgl2Hf",
- );
- const header = mutation.target.querySelector(".KgGEHd");
- header && header.setAttribute("target", "_top");
- if (btn && !btn.hasAttribute("isInstallBtn")) {
- btn.setAttribute("isInstallBtn", "true");
- chrome.runtime.sendMessage(
- {
- checkExtInstalledId: getExtensionId(window.location.href),
- },
- (resp) => {
- modifyNewCWSButton(btn, !resp.installed);
- },
- );
- }
- });
-});
if (is_ews.test(window.location.href)) {
new MutationObserver(function (mutations, observer) {
mutations.forEach(function (mutation) {
@@ -194,21 +171,14 @@ if (is_cws.test(window.location.href)) {
childList: true,
});
}
-function injectCSS(cssCode) {
- var style = document.createElement("style");
- style.textContent = cssCode;
- document.head.appendChild(style);
-}
-if (is_ncws.test(window.location.href)) {
- injectCSS(`
- div[jscontroller="o2G9me"].gSrP5d#c2[jsaction="rcuQ6b:npT2md;KKVPLd:KrIKWc;JIbuQc:M4KNod"] {
- display: none;
- }
- `);
- modifyButtonObserverNew.observe(document.body, {
- childList: true,
- });
+function injectScript(file_path, tag) {
+ var node = document.getElementsByTagName(tag)[0];
+ var script = document.createElement("script");
+ script.setAttribute("type", "text/javascript");
+ script.setAttribute("src", file_path);
+ node.appendChild(script);
}
+
if (
is_ows.test(window.location.href) &&
document.body.querySelector("#feedback-container") //built-ins don't have a feedback section
@@ -241,3 +211,16 @@ window.onload = () => {
}
});
};
+if (is_ncws.test(window.location.href)) {
+ chrome.storage.sync.get(
+ { webstore_integration: true },
+ function (stored_values) {
+ if (stored_values.webstore_integration) {
+ injectScript(
+ chrome.runtime.getURL("scripts/chromeApi.js"),
+ "head",
+ );
+ }
+ },
+ );
+}
diff --git a/src/scripts/util.js b/src/scripts/util.js
index 23d8517..32ed678 100644
--- a/src/scripts/util.js
+++ b/src/scripts/util.js
@@ -9,6 +9,7 @@ const DEFAULT_MANAGEMENT_OPTIONS = {
update_period_in_minutes: 60,
removed_extensions: {},
manually_install: false,
+ webstore_integration: true,
};
var fromXML;
// prettier-ignore
@@ -39,7 +40,7 @@ const is_ncws = /chromewebstore.google.com\//i;
const is_ows = /addons.opera.com\/.*extensions/i;
const is_ews = /microsoftedge\.microsoft\.com\/addons\//i;
const cws_re = /.*detail\/[^\/]*\/([a-z]{32})/i;
-const ncws_re = /.*detail\/[^\/]*\/([a-z]{32})/i;
+const ncws_re = /.*detail(?:\/[^\/]+)?\/([a-z]{32})/i;
const ows_re = /.*details\/([^\/?#]+)/i;
const ews_re = /.*addons\/.+?\/([a-z]{32})/i;