Interop

What is interop?

A smart desktop application consists of many smaller, independent applications, or apps. Your end-users can launch and organize these apps. When apps are running side by side on a smart desktop, we say that they are visually integrated.

Interop refers to apps that are logically integrated. In other words, they can exchange data with one another. Desktop agents such as Finsemble provide the infrastructure and APIs for apps to participate in desktop interop.

Some apps such as those provided by vendors, are preconfigured for interop using the FDC3 standard. (We’ll look at this standard later in this topic.) You need to modify other apps, such as your own, to expose interop data. We call the person who will be doing this work the pp developer. The app developer could be you or another developer at your firm, or it could be a developer at one of your vendors.

When you are dealing with many apps, you could be dealing with many app developers. To keep interop projects manageable, Finsemble keeps the surface area of new APIs low so that app developers can get their work done without having to learn all the intricacies of your entire Smart Desktop application.

This guide is the only one that an App Developer should need to integrate an app.

About the FDC3 standard

FDC3 is a standard maintained by the Fintech Open Source foundation (FINOS), which is part of the Linux Foundation. FDC3's goal is to promote interoperability among companies in finance, with a specific focus on desktop interop. The standard has broad support among enterprises, vendors, and integrators. Cosaic was an initial participant in defining the FDC3 specification and continues to be actively involved, maintaining a seat of the program management committee (PMC).

Much of FDC3's success is due to its simplicity. While there are a dozen or so API calls overall, most interop can be achieved with just 4 (see later).

Types of data

Desktop interop manages data exchange between apps. Let’s focus on 2 categories of desktop data:

  • Context. Apps that run side by side often need to be synchronized. For example, -you can synchronize an inventory app and a product app. when you click a product, the inventory displays the available quantity. This is a common type of data exchange that we call context. Apps can listen for (subscribe) and/or broadcast (publish) context changes.
  • Intents. An app could need another app to fulfill an action on its behalf. For example, on your phone when you tap the hare icon on an app, the phone provides a list of apps that support sharing data (such as Gmail, Slack, or Facebook). Behind the scenes your app has programmatically raised an intent to share, and your phone then delivered that intent to the app that you pick (or sometimes the phone picks the app automatically). Intents on the desktop behave the same way. Apps can raise intents and/or listen for intents.

Tip: For data exchange between the desktop (apps) and back end systems (cloud) continue to use standards such as Ajax (e.g. XMLHttpRequest and fetch) or websockets.

Interop's Big 4 API calls

You can implement most interop requirements with four FDC3 API calls. Apps running in Finsemble automatically have access to these APIs through a global variable called fdc3.For more info about the interop functions see the official FDC3 site.

Joining a channel

In FDC3, information flows along channels. Therefore, before your app can exchange any information with other apps, it must first join a channel.

Caution Caution: In FDC3, communication is primarily conducted through channels. Therefore, unless your app joins one of the available channels or uses the channels API directly, it can't communicate.

You can join a channel in one of 2 ways.

The first way is for the end user to link the app to a channel. Click the Join icon in the upper left, and pick the channel you want from the dropdown list.

The alternative is for a developer to explicitly join the channel by using fdc3.joinChannel(). For example, to join the red channel:

fdc3.joinChannel(redChannel.id);

For more info about joining a channel, see the Joining Channels section of the FDC3 API specification.

broadcast(context)

Sends updated contexts to any listening apps.

Example:


/**
 * When our app's state changes (the symbol changes) we convert that state into a
 * "context" object and then broadcast that object. Context objects can contain 
 * any fields. The only requirement is that they contain a field called "type" 
 * which apps that receive the context can use. 
 **/

let context = {
    type: "fdc3.instrument",
    name: "Tesla, Inc.",
    id: {
        ticker: "TSLA"
    }
}
fdc3.broadcast(context);

addContextListener(contextType, handler)

Listens for updates on the given context type. Finsemble calls your handler when a context update is available.

Example:

/**
 * We're receiving an inbound context. Typically, when new context is received 
 * your code updates the state of your app based on this context. Often 
 * this results in a visual change. 
*/

fdc3.addContextListener("fdc3.instrument", (context) => { 
   myAppLoadSymbol(context.id.ticker);
});

raiseIntent(intent, context)

Raises an intent for another app to handle.

Example:

