Native windows in snapshots
Here™ provides an extension to the standard platform behavior which allows a platform to include windows from native applications in snapshots.
This extension allows a platform to include details about native applications in an Here™ Core 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 with snapshots.
Snapshot capabilities: Three scenarios
There are three primary scenarios in getting and setting the state of snapshots.
-
Snapshots of Here™ Core 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. See Snapshots.
-
Snapshots of native applications 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 application 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 Here™ Core snapshot state. When the snapshot is restored, the native application is restored as much as possible to its previous state, and in the larger snapshot restore process, all of the Here™ Core apps, windows, and views are restored as well.
Because the state data for native applications 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 application's source code, the next option is preferred.
-
Snapshots of native applications where code modifications are possible (such as custom or in-house applications)
This scenario is for custom or in-house developed applications where changes to the source code of the application is realistic and possible. This is the realm of the application developer.
The application developer determines everything needed to restore the state of their application, and returns that to the platform. When the time comes to restore the state, the developer uses that information to restore the application 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 a Here™ Core 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, the developer of a native application might choose to store its state directly within the platform snapshot, meaning it is simply relying on Here™ Core to give it back that state the next time that snapshot is restored. This gives native applications a convenient mechanism for saving and 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 Here™ Core 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 Here™ 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 Here™ Native Window Interface (NWI) library.
Here are the steps to install and access the Native Window Integration library:
-
Install the Here NWI Library.
npm i @openfin/native-window-integration-client
-
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.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 thenode_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
-
Create a connection to the native provider.
Import the NWI 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 theconfiguration
parameter is of the same kind ofconfiguration
as in the Usage section code sample below.const myClient = await NativeWindowIntegrationClient.create({local: isLocal, url: asset, configuration, connection });
The important APIs
After the Here NWI Library is installed, three API calls are used to gather the state information of a native app and to restore its state:
API call | Description |
---|---|
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 is up to the platform developer.
Launch Sequence
-
Library calls
downloadAsset
with the supplied URL and package version -
Library calls
enableNativeWindowIntegrationProvider
with the given configuration, core validates the settings -
Library calls
launchExternalProcess
on the asset supplying the needed command line arguments such as connection uuid, runtime version, and channel name -
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 developer of the native application because no one else is better suited to determine what state information is necessary and important than the application developer.
Two API calls are used to gather the state information of a native application and to restore its state:
API call | Scope | Description |
---|---|---|
getSnapshot | Scoped to the current platform (same UUID). | Returns an object representing the serialized state of the platform. |
applySnapshot | Scoped 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 Here™ Core apps and windows.
The application 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 developer wishes to include native windows in its own snapshot, it is the platform developer'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.
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:
-
Here™ Core window options, such as position, URL, state, process affinity, and a layout object containing all views.
-
View options for every View object in a Window object’s Layout object, including position, URL, state, and process affinity.
-
Context and groups used by the platform's InteropBroker.
-
Metadata including ISO 8601 timestamp, Here™ Core 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.