Integrating native apps

With Finsemble you can assemble applications into a unified desktop experience. This includes both the web apps and the native apps, although the integration process is a little different. In fact, in Finsemble the native apps have access to the full set of services and APIs. This means that you can make the native apps a part of an integrated workflow with a small amount of work.

In this topic, we will learn how to integrate native apps. You can integrate them both visually and logically:

  • Visual integration allows your app to benefit from Finsemble's UX.
  • Logical integration allows Java and .NET applications to communicate through Finsemble's API. In fact, native applications in Finsemble have access to the full set of Finsemble services and APIs. This means that your native apps can be integrated into mult-app, multi-technology workflows.

From the end user perspective, the native apps look and behave similarly to web apps. In fact, users often can’t tell the difference. You can drag data from one app to another, you can link them so that they share context, and you can create low code workflows using both types of apps. In addition, authentication, authorization, storage and other enterprise services can be accessed the same way by both.

What is a native app?

A native app is an app that has been created for a specific device type (such as a laptop, a desktop, or a phone) and a specific operating system (for example, Windows or Mac). Native apps must be installed directly on the type of device they were developed for. Unlike Web-based apps, which are not specific to the platform they run on, native apps can’t run on a platform they are not developed for unless you modify them. The most common native apps used with Finsemble are written in .NET and Java, but there are many others. A typical native app has one window, but this isn’t always the case. There are other types of native apps, and we’ll look at them in a moment. In addition to native apps, there are also other kinds of apps, including web and hybrid apps. Not all companies have native apps that they want to integrate into their smart desktop. A typical end user can’t tell the difference between native apps and other types of apps, and they shouldn’t have to. On a smart desktop, they can drag and drop data from one app to another, link apps, create low code workflows between apps of different types, and expect the same authentication, authorization, and other enterprise service support for any app, web or native. However, from the desktop interop perspective, there is a difference. Let’s take a closer look.

Assimilation

To visually integrate a native app, you must first assimilate it. The executable AssimilationMain.exe acts as a bridge between native windows events (such as move, minimize, maximize, etc.) and Finsemble, communicating over a WebSocket that runs on the desktop’s loopback address (localhost).

AssimilationMain.exe is a component of Finsemble and you don’t have to manually install it on the user's desktops. It is included as a Finsemble asset and runs dynamically.Let’s first look at the concepts you will need to integrate native applications into Finsemble workflows. Later we’ll look at specific examples.

Note Note: Currently Assimilation works for Windows only.

How assimilation works

Assimilation extends the capabilities of the Electron-based container. At the first glance, the term “assimilation” suggests that it is a one time event—a native app undergoes assimilation and now functions like a web one within Finsemble. But this is not the case. In Finsemble, Assimilation runs continuously to support the visual integration of native windows.

Let’s look at the simple example in detail, and then we’ll tweak the process to account for different types of apps.

The process of visual integration

In this example, we assimilate an app launched by Finsemble, via a command line call. This diagram shows the process:

img

  1. You configure Finsemble to launch the native app from the command line, and provide the command line arguments and environment variables. This information is then passed to the Assimilation process.
  2. The assimilation process now runs the native .EXE or JAR file, which launches the window for the app. The assimilation process waits until the app opens a window.
  3. Now that the window handle is available, the assimilation process automatically acquires it by using the Microsoft Desktop Window Manager (MS DWM) APIs.
  4. The assimilation process can now track and control the window’s properties, such as position and size.
  5. Finsemble now can manage it as it would any other window.

This process results in visual integration of an app that has one window. Logical integration requires a little bit more work.

Assimilation with API integration

The process we just looked at doesn’t account for API integration. Let’s add it now. Here’s the tweaked process:

img

As you can see, this new process is similar to the one we looked at in the previous section. The difference is that now, instead of acquiring the window handle automatically, we use the Finsemble router to connect to the Finsemble API and specify the window handle to acquire.

With API integration, the app now has access to all of Finsemble's services, including FDC3 (which in Finsemble is called the interop service).