/**
 * Our app wants to open a chart when the user clicks on a button, so it calls the "ViewChart" intent.
 * Finsemble ensures that a chart is open and then delivers the intent to that chart. 
 *  
 * In this example we're using a more sophisticated context. Notice that the "type" is still a string. 
 * Here, we've used a standardized FDC3 data type "fdc3 instrument".
 */

button.addEventListener("onclick", () => {
   fdc3.raiseIntent("ViewChart", { type: "fdc3.instrument", id: { ticker: "AAPL" } });
});

addIntentListener(intent, handler)

Listens for intents raised by other apps.

Example:

/**
 * Our chart app is listening for the "ViewChart" intent. When the chart receives the intent it checks the intent's
 * context to ensure that it is of the expected type. Then we update our chart just like when we received the context directly. 
 * If no chart apps were running, then Finsemble would launch a new chart app and then deliver the intent only after the listener is added.
 */

fdc3.addIntentListener("ViewChart", (context) => {
   const { type } = context; 
   if(type==="fdc3.instrument"){
      myAppLoadSymbol(context.id.ticker); 
   }
})

Adding interop to your project

JavaScript

Finsemble automatically imports interop (fdc3) into your app. The fdc3 global object exists on the window object in your browser. You can code without needing to import any libraries.

Typescript

If you're working inside a Finsemble project (seed or exported from the Smart Desktop Designer), you get Typescript support automatically.

If you’re working in your own project, you must import the Finsemble library to gain access to fdc3 types. Here’s how:

(1) Run

yarn install @finsemble/finsemble-core 

or

npm install @finsemble/finsemble-core

(2) Modify the typeRoots entry in your tsconfig.json file to inform Typescript (and Visual Studio Code) to add Finsemble's global typedefs, which include fdc3.

"typeRoots": [	
   "node_modules/@types",			    
   "node_modules/@finsemble/finsemble-core/types"	
]

(3) Run or restart Visual Studio Code.

.NET

Interop (fdc3) is available in the finsemble.dll library. See Integrating Native Applications for information on coding Finsemble in .NET.

Registering intents

When you add an app with Finsemble's Smart Desktop Designer (SDD), the SDD creates and installs an app manifest. Manifests are stored in /public/configs/application/appd.json in your project folder.

Note Note: If you've created your project by cloning Finsemble's seed (instead of running the Smart Desktop Designer), you must manually add apps to your appd.json file. The process of registering intents is the same.

Note Note: Finsemble's manifests use FDC3's AppD JSON format. AppD is short for App Directory. See the Appendix for details on the AppD format.

Intents are how apps pass actions to other apps. For example, when a user clicks on an mail icon in a CRM app it might raise an intent, looking for another app that can send an email. If an email app is running and has called addIntentListener, Finsemble sends it the raised intent.

But if the email app isn’t running, Finsemble must launch the email app to handle the intent. Therefore, the email app must have registered the fact that it can handle that intent. This is done by adding intent types to an app's manifest in the appd.json file.

Example:

Adding an intent for EMAIL for app type Grid

"AX56ADSD": { 
   "appId": "AX56ADSD",
   "name": "Grid",
   "manifest": { 
      "window": { 
         "url": "https://finsemble.chartiq.com/grid.html",  
       }
    }, 
   "version": "1.0.0", 
   "intents" : ["EMAIL"] //<--- Added Intent
}

How it works: Intent resolution

Sometimes when an app raises an intent there are multiple ways to resolve it. For example, there may be more than one app listening for the intent (such as SHARE). Or, an intent could be sent to a running app, or trigger launching of another instance of the same app (such as CHART).

When intents can’t be resolved automatically, Finsemble presents the user with a Resolver Dialog to pick which app should get the intent.

Implementation advice

Adding the API calls

Wiring your app for interop requires adding new API calls. Add the calls to your app to expose the data according to your business case.

Example:

Add a context listener to your app

fdc3.addContextListener("account", (context) => { 
   // do something with the context you receive
});

Note Note: If you expect your app to run in desktop agents other than Finsemble, follow FDC3 guidelines and wrap your code in an FDC3 event handler. Although Finsemble ensures that the fdc3 global is always available, other desktop agents do not, and this is the only way to ensure cross-platform portability. In addition, FDCs recommends this approach if your app will also run in a browser (because none of this code will execute if the fdc3 object never exists).

if (window.fdc3) {
   fdc3stuff();
} else { 
   window.addEventListener("fdc3Ready", fdc3stuff);
}

Preloads : An alternative implementation technique

