Share state between extensions

Hello, I want to have different extensions and I want to have a main class for preserving state. I want this class to be shared between all extensions, how can I do that?

Hi, I think you can communicate between extensions using application plugins, see here for the documentation. Your main extension publishes a plugin, say IMyService, and then the other extensions can consume it like this. They get an instance of your main class when starting up:

const plugin: JupyterFrontEndPlugin<void> = {
  id: 'demoextension:plugin',
  autoStart: true,
  requires: [ICommandPalette, IMyService],

  activate: (app: JupyterFrontEnd, palette: ICommandPalette, myservice: IMyService) => {
    console.log('JupyterLab extension demoextension is activated!');
    // do something with myservice...
  }
}

In order to publish the plugin in your main extension, your plugin object needs to contain a field provides, and you need to return your instance from the activate function. There are a couple more steps involved (creating the IMyService token, editing some JSON files). It is probably best if you start from one of the templates, like extension-cookiecutter-ts or see how it is done in other extensions.

Now this only handles communication between extensions, not actually perserving state. I haven’t figured that out completely. For saving the panel layout, there is ILayoutRestorer. For actual data, you would probably use an extension that has a Python part and store that on the backend, I think.

2 Likes

Cool, thank you so much!

aside: big +1 to the IManager pattern, and actually using the IMyService interface everywhere instead of your concrete MyService. This way of thinking can really help clean up your logic, and make refactoring easier down the road.

As for state: indeed, it’s all a question of how durable you need it to be.

Going straight for the serverextension increases your power many fold, but does introduce some more opportunities to mess things up. If you do introduce a heavyweight service, I highly recommend building an at-rest JSON schema from which you can generate types/classes on the front and backend with e.g. quicktype, which helps a lot when keeping things in sync. Alternately, you can generate a JSON schema from your typescript types, and then use that on the backend.

If all you need is state in the browser, having it well-typed inside the typescript world will almost always work better. But these won’t persist between private browsing sessions, etc.

Under the hood LayoutRestorer uses IStateDB, which you can use directly… this uses the localStorage API, though you can go ahead and use that directly, if you like. There are some pointy edges on the API, and different behavior in browsers and modes like incognito.

On JupyterLite, we delegate many of these challenges to localForage, which is a pretty nice option, as it will use the “best” approach available in the current browser (usually IndexedDB).

Another, more persistent form (e.g. between two different browsers): depending on how volatile the state is (e.g. changes once a minute, not x times per second), you can also use the ISettingsRegistry. But this is inappropriate for very large data, more like, “this thing is enabled”. This has the side benefit of being easy to deploy/test in a specific setup with e.g. overrides.json.

4 Likes