Native windows in snapshots

OpenFin provides an extension to the standard platform behavior which allows a platform to include native windows in snapshots.

OpenFin provides an extension to the standard platform behavior which allows a platform to include details about native applications in an OpenFin Snapshot without any code modifications to the native application. Deeper integration is available with code modifications to the native applications which also allows saving and restoring custom state via snapshots .

Snapshot Capabilities: Three scenarios

There are three primary scenarios in getting and setting the state of snapshots.

  1. Snapshots of OpenFin windows and views that are part of a Platform.

    The Platform serializes and stores all state information for each app, window, and view, then restores that state when asked. This is discussed in the Snapshots section of the Platform API documentation.

  2. Snapshots of native apps where code modifications are not possible or desirable (Excel, external Windows apps, legacy applications, etc)

    This scenario is for programs like Microsoft Excel where there is no practical way to make code changes to the native app. Because the native app developer is, from a practical standpoint, inaccessible, this is the realm of the Platform developer.

    The platform needs to determine the state of the native app -- window positions, files open, cursor position, etc. That native app state information is stored as a decoration to the OpenFin snapshot state. When the snapshot is restored, the native app is restored as much as possible to its previous state, and in the larger snapshot restore process, all of the OpenFin apps, windows, and views are restored as well.

    Because the state data for native apps is gathered by querying the Windows operating system, there are some limits to the amount of information that can be gathered. If you have control of an app's source code, the next option is preferred.

  3. Snapshots of native apps where code modifications are possible (custom app, in-house app)

    This scenario is for custom or in-house developed apps where changes to the source code of the app is realistic and possible. This is the realm of the application developer.

    The application developer determines everything needed to restore the state of their app, and returns that to the platform. When the time comes to restore the state, the developer uses that information to restore the app state.

    This capability gives the developers of native applications a couple of options as to how they store and retrieve the state of their application. If their application already has an existing mechanism for saving its state, it could simply choose to associate some kind of key for that state as part of an OpenFin Platform Snapshot. When a platform applies a given snapshot, the native application would be given back the state it had stored as part of that snapshot - in this example that state is just a key that allows it to go look up the full set of state from its own store.

Alternatively, a native application may choose to store its state directly within the Platform snapshot meaning it is simply relying on OpenFin to give it back that state the next time that snapshot is restored. This gives native applications a convenient mechanism for saving/restoring state without needing to roll their own solution for storage.

Snapshots of native apps where code modifications are not possible

Because the application is unaware of the OpenFin API, the platform developer must do some work to gather the state information for the native app.

Installation

The first thing to do is to install the OpenFin NWI Library which is a Windows application that gathers the state information for native apps. The details about the tool can be found in the OpenFin NWI Library.

Here are the steps to install and access the Native Window Integration library:

  1. Install the OpenFin NWI Library.

    npm i @openfin/native-window-integration-client
    
  2. Host the native provider.

    The native provider is a .zip file by the name of provider.zip. It is very, very tempting to just copy this .zip file into your project and assume that's all you have to do. Alas, it is not quite so easy.

    It is very important to make sure the provider version and the library version remain in sync. Version mismatches will cause the NativeWindowClient.Create call to fail, which is most likely to occur when senior management "swings by" to "take a look at how you're doing." Nobody wants that.

    Instead, we recommend using a tool such as the webpack file-loader which will include the asset as part of your build to ensure versions stay in sync between the provider and the library.

    Here is an example of hosting the native provider with webpack's file-loader:

    module.exports = {
      module: {
        rules: [
          {
            test: /\.(zip)/,
            use: [
              {
                loader: 'file-loader',
                options: {
                name: '[name].[ext]',
                outputPath: 'static/',
                }
              },
            ],
          },
        ],
      },
    };
    

    If you favor the brave alternative, you can put a copy command into your build process to copy the provider.zip file from the node_modules directory to your build directory.

    If your build directory is /static, you could use a copy command like this:

    cp node_modules/openfin/nwi/lib/provider.zip static/provider.zip
    
  3. Create a connection to the native provider.

    Import the Native Window Integration client.

    import { NativeWindowIntegrationClient } from '@openfin/native-window-integration-client';
    import asset from '@openfin/native-window-integration-client/lib/provider.zip';
    

    Create the connection to the client, where the url parameter is the URL to the hosted native provider, and the configuration parameter is of the same kind of configuration found in the Usage section code sample below.

    const myClient = await NativeWindowIntegrationClient.create({local: isLocal, url: asset, configuration, connection });
    

The important APIs

After the OpenFin NWI Library is installed, three API calls are used to gather the state information of a native app and to restore its state:

API callDescription
static create(options)A factory method which launches the provider and creates a connection to it.
decorateSnapshot(snapshot)An instance method which adds native window information to a snapshot.
applySnapshot(snapshot)An instance which will look for native window information in a snapshot and attempt to restore it.

Usage

The usage of those three APIs would look like this:

