How to keep the query data up-to-date

When you've started integrating with our API, you will have queried some data to be used in your application. If your application is a script that gets run every so often, querying the data every time is pretty reasonable. But you might be developing a more long-lived application, such as our Portal application. In that case, you will run into the issue where you want to keep the data up-to-date.

For example, you might want to keep track of how many unread messages a user has, to display this as a badge in your component. For this purpose, IXON has set up a Change Notification System (CNS). This guide will show you how it works and you can integrate with it.

📘

UI Components

If you are developing a UI Component, this system is already implemented for you, and you can depend on the context for updates. When you use the ResourceDataClient to get data to be shown in your component, you will receive additional query responses when data is changed due to change notifications.

System overview

When you make a call to the API that makes a change, our API server will, besides executing that change, also create a list of Change Notifications (CNs). It will then broadcast these changes.

An API client can subscribe to these CNs. It can then apply the changes to its local copy of the data, and, for example, update the user interface with the new state.

Connecting

A client can start subscribing by connecting via a WebSocket. The URL can be found in the discovery:

curl --request GET \
     --url '<<url>>:443/api/' \
     --header 'Api-Version: 2'

Part of the response (example):

{
  "rel": "ChangeNotificationWebSocket",
  "href": "wss://domain:port"
},

🚧

Use the URL from the discovery!

Keep in mind that the URL can and does change. You really should use the discovery to find the URL dynamically, by looking for ChangeNotificationWebSocket.

If you open a WebSocket to this URL, you will find that it gets closed after a few seconds. This is because you need to authenticate first.

To set up the WebSocket connection, it is necessary to give the protocol change-notifications. Here is an example of the code.

const socket = new WebSocket(
    "wss://domain:port",
    "change-notifications",
);

Authenticating

To authenticate, you must send a message over the WebSocket connection within the authentication time window. Before you can do so, you have to create an AuthToken. The URL for this is, as always, found in the discovery:

curl --request GET \
     --url '<<url>>:443/api/' \
     --header 'Api-Version: 2'

Part of the response:

{
  "rel": "AuthTokenChangeNotificationsList",
  "href": "<<url>>/api/auth-tokens/cns"
},

To create an AuthToken, send a POST request to this URL. In the body, you will need to set an expiration time. The default is an hour, which is usually fine.

curl --request POST \
     --url 'https://domain:port/path' \
     --header 'Api-Version: 2' \
     --header "Api-Application: $application_id" \
     --header "Authorization: Bearer $bearer_token" \
     --data-raw '{"expiresIn":3600}'
       {
         "data" : {
           "expiresOn" : "2022-06-10T07:37:15Z",
           "publicId" : "$a_random_id",
           "secretId" : "$a_long_jwt_string"
          },
          "status" : "success",
          "type" : "AuthTokenCreateResponse"
       }

In the response, you will get a JSON Web Token that is signed by the platform. It contains the companies that you have access to at the time of token creation. If the user account accepts an invite to a different company, you will need to make a new token.

You now have the URL to connect to and the authorization to do so. Set up your WebSocket connection, and in the open handler directly send your message as follows:

{"aut":"a_long_jwt_string"}

If the token is accepted, your WebSocket connection will remain open.

🚧

Your AuthToken expires!

You've set the expiration time, and after that time, you will no longer receive any notifications over the WebSocket. To make sure you keep subscribed, please make sure to request a new token before the old one expires, and send it over the WebSocket like the first one.

Subscribing

Change Notifications are sent to targets, which work much like MQTT topics. However, targets are split between inherit targets and explicit targets. If you examine the JWT you received in the AuthToken with a tool such as https://jwt.io/, then you will see something like this:

{
  "cit": [
    "User/publid000002"
  ],
  "cet": [
    "Company/1111-2222-3333-4444-5502",
    "Company/1111-2222-3333-4444-5515"
  ],
  "iss": "apprentice",
  "exp": 1655105267
}

The cit list contains the list of inherited targets. You are automatically subscribed to these. The cet list contains the targets you are allowed to subscribe to if you so choose explicitly.

