How to replace/upgrade a Chrome extension with another? - google-chrome

My team owns a Chrome extension, and the other team has a similar extension but sooner will be deprecated, so we will take over all their existing users. Here comes the question - is there a way to migrate users from their extension to ours seamless? I.e. is there a way to auto-upgrade to our extension from user end?

There is no seamless way, as for obvious security reasons extensions can't install other extensions (even with "management" permission), but here's a possible upgrade path.
I'll call the "old" extension A and assume its ID is "extension_a_id", and the "new" extension B and assume its ID is `extension_b_id".
Make sure that extensions can talk to each other. Unless you specifically define "externally_connectable" in the manifest, that should be the case by default; if you do, make sure that extension B's includes the "extension_a_id" ID and vice versa.
Update extension B to include code responding to requests from A in all following steps. Do not proceed until you're reasonably sure most of the install base has this version (maybe wait a week or so).
//// Extension B (background code) ////
chrome.runtime.onMessageExternal.addListener(
function(message, sender, sendResponse) {
if(sender.id && sender.id == "extension_A_id") {
/* ..processing code.. */
}
}
);
In extension A, add a check that extension B is installed. Do so by pinging it:
//// Extension A ////
chrome.runtime.onStartup.addListener(function() {
isNewInstalled(function(response) {
if(response) {
transferPreferences(response.versionB); // See step 5
} else {
// Nag user to install, see step 4
}
});
});
function isNewInstalled(callback) {
chrome.runtime.sendMessage(
"extension_B_id",
{areYouThere: true},
passResponseOrFalse(callback)
);
}
function passResponseOrFalse(callback) {
return function(response) {
// It's important to evaluate chrome.runtime.lastError
// to prevent uncatchable exception, see http://stackoverflow.com/q/28431505
if(chrome.runtime.lastError || !response) {
callback(false);
} else {
callback(response);
}
}
}
//// Extension B (processing code) ////
// Send version number just in case
if(message.areYouThere) sendResponse({
versionB: chrome.runtime.getManifest().version
});
If extension B is not installed in step 3, nag the user to install it: show a page that explains why it is needed to upgrade and link to the CWS listing. This step requires user input.
If the extension B is already installed in step 3, transfer user options over and self-uninstall:
//// Extension A ////
function transferPreferences(versionB) {
/* ..validate version of B.. */
var prefs = {};
/* ..fill prefs with data that needs to be transfered (JSON-encodable).. */
chrome.runtime.sendMessage(
"extension_B_id",
{
versionA: chrome.runtime.getManifest().version,
preferences: prefs
},
passResponseOrFalse(goodbyeCruelWorld)
);
}
function goodbyeCruelWorld(response) {
if(response.processed) {
// Does not require management permission
chrome.management.uninstallSelf();
} else {
console.warn("It is not my time to die yet.");
}
}
//// Extension B, processing code ////
if(message.preferences) {
/* ..validate message.versionA and process message.preferences.. */
sendResponse({processed: true});
}
When extension B is installed, message the (possibly installed) extension A that the transfer can start immediately:
//// Extension B, background code ////
chrome.runtime.onInstalled.addListener(function(details) {
/* ..maybe check details.reason and new version.. */
chrome.runtime.sendMessage(
"extension_A_id",
{iAmHere: true, versionB: chrome.runtime.getManifest().version},
ignoreResponse
);
});
function ignoreResponse(response) {
// Again, evaluate to avoid exceptions;
// if needed, this is the place to check if A is still installed
return chrome.runtime.lastError;
}
//// Extension A, background code ////
chrome.runtime.onMessageExternal.addListener(
function(message, sender, sendResponse) {
if(sender.id && sender.id == "extension_B_id") {
if(message.iAmHere) {
sendResponse({
versionA: chrome.runtime.getManifest().version
});
transferPreferences(message.versionB);
}
}
}
);
Publish an update to extension B with all of the above.
Result:
Users that have B installed won't notice anything, since ignoreResponse will gobble up the messaging error;
Users that have both installed will initiate transfer as soon as B updates and it will quietly finish;
Users that only have A will be nagged on every extension restart to install B instead, upon which the transfer will start automatically.
One last concern is not to clobber the preferences of B with ones from A; left as an exercise to the reader (and also depends on the implementation).