import { NativeWindowIntegrationClient } from '@openfin/native-window-integration-client';
// configured to load with file-loader (see next.config.js)
import assetUrl from '@openfin/native-window-integration-client/lib/provider.zip';

const configuration = [
  {
    'name': 'Notepad',
    'title': 'my_platform_notes',
    'launch': {
      'alias': 'my_platform_notes',
      'target': 'my_platform_notes.txt',
      'lifetime': 'application',
      'arguments': ''
    }
  },
  {
    'name': 'Microsoft Excel',
    'title': 'Excel',
    'launch': {
      'path': 'C:\\Program Files\\Microsoft Office\\root\\Office16\\EXCEL.EXE',
      'lifetime': 'application'
    }
  }
]

fin.Platform.init({
  overrideCallback: async (PlatformProvider, ...args) => {
    try {
      console.log('Native Window Integration Client is connecting...');
      const myClient = await NativeWindowIntegrationClient.create({ url: assetUrl, configuration });
      console.log('Native Window Integration Client connected successfully!');

      class WithNative extends PlatformProvider {
        async getSnapshot(...args) {
          const snapshot = await super.getSnapshot(...args);
          try {
            const snapshotWithNativeWindows = await myClient.decorateSnapshot(snapshot);
            return snapshotWithNativeWindows;
          } catch (error) {
            console.log('Native Window Integration failed to get snapshotWithNativeWindows:');
            console.error(error);
            return snapshot;
          }
        }
        async applySnapshot(...args) {
          await super.applySnapshot(...args);
          try {
            await myClient.applySnapshot(args[0].snapshot);
            } catch (error) {
              console.log('Native Window Integration error applying native snapshot:');
              console.error(error);
            }
        }
      }
      console.log('Native Window Integration successfully enabled!');
      return new WithNative(...args);
    } catch (error) {
      console.log('Native Window Integration failed to initialize:');
      console.error(error);
      return new PlatformProvider(args);
      }
  }
});

[

Initialization

NativeWindowIntegrationClient.create should be called during platform initialization. Any errors launching or connecting will be raised to the Platform Developer at this phase. Whether to prevent initialization or continue will be left to the Platform Developer

Launch Sequence

  1. Library calls downloadAsset with the supplied url and package version

  2. Library calls enableNativeWindowIntegrationProvider with the given configuration, core validates the settings

  3. Library calls launchExternalProcess on the asset supplying the needed command line arguments such as connection uuid, runtime version, and channel name

  4. Library waits on the provider to initialize channel and connect

The decorateSnapshot function

The decorateSnapshot function will add the necessary information to the snapshot of all configured native apps that are currently running.

The applySnapshot function

The applySnapshot function attempts to restore all configured apps included in the snapshot. An app must be both in the snapshot and in the configuration to be launched.

The applySnapshot function should return information about any problems rather than error. applySnapshot is not reversible, so we don't want to stop applying the snapshot when we encounter an error.

The shape returned by applySnapshot is an updated version of the snapshot shape, with hasError.

Snapshots for native apps where code modifications are possible

The responsibility for gathering and setting snapshot data is on the native app developer because no one else is better suited to determine what state information is necessary and important than the app developer.

Two API calls are used to gather the state information of a native app and to restore its state:

API callScopeDescription
getSnapshotScoped to the current platform (same uuid).Returns an object representing the serialized state of the platform.
applySnapshotScoped to the current platform (same uuid).Attempts to restore the platform to the specified serialized state.

These API calls are intentionally similar to the snapshots API for OpenFin apps and windows.

The app developer first determines all the data needed to restore app state for their app. That information is returned from getSnapshot.

In applySnapshot the snapshot data is delivered as a parameter to the function. The applySnapshot uses the snapshot data to set the state of the app.

Composing Snapshots

When a platform executes its getSnapshot function, it automatically aggregates the necessary information for all windows and views hosted by the platform. However, if a Platform Builder wishes to include Native Windows in its own snapshot, it is the platform provider’s responsibility to aggregate the results of the various native window snapshots in getSnapshot and apply them in applySnapshot. This is best done in a Platform Override (link to platform override docs).

Snapshot data

Snapshots are serialized Platform state data. Snapshots contain the following information:

This is the Snapshot data collected for each Window in the Platform:

  • OpenFin Window Options, such as position, URL, state, process affinity, and a layout object containing all views.

  • View options for every View in a Window’s Layout object including position, URL, state, and process affinity)

  • Context and groups used by the Platform InteropBroker.

  • Metadata including ISO 8601 timestamp, OpenFin runtime version, and monitor information.

The Native Window Integration Provider will decorate the snapshot data with additional state data surrounding the configured native applications. This decoration includes:

  • The path to launch each application.

  • The Z-order information.

  • The window position and state (maximized, minimized, full screen, restored).

  • The arguments passed to each application which is used to restore the currently opened file for applications like Microsoft Excel, Notepad, and others.

  • Any necessary metadata.