To integrate an app, Finsemble generates command-line arguments and passes them to the native app so that the app can connect to Finsemble. These arguments specify:

  • the type of the app
  • the specific app instance
  • the initial position of the app window on the screen
  • the auth token if needed
  • other metadata that Finsemble might need (such as a server address).

Assimilating windowless apps

We have just seen the assimilation process for a typical app. There is one case in which this process is significantly simpler. Consider a windowless app. Such apps are useful when there is no need for a user interface. The app starts, connects to a data source and provides services to other applications. There is no UI, and hence no need for an actual window.

img

Now the assimilation process is simpler. We don't need to control the window size and position—there is no window! As a result, there is no need for WindowService either. Assimilation simply launches the app and it connects to the Finsemble router, just like before.

Here is an example of a windowless app.

Assimilating special types of native apps

The generic assimilation process we discussed in the previous section works for single-window apps that run within Finsemble and don’t need special consideration. But for some types of apps the process requires some additional finesse. These include freestanding, multi-window, and evergreen apps. Let’s now look at them in more detail and examine the tweaks in the integration process to handle the special needs of each type.

Finsemble recognizes several special types of native apps that require different consideration for assimilation:

  • Freestanding. These apps are launched outside of the Finsemble workspace, usually because a company prefers to keep them on the user desktop as an independent entity. Freestanding apps can be either native or web-based. They don't take part in workspaces, but can still communicate with other apps in Finsemble.
  • Multi-window. Some apps need multiple windows. You'll need to configure each window that the app can create, allowing Finsemble to identify, track, and recreate each window separately so that each window can be saved and restored in workspaces. Each window then can participate in visual and logical integration as usual.
  • Binary. Companies don’t always have the source code for an app. Or perhaps they do have the source code, but the app is so old and fragile that you don’t want to modify anything for fear of breaking it. Finsemble can still manage these apps by using the binary directly. With the assimilation technology, you can snap, dock, and group these apps like you would any other app. You don’t need to write any code.
  • Evergreen. Some apps are truly humongous and take forever to load. If you have such an app in multiple workflows and shut it down in one workflow only to reload it in another, you’re wasting a lot of time. To prevent this from happening, you can make a component containing such an app evergreen. Evergreen components don’t shut down when they aren’t needed in the current workflow. Instead, they keep running in the background, hidden from view. From the end user perspective they look like they have shut down. But when the user opens a workspace in which an evergreen app resides, Finsemble quickly rehydrates this app to its last known state.

Note Note: Deciding which app to make evergreen is a judgement call. In general, if an app takes more than a few seconds to load, it’s a good candidate for making it evergreen.