Related

Chrome manifest V3 extensions and externally_connectable documentation

After preparing the migration of my chrome manifest V2 extension to manifest V3 and reading about the problems with persistent service workers I prepared myself for a battle with the unknown. My V2 background script uses a whole bunch of globally declared variables and I expected I need to refactor that.
But to my great surprise my extension background script seems to work out of the box without any trouble in manifest V3. My extension uses externally_connectable. The typical use case for my extension is that the user can navigate to my website 'bla.com' and from there it can send jobs to the extension background script.
My manifest says:
"externally_connectable": {
"matches": [
"*://localhost/*",
"https://*.bla.com/*"
]
}
My background script listens to external messages and connects:
chrome.runtime.onMessageExternal.addListener( (message, sender, sendResponse) => {
log('received external message', message);
});
chrome.runtime.onConnectExternal.addListener(function(port) {
messageExternalPort = port;
if (messageExternalPort && typeof messageExternalPort.onDisconnect === 'function') {
messageExternalPort.onDisconnect(function () {
messageExternalPort = null;
})
}
});
From bla.com I send messages to the extension as follows
chrome.runtime.sendMessage(EXTENSION_ID, { type: "collect" });
From bla.com I receive messages from the extension as follows
const setUpExtensionListener = () => {
// Connect to chrome extension
this.port = chrome.runtime.connect(EXTENSION_ID, { name: 'query' });
// Add listener
this.port.onMessage.addListener(handleExtensionMessage);
}
I tested all scenarios including the anticipation of the famous service worker unload after 5 minutes or 30 seconds inactivity, but it all seems to work. Good for me, but something is itchy. I cannot find any documentation that explains precisely under which circumstances the service worker is unloaded. I do not understand why things seem to work out of the box in my situation and why so many others experience problems. Can anybody explain or refer to proper documentation. Thanks in advance.

Can a website detect extensions that are installed using Developed Mode? [duplicate]

