Roy
Roy

Reputation: 23

How to replace/upgrade a Chrome extension with another?

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?

Upvotes: 2

Views: 458

Answers (1)

Xan
Xan

Reputation: 77541

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".

  1. 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.

  2. 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.. */
        }
      }
    );
    
  3. 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
    });
    
  4. 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.

  5. 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});
    }
    
  6. 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);
          }
        }
      }
    );
    
  7. 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).

Upvotes: 1

Related Questions