Preloads are JavaScript files that load and execute in your app's window before your app runs. Preloads exist only when your app is loaded within Finsemble, so they are a good way to inject interop calls without rewriting your app. Preloads can be helpful when the release schedule of an app is not under your direct control (such as when it is developed by another team or vendor).

You specify preloads are within your app's manifest in appd.json.

Example:

Adding a preload to a "Grid" app:

"AX56ADSD": {
   "appId": "AX56ADSD", 
   "name": "Grid", 
   "manifest": {
      "window": { 
         "url": "https://finsemble.chartiq.com/grid.html",  
      },  
      "component": { 
         "preload": "https://myapp.com/grid/preload.js"
      } }, 
   "version": "1.0.0",
   "intents" : ["EMAIL"]
}

Now you can write your preload to create the interop interface for your app. This of course requires knowledge of how your app operates and where you can hook your preload code.

Example:

Adding a context listener to an existing app

// yourPreloadFile.js

/** 
 * Listen for updates on the "account" context type. When those updates arrive
 * call a function that my app has exposed to update the account. 
 **/

fdc3.addContextListener("account", (context) => { 
   window.myAppsEntryPoint(context.account);
});

Example:

Hooking into a redux store

/** Because your preload runs before the rest of your code. you need some way to wait until your main code is ready.
 * Here we poll until a global redux store exists and then we wire our fdc3 interface into that store.
 * Your app might require a different mechanism.
 * */

const attachFDC3 = (store) => {
   fdc3.addContextListener("symbol", (context) => { 
      store.dispatch("updateSymbol", { 
         symbol: context.symbol 
      }); 
   });
};
const startupHandle = setInterval(() => { 
   if(window.store){ 
      clearInterval(startupHandle); 
      attachFDC3(window.store); }
}, 50);

Whether preloads are a good option depends on your app's architecture. Contact us at support@finsemble.com if you need advice about how to integrate.

Note **Note:**Preloads are a feature of Electron. They are similar to Chrome's content scripts. You're probably running them right now if you have any Chrome extensions installed.

Best practices

Indirection

Desktop Interop's best advantage is loose coupling : each app in your Smart Desktop can be built and deployed independently. With loose coupling, you can swap apps in and out without rewriting your entire desktop. It's this loosely coupled approach that allows Finsemble to cut the costs of integration, and at the same time also increase customizability.

The Interop API brings loose coupling to data integration through the concept of indirection. For indirection to work, developers must design apps not to depend on other apps, but rather to depend on capabilities. Apps trust the desktop agent, in our case Finsemble, to fulfill those capabilities by forwarding information to other apps.

As an example, on your mobile phone, apps don't raise intents for other apps (e.g. Gmail). Instead, they raise intents for capabilities (e.g. Share). The phone's operating system acts as an intermediary to fulfill app needs just as Finsemble does on the desktop.

With indirection, apps work independently and often anonymously. Aside from implementing the interop API, apps don't even need to know that they are running on a desktop. They can run independently in a browser, yet still integrate with other apps when they are loaded on the desktop.

To implement indirection in your applications, what the app needs rather than on how it will get it.

Good:

raiseIntent("news")

Bad:

raiseIntent("NewYorkTimes")

FDC3: Keep it simple

The FDC3 standard includes more functions than just the Big 4 we looked at earlier. While Finsemble provides complete coverage of the FDC3 standard, we recommend that you stick to the Big 4 until you gain experience with FDC3. By sticking to the Big 4 you are more likely to create loosely coupled apps and quickly realize your integration goals.

Avoid findIntent() or findIntentsByContext()

These calls figure out specifically which intents are available for the purpose of building UI pickers for users. Finsemble automatically discovers and resolves intents, and when it finds an ambiguity the built-in Resolver Dialog automatically displays for the user.

Note Note: In FDC3 1.2, raiseIntentForContext() function provides an easier alternative to findIntentsByContext(). It displays Finsemble's built-in intent resolver, which allows the user to pick an intent and then an app to handle it.

Avoid calling open()

It’s Finsemble’s job to launch apps. Loose coupling is lost when you program apps to specifically launch other apps. Ideally, apps should use raiseIntent() rather than open(). This way,the desktop agent or user can pick the correct app.

Note Note: Finsemble launches apps, through raiseIntent() and app menus.

What About AppD?

AppD is an FDC3 standard that consists of two parts:

  • A standard JSON manifest format, and
  • A REST protocol for building an AppD Service.

Finsemble internally uses AppD's JSON manifest format, storing app manifests in `/public/configs/applications/appd.json. For most implementations, this static approach is fine.