I am in the process of building a Chrome extension, and for the whole thing to work the way I would like it to, I need an external JavaScript script to be able to detect if a user has my extension installed.
For example: A user installs my plugin, then goes to a website with my script on it. The website detects that my extension is installed and updates the page accordingly.
Is this possible?
Chrome now has the ability to send messages from the website to the extension.
So in the extension background.js (content.js will not work) add something like:
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (request) {
if (request.message) {
if (request.message == "version") {
sendResponse({version: 1.0});
}
}
}
return true;
});
This will then let you make a call from the website:
var hasExtension = false;
chrome.runtime.sendMessage(extensionId, { message: "version" },
function (reply) {
if (reply) {
if (reply.version) {
if (reply.version >= requiredVersion) {
hasExtension = true;
}
}
}
else {
hasExtension = false;
}
});
You can then check the hasExtension variable. The only drawback is the call is asynchronous, so you have to work around that somehow.
Edit:
As mentioned below, you'll need to add an entry to the manifest.json listing the domains that can message your addon. Eg:
"externally_connectable": {
"matches": ["*://localhost/*", "*://your.domain.com/*"]
},
2021 Update:
chrome.runtime.sendMessage will throw the following exception in console if the extension isn't installed or it's disabled.
Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist
To fix this, add this validation inside the sendMessage callback
if (chrome.runtime.lastError) {
// handle error
}
I am sure there is a direct way (calling functions on your extension directly, or by using the JS classes for extensions), but an indirect method (until something better comes along):
Have your Chrome extension look for a specific DIV or other element on your page, with a very specific ID.
For example:
<div id="ExtensionCheck_JamesEggersAwesomeExtension"></div>
Do a getElementById and set the innerHTML to the version number of your extension or something. You can then read the contents of that client-side.
Again though, you should use a direct method if there is one available.
EDIT: Direct method found!!
Use the connection methods found here: https://developer.chrome.com/extensions/extension#global-events
Untested, but you should be able to do...
var myPort=chrome.extension.connect('yourextensionid_qwerqweroijwefoijwef', some_object_to_send_on_connect);
Another method is to expose a web-accessible resource, though this will allow any website to test if your extension is installed.
Suppose your extension's ID is aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, and you add a file (say, a transparent pixel image) as test.png in your extension's files.
Then, you expose this file to the web pages with web_accessible_resources manifest key:
"web_accessible_resources": [
"test.png"
],
In your web page, you can try to load this file by its full URL (in an <img> tag, via XHR, or in any other way):
chrome-extension://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/test.png
If the file loads, then the extension is installed. If there's an error while loading this file, then the extension is not installed.
// Code from https://groups.google.com/a/chromium.org/d/msg/chromium-extensions/8ArcsWMBaM4/2GKwVOZm1qMJ
function detectExtension(extensionId, callback) {
var img;
img = new Image();
img.src = "chrome-extension://" + extensionId + "/test.png";
img.onload = function() {
callback(true);
};
img.onerror = function() {
callback(false);
};
}
Of note: if there is an error while loading this file, said network stack error will appear in the console with no possibility to silence it. When Chromecast used this method, it caused quite a bit of controversy because of this; with the eventual very ugly solution of simply blacklisting very specific errors from Dev Tools altogether by the Chrome team.
Important note: this method will not work in Firefox WebExtensions. Web-accessible resources inherently expose the extension to fingerprinting, since the URL is predictable by knowing the ID. Firefox decided to close that hole by assigning an instance-specific random URL to web accessible resources:
The files will then be available using a URL like:
moz-extension://<random-UUID>/<path/to/resource>
This UUID is randomly generated for every browser instance and is not your extension's ID. This prevents websites from fingerprinting the extensions a user has installed.
However, while the extension can use runtime.getURL() to obtain this address, you can't hard-code it in your website.
I thought I would share my research on this.
I needed to be able to detect if a specific extension was installed for some file:/// links to work.
I came across this article here
This explained a method of getting the manifest.json of an extension.
I adjusted the code a bit and came up with:
function Ext_Detect_NotInstalled(ExtName, ExtID) {
console.log(ExtName + ' Not Installed');
if (divAnnounce.innerHTML != '')
divAnnounce.innerHTML = divAnnounce.innerHTML + "<BR>"
divAnnounce.innerHTML = divAnnounce.innerHTML + 'Page needs ' + ExtName + ' Extension -- to intall the LocalLinks extension click here';
}
function Ext_Detect_Installed(ExtName, ExtID) {
console.log(ExtName + ' Installed');
}
var Ext_Detect = function (ExtName, ExtID) {
var s = document.createElement('script');
s.onload = function () { Ext_Detect_Installed(ExtName, ExtID); };
s.onerror = function () { Ext_Detect_NotInstalled(ExtName, ExtID); };
s.src = 'chrome-extension://' + ExtID + '/manifest.json';
document.body.appendChild(s);
}
var is_chrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
if (is_chrome == true) {
window.onload = function () { Ext_Detect('LocalLinks', 'jllpkdkcdjndhggodimiphkghogcpida'); };
}
With this you should be able to use Ext_Detect(ExtensionName,ExtensionID) to detect the installation of any number of extensions.
Another possible solution if you own the website is to use inline installation.
if (chrome.app.isInstalled) {
// extension is installed.
}
I know this an old question but this way was introduced in Chrome 15 and so I thought Id list it for anyone only now looking for an answer.
Here is an other modern approach:
const checkExtension = (id, src, callback) => {
let e = new Image()
e.src = 'chrome-extension://'+ id +'/'+ src
e.onload = () => callback(1), e.onerror = () => callback(0)
}
// "src" must be included to "web_accessible_resources" in manifest.json
checkExtension('gighmmpiobklfepjocnamgkkbiglidom', 'icons/icon24.png', (ok) => {
console.log('AdBlock: %s', ok ? 'installed' : 'not installed')
})
checkExtension('bhlhnicpbhignbdhedgjhgdocnmhomnp', 'images/checkmark-icon.png', (ok) => {
console.log('ColorZilla: %s', ok ? 'installed' : 'not installed')
})
I used the cookie method:
In my manifest.js file I included a content script that only runs on my site:
"content_scripts": [
{
"matches": [
"*://*.mysite.co/*"
],
"js": ["js/mysite.js"],
"run_at": "document_idle"
}
],
in my js/mysite.js I have one line:
document.cookie = "extension_downloaded=True";
and in my index.html page I look for that cookie.
if (document.cookie.indexOf('extension_downloaded') != -1){
document.getElementById('install-btn').style.display = 'none';
}
You could have the extension set a cookie and have your websites JavaScript check if that cookie is present and update accordingly. This and probably most other methods mentioned here could of course be cirvumvented by the user, unless you try and have the extension create custom cookies depending on timestamps etc, and have your application analyze them server side to see if it really is a user with the extension or someone pretending to have it by modifying his cookies.
There's another method shown at this Google Groups post. In short, you could try detecting whether the extension icon loads successfully. This may be helpful if the extension you're checking for isn't your own.
Webpage interacts with extension through background script.
manifest.json:
"background": {
"scripts": ["background.js"],
"persistent": true
},
"externally_connectable": {
"matches": ["*://(domain.ext)/*"]
},
background.js:
chrome.runtime.onMessageExternal.addListener(function(msg, sender, sendResponse) {
if ((msg.action == "id") && (msg.value == id))
{
sendResponse({id : id});
}
});
page.html:
<script>
var id = "some_ext_id";
chrome.runtime.sendMessage(id, {action: "id", value : id}, function(response) {
if(response && (response.id == id)) //extension installed
{
console.log(response);
}
else //extension not installed
{
console.log("Please consider installig extension");
}
});
</script>
Your extension could interact with the website (e.g. changing variables) and your website could detect this.
But there should be a better way to do this. I wonder how Google is doing it on their extension gallery (already installed applications are marked).
Edit:
The gallery use the chrome.management.get function. Example:
chrome.management.get("mblbciejcodpealifnhfjbdlkedplodp", function(a){console.log(a);});
But you can only access the method from pages with the right permissions.
A lot of the answers here so far are Chrome only or incur an HTTP overhead penalty. The solution that we are using is a little different:
1. Add a new object to the manifest content_scripts list like so:
{
"matches": ["https://www.yoursite.com/*"],
"js": [
"install_notifier.js"
],
"run_at": "document_idle"
}
This will allow the code in install_notifier.js to run on that site (if you didn't already have permissions there).
2. Send a message to every site in the manifest key above.
Add something like this to install_notifier.js (note that this is using a closure to keep the variables from being global, but that's not strictly necessary):
// Dispatch a message to every URL that's in the manifest to say that the extension is
// installed. This allows webpages to take action based on the presence of the
// extension and its version. This is only allowed for a small whitelist of
// domains defined in the manifest.
(function () {
let currentVersion = chrome.runtime.getManifest().version;
window.postMessage({
sender: "my-extension",
message_name: "version",
message: currentVersion
}, "*");
})();
Your message could say anything, but it's useful to send the version so you know what you're dealing with. Then...
3. On your website, listen for that message.
Add this to your website somewhere:
window.addEventListener("message", function (event) {
if (event.source == window &&
event.data.sender &&
event.data.sender === "my-extension" &&
event.data.message_name &&
event.data.message_name === "version") {
console.log("Got the message");
}
});
This works in Firefox and Chrome, and doesn't incur HTTP overhead or manipulate the page.
You could also use a cross-browser method what I have used.
Uses the concept of adding a div.
in your content script (whenever the script loads, it should do this)
if ((window.location.href).includes('*myurl/urlregex*')) {
$('html').addClass('ifextension');
}
in your website you assert something like,
if (!($('html').hasClass('ifextension')){}
And throw appropriate message.
If you have control over the Chrome extension, you can try what I did:
// Inside Chrome extension
var div = document.createElement('div');
div.setAttribute('id', 'myapp-extension-installed-div');
document.getElementsByTagName('body')[0].appendChild(div);
And then:
// On web page that needs to detect extension
if ($('#myapp-extension-installed-div').length) {
}
It feels a little hacky, but I couldn't get the other methods to work, and I worry about Chrome changing its API here. It's doubtful this method will stop working any time soon.
If you're trying to detect any extension from any website,
This post helped: https://ide.hey.network/post/5c3b6c7aa7af38479accc0c7
Basically, the solution would be to simply try to get a specific file (manifest.json or an image) from the extension by specifying its path. Here's what I used. Definitely working:
const imgExists = function(_f, _cb) {
const __i = new Image();
__i.onload = function() {
if (typeof _cb === 'function') {
_cb(true);
}
}
__i.onerror = function() {
if (typeof _cb === 'function') {
_cb(false);
}
}
__i.src = _f;
__i = null;
});
try {
imgExists("chrome-extension://${CHROME_XT_ID}/xt_content/assets/logo.png", function(_test) {
console.log(_test ? 'chrome extension installed !' : 'chrome extension not installed..');
ifrm.xt_chrome = _test;
// use that information
});
} catch (e) {
console.log('ERROR', e)
}
Here is how you can detect a specific Extension installed and show a warning message.
First you need to open the manifest file of the extension by going to chrome-extension://extension_id_here_hkdppipefbchgpohn/manifest.json and look for any file name within "web_accessible_resources" section.
<div class="chromewarning" style="display:none">
<script type="text/javascript">
$.get("chrome-extension://extension_id_here_hkdppipefbchgpohn/filename_found_in_ web_accessible_resources.png").done(function () {
$(".chromewarning").show();
}).fail(function () {
// alert("failed.");
});
</script>
<p>We have detected a browser extension that conflicts with learning modules in this course.</p>
</div>
Chrome Extension Manifest v3:
const isFirefox = chrome.runtime.OnInstalledReason.CHROME_UPDATE != "chrome_update";
For FireFox, I believe chrome.runtime.OnInstalledReason.BROWSER_UPDATE will be "browser_update": https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/OnInstalledReason

Detect if another Chrome Extension is installed

I'm developing an extension for Chrome, but I want my extension to work only if another is disabled/removed.
It means that when my users install my extension, I would like to tell them "if you install my extension, you have to accept to disable this other extension".
I don't want my extension to work if the other one is active.
Do you know how I could proceed?
To detect if another extension is installed:
1) Update the manifest.json to include the necessary management permission:
{
"name": "My extension",
...
"permissions": [
"management"
],
...
}
2) To check if the extension is installed exist 2 options:
a) If you know the extension id, use the chrome.management.get(extensionId, function callback) method:
var extensionId = '<the_extension_id>';
chrome.management.get(extensionId, function(extensionInfo) {
var isInstalled;
if (chrome.runtime.lastError) {
//When the extension does not exist, an error is generated
isInstalled = false;
} else {
//The extension is installed. Use "extensionInfo" to get more details
isInstalled = true;
}
});
b) If you know the extension name (or any other parameter), you can use chrome.management.getAll(function callback):
var extensionName = '<the_extension_name>';
chrome.management.getAll(function(extensions) {
var isInstalled = extensions.some(function(extensionInfo) {
return extensionInfo.name === extensionName;
});
});

