Skip to main content

Add-ons

The add-on system is the basic component for dynamic contents for the Mozilla VPN client. Guides and messages are add-ons and they are dynamically loaded from a trusted source (archive.mozilla.org).

Add-on index and the signature verification

The VPN client downloads the add-on index file and its signature from a trusted source. These 2 files are stored on disk for a quick loading of the add-ons at startup.

The signature of the add-on index file is verified at any loading. We use RSA 4096 + SHA256 as sign algorithm.

The add-on index file is a JSON array containing add-on IDs and hash (SHA256) of the add-on content. The app downloads the add-ons listed in the index file if they are not on disk already or if the hash does not match.

Add-on format

Add-ons are RCC files containing at least a manifest.json file. The properties of this JSON file are:

PropertyDescriptionTypeRequired
idThe ID of the add-on. It must match the file nameStringYes
nameThe name of the add-onStringYes
api_versionThe version of the add-on frameworkStringYes
typeOne of the supported types (message, guide, ...)StringYes
conditionsList of conditions to meetArray of Condition objectsNo
stateObject describing the state of the addonCollection of state objectsNo

Based on the add-on type, extra properties can be in added. See the guide, message, and replacer documentation.

State Object

An addon may have three different types of state: session, local or global.

PropertyDescriptionTypeRequired
sessionAn object describing the addons session stateStateNo
localAn object describing the addons local stateStateNo
globalAn object describing the addons global stateStateNo

The State interface is the same, regardless of the type of state. There is no limit of properties for a state, however the only supported state types are string, number and boolean. The state object may not be nested.

Following is the state interface, described using Typescript notation.

interface State {
[key: string]: {
type:string|number|boolean,
default: string | number | boolean,
}
}

Session state

Session state is not persisted throughout user sessions i.e. it is wiped once the application is killed.

Local state

Note: Local state is not supported yet, if you need to implement an addon that requires it please refer to VPN-3929.

Local state is persisted throughout user sessions i.e. not wiped after the application is killed. It is not synced among the devices of a given user. It is local to each device.

Global state

Note: Global state is not supported yet, if you need to implement an addon that requires it please refer to VPN-2795.

Global state is persisted throughout user sessions and synced among user devices.

Condition object

Add-ons can enable and disable themselves using the conditions key in the manifest. The condition object contains the following properties:

PropertyDescriptionTypeRequiredDynamic
enabled_featuresAn array of features to be enabledArray of stringNoNo
envA string to match a particular env: staging, productionStringNoNo
localesAn array of locales to be checkedArray of stringNoYes
min_client_versionThe min client versionStringNoNo
max_client_versionThe max client versionStringNoNo
platformsAn array of platforms to be checkedArray of stringNoNo
settingsAn array of Condition Setting object. See belowArray of Condition Setting objectNoNo
trigger_timeA number identifying the number of seconds from the first execution of the clientIntegerNoYes
start_timeThe epoch time that activates the current add-onIntegerNoYes
end_timeThe epoch time that deactivates the current add-onIntegerNoYes
javascriptA script file is executed to change the condition status. See belowStringNoYes
translation_thresholdThe translation threshold to use. By default 1 (full translation required)NumberNoNo

Some conditions are dynamic. This means that the value can change their status dynamically during the app execution.

The add-on is considered enabled if all the conditions are met.

Condition Setting object

When a setting must be checked as a condition, the JSON object must contain the following properties:

PropertyDescriptionTypeRequired
settingThe name of the setting keyStringYes
valueThe value of the setting keyStringYes
opThe compare operator: eq or neqStringYes

The list of setting keys can be found here: https://github.com/mozilla-mobile/mozilla-vpn-client/blob/main/src/settingslist.h

Javascript conditions

When the add-on manifest contains a javascript property in the conditions object, its value must be a javascript filename.

The javascript file is executed when the add-on is loaded and it has to expose a function. For instance:

(function(api, condition) {
// your code goes here.
})

The function will be executed at the first add-on loading with 2 parameters.

The condition object exposes 2 methods:

  • condition.enable() - to be called to enable the add-on
  • condition.disable() - to be called to disable the add-on

An example of javascript conditional object is the following:

(function(vpn, condition) {
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://mozilla.org');
xhr.send();
xhr.onreadystatechange = () => {
if (xhr.readystate === 4) condition.enable()
}
})

How to implement and test add-ons

If you want to implement new add-ons, you need to follow these steps:

  1. Create a manifest in a separate folder in the addons directory of the mozilla VPN repository.
  2. Read and follow the documentation for the add-on type you want to implement.
  3. Build the CMake project in the addons directory.
mkdir -p build-addons/
cmake -S <source>/addons -B build-addons -GNinja
cmake --build build-addons
  1. Expose the generated build directory through a webservice. For example: python3 -m http.server --directory build-addons/
  2. Open the dev-menu from the get-help view and set a custom add-on URL: http://localhost:8000/
  3. Scroll down and disable the signature-addon feature from the dev-menu, list of features
  4. Be sure you are doing all of this using a staging environment

If all has done correctly, you can see the app fetching the manifest.json (and not! the manifest.json.sig) resource from the webservice.