An AppD Service might be necessary when a firm has dozens or even hundreds of applications. In these situations, all of a firm's apps are loaded into an AppD Service and made available to users as a graphical App Catalog. Users then pick the apps to install into their desktop. Finsemble provides an optional AppD-compatible App Catalog for this use case.

Another use case for AppD is when a firm wants to deliver a different set of apps to different users. This is an aspect of what we call dynamic config or entitlement-driven config. In this scenario, Finsemble authenticates the user and then passes this information to an AppD server to retrieve the manifests.

Vendors also can provide an AppD Service as an app discovery mechanism. In this scenario, a desktop agent makes calls to one or more AppD services. This use case is not yet prevalent but could become more common as vendors increasingly adopt FDC3.You'll know you need AppD when you need it. Until then, you can probably keep things simple and stick with static config. If there's ever any doubt, contact us at support@finsemble.com and we'll offer advice.

Use FDC3 data definitions when available

The FDC3 working group has defined a small initial catalog of standardized Financial Objects. Make this catalog your first stop when defining your app's interop interface. Picking an established context type is particularly important if you plan to interoperate with third parties.Increasing the breadth and depth of standard data definitions is a strategic focus of the FDC3 working group.

Use app channels only when necessary

FDC3 provides the ability to programmatically create named channels (called App Channels), but most use cases don't require them.App Channels are distinct from linked channels (also called System Channels or joined channels) because they are hard wired. When two apps open the same channel, they then exchange messages regardless of whether a user has linked the apps. This property makes named channels useful for data exchange between related apps. Named channels are also useful for providing a well known location, for example to subscribe to data feeds as context updates.

Tip: If you're confused about channels, just skip them for now and implement the Big 4 we covered here. Finsemble's linker will get you pretty far.

Use the appd.manifest.interop.useLinker configuration to turn off the linker if your app only uses App Channels.

Note

All channels in FDC3 are public. Any app can join any channel. For private data exchange use the authorize, to, or from SelectConnect rules (see later in this topic).

Don't daisy chain

One common mishap occurs when apps broadcast or raise an intent after receiving an inbound context or intent. We call this daisy chaining and it can complicate desktop implementations.

When apps are daisy chained they make a transitive assumption (A -> B -> C). In other words, App B assumes that App C needs to know what App A did. But it is quite likely that App A has already communicated with App C, or that the desktop agent itself has already acted upon this communication. Remember that loose coupling, indirection, and anonymity are touchstones for desktop interop.

A good rule of thumb is that inbound events shouldn't have any effects outside of the receiving app. Following this practice avoids broadcast loops, duplicated messages, and unexpected UI side-effects.

Example: Daisy chain that causes a broadcast loop

fdc3.addContextListener("symbol", (context)=> { 
   myApp.changeSymbol(context.symbol);
});

myApp.symbolChanged((symbol) => {
   fdc3.broadcast({type: "symbol", symbol: symbol});
});

In this example, the inbound context immediately triggers an outbound broadcast. If two apps are linked by this code, an infinite loop results. Finsemble is smart enough to detect and protect against broadcast loops but you should still avoid this practice to ensure that your app can run in less sophisticated desktop agents.

Pay special attention to inadvertent daisy chaining. For example, an inbound context change might get picked up by another piece of code in your app that triggers a broadcast of a different piece of data.

Example: Inadvertent daisy chain

fdc3.addContextListener("symbol", (context)=> { 
   myApp.changeSymbol(context.symbol);
});
myApp.symbolChanged((symbol) => { 
   fdc3.broadcast({type: "position", position: currentPosition[symbol]});
});

Finsemble doesn’t prevent this type of daisy chaining because they could possibly be intentional. But daisy chains are still dangerous. If another app is configured with the opposite polarity (receiving a position and then transmitting a symbol) then an infinite loop will result.

It's better to show the flow of control explicitly if possible:

fdc3.addContextListener("symbol", async (context)=> {
   await myApp.changeSymbol(context.symbol); 
   fdc3.broadcast({type: "position", position: currentPosition[symbol]});
});

The risk still exists but the control flow is intentional and therefore less likely to result in an actual broadcast loop.

Note

Finsemble's Interop Service contains logic to detect out-of-control broadcast loops. When Finsemble detects such a loop, it disconnects the offending app from interop and notified the user. This prevents systems from locking up.

