How to know if I have to wait for onInstalled

I use browser.runtime.onIstalled.addListener for doing some presettings before starting the extension.
But in case the user starts Firefox with the extension already been installed I want to skip the presettings and start the extension right away.
How can i distinguish if I have to wait for the onInstalled event or not?

Here the simplified code in the background script:

{
  init() {
    // invoke this.start if we don't have to wait for `onInstalled`
    // (i.e. on browser start)

    browser.runtime.onInstalled.addListener(details => {
      this.preset(details).then(() => this.start());
    });
  },
  start() { /* start app */ },
  preset() { /* does some presettings on install and returns promise */ }
}

If you need to run a preset first, I’d recommend storing if you’ve ran the preset instead and just invoke it whenever the add-on is loaded and that flag is not set.

1 Like

A good idea, but I would like to run the preset on install as well as on update and if I set the flag to true on install/update of this version it will still be true on the next extension update. In this case I would need to inititially reset it to false before listening to onInstalled and before having started the app. This seems impossible - because for initially (=only the first time, so on update/install) resetting it to false I would already have to have the information if this is just a browser start or an install/update - but this information is not available. there the cat bites in the tail.

The way to solve this is to version your storage schema, so you can rebuild it if the expected schema version changes.

So you mean I just add a version field to the storage and in init I check if this version string is equal to the the current/expected version string and if not I do the preset and update this version string (to avoid another preset on the next browser start)?

Yes. If there are things that are not coupled to the storage schema that your preset is doing I’d recommend separating them and calling those from onInstalled as you initially intended. This way you upgrade your storage schema and run other update stuff whenever you have to. The other update stuff tends to be rather little, since a lot will be bound to storage.

Another way of dealing with it is to use default values for storage and only upgrade between storage versions, which you can do from onInstalled, depending on how your extension logic works (as in, how dependent it is on the state while loading).

1 Like

This seems the safest hack for dealing with the asynchrony.
The latter I can’t do, since I need to migrate the storage and do other presetting stuff before i can savely start the app. This is mainly due to the migration to WebExtensions I’m proceeding with this version.
Thanks a lot for your help!

Here the init code in case someone has a similar situation:

init() {
  const version = browser.runtime.getManifest().version;

  browser.runtime.onInstalled.addListener(details => {
    this.preset(details.reason).then(() => this.start());
  });
  browser.storage.sync.get().then(storage => {
    if (storage.version && storage.version === version) {
      this.start();
    } else {
      // do not register the onInstalled listener here!
      browser.storage.sync.set({ version: version });
    }
  });
}
  1. If ‘update’ or ‘install’:
  • on onInstalled preset and then start will be invoked
  • storage.version will not be equal to the expected value => start won’t be invoked again
  1. Else:
  • start won’t be invoked on onInstalled, since this event is not being fired
  • start will be invoked due to storage.version being equal to the expected value

Be aware!

  • Do not register the onInstalled listener inside the else block of the then callback -> onInstalled is getting fired BEFORE you are in this callback.