How can I pass data between two Chrome apps?

I have created two Chrome apps and I want to pass some data (string format) from one Chrome app to another Chrome app. Appreciate if someone can help me with showing the correct way of doing this?
It's an RTFM question.
From Messaging documentation (note that it mentions extensions, but it works for apps):
In addition to sending messages between different components in your extension, you can use the messaging API to communicate with other extensions. This lets you expose a public API that other extensions can take advantage of.
You need to send messages using chrome.runtime.sendMessage (using app ID) and receive them using chrome.runtime.onMessageExternal event. If required, long-lived connections can also be established.
// App 1
var app2id = "abcdefghijklmnoabcdefhijklmnoab2";
chrome.runtime.onMessageExternal.addListener(
// This should fire even if the app is not running, as long as it is
// included in the event page (background script)
function(request, sender, sendResponse) {
if(sender.id == app2id && request.data) {
// Use data passed
// Pass an answer with sendResponse() if needed
}
}
);
// App 2
var app1id = "abcdefghijklmnoabcdefhijklmnoab1";
chrome.runtime.sendMessage(app1id, {data: /* some data */},
function(response) {
if(response) {
// Installed and responded
} else {
// Could not connect; not installed
// Maybe inspect chrome.runtime.lastError
}
}
);

Detect Chrome extension first run / update