It is possible for an app to fit more than one type. For example, you can have a freestanding multi-window app, or any other such combination. The one exception is that a freestanding app can’t be evergreen (as they don't participate in workspaces).Now that we know about these different types of apps, let’s assimilate them.

Assimilating freestanding apps

Although a freestanding app is launched outside of Finsemble, it can still use the Finsemble libraries to connect to the API. Here's the process:

img

  1. The app must first connect to the Finsemble router.
  2. Like other native component types, it needs to register the window handle to participate in visual integration.
  3. Next, Finsemble registers the freestanding app with the LauncherService as a freestanding component, of an unknown type.
  4. The assimilation module can now control its position and window size.
  5. The position and window size control now passes to the WindowService.

Note Note: Because Finsemble can't initialize a freestanding app, the workspace state is not supported.

Assimilating multi-window native apps

This is the most complex case we will look at. Individual windows can connect to Finsemble as apps, both freestanding or regular, or you can build the capability for Finsemble to launch your app’s individual windows into your application (which will allow them to participate in workspaces). Let's look at the diagram and examine the details.

img

  1. The launch begins as usual.
  2. The parent process is launched with command line arguments generated by Finsemble.
  3. If this is the only instance of that process, the parent now spawns a child window, connects it to Finsemble and sets up a mechanism to receive messages from other copies of the process in future.
    • If there is an instance of the process already running, it should use the mechanism set up by the first copy of the process to send along the generated command line arguments. The original parent process can then use them to spawn a further window and connect it to FInsemble.
    • As far as Finsemble is concerned, these are separate, independent windows. To Finsemble it looks like it's launching separate apps. It’s only behind the scenes that these windows are treated as related and owned by the same parent process.
  4. Each child window independently connects to the Finsemble API. If the parent window also needs to connect to the Finsemble API, this happens at this step also.
  5. ach child window registers the window handle with WindowService, the same way as a freestanding app would.
  6. and 7. The last 2 steps represent a loop of positioning, resizing, and collecting data.

Check out the full example.

Integrating evergreen apps

The first time you launch an evergreen app, it will launch as if it’s not evergreen. In other words, you will use one of the processes we just described. (Most likely your evergreen app will be a single window, and you can use the general process described at the beginning. It could also be binary, and then the binary process applies.) The difference comes in when a user moves from a workspace in which the evergreen app is active to another workspace without that app.

Normally, Finsemble would close the app, and relaunch it when needed again. But there is another option. You can configure this app as evergreen. This way, when the user switches workflows, this app is hidden and the user isn't even aware that it is still running. img

When the user switches to a workflow that includes the evergreen app, the app is ready. All Finsemble needs to do is restore its location, apply the grouping and tabbing states, and pass any state info saved in the workspace back to the application.

Modifying assimilation

Configure and launch native apps (without logical integration)

You can configure and launch native apps by using appD.json. You need to:

  • Set manifest.window.windowType: “assimilation”.
  • Pass the appropriate command line arguments in the arguments parameter. Separate the arguments by spaces.
  • Pass any necessary environment variables that should be passed to the application using the env property.

You can use one of the two available methods of launching the process: by path or by alias. In either case, to launch, you need to modify AppD.json.

Here is an example:

{
	...
	"appd": {
		"MyNativeApp": {
			"appId": "MyNativeApp",
			"name": "My Native App",
			"description": "An example native application",
			"manifest": {
				"window": {
					"windowType": "assimilation",
					"path": "C:/some/path/on/disk/mynativeapp.exe",
					"arguments": "mode=a --type b",
					"env": {
						"VAR": "value"
					},
					"width": 800,
					"height": 600,
					"top": "center",
					"left": "center"
				},
				"foreign": {
					"components": {
						"App Launcher": { "launchableByUser": true },
						"Toolbar": { "iconClass": "ff-settings" }
					}
				}
			},
			"version": "1.0.0",
			"tooltip": "My Native App",
			"images": [
				{
					"url": "http://someserver.com/my-native-app.png",
					"tooltip": "App screenshot"
				}
			],
			"contactEmail": "info@cosaic.io",
			"supportEmail": "support@finsemble.com",
			"publisher": "Cosaic",
			"icons": [
				{
					"url": "http://someserver.com/myNativeApp-icon.png"
				}
			]
		},
		...
By path

To launch by path, you need to set the manifest.window.path argument to one of these:

  • the name of an .exe file on the system path. This file must already be installed on the system.
  • the full path to the executable file. This path must be absolute, such as C:/Program Files/...
  • a system installed URI, such as myuri://myapp, which will cause a protocol handler to launch the app.
By alias

Alternatively, you can launch your native app using an alias. To do so, you need to add an appAsset to your manifest file. The appAsset describes a zip file that contains your app so that Finsemble can use this information to download, install and launch the app for you.

Here’s the information you need to specify:

  • src - the location of the .zip file where your app resides
  • alias - the name your appAsset will be known by. This is how components.json refers to your app
  • version - used for controlling updates. If you increase this number, the zip file will be re-downloaded on the next launch of Finsemble.
  • target - the path to the binary within the .zip that you specified in the src.
  • args - a default string of arguments passed in the same way as you would on the command prompt, such as “-mode=a b c”. (optional)

Here’s an example app asset configuration:

{
    "appAssets":[
        ...
        {
            "src": "http://localhost:3375/example.zip",
            "alias": "NativeExample",
            "version": "1.0",
            "target": "example.exe",
            "args": "arg1 arg2"
        }
    ]
}

which is referred to in this app configuration:

{
	...
	"appd": {
		"MyNativeApp": {
			...
			"manifest": {
				"window": {
					"windowType": "assimilation",
					"alias": "NativeExample",
					...
				},
				...

Integrating apps that have splash screens

By design, assimilation can handle one window per app. It observes the process it spawned and when it creates a window handle, it is automatically acquired. But some apps, such as the Microsoft Office suite, consist of multiple windows. Examples include the loading window (or splash screen) that the Microsoft Office Suite launches, or tool palettes and dialogs created by other applications. These windows are often short-lived and don’t participate in workflows. Therefore, this is not necessarily a serious limitation. All you need to do is decide which windows you want to include and which ones you want to exclude.

To help you achieve this we provide two configs, manifest.window.windowTitleWhiteList and windowTitleBlackList. Use windowTitleWhiteList to specify the name of the window that you want to finsemblize. Exclude windows that you don’t want to assimilate, such as loading windows, using manifest.window.windowTitleBlackList. Both fields take regular expression strings that contain match criteria for the window titles.

Here is an example for Outlook:

{
	...
	"appd": {
		"Outlook": {
			...
			"manifest": {
				"window": {
					"id": "Outlook",
					"name": "Outlook",
					"windowTitleWhiteList": ".*(Outlook|Today).*",
					"windowTitleBlackList": "Opening.*",
					"windowType": "assimilation",
					"path": "OUTLOOK.EXE",
                    "addToWorkspace": true
				},
				...


In this example, we use both the white list and the black list, but it’s not necessary to place every window of your app in one list or the other. In some cases it’s easier to specify only the black list, or only the white list. Either one will work. If you use only one list, any window that is not on that list is assumed to be on the other one.

Note Note: If you use both lists and accidentally place an app on both, it’s the same as placing the app on the black list.

Native apps launched by an intermediary process

If you want to integrate a native app into Finsemble that is launched via an intermediary process, such as a batch script, launcher process or protocol handler registered with your operating system (e.g., myLauncher://myapp), you need to integrate the Finsemble DLL into the app so that it can register itself with Assimilation by passing in the window handle of its window(s).

When you use windowType: "assimilation", Finsemble Assimilation observes the process and captures the first window handle that it creates (that passes any windowTitleWhiteList or windowTitleBlackList set) and then begins managing that window. However, where an intermediary process launches the application, Assimilation can’t observe the creation of window handles. By integrating the Finsemble DLL into the application and using windowType: "native", the launched app can instead proactively register its window handle(s) with Finsemble Assimilation, bypassing this issue.

Logically integrating native apps

You can achieve a deeper level of integration when your native apps communicate with each other via Finsemble. This is called logical integration, and it allows users to create custom workflows between two or more interoperating apps.

Finsemble-aware native apps are specified with a windowType of native. As before, you can use a full path or an alias for a downloaded asset.

Here is an example:

{
	...
	"appd": {
		"MyNativeApp": {
			"appId": "MyNativeApp",
			"name": "My Native App",
			"description": "An example native application",
			"manifest": {
				"window": {
					"windowType": "native",
					"path": "C:/some/path/on/disk/mynativeapp.exe",
					...
				},
				...
			},
			...
		},
		...

Connecting to Finsemble

To connect to Finsemble, you need to make sure that the window exists and that its handle is available. Let’s look at some examples in different languages. It is entirely up to you which language you choose. From the Finsemble perspective all work equally well.

For each of these examples, we provide you with a link to the complete code in GitHub.

Here is the ,NET WPF example.

    public MainWindow(string[] args)
    {
        // Trigger actions on close when requested by Finsemble, e.g.:    
        this.Closing += MainWindow_Closing;
        //Make sure your window has been created (so that its window handle exists) before connecting to Finsemble.
        FSBL = new Finsemble(args, this); // Finsemble needs the command line arguments to connect and also this Window to manage snapping, docking etc.
        FSBL.Connected += Finsemble_Connected;
        FSBL.Connect();
    }

And here is the Winforms example:

    public FormExample(String[] args)
    {

#if DEBUG System.Diagnostics.Debugger.Launch(); #endif InitializeComponent();

        //connect to Finsemble
        //Ensure that your window has been created (so that its window handle exists) 
		// before connecting to Finsemble.
        FSBL = new Finsemble(args, this);
 
        // Use handle
        // FSBL = new Finsemble(args, this.Handle);
 
        ArrangeComponents();
 
        FSBL.Connected += FinsembleConnected;
        FSBL.Connect();
    }

Let’s now try to connect with this Java Swing example:

private void initFinsemble() {
       fsbl = new Finsemble(launchArgs, this);
       try {
           fsbl.connect();
           appendMessage("Connected to Finsemble");

       fsbl.addListener(new ConnectionListener() {
           @Override
           public void disconnected(ConnectionEventGenerator from) {
               LOGGER.info("Finsemble connection closed");
               appendMessage("Finsemble connection closed");
           }

           @Override
           public void error(ConnectionEventGenerator from, Exception e) {
               LOGGER.log(Level.SEVERE, "Error from Finsemble", e);
           }

           @Override
           public void onWindowStateReady(ConnectionEventGenerator from) {
               // NoOp
           }
       });

       appendMessage("Window registered with Finsemble");

       initForm();
       setFormEnable(true);
   } catch (Exception ex) {
       LOGGER.log(Level.SEVERE, "Error initializing Finsemble connection", ex);
       appendMessage("Error initializing Finsemble connection: " + ex.getMessage());
       try {
           fsbl.close();
       } catch (IOException e1) {
           LOGGER.log(Level.SEVERE, "Error closing Finsemble connection", e1);
       }
   }

   }

And finally, here is the JavaFX example:

void connect() {
        if (fsbl != null) {
            // Has already connected once, and reconnecting doesn't work.
            return;
        }

    // the following statement is used to log any messages
    LOGGER.info(String.format("Starting JavaExample: %s", String.join(", ", args)));

    fsbl = new Finsemble(args, window);
    try {
        fsbl.connect();
        appendMessage("Connected to Finsemble");

        fsbl.addListener(new ConnectionListener() {
            @Override
            public void disconnected(ConnectionEventGenerator from) {
                LOGGER.info("Finsemble connection closed");
                appendMessage("Finsemble connection closed");
            }

            @Override
            public void error(ConnectionEventGenerator from, Exception e) {
                LOGGER.log(Level.SEVERE, "Error from Finsemble", e);
            }

            @Override
            public void onWindowStateReady(ConnectionEventGenerator from) {
                // NoOp
            }
        });

        appendMessage("Window registered with Finsemble");

APIs follow the same pattern

If you already worked with web apps and Finsemble, you’ll be happy to know that all of the APIs follow the same pattern as the JavaScript APIs. The syntax for accessing each API client is slightly different, but once you know the pattern, it's easy to follow. Let’s take a look at an example of the same call in 3 different languages. In our example, we define the context and then broadcast it.

Here’s our example using JavaScript:

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

Here’s the same call using .NET:

JObject context = new JObject
{
	["type"] = "fdc3.instrument",
	["name"] = "Tesla, Inc.",
	["id"] = new JObject
	{
		["ticker"] = "TSLA"
	}
}
FSBL.FDC3Client.fdc3.broadcast(context);

And finally, here is the same example in Java:

Context context = new Context();
context.setType("fdc3.instrument");
Map<String, Object> contextId = new HashMap<>();
contextId.put("ticker", "TSLA");
context.setId(contextId);
context.setName("Tesla, Inc.")
FinsembleDesktopAgent finsembleDesktopAgent = new FinsembleDesktopAgent(fsbl);
finsembleDesktopAgent.broadcast(context);

Important: When your apps don’t communicate as expected, authorization could be the problem. See Controlling information flow with selectConnect for more information.

Native Finsemble clients for Java applications

For the most part, Java applications have parity with the JavaScript API. The Java API clients follow a paradigm appropriate for that language: they are accessed at FSBL.getClients().getClientName().method, e.g., FSBL.getClients().getLauncherClient().getComponentsThatCanReceiveDataTypes.As examples, we've made a few Java sample projects.

Native Finsemble clients for .NET/C#/WPF apps

You can integrate .NET applications by using our .NET library, finsemble.dll. As examples, we've made a few .NET sample projects.

See also