Skip to main content
This is an expert-level functionality

Misuse of Runners can cause issues, including device misconfiguration, device disconnection, or other unforeseeable situations.

Use this feature wisely and always test your runners in lab before running them on production devices.

Introduction

A Runner is designed for continuous execution. Once activated, it starts and runs indefinitely as a background process on the device. To ensure stability, signageOS actively monitors the health of each Runner and reports its status back to Box. If a Runner fails, the system can be configured to automatically restart it.

Prerequisites

  • signageOS CLI - version 2.6.0-rc.0 and newer
  • Minimal Core App version per platform that supports Runners:
Core AppMinimal Version
Tizen2.10.2-runners-plugins.5520
webOS2.10.0-runners-plugins.4373
BrightSigncoming soon
Linuxcoming soon
Windowscoming soon
Androidcoming soon
ChromeOScoming soon
Emulator14.21.3-plugins-runners.9762

Runners allow users to send its implementation in operating-system-specific programming languages. The runner can use device-specific native APIs.

Operating systemRunner type
TizenHTML + JS + Tizen APIs
webOSHTML + JS + webOS APIs

Create a new project

Generate boilerplate project with signageOS CLI:

sos runner generate

Alternatively, copy the boilerplate code from signageOS/runners-boilerplate.

You will have a project with the following structure:

.
├── README.md
├── schema.json
├── .sosconfig.json
├── .gitignore
├── default
│ └── setBrightness.js
├── tizen
│ └── setBrightness.js

Every runner must implement three methods: run, set, and get.

var sosRunner = {
/**
* Required runner daemon function
*/
run: function () {
setInterval(function () {
console.log('Running SOS Runner...');
sos.management.network.listInterfaces().then(function(interfaces){addTelemetry(interfaces)})
// Your code
}, 60e3);
},

/**
* Required for settings, policy and bulk.
*/
set: function (data) {
return new Promise(function (resolve, reject) {
// Your code
});
},
/**
* Required for telemetry.
*/
get: function () {
return new Promise(function (resolve, reject) {
// Your code
});
}
};

The structure of the runner is similar to the plugin, with the main difference being the continuous run method.

The run method

The run method is called once when the runner starts and is expected to run indefinitely. It is the main entry point for the runner's background logic. Use setInterval or similar mechanisms for periodic work.

var sosRunner = {
run: function () {
setInterval(function () {
console.log('Running SOS Runner...');
sos.management.network.listInterfaces().then(function (interfaces) {
addTelemetry({ network: interfaces[0] });
});
}, 60e3);
}
};

The set method

The set method receives a data object containing key-value pairs that match the fields defined in the input section of schema.json. The platform validates the data against the input schema before delivering it to the device.

The method must return a Promise. Call resolve() when the configuration is applied successfully, or reject(error) if something goes wrong.