Daisy chains can create duplicates that could create problems for your business. It is critical that transactional systems such as order management systems have their own algorithms for detecting and preventing duplicates. Before integrating mission-critical systems to Finsemble, make sure that adequate protection is in place. Keep in mind that desktop interop systems by design can integrate many apps in many permutations.

Don't over-respond to intents

It can be tempting to program apps to trigger intents or context changes based on an inbound intent and create a makeshift workflow. This is daisy chaining again but with additional nuances.

Inbound intents with context implicitly change the context of your app. Finsemble is smart enough to automatically link apps to that new context to keep them all synchronized. You don’t need to rebroadcast contexts that are received with intents.

Apps generally shouldn’t change their shape or form in response to an intent unless they would also do so in response to an updated context. For example, an app should not change its layout, color scheme, etc simply because it received an intent.

Design for multiple instances

Within a desktop interop environment, multiple instances of the same app can be running simultaneously. Often, apps that were initially built to run in browsers encounter state problems because they assume that only a single instance is running. A common case is when you save the state of the application to a cookie or localStorage. When multiple instances are running on a desktop, they share the same cookies and localStorage. This causes apps to overwrite each other's states. When apps are rehydrated (when Finsemble is restarted or a user switches workspaces) their states will be incorrect.

You can test if this condition exists by running your app simultaneously in two browser tabs in Chrome. Change the state of one app and then reload the second app. If the second app's state now matches your first app, you've got a state problem that will manifest on the desktop.

One way to avoid this issue is to leverage the desktop agent's ability to save state. In Finsemble, you can do this with zero coding by using the #Workspace SelectConnect module (see later in this topic).

Debugging

Finsemble's Interop Service is based on Redux. Redux's devtools are automatically enabled for debugging. To display the devtools, add this config entry to /public/configs/application/config.json and then restart Finsemble.

"servicesConfig" : { 
   "interop" : {  
      "visible": true 
   }
}

When the interop devtools appear, you can begin debugging. You can check if your app is connected to the Interop Service by watching for a resolver/register redux action. The Big 4 API calls result in resolver/broadcast, resolver/raiseIntent, resolver/subscribeContext or resolver/subscribeIntent actions. You can peek inside any action to verify that the data looks accurate.

In the redux store, the root/testing section shows the results of the Interop Service's internal algorithms. Here you can see the destinations for broadcast messages as well as the SelectConnect rules that were triggered.

If you need help with interop issues, contact us at support@finsemble.com.

See also

For more info, see Integrating native apps.

Appendix: The AppD format

{
   /**
     * A unique application identifier (key). This can be any string and does not necessarily need to describe the app.
     * @type String
     */
  "appId": "X39A765",

    /**
     * The name of the application. This name should be descriptive of the app (e.g. Gmail, Slack). There may be multiple instances with the same name, for instance with different versions.
     * @type String
     */
   "name": "MyApp",

    /**
     * Version of the application.
     * @type String
     */
   "version": "1.0.0",

    /**
     * Icons used for the application. This can be a single icon, or a set of icons of different sizes. Should be an array of url references to images.
     * @type Array<String>
     */
   "icons": ["http://myapp.com/icons/myapp.png"],

    /**
     * The list of intents handled by the App.
     * @type Array<string>
     */
   "intents": ["EMAIL", "PHONE"],

    /**
     * Type of manifest. Always "finsemble".
     * @type String
     */
   "manifestType": "finsemble",

    /**
     * Finsemble's proprietary application config (Following are the most important fields. See https://documentation.finsemble.com/tutorial-ConfigReference.html for all configuration possibilities)
     * @type Object
     */

    "manifest": {
      "window": {
       /**
        * The url of the app.
        * @type String
        */
        "url" : "http://myapp.com/myapp.html"
      },
      "component": {
       /**
        * The url of a JavaScript preload to be loaded into the app.
        */
        "preload": "http://myapp.com/preload.js"
      }
    }
}

The following optional AppD fields are generally used only when publishing in an "App Catalog":

title: string
Optional title for the application.

tooltip: string
Optional tooltip description.

description: string
Optional long form description.

images: array of images
Optional array of images to display in App Catalog.

contactEmail: string
Optional e-mail to receive queries about the application

supportEmail: string
Optional e-mail to receive support requests for the application

publisher: string
The name of the company that owns the application.

customConfig: array
An optional set of name value pairs that can be used to deliver custom data from an App Directory to a launcher.

See also

Integrating FDC3-compliant apps into a workflow using the FDC3 workbench