docs
  1. SCAYLE Resource Center
  2. Add-on Guide
  3. Getting Started
  4. Write your first Add-on

Write your first Add-on

Any new Add-on application will involve setup and configuration similar to the demo add on. Each Add-on app requires a manifest file where application data and routes are set.

Specific app functionality can be defined using the Admin API (for access to the backend services, such as shop/product data) and the Add-on API (for access to the SCAYLE Panel session data, such as user information).

SPA Overview

Add-on Apps are:

  • Written as JavaScript applications
  • Mounted through single-spa
  • Packaged in the systemjs format

Applications should be wrapped as a single-spa application. In other words, it is an object containing three methods:

  1. bootstrap
  2. mount
  3. unmount

bootstrap is called the first time the application is mounted while mount and unmount contain the logic for mounting and un-mounting the application.

How Add-on Apps Are Loaded

On the initial page load, the SCAYLE Panel will request the manifest file of each active add-on. Inside the manifest, the Add-on app will be registered with single-spa.

Single-spa then automatically loads or unloads applications on URL changes. When the URL matches /add-ons/{addOnName}/* or /shops/:id/{addOnName}/* the Add-on with that name is loaded.

Each Add-on should have a unique URL. In the case that the app URL of multiple add ons is the same, opening one of the add ons results in loading all of them. This can cause undesirable behaviour such as the sidebar loading multiple times.

Manifest file

Each Add-on app must have a manifest file. This is the file that the SCAYLE Panel fetches in order to load the application. The manifest should export a single function manifestRegistration.

export default manifestRegistration;

The manifest should be formatted in the System.register module format. With webpack, this can be configured by setting output.library.type to be system (or set rollup's format to "system").

When using Webpack

  • Make sure you are using Webpack 5 or greater. Since Webpack 4 does not support outputting in the System.js format, it will not work properly.
  • It’s also important to configure the webpack public path so that additional chunks will be loaded from your Add-on server instead of the cloud-panel.

This library provides an easy way to set the public path on the fly. Simply install the package and add this line to the top of your manifest file.

import "systemjs-webpack-interop/auto-public-path";

Here’s an example of an app manifest (from the demo add-on):

// This config function does nothing but return its input
// But it does provide some helpful type hints
import { config, RouteDefinition } from '@scayle/add-on-utils';
import { AddOnRoute, routes } from './router';
import { GroupRouteDefinition } from './types';
import { ADD_ON_ID, generateGroupName } from './utils';

const applyDefaultRouteProps = (routes: RouteDefinition[]) => routes.map(route => ({...route, sidebar: route.sidebar === null ? null : ADD_ON_ID}));

const mappedRoutes = routes.map(originalRoute => {
    const route = JSON.parse(JSON.stringify(originalRoute)) as AddOnRoute;
    const children = route.children;
    const meta = route.meta as RouteDefinition;

    if(children && children.length && !meta.children) {
        meta.children = children.map(childRoute => {
            const pathArray = childRoute.path.split("/");
            const pathForManifest = pathArray[pathArray.length - 1];

            // if the path for the manifest also contains the path of the parent,
            // the active item highlighting in the sidebar does not work that's what the lines above are for

            return "/" + pathForManifest;
        });
    }

    return meta;
});

const generalRoutes: GroupRouteDefinition[] = [
    {
        id: 'general-group',
        name: {
            'en': 'General',
            'de': 'Allgemeines'
        },
        group: generateGroupName('general'),
        isGroup: true,
    },
    ...mappedRoutes
];

const manifestRegistration = config(function (registerApplication, registerRoutes) {
    registerApplication({
        name: ADD_ON_ID,
        // Make sure to use a dynamic import to create a code-split point
        // and minimize the size of the manifest since it is loaded on every page
        app: () => import('./add-on'),
    });

    registerRoutes({
        [ADD_ON_ID]: [
            ...applyDefaultRouteProps(generalRoutes as RouteDefinition[]),
        ]
    })
});

// !!!!!DO NOT CHANGE LINE BELOW
// IT IS USED FOR HOT RELOADING ON CLOUD PANEL ADDON
// AND WILL BREAK IF THE LINE BELOW IS CHANGED
export default manifestRegistration;

Routes

For the routes, the routes.ts file is the single source of truth. Import and map routes in the manifest.ts file as shown in the example below. Properties required for the manifest routes should be defined in the route meta of routes.ts. To disable the SCAYLE Panel sidebar, set route.meta.sidebar to null.

To define the path of route children, use one of the following formats: :id OR /table-listing/:id.

Here’s an example router.ts file:

import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import { GroupRouteDefinition } from './types';
import { ADD_ON_ID, BASE_URL, generateGroupName } from './utils';

// Use dynamic imports to enable code-splitting
const DashboardPage = () => import('./pages/DashboardPage.vue');
const AlertsPage = () => import('./pages/AlertsPage.vue');
const FormPage = () => import('./pages/FormPage.vue');
const TableListing = () => import('./pages/TableListing.vue');
const ComponentsPage = () => import('./pages/ComponentsPage.vue');

export type AddOnRoute = Omit<RouteRecordRaw, "meta"> & { meta: GroupRouteDefinition };

export const routes: AddOnRoute[] = [
    {
        name: 'dashboard',
        path: '/',
        component: DashboardPage,
        meta: {
            id: 'dashboard',
            name: {
                'en': 'Dashboard',
                'de': 'Armaturenbrett'
            },
            icon: 'dashboard',
            path: BASE_URL + '/',
            sidebar: ADD_ON_ID,
            group: generateGroupName('general'),
        }
    },
    {
        path: '/table-listing',
        component: TableListing,
        children: [
            {
                name: "table-detail",
                path: ":id",
                component: TableListing
            }
        ],
        meta: {
            id: 'table-listing',
            name: {
                'en': 'Table Listing',
                'de': 'Tabellenauflistung'
            },
            icon: 'data-table',
            path: BASE_URL + '/table-listing',
            sidebar: ADD_ON_ID,
            group: generateGroupName('general'),
        }
    },
    {
        path: '/form',
        component: FormPage,
        meta: {
            id: 'form',
            name: {
                'en': 'Form',
                'de': 'Form'
            },
            icon: 'search',
            path: BASE_URL + '/form',
            group: generateGroupName('general'),
        }
    },
    {
        path: '/alerts',
        component: AlertsPage,
        meta: {
            id: 'alerts',
            name: {
                'en': 'Alerts',
                'de': 'Alerts'
            },
            icon: 'warning',
            path: BASE_URL + '/alerts',
            group: generateGroupName('general'),
        }
    },
    {
        path: '/components',
        component: ComponentsPage,
        meta: {
            id: 'components',
            name: {
                'en': 'Components',
                'de': 'Components'
            },
            icon: 'ufo',
            path: BASE_URL + '/components',
            group: generateGroupName('general'),
        }
    },
];

export default createRouter({
    routes: routes as RouteRecordRaw[],
    // Make sure to set the base because all Add-on pages will be under add-ons/demo
    history: createWebHistory(BASE_URL),
});

##Add-on User Flow

How logged-in user info flows between the SCAYLE Panel and add-ons.

Permissions

Each Add-on defines its own permissions. They are published to the SCAYLE Panel via the Add-on API. Permissions can then be assigned to roles, which are attached to users, using the SCAYLE Panel.

Permissions are defined for each Add-on and published to the SCAYLE Panel via the Add-on API.

Permission Naming Rules

A permission name follows a simple structure to be displayed correctly inside the role creation.

{{add-on-name:customer-app}}__{{headline:administration}}__{{permission:manage}}

For example, you may want to have your own section within the permissions that is called “Administration”, and within this is the permission “Manage administration”. The full permission name could look as follows: customer-app__administration__manage .

Read more about the translation of permissions inside the section Translations.

Authentication

Once the user is logged into the SCAYLE Panel, all registered add-ons will have access to the current user’s Authorization Token (available under window.Cloud.Config.auth.token). That Authorization Token can then be used by your add-on, to authorize the user against the SCAYLE Panel API in your backend application.

The Authorization header must start with the word Bearer (e.g., Authorization: Bearer 3|yUt2B8yE*****Cg4RRwWJwVTIn6RRyQ9).

This information allows the Add-on developer to initiate their own dedicated Session Token for calls to their own API. We recommend a dedicated route for this initial authentication process (e.g., /api/v1/external/add-ons/user).

Token-based authentication between the SCAYLE Panel and Add-on.

The response only returns the information that belongs to the add-on. For example, User Custom Data only returns the permissions and custom data that was added through the add-on. All other information is based on the SCAYLE Panel user.

Example response
{
   "id":1,
   "name":"John Doe",
   "email":"[email protected]",
   "locale":"de_DE",
   "timezone":"Europe/Berlin",
   "phoneNumber":"12345678",
   "companies":[
      1,
      2
   ],
   "allowedShopCountries":[
      100,
      200
   ],
   "allowedShops":[
      1,
      2
   ],
   "allowedMerchants":[
      1,
      2
   ],
   "permissions":[
      "create",
      "delete"
   ],
   "customData":{
      "alias":"Max",
      "color":"red"
   },
   "roles":[
      "developer",
      "administrator"
   ],
   "createdAt":"2023-01-01T00:00:00.000000Z",
   "updatedAt":"2023-01-02T05:00:00.000000Z"
}

Translations

The Add-on API translations endpoint allows developers to easily create and modify translations for add-ons. Translations can be applied to the Add-on permissions, title, and descriptions. You can not, for example, apply a buttons translation.

Translations are divided into two types:

  • System Translations
    • permission translations (e.g., add-on-dev-guide__dashboard__view). Those translations can be seen in the Settings/Roles section where the permissions are configured
  • Add-on Translations (title, description) title or description
    • Those translations can be seen in Add-on Edit page next to the title and description.
{
  "translations":{
    "title":{
      "de_DE":"Developer Guide Test",
      "en_GB":"Developer Guide Test"
      },
      "description":{
        "de_DE":"Developer Guide Test",
        "en_GB":"Developer Guide Test"
      }
   }
}

System Translations

For System translations, we first need to set up some permissions.

  1. Create a set of permissions with the Add-on API
{
  "permissions":[
    "add-on-dev-guide__dashboard__view",
    "add-on-dev-guide__dashboard__edit"
  ]
}
  1. Add translations for the add-on, permission groups and the permissions
{
  "translations":{
      "permission.add-on-dev-guide":{
        "de_DE":"Developer Guide Test",
        "en_GB":"Developer Guide Test"
      },
      "permission.add-on-dev-guide__dashboard":{
        "de_DE":"Dashboard",
        "en_GB":"Dashboard"
      },
      "permission.add-on-dev-guide__dashboard__view":{
        "de_DE":"Dashboard Ansehen",
        "en_GB":"View Dashboard"
      },
      "permission.add-on-dev-guide__dashboard__edit":{
        "de_DE":"Dashboard Editieren",
        "en_GB":"Edit Dashboard"
      }
  }
}

Follow our Add-on API Guide to see how you can create translations, from your backend application.

These translations are then visible under Settings > Users management > Roles for the corresponding add-on.

Add-on permissions with Add-on title, permission group, and permissions.

Webhooks

It is possible for add-ons to receive messages when certain events happen in the SCAYLE Panel (i.e, user updates, role updates).

In order to receive these messages, two conditions need to be met:

  • A callback URL is configured for the add-on.
  • The Add-on is active in the panel.

That callback URL should accept a POST request with content type: application/json.

To increase security of incoming webhooks calls, the best option is to whitelist the domains that the calls can come from (e.g., *.scayle.cloud).

Message Structure

Every message sent to the callback URL will have the same structure:

PropertyTypeNotes
eventstringContains the event key, which will be used by the Add-on to determine which flow should be used in any case. Dot-separated string with the following structure: {model}.{action} (e.g., user.created)
contextobjectContains any exposed model attribute. Its content varies from model to model, but all events related to the same entity will have the same fields.
idintID of the entity that has been affected (i.e., the user ID for User)

Model Definition

Every model that will post messages is defined below.

Deleted events will not contain any field in their context attribute of the message.

ModelUser
Possible eventsUserCreated, UserUpdated, UserDeleted, UserLoggedOut
Context fieldsnone
Structure example{"id": 4, "event": "UserCreated", "context": {},}
ModelRole
Possible eventsRoleCreated, RoleUpdated, RoleDeleted
Context fieldsnone
Structure example{ "id": 6, "event": "RoleUpdated", "context": {"userIds": [3,5,6]},}

User Custom Data

Custom data can be used to store additional data that you need directly after initialization in the SCAYLE Panel handshake. It will appear in the user property of the context object that is passed to the add-on.

With custom data, you could configure any information on the user. For example, you could add a specific property to all users that have the role administrator.

Notifications

The Notification API allows Add-on Developers to interact with the logged-in users of the panel. Currently, only the generic message is available.

Follow our Add-on API Guide to see how you can create user notifications, from your backend application.

Notifications in the SCAYLE Panel.

Further education - SCAYLE Academy