So, in the example above, you can see that you are automatically subscribed to the User/publid000002 topic, meaning that any notifications that the API sends to your User target will always reach you. These notifications are specific to your account. For example, if you are logged into two apps or two browser tabs, and you delete an access token in one of them, both can keep their list of access tokens up-to-date this way.

The targets you can subscribe to are the companies that you are a member of. So usually you have a step in your application where the user can select a company to log into, and you would subscribe to changes for that company.
Subscribing to a company's target doesn't require any special rights. Any member of a company gets these notifications.

To subscribe, send a message over the WebSocket connection as follows:

{"sub":["Company/1111-2222-3333-4444-5502"]}

You can only subscribe to one explicit target at a time. Sending a new subscription will unsubscribe from the previous target, even if you are not authorized to subscribe to the new target.

Processing

You are now subscribed to the Change Notifications and are ready to process them. We will first show two examples, to be used as in the explanations below.

If you delete an access token, you will receive a message like this:

{
  "sel": [
    {"typ": "AccessToken"}
  ],
  "act": "MDL",
  "dat": [
    {"publicId": "publid000003"}
  ]
}

When making a change to an Agent's firewall settings, you will receive a message like this:

[
  {
    "sel": [
      {"typ": "Agent", "pat": "#publid000004.config.routerLan"}
    ],
    "act": "EXT"
  },
  {
    "sel": [
      {"typ": "Agent", "pat": "#publid000004.config"}
    ],
    "act": "EXT",
    "dat": {
      "differentConfigs": true,
      "differentWanConfigs": false,
      "differentStewardConfigs": false
    }
  }
]

You can see that a Change Notification consists of an action (act), a selector (sel), and possibly data (dat).

Also note that in the second example, the message is a list of change notifications, as the API server bundles change notifications per API request to reduce resource cost and to prevent re-ordering issues.

Action

The action determines what you should do to apply the change. It can be one of the following:

actiondescription
ADDA new resource was added.
MDDMultiple new resources were added (data is a list).
REPA value was replaced, that is, the value in the data should be stored for the new value as is.
The full path is in the selector.
EXTA value was replaced, that is, the value in the data should be stored for the new value as is.
The full path is in the selector.
MXTMultiple resources were extended (data is a list).
DELA resource was deleted.
MDLMultiple resources were deleted (data is a list).

Selector

The selector determines which resource was changed, and sometimes even what property or sub-resource of that resource was changed. It consists of the type of the resource (typ) and optionally a pattern (pat) that you can use to match a specific resource or property.

In the first example, the type is AccessToken, and there is no pattern. This is common for actions that deal with multiple resources (MDD, MXT, MDL). If you want to know which resources were updated, you should look at the publicId in the data.

In the second example, the first change notification has as type Agent, and the pattern refers to both a specific agent, as well as the property within the resource that was changed. So, in that case, the data contains an object that you should combine with the .config.routerLan property of the Agent with public ID publid000004.

It is sometimes possible for there to be multiple selectors. In which case the action and data should simply be applied to every selector. This won't happen when there is no pattern, as the data is a list then, and combining a selector list with a data list does not happen.

Data

When possible, the dat property will contain the new values. This lets you apply the changes without having to query the server first. In the first example, the data only contains the public ID, as that's all you need for a delete.

In the second example, the first change notification contains no data, as these changes get broadcasted to all users in the company, including users that might not have access to this agent. In the second change notification in that example, the data is sent along, because there is no sensitive information in it.

When the action is related to multiple resources (MDD, MXT, MDL), then the data will be a list. Otherwise, it will be a single resource or value.

If there is no data, and you are interested in it, you have to query the data from the API server yourself. When the action deals with multiple resources (MDD, MXT, MDL), and the information is sensitive, we still need to broadcast an object, so the object will contain a $dirty flag. This flag can be in a child resource. In that case, only that part of the change notification is missing the data. Again, if your application needs the remaining data, you will have to do an API query.