Plugins and the plugin catalog
Modalities and annotation types are plugins: the built-in ones (ygg.*) ship with the app, and an installation can add others from its plugin catalog. A superadmin adds a plugin package in the console’s Plugins section and enables it; every member’s app then loads it when it starts, and the plugin’s annotation types appear in the setup wizard beside the built-in ones.
- Writing a task plugin: an annotation type, from the template plugin.
- Writing a modality plugin: a kind of data, with its formats, ingest and viewer.
What a plugin can do, and what it is trusted with
Section titled “What a plugin can do, and what it is trusted with”A plugin is code that runs in every member’s browser with the app’s privileges. It sees what the app decrypts on that page (the images and answers a member opens), as the built-in plugins do; it never sees private keys, which stay in the app’s crypto worker. Enabling a plugin is therefore trusting its code like the app’s own: enable only code you reviewed, from people you trust.
What the platform guarantees:
- The bytes are the ones you enabled. A package names its module’s Subresource Integrity hash (SHA-384). The catalog refuses a module that does not match, and browsers refuse to run one that does not. The hash does not protect against the server itself, which serves the whole app.
- No code from text. The app’s Content Security Policy allows scripts from its own origin only, and never
eval. The catalog serves each module from the app’s origin, so plugins run under the same policy; a plugin that compiles code at runtime, or loads scripts from elsewhere, does not work. - Namespaces. A package’s ID is outside the reserved
yggnamespace, and everything it provides lies under that ID (org.example.ratingprovidesorg.example.rating@1ororg.example.rating.stars@1), so two packages never provide the same plugin. - Exactly what it declares. The app loads a plugin only when its module registers exactly the plugins its manifest lists, and each passes the plugin contract’s checks. A plugin that fails to load is left out, and the console says why; the app starts without it.
The server stores the catalog as plaintext instance metadata (programs, not patients) and records every addition, enabling, disabling and removal on the instance’s audit chain.
The plugin package
Section titled “The plugin package”A package is two files:
| File | What it is |
|---|---|
yggdrasil-plugin.json |
The package manifest (schema plugin/package-v1): ID, version, name, description, author, licence, homepage, the plugin API version it is built against, the module’s file name and integrity, and the refs it provides. |
plugin.js |
One ES module (at most 4 MiB) whose default export is a plugin factory. It imports nothing at runtime: React, React Intl, the design system and the plugin API’s runtime come from the app. |
{ "id": "org.example.rating", "version": "1.0.0", "name": "Star rating", "apiVersion": "1.6.0", "module": "plugin.js", "integrity": "sha384-…", "provides": { "annotationTypes": ["org.example.rating@1"] }}The catalog holds one entry per package ID and version. Enabling a version disables the package’s other versions.
The plugin API and its versions
Section titled “The plugin API and its versions”The plugin API is the package @yggdrasil/plugin-api (its README), versioned on its own with semantic versioning: PLUGIN_API_VERSION, now 1.6.0. A plugin names the version it was built against in apiVersion; an app loads it when its own plugin API has the same major and at least that minor. A minor release only adds to the contract; a major release breaks it, and plugins are rebuilt against it.
Adding a plugin to an installation
Section titled “Adding a plugin to an installation”- In the console, open Plugins and choose Add a plugin package. Pick both files of the package.
- Check the details: the ID, the version, what it provides and its integrity. Add, disabled.
- Enable it. Members’ apps load it from their next start; reload the app to load it in yours. The entry then says Loaded in this browser, or why it was not loaded.
Disabling a plugin stops loading it. Annotations its types wrote stay, encrypted like any other, but no app can show or edit them until the plugin is enabled again. Only a disabled plugin can be removed.
Limits
Section titled “Limits”- A plugin’s components use the design system’s components and the app’s styles: the app ships no styles for class names it does not use itself.
- Strings: plugins supply their own English text through React Intl message descriptors; the app’s translations do not cover them.
- The server cannot check a plugin’s annotations (it never sees them decrypted). Exports carry them like any answer, with the plugin’s ref.
- A modality plugin’s ingest worker runs without an integrity check, like the app’s own workers: browsers take none for workers.