var sosRunner = {
set: function (data) {
return new Promise(function (resolve, reject) {
try {
window.parent.document.body.style.backgroundColor = data.backgroundColor;
window.fetch(data.myBaseUrl)
.then(function (response) {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(function (responseData) {
console.log(responseData);
})
.catch(function (error) {
console.error('Fetch error:', error);
});
resolve();
} catch (error) {
reject(error);
}
});
}
};

In this example, data.myBaseUrl corresponds to the myBaseUrl field defined in the input array of schema.json. The platform ensures only fields defined in the input schema are sent to the runner, and validates their types (string, number, url, enum, encrypted) and constraints (mandatory, maxLength, min, max) before delivery.

If the input schema contains encrypted fields, the platform re-encrypts the values for secure device execution before dispatching them.

The get method

The get method retrieves the current state of the runner on the device. It takes no parameters and must return a Promise that resolves with an object containing the current values (matching the output schema).

var sosRunner = {
get: function () {
return new Promise(function (resolve, reject) {
try {
var viewportWidth = window.innerWidth;
var viewportHeight = window.innerHeight;
resolve({
resolution: viewportWidth + ' x ' + viewportHeight,
});
} catch (error) {
reject(error);
}
});
}
};

The returned object should contain keys matching the fields defined in the output section of schema.json. This data is used for telemetry reporting — the platform periodically calls get to collect the runner's current state.

Data flow

  1. A set or get request is made through the platform (via settings, policy, or bulk operations in Box).
  2. For set, the platform validates the data against the runner version's input schema.
  3. If the schema contains encrypted fields, values are re-encrypted for secure device execution.
  4. A command is dispatched to the device.
  5. The device executes the runner's set or get method.
  6. For get, the result is reported back as telemetry data matching the output schema.

Global object

The runner must be exposed as a global window.sosRunner object:

if (typeof window !== 'undefined') {
window.sosRunner = sosRunner;
}

How it works

Schema file

Contains the schema definition for the runner. Input is for set parameters and output is for get return values. Telemetry is custom telemetry from runner which is used through addTelemetry(your telemetry data)

{
"input": [
{
"name": "myBaseUrl",
"valueType": "url",
"description": "A base URL to My cloud. No slash at the end required.",
"placeholder": "https://api.signageos.io"
},
{
"name": "refreshIntervalMs",
"valueType": "number",
"description": "Frequency of trying to generate new playlist from My cloud in milliseconds.",
"placeholder": "65000",
"min": 60000,
"max": 70000
}
],
"output": [
{
"name": "resolution",
"description": "Resolution output",
"valueType": "string"
}
],
"telemetry": [
{
"name": "saturation",
"description": "Saturation telemetry",
"valueType": "number"
},
{
"name": "brightness",
"description": "Brightness telemetry",
"valueType": "number"
}
]
}

addTelemetry function

The addTelemetry function is a global function injected into the runner's execution context by signageOS. It allows runners to send custom telemetry data back to the platform, which can then be viewed in Box.

Function signature

addTelemetry(keyOrObject, value?)

The function accepts two calling patterns:

Key-value mode — pass a string key and a value:

addTelemetry('brightness', 75);
addTelemetry('status', 'connected');

Object mode — pass an object with multiple telemetry entries at once:

addTelemetry({ brightness: 75, saturation: 50 });
addTelemetry({ network: interfaces[0] });

Both patterns send the telemetry data to the parent signageOS application, where it is associated with the current runner assignment.

How telemetry data is sent

Calling addTelemetry does not immediately send data to the server. Instead, it caches the latest value locally. The platform periodically collects the cached telemetry and sends it to the server in a batch. If you call addTelemetry multiple times before the next collection interval, only the most recent value for each key is sent.

addTelemetry('brightness', 50);
addTelemetry('brightness', 75); // overwrites the previous value
// Only { brightness: 75 } will be reported in the next telemetry collection

Defining telemetry in the schema

When using addTelemetry, you should define the expected telemetry fields in the telemetry array of your schema.json file. Each telemetry field has the following properties:

KeyValue typeDescription
namestringName of the telemetry field
descriptionstringHuman-readable description
valueTypestringExpected value type (e.g., number, string)

Usage examples

Periodic telemetry reporting:

var sosRunner = {
run: function () {
var counter = 0;
setInterval(function () {
addTelemetry('counter', counter++);
}, 60e3);
},
set: function (data) { return Promise.resolve(); },
get: function () { return Promise.resolve(); }
};

Reporting device network information:

var sosRunner = {
run: function () {
setInterval(function () {
sos.management.network.listInterfaces().then(function (interfaces) {
addTelemetry({ network: interfaces[0] });
});
}, 60e3);
},
set: function (data) { return Promise.resolve(); },
get: function () { return Promise.resolve(); }
};

Reporting multiple telemetry values:

addTelemetry({
profile: currentProfile ? currentProfile.name : null,
networkStatus: JSON.stringify(networkStatus)
});
tip

The addTelemetry function is available only inside the runner's execution context. If you need to check for its availability (e.g., in shared code), guard the call:

if (typeof addTelemetry === 'function') {
addTelemetry('myKey', myValue);
}

Config file

When calling sos runner upload, it reads the .sosconfig.json file. This file contains several fields:

  • name - Runner will be created and displayed using this name
  • description - Short description of the Runner that will be displayed in Runner detail. It should help the user to understand what the Runner does.
  • version - Used for version control. Must follow the semantic versioning format. Each time the Runner Version changes, it should be incremented, however it's possible to overwrite the same version as long as its not published.
  • platforms - List of platforms and their files that will be uploaded to the signageOS platform.
  • configDefinition - list of accepted configuration parameters. It's a mandatory item, keep it empty if you do not need any variables ("configDefinition": [])

Platforms

The general structure of the platforms field is as follows:

{
"{platform}": {
"rootDir": "{rootDir}",
"mainFile": "{mainFile}",
"runtime": "{runtime}"
}
}
  • {platform} - name of the platform. It should be one of:
    • default, tizen, webos
  • {rootDir} - relative path to the platform implementation. This is where the platform-specific files are located in this repository.
  • {mainFile} - entry point of the platform implementation. This file will be executed
  • {runtime} - runtime of the platform. It should be one of:
    • browser

Platforms and supported runtimes matrix:

PlatformAvailable Runtimes
Samsung Tizenbrowser
LG webOSbrowser
Windowscoming soon
Linuxcoming soon
Androidcoming soon
BrightSigncoming soon
ChromeOScoming soon
Emulator (default)browser

Config Definition

configDefinition is a list of accepted configuration parameters. It's a mandatory item, keep it empty if you do not need any variables ("configDefinition": []).

Config Definition item has the following options (same as Applet configuration):

KeyValue typeDescription
namestringname of the configuration
valueTypestring | url | enum | number | secret | encryptedexpected value type
listarray of strings | numbersonly for valueType enum, list of predefined options
mandatorybooleanrequired to be filled before executing runner
descriptionstringdescription shown in the UI to guide user
placeholderstringfield placeholder in the UI to guide user; placeholder is never used as default value
minnumberminimum number value user can fill in to the field
maxnumbermaximum number value user can fill in to the field

secret and encrypted type in configuration

Runner configuration is often used to pass sensitive data, such as tokens, credentials, and passwords. To protect these values, use the Runner configuration with one of these value types.

  • secret value type

    Use this type if you want to mask the value in the Box UI. It is suitable for less sensitive data. The value is stored in the database in its raw form and may be visible, for example, in device history.

  • encrypted value type:

    This type provides stronger protection. The value is encrypted using asymmetric encryption with a provided public key, which is generated for your company on demand. It is stored in encrypted form in the database and cannot be read anywhere.

Full example

.sosconfig.json with defined configDefinition
{
// ... .sosconfig.json
"configDefinition": [
{
"name": "wifiSSID",
"valueType": "secret",
"mandatory": true,
"description": "An SSID for the WiFi network."
},
{
"name": "myBaseUrl",
"valueType": "URL",
"description": "A base URL to My cloud. No slash at the end required.",
"placeholder": "https://api.signageos.io"
},
{
"name": "playerDuration",
"valueType": "string",
"description": "A length of generated playlist in '##s' format (seconds).",
"placeholder": "172800s"
},
{
"name": "refreshIntervalMs",
"valueType": "number",
"description": "Frequency of trying to generate new playlist from My cloud in milliseconds.",
"placeholder": "65000",
"min": 60000,
"max": 70000
},
{
"name": "playerId",
"valueType": "string",
"description": "An identifier for the device used to identifying against cloud. E.g. platform: SSSP, WEBOS, BRIGHTSIGN, ANDROID, WINDOWS, LINUX",
"placeholder": "{PLATFORM}_{SERIAL_NUMBER}"
},
{
"name": "proxyUrl",
"valueType": "URL",
"description": "A prefix for all HTTP(s) requests done by My player to My cloud. Default is no proxy.",
"placeholder": "https://cors-anywhere.herokuapp.com/"
}
]
}

Or leave empty when not using configDefinition.

.sosconfig.json when not using configDefinition
{
// ... .sosconfig.json
"configDefinition": []
}

Upload runner

Run the following command to upload your runner to signageOS

sos runner upload

When calling sos runner upload, it reads the .sosconfig.json file. After the upload is completed, you will get runnerUid in the uid field in .sosconfig.json file.

Debugging

Browser runtime runner

The best way to debug runner runtime is to enable Native debug on your target device and inspect the browser console for logs and errors.