How can an extension find out that it is being run for the first time or has just been updated, so that the extension can perform some specific actions? (e.g. open a help page or update settings)
In newer versions of Chrome (since Chrome 22), you can use the chrome.runtime.onInstalled event, which is much cleaner.
Example:
// Check whether new version is installed
chrome.runtime.onInstalled.addListener(function(details){
if(details.reason == "install"){
console.log("This is a first install!");
}else if(details.reason == "update"){
var thisVersion = chrome.runtime.getManifest().version;
console.log("Updated from " + details.previousVersion + " to " + thisVersion + "!");
}
});
Updated answer to reflect v3 of manifest:
Chromium now has a chrome.runtime set of APIs, which allow you to fetch the version of the extension.
To get the current version:
chrome.runtime.getManifest().version
To listen when the extension has been first installed, when the extension is updated to a new version, and when Chromium is updated to a new version, you can use the onInstalled event.
chrome.runtime.onInstalled.addListener((details) => {
const currentVersion = chrome.runtime.getManifest().version
const previousVersion = details.previousVersion
const reason = details.reason
console.log(`Previous Version: ${previousVersion }`)
console.log(`Current Version: ${currentVersion }`)
switch (reason) {
case 'install':
console.log('New User installed the extension.')
break;
case 'update':
console.log('User has updated their extension.')
break;
case 'chrome_update':
case 'shared_module_update':
default:
console.log('Other install events within the browser')
break;
}
})
Thats all!
Old answer, prior to 2011
If you want to check if the extension has been installed or updated, you can do something like this:
function onInstall() {
console.log("Extension Installed");
}
function onUpdate() {
console.log("Extension Updated");
}
function getVersion() {
var details = chrome.app.getDetails();
return details.version;
}
// Check if the version has changed.
var currVersion = getVersion();
var prevVersion = localStorage['version']
if (currVersion != prevVersion) {
// Check if we just installed this extension.
if (typeof prevVersion == 'undefined') {
onInstall();
} else {
onUpdate();
}
localStorage['version'] = currVersion;
}
Fortunately, there are now events for this (since Chrome version 22, and 25 for update events).
For an installed event:
chrome.runtime.onInstalled.addListener(function() {...});
For an OnUpdateAvailable event:
chrome.runtime.onUpdateAvailable.addListener(function() {...});
An important excerpt about OnUpdateAvailable from the developer docs says:
Fired when an update is available, but isn't installed immediately because the app is currently running. If you do nothing, the update will be installed the next time the background page gets unloaded, if you want it to be installed sooner you can explicitly call chrome.runtime.reload().
Simple. When the extension first runs, the localStorage is empty. On first run, you can write a flag there to mark all consequent runs as non-first.
Example, in background.htm:
var first_run = false;
if (!localStorage['ran_before']) {
first_run = true;
localStorage['ran_before'] = '1';
}
if (first_run) alert('This is the first run!');
EDIT: To check whether the extension has just been updated, store the version instead of a simple flag on first run, then when the current extension version (get it by XmlHttpRequesting the manifest) doesn't equal the one stored in localStorage, the extension has been updated.